From 734f815e67659ab64339e67600b80e3bd03bf2bc Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 7 Jul 2024 15:12:05 +0200 Subject: [PATCH 001/726] Sorting tasks after buildPlan Tasks need to be sorted again after buildPlan as otherwise the correct order isn't guaranteed. This led to inconsistent behavior by the AI. --- AI/Nullkiller/Engine/Nullkiller.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index fa15363f6..81a144b72 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -399,6 +399,11 @@ void Nullkiller::makeTurn() auto selectedTasks = buildPlan(bestTasks); + std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b) + { + return a->priority > b->priority; + }); + logAi->debug("Decision madel in %ld", timeElapsed(start)); if(selectedTasks.empty()) From 54c6d99de3e2ca80208ebe2f0a9cd93ea4cdb50c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 7 Jul 2024 15:17:38 +0200 Subject: [PATCH 002/726] Using only one bucket Nullkiller suggested that this change would help to further fix inconsistent behavior by the AI. I tested it and it did indeed fix different orders of how AI does things. "Important to make count 1 to not relay on object addresses They are source of random" - Nullkiller --- AI/Nullkiller/Pathfinding/AINodeStorage.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 5c160238d..9f51cd531 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -27,8 +27,8 @@ namespace NKAI { namespace AIPathfinding { - const int BUCKET_COUNT = 3; - const int BUCKET_SIZE = 7; + const int BUCKET_COUNT = 1; + const int BUCKET_SIZE = 32; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; const int CHAIN_MAX_DEPTH = 4; } From e0a81b3e69e081bdeaf846b3db42758777db861d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 7 Jul 2024 22:08:19 +0200 Subject: [PATCH 003/726] Fixed AI-exploration-data being lost after loading savegame The information of whether objects like a redwood-observatory or subterranian gates have been interacted with by the AI will now be retrieved from the game-state instead of using an AI-internal memory that won't survive loading a save-game. --- .../Behaviors/ExplorationBehavior.cpp | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp b/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp index cab2707f3..b088a86a4 100644 --- a/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp @@ -35,44 +35,29 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const for(auto obj : ai->memory->visitableObjs) { - if(!vstd::contains(ai->memory->alreadyVisited, obj)) + switch (obj->ID.num) { - switch(obj->ID.num) + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj)))); - break; - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE != ai->memory->knownTeleportChannels[tObj->channel]->passability) - { - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); - } + auto rObj = dynamic_cast(obj); + if(!rObj->wasScouted(ai->playerID)) + tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj)))); break; } - } - else - { - switch(obj->ID.num) + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: { - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability) - break; - for(auto exit : ai->memory->knownTeleportChannels[tObj->channel]->exits) + auto tObj = dynamic_cast(obj); + for (auto exit : cb->getTeleportChannelExits(tObj->channel)) { - if(!cb->getObj(exit)) - { - // Always attempt to visit two-way teleports if one of channel exits is not visible - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); - break; + if (exit != tObj->id) + { + if (!cb->isVisible(cb->getObjInstance(exit))) + tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); } } - break; } } } From aa891cb8b12909a7cfbe56733a63921111ae81d9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 7 Jul 2024 22:38:37 +0200 Subject: [PATCH 004/726] Armycost Added new method to retrieve the cost of an army to be used for AI-decision-making. --- lib/CCreatureSet.cpp | 14 ++++++++++++++ lib/CCreatureSet.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 2a184b8e9..84e590649 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -366,6 +366,14 @@ ui64 CCreatureSet::getArmyStrength() const return ret; } +ui64 CCreatureSet::getArmyCost() const +{ + ui64 ret = 0; + for (const auto& elem : stacks) + ret += elem.second->getCost(); + return ret; +} + ui64 CCreatureSet::getPower(const SlotID & slot) const { return getStack(slot).getPower(); @@ -858,6 +866,12 @@ ui64 CStackInstance::getPower() const return type->getAIValue() * count; } +ui64 CStackInstance::getCost() const +{ + assert(type); + return type->getFullRecruitCost().marketValue() * count; +} + ArtBearer::ArtBearer CStackInstance::bearerType() const { return ArtBearer::CREATURE; diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index df7ab8dd4..cb2f0afca 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -109,6 +109,7 @@ public: FactionID getFaction() const override; virtual ui64 getPower() const; + virtual ui64 getCost() const; CCreature::CreatureQuantityId getQuantityID() const; std::string getQuantityTXT(bool capitalized = true) const; virtual int getExpRank() const; @@ -274,6 +275,7 @@ public: int stacksCount() const; virtual bool needsLastStack() const; //true if last stack cannot be taken ui64 getArmyStrength() const; //sum of AI values of creatures + ui64 getArmyCost() const; //sum of cost of creatures ui64 getPower(const SlotID & slot) const; //value of specific stack std::string getRoughAmount(const SlotID & slot, int mode = 0) const; //rough size of specific stack std::string getArmyDescription() const; From 7b407b6432570c4eb1a5f246d0c1e7095ba52483 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 7 Jul 2024 22:44:52 +0200 Subject: [PATCH 005/726] AI-variant without fuzzy-logic It is now possible to switch to an AI-variant that uses hand-written heuristics for decision-making rather than the FuzzyLite-engine. This is configurable in nkai-settings.json via the new parameter "useFuzzy". --- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 8 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 84 +++++++++++++------ AI/Nullkiller/Engine/Settings.cpp | 8 +- AI/Nullkiller/Engine/Settings.h | 2 + config/ai/nkai/nkai-settings.json | 2 +- 5 files changed, 76 insertions(+), 28 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 7b9607390..5c4bb4cea 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -469,7 +469,9 @@ void ObjectClusterizer::clusterizeObject( float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); - if(priority < MIN_PRIORITY) + if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) + continue; + else if (priority <= 0) continue; ClusterMap::accessor cluster; @@ -490,7 +492,9 @@ void ObjectClusterizer::clusterizeObject( float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); - if(priority < MIN_PRIORITY) + if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) + continue; + else if (priority <= 0) continue; bool interestingObject = path.turn() <= 2 || priority > 0.5f; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f29153c20..73d77623a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1113,36 +1113,71 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) double result = 0; - try + bool useFuzzy = ai->settings->isUseFuzzy(); + + if (task->hero) { - armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); - heroRoleVariable->setValue(evaluationContext.heroRole); - mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); - scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); - goldRewardVariable->setValue(goldRewardPerTurn); - armyRewardVariable->setValue(evaluationContext.armyReward); - armyGrowthVariable->setValue(evaluationContext.armyGrowth); - skillRewardVariable->setValue(evaluationContext.skillReward); - dangerVariable->setValue(evaluationContext.danger); - rewardTypeVariable->setValue(rewardType); - closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); - strategicalValueVariable->setValue(evaluationContext.strategicalValue); - goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure()); - goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f)); - turnVariable->setValue(evaluationContext.turn); - fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); - - engine->process(); - - result = value->getValue(); + if (task->hero->getOwner().getNum() > 1) + useFuzzy = true; } - catch(fl::Exception & fe) + + if (useFuzzy) { - logAi->error("evaluate VisitTile: %s", fe.getWhat()); + try + { + armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); + heroRoleVariable->setValue(evaluationContext.heroRole); + mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); + scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); + goldRewardVariable->setValue(goldRewardPerTurn); + armyRewardVariable->setValue(evaluationContext.armyReward); + armyGrowthVariable->setValue(evaluationContext.armyGrowth); + skillRewardVariable->setValue(evaluationContext.skillReward); + dangerVariable->setValue(evaluationContext.danger); + rewardTypeVariable->setValue(rewardType); + closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); + strategicalValueVariable->setValue(evaluationContext.strategicalValue); + goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure()); + goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f)); + turnVariable->setValue(evaluationContext.turn); + fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); + + engine->process(); + + result = value->getValue(); + } + catch (fl::Exception& fe) + { + logAi->error("evaluate VisitTile: %s", fe.getWhat()); + } + } + else + { + float score = evaluationContext.armyReward + evaluationContext.skillReward * 2000 + std::max((float)evaluationContext.goldReward, std::max((float)evaluationContext.armyGrowth, evaluationContext.strategicalValue * 1000)); + + if (task->hero) + { + score -= evaluationContext.armyLossPersentage * task->hero->getArmyCost(); + if (evaluationContext.enemyHeroDangerRatio > 1) + score /= evaluationContext.enemyHeroDangerRatio; + } + + if (score > 0) + { + result = score * evaluationContext.closestWayRatio / evaluationContext.movementCost; + if (task->hero) + { + if (task->hero->getArmyCost() > score + && evaluationContext.strategicalValue == 0) + result /= task->hero->getArmyCost() / score; + //logAi->trace("Score %s: %f Armyreward: %f skillReward: %f GoldReward: %f Strategical: %f Armygrowth: %f", task->toString(), score, evaluationContext.armyReward, evaluationContext.skillReward, evaluationContext.goldReward, evaluationContext.strategicalValue, evaluationContext.armyGrowth); + logAi->trace("Score %s: %f Cost: %f Dist: %f Armygrowth: %f Prio: %f", task->toString(), score, task->hero->getArmyCost(), evaluationContext.movementCost, evaluationContext.armyGrowth, result); + } + } } #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f", + logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f", task->toString(), evaluationContext.armyLossPersentage, (int)evaluationContext.turn, @@ -1151,6 +1186,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) goldRewardPerTurn, evaluationContext.goldCost, evaluationContext.armyReward, + evaluationContext.skillReward, evaluationContext.danger, evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.strategicalValue, diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index db4e3f455..3631ce816 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -30,7 +30,8 @@ namespace NKAI maxpass(10), allowObjectGraph(true), useTroopsFromGarrisons(false), - openMap(true) + openMap(true), + useFuzzy(false) { JsonNode node = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings"); @@ -69,6 +70,11 @@ namespace NKAI openMap = node.Struct()["openMap"].Bool(); } + if (!node.Struct()["useFuzzy"].isNull()) + { + useFuzzy = node.Struct()["useFuzzy"].Bool(); + } + if(!node.Struct()["useTroopsFromGarrisons"].isNull()) { useTroopsFromGarrisons = node.Struct()["useTroopsFromGarrisons"].Bool(); diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index 775f7f399..b0ec08b0d 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -29,6 +29,7 @@ namespace NKAI bool allowObjectGraph; bool useTroopsFromGarrisons; bool openMap; + bool useFuzzy; public: Settings(); @@ -41,5 +42,6 @@ namespace NKAI bool isObjectGraphAllowed() const { return allowObjectGraph; } bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; } bool isOpenMap() const { return openMap; } + bool isUseFuzzy() const { return useFuzzy; } }; } diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index f597be497..1e757dee3 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -6,5 +6,5 @@ "maxGoldPressure" : 0.3, "useTroopsFromGarrisons" : true, "openMap": true, - "allowObjectGraph": true + "allowObjectGraph": false } \ No newline at end of file From a72d23ed8d1eebdc6a7bcf653d712a0dc8eb65b1 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 7 Jul 2024 22:51:50 +0200 Subject: [PATCH 006/726] Debug-Info Added some debug-info and non-fuzzy-specific-priority-cutoff. --- AI/Nullkiller/Engine/Nullkiller.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index fa15363f6..e85901ff0 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -349,6 +349,18 @@ void Nullkiller::makeTurn() const int MAX_DEPTH = 10; const float FAST_TASK_MINIMAL_PRIORITY = 0.7f; + float totalHeroStrength = 0; + int totalTownLevel = 0; + for (auto heroInfo : cb->getHeroesInfo()) + { + totalHeroStrength += heroInfo->getTotalStrength(); + } + for (auto townInfo : cb->getTownsInfo()) + { + totalTownLevel += townInfo->getTownLevel(); + } + logAi->info("%d Turn: %d Power: %f Townlevel: %d", cb->getPlayerID()->getNum(), cb->getDate(Date::DAY), totalHeroStrength, totalTownLevel); + resetAiState(); Goals::TGoalVec bestTasks; @@ -438,7 +450,7 @@ void Nullkiller::makeTurn() bestTask->priority); } - if(bestTask->priority < MIN_PRIORITY) + if((settings->isUseFuzzy() && bestTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && bestTask->priority <= 0)) { auto heroes = cb->getHeroesInfo(); auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool @@ -463,7 +475,8 @@ void Nullkiller::makeTurn() continue; } - + if (bestTask->getHero()) + logAi->info("Best task for %s should be %s with Prio: %f", bestTask->getHero()->getNameTranslated(), bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) { if(hasAnySuccess) From 94e5b5519c56a54e244b3a191a341e6fafaafa2a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 8 Jul 2024 16:53:14 +0200 Subject: [PATCH 007/726] Fixed AI constantly visiting towns thinking they can get a huge upgrade Due to morale-considerations the AI sometimes calculated that their strongest army after doing an exchange had slightly lower total value than the army they used before. But by using unsigned "slightly lower" became near infinite. So they constantly wanted to upgrade their army because they considered it more useful than anything else. Changing the unsigned into signed fixes this. --- AI/Nullkiller/Analyzers/ArmyManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Analyzers/ArmyManager.h b/AI/Nullkiller/Analyzers/ArmyManager.h index 1617bd1bd..96b178c6d 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.h +++ b/AI/Nullkiller/Analyzers/ArmyManager.h @@ -32,7 +32,7 @@ struct SlotInfo struct ArmyUpgradeInfo { std::vector resultingArmy; - uint64_t upgradeValue = 0; + int64_t upgradeValue = 0; TResources upgradeCost; void addArmyToBuy(std::vector army); From 13bbb573bdfdb40cc6683e0a253984a744cfb5fe Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 9 Jul 2024 22:55:39 +0200 Subject: [PATCH 008/726] Spellcasting-bug-fix Fixed a bug that prevented the AI from using spells when attacking an enemy settlement that has towers. The bug was caused by noticing how greatly effective spells would be against towers but not being able to actually target them. By skipping invalid targets, this no longer is an issue. --- AI/BattleAI/BattleEvaluator.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 7ad134b7c..98d1bf3e6 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -623,6 +623,9 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) for(const auto & unit : allUnits) { + if (!unit->isValidTarget()) + continue; + auto newHealth = unit->getAvailableHealth(); auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units From 073c5bee45e71cbcd037bf1b3ff659bc94a8e972 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 10 Jul 2024 02:40:42 +0200 Subject: [PATCH 009/726] Spellcasting fixes Allowed the AI to cast spells that are aimed at a location instead of a unit. For example meteor shower. Fixed an issue that caused the AI to not kill unit-stacks with spells due to only considering stacks where at least one unit survives in it's score-calculations. --- AI/BattleAI/BattleEvaluator.cpp | 72 ++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 98d1bf3e6..3c4b2d52a 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -392,7 +392,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) vstd::erase_if(possibleSpells, [](const CSpell *s) { - return spellType(s) != SpellTypes::BATTLE || s->getTargetType() == spells::AimType::LOCATION; + return spellType(s) != SpellTypes::BATTLE; }); LOGFL("I know how %d of them works.", possibleSpells.size()); @@ -403,9 +403,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) { spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell); - if(spell->getTargetType() == spells::AimType::LOCATION) - continue; - const bool FAST = true; for(auto & target : temp.findPotentialTargets(FAST)) @@ -574,7 +571,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) auto & ps = possibleCasts[i]; #if BATTLE_TRACE_LEVEL >= 1 - logAi->trace("Evaluating %s", ps.spell->getNameTranslated()); + logAi->trace("Evaluating %s to %d", ps.spell->getNameTranslated(), ps.dest.at(0).hexValue.hex ); #endif auto state = std::make_shared(env.get(), cb->getBattle(battleID)); @@ -582,7 +579,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) spells::BattleCast cast(state.get(), hero, spells::Mode::HERO, ps.spell); cast.castEval(state->getServerCallback(), ps.dest); - auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return true; }); + auto allUnits = state->battleGetUnitsIf([](const battle::Unit * u) -> bool { return u->isValidTarget(); }); auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool { @@ -621,11 +618,62 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) ps.value = scoreEvaluator.evaluateExchange(*cachedAttack, 0, *targets, innerCache, state); } - for(const auto & unit : allUnits) + //! Some units may be dead alltogether. So if they existed before but not now, we know they were killed by the spell + for (const auto& unit : all) { if (!unit->isValidTarget()) continue; - + bool isDead = true; + for (const auto& remainingUnit : allUnits) + { + if (remainingUnit->unitId() == unit->unitId()) + isDead = false; + } + if (isDead) + { + auto newHealth = 0; + auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); + if (oldHealth != newHealth) + { + auto damage = std::abs(oldHealth - newHealth); + auto originalDefender = cb->getBattle(battleID)->battleGetUnitByID(unit->unitId()); + + auto dpsReduce = AttackPossibility::calculateDamageReduce( + nullptr, + originalDefender && originalDefender->alive() ? originalDefender : unit, + damage, + innerCache, + state); + + auto ourUnit = unit->unitSide() == side ? 1 : -1; + auto goodEffect = newHealth > oldHealth ? 1 : -1; + + if (ourUnit * goodEffect == 1) + { + if (ourUnit && goodEffect && (unit->isClone() || unit->isGhost())) + continue; + + ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier(); + } + else + ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier(); + +#if BATTLE_TRACE_LEVEL >= 1 + logAi->trace( + "Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", + ps.spell->getNameTranslated(), + ps.dest.at(0).hexValue.hex, + unit->creatureId().toCreature()->getNameSingularTranslated(), + unit->getCount(), + dpsReduce, + oldHealth, + newHealth); +#endif + } + } + } + for(const auto & unit : allUnits) + { auto newHealth = unit->getAvailableHealth(); auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units @@ -656,10 +704,14 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) #if BATTLE_TRACE_LEVEL >= 1 logAi->trace( - "Spell affects %s (%d), dps: %2f", + "Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", + ps.spell->getNameTranslated(), + ps.dest.at(0).hexValue.hex, unit->creatureId().toCreature()->getNameSingularTranslated(), unit->getCount(), - dpsReduce); + dpsReduce, + oldHealth, + newHealth); #endif } } From 3fed58c47bbbf0832b0d976cf1518c7c6712f33f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 15:36:29 +0200 Subject: [PATCH 010/726] Gold-pressure --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 2580a3b7e..6649dd00c 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -165,15 +165,8 @@ void BuildAnalyzer::update() updateDailyIncome(); - if(ai->cb->getDate(Date::DAY) == 1) - { - goldPressure = 1; - } - else - { - goldPressure = ai->getLockedResources()[EGameResID::GOLD] / 5000.0f + goldPressure = ai->getLockedResources()[EGameResID::GOLD] / 5000.0f + (float)armyCost[EGameResID::GOLD] / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f); - } logAi->trace("Gold pressure: %f", goldPressure); } From fdaac9d3c3681999234cd0230b8fabb139e02ed7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 15:39:50 +0200 Subject: [PATCH 011/726] Hero-hiring When we have no hero, we will definitely want to hire one. We will also want to hire heroes who already pay for more than themselves by coming with an army that has more value than the hero costs. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index f086b62cd..afe263076 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -45,6 +45,9 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const minScoreToHireMain = newScore; } } + // If we don't have any heros we might want to lower our expectations. + if (ourHeroes.empty()) + minScoreToHireMain = 0; for(auto town : towns) { @@ -55,8 +58,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const for(auto hero : availableHeroes) { auto score = ai->heroManager->evaluateHero(hero); - - if(score > minScoreToHireMain) + if(score > minScoreToHireMain || hero->getArmyCost() > GameConstants::HERO_GOLD_COST) { tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(200))); break; From 663ca23f6febd9fb39bc14b65fe24d7412152346 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 15:41:55 +0200 Subject: [PATCH 012/726] Army-hiring Supressing hiring army on turn one seems just bad. Starting the main-hero as strong as possible seems like a good idea to me and hiring the available troops outright will help achieve that goal. However, if there's a hero for hire, who has army with him that is a better deal, we hire that one first. --- AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index d53adc023..e988c4519 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -28,9 +28,6 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const { Goals::TGoalVec tasks; - if(ai->cb->getDate(Date::DAY) == 1) - return tasks; - auto heroes = cb->getHeroesInfo(); if(heroes.empty()) @@ -40,17 +37,28 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const for(auto town : cb->getTownsInfo()) { + //If we can recruit a hero that comes with more army than he costs, we are better off spending our gold on them + if (ai->heroManager->canRecruitHero(town)) + { + auto availableHeroes = ai->cb->getAvailableHeroes(town); + for (auto hero : availableHeroes) + { + if (hero->getArmyCost() > GameConstants::HERO_GOLD_COST) + return tasks; + } + } + + if (ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL)) + { + return tasks; + } + auto townArmyAvailableToBuy = ai->armyManager->getArmyAvailableToBuyAsCCreatureSet( town, ai->getFreeResources()); for(const CGHeroInstance * targetHero : heroes) { - if(ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL)) - { - continue; - } - if(ai->heroManager->getHeroRole(targetHero) == HeroRole::MAIN) { auto reinforcement = ai->armyManager->howManyReinforcementsCanGet( From 638c1350b8a4485974892801d9e06d39b88643a1 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 15:43:40 +0200 Subject: [PATCH 013/726] Path-safety No longer excluding paths for exposing a hero to an enemy in the behaviors. There definitely are reasons for doing something anyways, even if threatened. The logic for that should be done in the PriorityEvaluator. --- .../Behaviors/CaptureObjectsBehavior.cpp | 8 -------- AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp | 17 ----------------- 2 files changed, 25 deletions(-) diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index e20753317..1b5891a7c 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -68,14 +68,6 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals( logAi->trace("Path found %s", path.toString()); #endif - if(nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.getHeroStrength()); -#endif - continue; - } - if(objToVisit && !force && !shouldVisit(nullkiller, path.targetHero, objToVisit)) { #if NKAI_TRACE_LEVEL >= 2 diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index b9a675837..4b0dc34d1 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -89,14 +89,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con continue; } - if(path.turn() > 0 && ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength()); -#endif - continue; - } - if(ai->arePathHeroesLocked(path)) { #if NKAI_TRACE_LEVEL >= 2 @@ -294,15 +286,6 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT auto heroRole = ai->heroManager->getHeroRole(path.targetHero); - if(heroRole == HeroRole::SCOUT - && ai->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) - { -#if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength()); -#endif - continue; - } - auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources); if(!upgrader->garrisonHero From 9456ab41dacf06bc33d68df47d540995c207abf7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 17:39:52 +0200 Subject: [PATCH 014/726] Priority-tier Openmap is no longer tied to difficulty-level due to being configurable anyways. Tasks are now in different priority-tiers. For now there's 2 tiers. One for regular tasks and one for tasks of the new "conquest"-type. Regular tasks will only be considered when no possible conquest-type tasks were found. Recruit-hero-behavior is now evaluated before movement to make it more likely a new hero can exchange their stuff with others. --- AI/Nullkiller/Engine/Nullkiller.cpp | 21 +++++++++++---------- AI/Nullkiller/Engine/Nullkiller.h | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index f10f55927..2d3ed5243 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -62,7 +62,7 @@ bool canUseOpenMap(std::shared_ptr cb, PlayerColor playerID) return false; } - return cb->getStartInfo()->difficulty >= 3; + return true; } void Nullkiller::init(std::shared_ptr cb, AIGateway * gateway) @@ -122,6 +122,9 @@ void TaskPlan::merge(TSubgoal task) { TGoalVec blockers; + if (task->asTask()->priority <= 0) + return; + for(auto & item : tasks) { for(auto objid : item.affectedObjects) @@ -166,11 +169,11 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const return taskptr(*bestTask); } -Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const +Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const { TaskPlan taskPlan; - tbb::parallel_for(tbb::blocked_range(0, tasks.size()), [this, &tasks](const tbb::blocked_range & r) + tbb::parallel_for(tbb::blocked_range(0, tasks.size()), [this, &tasks, priorityTier](const tbb::blocked_range & r) { auto evaluator = this->priorityEvaluators->acquire(); @@ -179,7 +182,7 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const auto task = tasks[i]; if(task->asTask()->priority <= 0) - task->asTask()->priority = evaluator->evaluate(task); + task->asTask()->priority = evaluator->evaluate(task, priorityTier); } }); @@ -359,7 +362,6 @@ void Nullkiller::makeTurn() { totalTownLevel += townInfo->getTownLevel(); } - logAi->info("%d Turn: %d Power: %f Townlevel: %d", cb->getPlayerID()->getNum(), cb->getDate(Date::DAY), totalHeroStrength, totalTownLevel); resetAiState(); @@ -376,6 +378,7 @@ void Nullkiller::makeTurn() { bestTasks.clear(); + decompose(bestTasks, sptr(RecruitHeroBehavior()), 1); decompose(bestTasks, sptr(BuyArmyBehavior()), 1); decompose(bestTasks, sptr(BuildingBehavior()), 1); @@ -394,7 +397,6 @@ void Nullkiller::makeTurn() } } - decompose(bestTasks, sptr(RecruitHeroBehavior()), 1); decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1); decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH); decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH); @@ -408,8 +410,9 @@ void Nullkiller::makeTurn() { decompose(bestTasks, sptr(StartupBehavior()), 1); } - - auto selectedTasks = buildPlan(bestTasks); + auto selectedTasks = buildPlan(bestTasks, 0); + if (selectedTasks.empty() && !settings->isUseFuzzy()) + selectedTasks = buildPlan(bestTasks, 1); std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b) { @@ -480,8 +483,6 @@ void Nullkiller::makeTurn() continue; } - if (bestTask->getHero()) - logAi->info("Best task for %s should be %s with Prio: %f", bestTask->getHero()->getNameTranslated(), bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) { if(hasAnySuccess) diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index af05e354b..0494464cd 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -126,7 +126,7 @@ private: void updateAiState(int pass, bool fast = false); void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const; Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const; - Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks) const; + Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier = 1) const; bool executeTask(Goals::TTask task); bool areAffectedObjectsPresent(Goals::TTask task) const; HeroRole getTaskRole(Goals::TTask task) const; From 92bed2305e0431d547abfb345606ac77e06e099b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 17:41:46 +0200 Subject: [PATCH 015/726] priority-tiers Tasks are now in different priority-tiers. For now there's 2 tiers. One for regular tasks and one for tasks of the new "conquest"-type. Regular tasks will only be considered when no possible conquest-type tasks were found. Slightly reworked scoring heuristics. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 156 ++++++++++++++------- AI/Nullkiller/Engine/PriorityEvaluator.h | 5 +- 2 files changed, 108 insertions(+), 53 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 73d77623a..59a0a5b3c 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -51,9 +51,11 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai) heroRole(HeroRole::SCOUT), turn(0), strategicalValue(0), + conquestValue(0), evaluator(ai), enemyHeroDangerRatio(0), - armyGrowth(0) + armyGrowth(0), + armyInvolvement(0) { } @@ -551,6 +553,54 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons } } +float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const +{ + if (!target) + return 0; + if (target->getOwner() == ai->playerID) + return 0; + switch (target->ID) + { + case Obj::TOWN: + { + if (ai->buildAnalyzer->getDevelopmentInfo().empty()) + return 10.0f; + + auto town = dynamic_cast(target); + + if (town->getOwner() == ai->playerID) + { + auto armyIncome = townArmyGrowth(town); + auto dailyIncome = town->dailyIncome()[EGameResID::GOLD]; + + return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f); + } + + auto fortLevel = town->fortLevel(); + auto booster = 1.0f; + + if (town->hasCapitol()) + return booster * 1.5; + + if (fortLevel < CGTownInstance::CITADEL) + return booster * (town->hasFort() ? 1.0 : 0.8); + else + return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2); + } + + case Obj::HERO: + return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES + ? getEnemyHeroStrategicalValue(dynamic_cast(target)) + : 0; + + case Obj::KEYMASTER: + return 0.6f; + + default: + return 0; + } +} + float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const { auto rewardable = dynamic_cast(hut); @@ -880,7 +930,9 @@ public: evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army); evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole); evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target)); + evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); + evaluationContext.armyInvolvement += army->getArmyCost(); } vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength()); @@ -924,6 +976,7 @@ public: evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost; evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost; evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost); + evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost; evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost; evaluationContext.movementCost += objInfo.second.movementCost / boost; @@ -1100,7 +1153,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal return context; } -float PriorityEvaluator::evaluate(Goals::TSubgoal task) +float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { auto evaluationContext = buildEvaluationContext(task); @@ -1113,66 +1166,65 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) double result = 0; - bool useFuzzy = ai->settings->isUseFuzzy(); - - if (task->hero) + float fuzzyResult = 0; + try { - if (task->hero->getOwner().getNum() > 1) - useFuzzy = true; + armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); + heroRoleVariable->setValue(evaluationContext.heroRole); + mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); + scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); + goldRewardVariable->setValue(goldRewardPerTurn); + armyRewardVariable->setValue(evaluationContext.armyReward); + armyGrowthVariable->setValue(evaluationContext.armyGrowth); + skillRewardVariable->setValue(evaluationContext.skillReward); + dangerVariable->setValue(evaluationContext.danger); + rewardTypeVariable->setValue(rewardType); + closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); + strategicalValueVariable->setValue(evaluationContext.strategicalValue); + goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure()); + goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f)); + turnVariable->setValue(evaluationContext.turn); + fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); + + engine->process(); + + fuzzyResult = value->getValue(); } - - if (useFuzzy) + catch (fl::Exception& fe) { - try - { - armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); - heroRoleVariable->setValue(evaluationContext.heroRole); - mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); - scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); - goldRewardVariable->setValue(goldRewardPerTurn); - armyRewardVariable->setValue(evaluationContext.armyReward); - armyGrowthVariable->setValue(evaluationContext.armyGrowth); - skillRewardVariable->setValue(evaluationContext.skillReward); - dangerVariable->setValue(evaluationContext.danger); - rewardTypeVariable->setValue(rewardType); - closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); - strategicalValueVariable->setValue(evaluationContext.strategicalValue); - goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure()); - goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f)); - turnVariable->setValue(evaluationContext.turn); - fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); - - engine->process(); - - result = value->getValue(); - } - catch (fl::Exception& fe) - { - logAi->error("evaluate VisitTile: %s", fe.getWhat()); - } + logAi->error("evaluate VisitTile: %s", fe.getWhat()); + } + if (ai->settings->isUseFuzzy()) + { + result = fuzzyResult; } else { - float score = evaluationContext.armyReward + evaluationContext.skillReward * 2000 + std::max((float)evaluationContext.goldReward, std::max((float)evaluationContext.armyGrowth, evaluationContext.strategicalValue * 1000)); - - if (task->hero) + float score = 0; + if (priorityTier == 0) { - score -= evaluationContext.armyLossPersentage * task->hero->getArmyCost(); - if (evaluationContext.enemyHeroDangerRatio > 1) - score /= evaluationContext.enemyHeroDangerRatio; + score += evaluationContext.conquestValue * 1000; + if (score == 0) + return score; + } + else + { + score += evaluationContext.strategicalValue * 1000; + score += evaluationContext.goldReward; + score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; + score += evaluationContext.armyReward; + score += evaluationContext.armyGrowth; + score -= evaluationContext.goldCost; } - if (score > 0) { - result = score * evaluationContext.closestWayRatio / evaluationContext.movementCost; - if (task->hero) - { - if (task->hero->getArmyCost() > score - && evaluationContext.strategicalValue == 0) - result /= task->hero->getArmyCost() / score; - //logAi->trace("Score %s: %f Armyreward: %f skillReward: %f GoldReward: %f Strategical: %f Armygrowth: %f", task->toString(), score, evaluationContext.armyReward, evaluationContext.skillReward, evaluationContext.goldReward, evaluationContext.strategicalValue, evaluationContext.armyGrowth); - logAi->trace("Score %s: %f Cost: %f Dist: %f Armygrowth: %f Prio: %f", task->toString(), score, task->hero->getArmyCost(), evaluationContext.movementCost, evaluationContext.armyGrowth, result); - } + score *= evaluationContext.closestWayRatio; + if (evaluationContext.enemyHeroDangerRatio > 1) + score /= evaluationContext.enemyHeroDangerRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + score *= (1 - evaluationContext.armyLossPersentage); + result = score; } } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 02dbb2fad..3353ff0b9 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -40,6 +40,7 @@ public: float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const; float getResourceRequirementStrength(int resType) const; float getStrategicalValue(const CGObjectInstance * target) const; + float getConquestValue(const CGObjectInstance* target) const; float getTotalResourceRequirementStrength(int resType) const; float evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const; float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const; @@ -64,10 +65,12 @@ struct DLL_EXPORT EvaluationContext int32_t goldCost; float skillReward; float strategicalValue; + float conquestValue; HeroRole heroRole; uint8_t turn; RewardEvaluator evaluator; float enemyHeroDangerRatio; + float armyInvolvement; EvaluationContext(const Nullkiller * ai); @@ -90,7 +93,7 @@ public: ~PriorityEvaluator(); void initVisitTile(); - float evaluate(Goals::TSubgoal task); + float evaluate(Goals::TSubgoal task, int priorityTier = 1); private: const Nullkiller * ai; From 102b537353c7558dbbbaafa70b6367e8e3ec7ce9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 23:33:58 +0200 Subject: [PATCH 016/726] Recruit-behavior Moved recruit-behavior back to tasks that plans get made for since otherwise it can mess up plans by blocking town-exits. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 2d3ed5243..de45b02d8 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -378,7 +378,6 @@ void Nullkiller::makeTurn() { bestTasks.clear(); - decompose(bestTasks, sptr(RecruitHeroBehavior()), 1); decompose(bestTasks, sptr(BuyArmyBehavior()), 1); decompose(bestTasks, sptr(BuildingBehavior()), 1); @@ -397,6 +396,7 @@ void Nullkiller::makeTurn() } } + decompose(bestTasks, sptr(RecruitHeroBehavior()), 1); decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1); decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH); decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH); From d878d0ce18792c01c91e0dd66027fc1e4004b364 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 12 Jul 2024 23:36:41 +0200 Subject: [PATCH 017/726] Avoid being killed Heroes with conquest-tasks will only endanger themselves to be killed when they can execute a conquest in the same turn. Heroes with other tasks will dismiss any tasks except of defending when they'd be within one turn of an enemy hero that could kill them. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 13 +++++++++---- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 59a0a5b3c..f4ccd68a2 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -37,7 +37,7 @@ namespace NKAI #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us const float MIN_CRITICAL_VALUE = 2.0f; -EvaluationContext::EvaluationContext(const Nullkiller * ai) +EvaluationContext::EvaluationContext(const Nullkiller* ai) : movementCost(0.0), manaCost(0), danger(0), @@ -55,7 +55,8 @@ EvaluationContext::EvaluationContext(const Nullkiller * ai) evaluator(ai), enemyHeroDangerRatio(0), armyGrowth(0), - armyInvolvement(0) + armyInvolvement(0), + isDefend(false) { } @@ -874,6 +875,8 @@ public: else evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue); + evaluationContext.isDefend = true; + vstd::amax(evaluationContext.danger, defendTown.getTreat().danger); addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength()); } @@ -1204,11 +1207,13 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (priorityTier == 0) { score += evaluationContext.conquestValue * 1000; - if (score == 0) - return score; + if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.movementCost > 1)) + return 0; } else { + if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && evaluationContext.movementCost <= 1) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 3353ff0b9..60295b397 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -71,6 +71,7 @@ struct DLL_EXPORT EvaluationContext RewardEvaluator evaluator; float enemyHeroDangerRatio; float armyInvolvement; + bool isDefend; EvaluationContext(const Nullkiller * ai); From 53c51b427864fd88106ef386b6a0d71e292f96f6 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:26:06 +0200 Subject: [PATCH 018/726] Allow all buildings Added resource-silo and special buildings to things that AI can theoretically build. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 6649dd00c..641c75c2c 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -40,7 +40,6 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) for(BuildingID prefix : prefixes) { BuildingID building = BuildingID(prefix + level); - if(!vstd::contains(buildings, building)) continue; // no such building in town @@ -79,6 +78,12 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) otherBuildings.push_back({BuildingID::HORDE_2}); } + otherBuildings.push_back({ BuildingID::RESOURCE_SILO }); + otherBuildings.push_back({ BuildingID::SPECIAL_1 }); + otherBuildings.push_back({ BuildingID::SPECIAL_2 }); + otherBuildings.push_back({ BuildingID::SPECIAL_3 }); + otherBuildings.push_back({ BuildingID::SPECIAL_4 }); + for(auto & buildingSet : otherBuildings) { for(auto & buildingID : buildingSet) From 48ecbd4cbf6b58a7a75dbf269e81a7e5e4f835ee Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:28:10 +0200 Subject: [PATCH 019/726] Threat in DangerHitMap Added new value threat to DangerHitMapAnalayzer. The purpose is to allow decisions to be less binary around enemy heros. --- AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp | 1 + AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 6d11d01ba..2f9662a62 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -96,6 +96,7 @@ void DangerHitMapAnalyzer::updateHitMap() newThreat.hero = path.targetHero; newThreat.turn = path.turn(); + newThreat.threat = path.getHeroStrength() * (1 - path.movementCost() / 2.0); newThreat.danger = path.getHeroStrength(); if(newThreat.value() > node.maximumDanger.value()) diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h index fc2890846..2bd39a2d8 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h @@ -22,6 +22,7 @@ struct HitMapInfo uint64_t danger; uint8_t turn; + float threat; HeroPtr hero; HitMapInfo() @@ -33,6 +34,7 @@ struct HitMapInfo { danger = 0; turn = 255; + threat = 0; hero = HeroPtr(); } From e2e3f9e638114006237c7510c9f43beaf7293a3c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:30:42 +0200 Subject: [PATCH 020/726] Take hero-stats into account for attacking hero-defended entities Instead of using just the strength of the raw army, the hero-stats are now taking into account for the generation of the danger-map. --- AI/Nullkiller/Engine/FuzzyHelper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Engine/FuzzyHelper.cpp b/AI/Nullkiller/Engine/FuzzyHelper.cpp index ed7abb7c1..10fab5cc9 100644 --- a/AI/Nullkiller/Engine/FuzzyHelper.cpp +++ b/AI/Nullkiller/Engine/FuzzyHelper.cpp @@ -71,6 +71,7 @@ ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visit { objectDanger += evaluateDanger(hero->visitedTown.get()); } + objectDanger *= ai->heroManager->getFightingStrengthCached(hero); } if(objectDanger) From 83ffbdff2b3ec38658ab842a1dc973a3a61b7d4e Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:35:54 +0200 Subject: [PATCH 021/726] Fix for double army-loss Fixed that army loss was taken into account both for the path and the target-object. In certain cases, like a hero defending a town, this could lead to armyloss being twice as high as it should be. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index b113e27ae..b5cc485c3 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1509,7 +1509,7 @@ uint8_t AIPath::turn() const uint64_t AIPath::getHeroStrength() const { - return targetHero->getFightingStrength() * getHeroArmyStrengthWithCommander(targetHero, heroArmy); + return targetHero->getHeroStrength() * getHeroArmyStrengthWithCommander(targetHero, heroArmy); } uint64_t AIPath::getTotalDanger() const @@ -1536,7 +1536,7 @@ bool AIPath::containsHero(const CGHeroInstance * hero) const uint64_t AIPath::getTotalArmyLoss() const { - return armyLoss + targetObjectArmyLoss; + return armyLoss > targetObjectArmyLoss ? armyLoss : targetObjectArmyLoss; } std::string AIPath::toString() const From f8f10adb2e4eb8d38a9a2a07a391cca033e925b4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:42:02 +0200 Subject: [PATCH 022/726] Take magic-capability into account for overall strength-estimation of hero-lead-armies The magic-strength of a hero now checks if the hero has a spellbook and at least one combat-spell. The impact of knowledge and spellpower to the hero's magic-strength is now also depending on it's current and max mana-pool-size as an empty mana-pool does not exactly contribute well to fights. Replaced every call of getFightingStrength() with getHeroStrength() which uses both the fightingStrength and the (reworked) magicStrength to guess how much stronger a hero-lead army is. --- AI/Nullkiller/AIUtility.cpp | 2 +- AI/Nullkiller/Analyzers/HeroManager.cpp | 6 +++--- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 17 +++++++++++++++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index e55330fbd..4de840cff 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -149,7 +149,7 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength) { - const ui64 heroStrength = h->getFightingStrength() * heroArmy->getArmyStrength(); + const ui64 heroStrength = h->getHeroStrength() * heroArmy->getArmyStrength(); if(dangerStrength) { diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 44b66b46b..e2406a023 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -109,7 +109,7 @@ void HeroManager::update() for(auto & hero : myHeroes) { scores[hero] = evaluateFightingStrength(hero); - knownFightingStrength[hero->id] = hero->getFightingStrength(); + knownFightingStrength[hero->id] = hero->getHeroStrength(); } auto scoreSort = [&](const CGHeroInstance * h1, const CGHeroInstance * h2) -> bool @@ -205,7 +205,7 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const auto cached = knownFightingStrength.find(hero->id); //FIXME: fallback to hero->getFightingStrength() is VERY slow on higher difficulties (no object graph? map reveal?) - return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength(); + return cached != knownFightingStrength.end() ? cached->second : hero->getHeroStrength(); } float HeroManager::getMagicStrength(const CGHeroInstance * hero) const @@ -298,7 +298,7 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) co continue; } - if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength()) + if(!weakestHero || weakestHero->getHeroStrength() > existingHero->getHeroStrength()) { weakestHero = existingHero; } diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index ae6b6446e..1af649e66 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -46,7 +46,7 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t initialMovement = hero->movementPointsRemaining(); initialTurn = 0; armyValue = getHeroArmyStrengthWithCommander(hero, hero); - heroFightingStrength = hero->getFightingStrength(); + heroFightingStrength = hero->getHeroStrength(); tiCache.reset(new TurnInfo(hero)); } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c99667f58..b4119f891 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -651,7 +651,20 @@ double CGHeroInstance::getFightingStrength() const double CGHeroInstance::getMagicStrength() const { - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER))); + if (!hasSpellbook()) + return 1; + bool atLeastOneCombatSpell = false; + for (auto spell : spells) + { + if (spellbookContainsSpell(spell) && spell.toSpell()->isCombat()) + { + atLeastOneCombatSpell = true; + break; + } + } + if (!atLeastOneCombatSpell) + return 1; + return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * mana / manaLimit()) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER) * mana / manaLimit())); } double CGHeroInstance::getHeroStrength() const @@ -661,7 +674,7 @@ double CGHeroInstance::getHeroStrength() const ui64 CGHeroInstance::getTotalStrength() const { - double ret = getFightingStrength() * getArmyStrength(); + double ret = getHeroStrength() * getArmyStrength(); return static_cast(ret); } From ce4905ac4c882ec9d47285c403e9a70322e37693 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:47:27 +0200 Subject: [PATCH 023/726] Ivan's fix to brown arrows after new turn A fix Ivan posted about on Discord that takes care of a newly introduced bug in development-branch that you had to reselect your hero manually after a new turn because he would otherwise think he's still on last-turn when it came to executing planned movement. --- client/adventureMap/AdventureMapInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 43f005199..2a4dbe0be 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -444,7 +444,7 @@ void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID) LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0)); } - centerOnObject(LOCPLINT->localState->getCurrentArmy()); + onSelectionChanged(LOCPLINT->localState->getCurrentArmy()); //show new day animation and sound on infobar, except for 1st day of the game if (LOCPLINT->cb->getDate(Date::DAY) != 1) From 95ba57dfe23daf947bc666e5557a78d40bceb5cd Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 17:50:30 +0200 Subject: [PATCH 024/726] No more Startup-behavior Startup-behavior was messing with my intended logic. Mostly by getting excess heroes for no real purpose other than that it could. This wasted a lot of money that could be better invested on subsequent turns. I removed it and playing-strength actually went up. --- AI/Nullkiller/Engine/Nullkiller.cpp | 9 +++++---- AI/Nullkiller/Engine/Nullkiller.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index de45b02d8..f448f7b43 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -371,6 +371,7 @@ void Nullkiller::makeTurn() { auto start = std::chrono::high_resolution_clock::now(); updateAiState(i); + //logAi->info("Gold: %d", cb->getResourceAmount(EGameResID::GOLD)); Goals::TTask bestTask = taskptr(Goals::Invalid()); @@ -385,6 +386,7 @@ void Nullkiller::makeTurn() if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY) { + //logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) return; @@ -406,13 +408,11 @@ void Nullkiller::makeTurn() if(!isOpenMap()) decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH); - if(cb->getDate(Date::DAY) == 1 || heroManager->getHeroRoles().empty()) - { - decompose(bestTasks, sptr(StartupBehavior()), 1); - } auto selectedTasks = buildPlan(bestTasks, 0); if (selectedTasks.empty() && !settings->isUseFuzzy()) selectedTasks = buildPlan(bestTasks, 1); + if (selectedTasks.empty() && !settings->isUseFuzzy()) + selectedTasks = buildPlan(bestTasks, 2); std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b) { @@ -483,6 +483,7 @@ void Nullkiller::makeTurn() continue; } + //logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) { if(hasAnySuccess) diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 0494464cd..4b25dd3ec 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -126,7 +126,7 @@ private: void updateAiState(int pass, bool fast = false); void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const; Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const; - Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier = 1) const; + Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier = 3) const; bool executeTask(Goals::TTask task); bool areAffectedObjectsPresent(Goals::TTask task) const; HeroRole getTaskRole(Goals::TTask task) const; From 4a552d411ce4390fc169f622cabf210c0d9b31ec Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 18:12:52 +0200 Subject: [PATCH 025/726] Decisionmaking-changes There's 3 new evaluation-contexts that are now taken into account: Whether an action is building, whether an action involves sailing and the newly introduced threat. The value-evaluation of creatures now also takes special resources into account. No longer treating other AIs differently than players when it comes to how afraid we shall be of them. The cost of buildings for decision-making now determines missing resources. Available resources are ignored when it comes to how they impact the cost. But missing-resources will heftily impact the assumed price by calculating their market-value. This shall encourage the AI to rather build what it currently can build instead of saving up for something that it lacking the special resources for. AI is no longer willing to sacrifice more than 25% of their army for any attack except when it has no towns left. Revamped the priority-tiers of AI decision-making. Higest priority is conquering enemy towns and killing enemy heroes. However, the AI will no longer try to do so when the target is more than one turn away and protected by a nearby enemy-hero that could kill the one tasked with dealing with the target. Except when they have no towns left. Then they get desperate and try everything. As a general rule of thumb one could say the AI will prioritize conquest over collecting freebies over investing army to get something that isn't a city. It's a bit more complex than that but this is roughly what can be expected. It will also highly value their own heroes safety during all this. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 151 +++++++++++++++------ AI/Nullkiller/Engine/PriorityEvaluator.h | 5 +- 2 files changed, 117 insertions(+), 39 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f4ccd68a2..a726be3d4 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -54,9 +54,12 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) conquestValue(0), evaluator(ai), enemyHeroDangerRatio(0), + threat(0), armyGrowth(0), armyInvolvement(0), - isDefend(false) + isDefend(false), + isBuild(false), + involvesSailing(false) { } @@ -246,7 +249,7 @@ int getDwellingArmyCost(const CGObjectInstance * target) auto creature = creLevel.second.back().toCreature(); auto creaturesAreFree = creature->getLevel() == 1; if(!creaturesAreFree) - cost += creature->getRecruitCost(EGameResID::GOLD) * creLevel.first; + cost += creature->getFullRecruitCost().marketValue() * creLevel.first; } } @@ -686,7 +689,7 @@ int32_t getArmyCost(const CArmedInstance * army) for(auto stack : army->Slots()) { - value += stack.second->getCreatureID().toCreature()->getRecruitCost(EGameResID::GOLD) * stack.second->count; + value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->count; } return value; @@ -823,15 +826,8 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin if(enemyDanger.danger) { auto dangerRatio = enemyDanger.danger / (double)ourStrength; - auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false); - bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb); - - if(isAI) - { - dangerRatio *= 1.5; // lets make AI bit more afraid of other AI. - } - vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio); + vstd::amax(evaluationContext.threat, enemyDanger.threat); } } @@ -907,6 +903,8 @@ public: for(auto & node : path.nodes) { vstd::amax(costsPerHero[node.targetHero], node.cost); + if (node.layer == EPathfindingLayer::SAIL) + evaluationContext.involvesSailing = true; } for(auto pair : costsPerHero) @@ -1056,8 +1054,15 @@ public: evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have evaluationContext.heroRole = HeroRole::MAIN; evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount; - evaluationContext.goldCost += bi.buildCostWithPrerequisites[EGameResID::GOLD]; + int32_t cost = bi.buildCostWithPrerequisites[EGameResID::GOLD]; + auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); + TResources missing = bi.buildCostWithPrerequisites - resourcesAvailable; + missing[EGameResID::GOLD] = 0; + missing.positive(); + cost += missing.marketValue(); + evaluationContext.goldCost += cost; evaluationContext.closestWayRatio = 1; + evaluationContext.isBuild = true; if(bi.creatureID != CreatureID::NONE) { @@ -1204,37 +1209,105 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) else { float score = 0; - if (priorityTier == 0) + float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; + switch (priorityTier) { - score += evaluationContext.conquestValue * 1000; - if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.movementCost > 1)) - return 0; - } - else - { - if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && evaluationContext.movementCost <= 1) - return 0; - score += evaluationContext.strategicalValue * 1000; - score += evaluationContext.goldReward; - score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; - score += evaluationContext.armyReward; - score += evaluationContext.armyGrowth; - score -= evaluationContext.goldCost; - } - if (score > 0) - { - score *= evaluationContext.closestWayRatio; - if (evaluationContext.enemyHeroDangerRatio > 1) - score /= evaluationContext.enemyHeroDangerRatio; - if (evaluationContext.movementCost > 0) - score /= evaluationContext.movementCost; - score *= (1 - evaluationContext.armyLossPersentage); - result = score; + case 0: //Take towns + { + score += evaluationContext.conquestValue * 1000; + if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 0.5 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) + return 0; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) + score *= evaluationContext.armyInvolvement / evaluationContext.threat; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + score *= (maxWillingToLose - evaluationContext.armyLossPersentage); + break; + } + case 1: //Collect unguarded stuff + { + if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + return 0; + if (evaluationContext.isDefend && evaluationContext.enemyHeroDangerRatio == 0) + return 0; + if (evaluationContext.armyLossPersentage > 0) + return 0; + if (evaluationContext.involvesSailing && evaluationContext.movementCostByRole[HeroRole::MAIN] > 0) + return 0; + score += evaluationContext.strategicalValue * 1000; + score += evaluationContext.goldReward; + score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; + score += evaluationContext.armyReward; + score += evaluationContext.armyGrowth; + if (evaluationContext.isBuild) + { + score += 1000; + score /= evaluationContext.goldCost; + } + else + { + if (score <= 0) + return 0; + else + score = 1000; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) + score *= evaluationContext.armyInvolvement / evaluationContext.threat; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + } + break; + } + case 2: //Collect guarded stuff + { + if (evaluationContext.isBuild) + return 0; + if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + return 0; + score += evaluationContext.strategicalValue * 1000; + score += evaluationContext.goldReward; + score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; + score += evaluationContext.armyReward; + score += evaluationContext.armyGrowth; + score -= evaluationContext.goldCost; + score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage; + if (score > 0) + { + if (evaluationContext.enemyHeroDangerRatio > 1) + if (evaluationContext.threat > 0) + score = evaluationContext.armyInvolvement / evaluationContext.threat; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) + score *= evaluationContext.armyInvolvement / evaluationContext.threat; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + score *= (maxWillingToLose - evaluationContext.armyLossPersentage); + } + break; + } + case 3: //Pre-filter to see if anything is worth to be done at all + { + score += evaluationContext.conquestValue * 1000; + score += evaluationContext.strategicalValue * 1000; + score += evaluationContext.goldReward; + score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; + score += evaluationContext.armyReward; + score += evaluationContext.armyGrowth; + if (evaluationContext.isBuild) + { + score += 1000; + score /= evaluationContext.goldCost; + } + break; + } } + result = score; } #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f", + logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, fuzzy: %f, result %f", + priorityTier, task->toString(), evaluationContext.armyLossPersentage, (int)evaluationContext.turn, @@ -1245,10 +1318,12 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.armyReward, evaluationContext.skillReward, evaluationContext.danger, + evaluationContext.threat, evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.strategicalValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, + fuzzyResult, result); #endif diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 60295b397..5701e2381 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -70,8 +70,11 @@ struct DLL_EXPORT EvaluationContext uint8_t turn; RewardEvaluator evaluator; float enemyHeroDangerRatio; + float threat; float armyInvolvement; bool isDefend; + bool isBuild; + bool involvesSailing; EvaluationContext(const Nullkiller * ai); @@ -94,7 +97,7 @@ public: ~PriorityEvaluator(); void initVisitTile(); - float evaluate(Goals::TSubgoal task, int priorityTier = 1); + float evaluate(Goals::TSubgoal task, int priorityTier = 3); private: const Nullkiller * ai; From d4b4a6c4dbf71ffe1fefc49ebab515169a796b91 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 19:57:33 +0200 Subject: [PATCH 026/726] Warnings Commented out relics from debugging that were causing warnings for unused variables. --- AI/Nullkiller/Engine/Nullkiller.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index f448f7b43..1fc02aaea 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -352,16 +352,16 @@ void Nullkiller::makeTurn() const int MAX_DEPTH = 10; const float FAST_TASK_MINIMAL_PRIORITY = 0.7f; - float totalHeroStrength = 0; - int totalTownLevel = 0; - for (auto heroInfo : cb->getHeroesInfo()) - { - totalHeroStrength += heroInfo->getTotalStrength(); - } - for (auto townInfo : cb->getTownsInfo()) - { - totalTownLevel += townInfo->getTownLevel(); - } + //float totalHeroStrength = 0; + //int totalTownLevel = 0; + //for (auto heroInfo : cb->getHeroesInfo()) + //{ + // totalHeroStrength += heroInfo->getTotalStrength(); + //} + //for (auto townInfo : cb->getTownsInfo()) + //{ + // totalTownLevel += townInfo->getTownLevel(); + //} resetAiState(); From 455ad648ae751d52d2790737f82d315c91b8d612 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 15 Jul 2024 20:09:11 +0200 Subject: [PATCH 027/726] More warning fixes heroRole is no longer needed here. --- AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 4b0dc34d1..4a17bb429 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -284,8 +284,6 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT continue; } - auto heroRole = ai->heroManager->getHeroRole(path.targetHero); - auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources); if(!upgrader->garrisonHero From 37c0972a5093eb4c71cf668b08bef0db07cc1b1f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:16:05 +0200 Subject: [PATCH 028/726] New and restored functionality Added a new div-function to resource-sets. It allows to calculate the amount of times one resource set, for example income, has to be accumulated in order to reach another resource-set, for example required resource. It will return INT_MAX if it's impossible. Restored the "<" operator-function and made it actually work like it's supposed to. --- lib/ResourceSet.h | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index 4d9a0e695..c16fdf01b 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -148,6 +148,26 @@ public: return ret; } + int div(const ResourceSet& income) { + int ret = 0; // Initialize to 0 because we want the maximum number of accumulations + + for (size_t i = 0; i < container.size(); ++i) { + if (container.at(i) > 0) { // We only care about fulfilling positive needs + if (income[i] == 0) { + // If income is 0 and we need a positive amount, it's impossible to fulfill + return INT_MAX; + } + else { + // Calculate the number of times we need to accumulate income to fulfill the need + float divisionResult = static_cast(container.at(i)) / static_cast(income[i]); + int ceiledResult = static_cast(std::ceil(divisionResult)); + ret = std::max(ret, ceiledResult); + } + } + } + return ret; + } + ResourceSet & operator=(const TResource &rhs) { for(int & i : container) @@ -171,14 +191,14 @@ public: // WARNING: comparison operators are used for "can afford" relation: a <= b means that foreach i a[i] <= b[i] // that doesn't work the other way: a > b doesn't mean that a cannot be afforded with b, it's still b can afford a -// bool operator<(const ResourceSet &rhs) -// { -// for(int i = 0; i < size(); i++) -// if(at(i) >= rhs[i]) -// return false; -// -// return true; -// } + bool operator<(const ResourceSet &rhs) + { + for(int i = 0; i < size(); i++) + if (this->container.at(i) < rhs[i]) + return true; + + return false; + } template void serialize(Handler &h) { From 9c6d8762c5aa060f84935a0a180edcfe5f6befaf Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:18:02 +0200 Subject: [PATCH 029/726] Lowered restrictions from hero-hiring. Removed two restrictions from hero-hiring, that prevented AI from hiring heros in certain scenarios. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index afe263076..c0d3f8f31 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -83,9 +83,6 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const } } - if(treasureSourcesCount < 5 && (town->garrisonHero || town->getUpperArmy()->getArmyStrength() < 10000)) - continue; - if(ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh())) { From f094bf96022fa9e45428ce7824c155070f253f8d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:19:27 +0200 Subject: [PATCH 030/726] Added marketplace to buildable buildings Before marketplaces could only be built as part of a requirement for other buildings but not on their own when that other building already existed like it is the case in certain campaign-missions. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 641c75c2c..b8d2929b9 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -83,6 +83,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) otherBuildings.push_back({ BuildingID::SPECIAL_2 }); otherBuildings.push_back({ BuildingID::SPECIAL_3 }); otherBuildings.push_back({ BuildingID::SPECIAL_4 }); + otherBuildings.push_back({ BuildingID::MARKETPLACE }); for(auto & buildingSet : otherBuildings) { From 2d715f4d7e774fb3c2892d56ee9b6c4c9c213327 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:21:56 +0200 Subject: [PATCH 031/726] Trading logic Added trading-logic to Nullkiller-AI. The AI can now identify which resources it is lacking the most and buy them to fix softlocks in their build-order. It can also sell excess resources that it doesn't have a need for. --- AI/Nullkiller/Engine/Nullkiller.cpp | 123 ++++++++++++++++++++++++---- AI/Nullkiller/Engine/Nullkiller.h | 1 + 2 files changed, 110 insertions(+), 14 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 1fc02aaea..046bccf59 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -352,16 +352,18 @@ void Nullkiller::makeTurn() const int MAX_DEPTH = 10; const float FAST_TASK_MINIMAL_PRIORITY = 0.7f; - //float totalHeroStrength = 0; - //int totalTownLevel = 0; - //for (auto heroInfo : cb->getHeroesInfo()) - //{ - // totalHeroStrength += heroInfo->getTotalStrength(); - //} - //for (auto townInfo : cb->getTownsInfo()) - //{ - // totalTownLevel += townInfo->getTownLevel(); - //} + float totalHeroStrength = 0; + int totalTownLevel = 0; + for (auto heroInfo : cb->getHeroesInfo()) + { + totalHeroStrength += heroInfo->getTotalStrength(); + } + for (auto townInfo : cb->getTownsInfo()) + { + totalTownLevel += townInfo->getTownLevel(); + } + logAi->info("Resources: %s Strength: %f Townlevel: %d", cb->getResourceAmount().toString(), totalHeroStrength, totalTownLevel); + resetAiState(); @@ -371,7 +373,6 @@ void Nullkiller::makeTurn() { auto start = std::chrono::high_resolution_clock::now(); updateAiState(i); - //logAi->info("Gold: %d", cb->getResourceAmount(EGameResID::GOLD)); Goals::TTask bestTask = taskptr(Goals::Invalid()); @@ -386,7 +387,7 @@ void Nullkiller::makeTurn() if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY) { - //logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); + logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) return; @@ -483,7 +484,7 @@ void Nullkiller::makeTurn() continue; } - //logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); + logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) { if(hasAnySuccess) @@ -491,10 +492,11 @@ void Nullkiller::makeTurn() else return; } - hasAnySuccess = true; } + hasAnySuccess |= handleTrading(); + if(!hasAnySuccess) { logAi->trace("Nothing was done this turn. Ending turn."); @@ -574,4 +576,97 @@ void Nullkiller::lockResources(const TResources & res) lockedResources += res; } +bool Nullkiller::handleTrading() +{ + bool haveTraded = false; + bool shouldTryToTrade = true; + int marketId = -1; + for (auto town : cb->getTownsInfo()) + { + if (town->hasBuiltSomeTradeBuilding()) + { + marketId = town->id; + } + } + if (marketId == -1) + return false; + if (const CGObjectInstance* obj = cb->getObj(ObjectInstanceID(marketId), false)) + { + if (const auto* m = dynamic_cast(obj)) + { + while (shouldTryToTrade) + { + shouldTryToTrade = false; + buildAnalyzer->update(); + TResources required = buildAnalyzer->getTotalResourcesRequired(); + TResources income = buildAnalyzer->getDailyIncome(); + TResources available = getFreeResources(); + + int mostWanted = -1; + int mostExpendable = -1; + float minRatio = std::numeric_limits::max(); + float maxRatio = std::numeric_limits::min(); + + for (int i = 0; i < required.size(); ++i) + { + if (required[i] == 0) + continue; + float ratio = static_cast(available[i]) / required[i]; + + if (ratio < minRatio) { + minRatio = ratio; + mostWanted = i; + } + } + + for (int i = 0; i < required.size(); ++i) + { + float ratio = available[i]; + if (required[i] > 0) + ratio = static_cast(available[i]) / required[i]; + + bool okToSell = false; + + if (i == 6) + { + if (income[i] > 0) + okToSell = true; + } + else + { + if (available[i] > required[i]) + okToSell = true; + } + + if (ratio > maxRatio && okToSell) { + maxRatio = ratio; + mostExpendable = i; + } + } + + //logAi->info("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted); + + if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1) + return false; + + int acquiredResources = 0; + + int toGive; + int toGet; + m->getOffer(mostExpendable, mostWanted, toGive, toGet, EMarketMode::RESOURCE_RESOURCE); + //logAi->info("Offer is: I get %d of %s for %d of %s at %s", toGet, mostWanted, toGive, mostExpendable, obj->getObjectName()); + //TODO trade only as much as needed + if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources + { + cb->trade(m, EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); + logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); + haveTraded = true; + shouldTryToTrade = true; + } + } + } + } + return haveTraded; +} + } diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 4b25dd3ec..5166421fb 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -120,6 +120,7 @@ public: ScanDepth getScanDepth() const { return scanDepth; } bool isOpenMap() const { return openMap; } bool isObjectGraphAllowed() const { return useObjectGraph; } + bool handleTrading(); private: void resetAiState(); From e374f24016608b7dfab737353d234c5d6b96e743 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:25:24 +0200 Subject: [PATCH 032/726] complete Building-costs as evaluation-context Added building-cost including all resoruces as evaluation-context for more sophisticated building-selection and also as a countermeasure to softlocking a build-order by having no ways to obtain certain resources. For example, if the AI would drop below 5 wood, while having no market-place and no wood-income it will avoid building any buildings that neither allow trading nor produce wood. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 90 +++++++++++++++------- AI/Nullkiller/Engine/PriorityEvaluator.h | 3 +- AI/Nullkiller/Goals/AbstractGoal.h | 1 + 3 files changed, 65 insertions(+), 29 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 446b60967..6d8a5a122 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -58,8 +58,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) armyGrowth(0), armyInvolvement(0), isDefend(false), - isBuild(false), - involvesSailing(false) + involvesSailing(false), + isTradeBuilding(false) { } @@ -1182,14 +1182,13 @@ public: evaluationContext.heroRole = HeroRole::MAIN; evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount; int32_t cost = bi.buildCostWithPrerequisites[EGameResID::GOLD]; - auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); - TResources missing = bi.buildCostWithPrerequisites - resourcesAvailable; - missing[EGameResID::GOLD] = 0; - missing.positive(); - cost += missing.marketValue(); evaluationContext.goldCost += cost; evaluationContext.closestWayRatio = 1; - evaluationContext.isBuild = true; + evaluationContext.buildingCost += bi.buildCostWithPrerequisites; + if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) + evaluationContext.isTradeBuilding = true; + + logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue()); if(bi.creatureID != CreatureID::NONE) { @@ -1278,6 +1277,7 @@ EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal for(auto subgoal : parts) { context.goldCost += subgoal->goldCost; + context.buildingCost += subgoal->buildingCost; for(auto builder : evaluationContextBuilders) { @@ -1337,6 +1337,26 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; + + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, fuzzy: %f", + priorityTier, + task->toString(), + evaluationContext.armyLossPersentage, + (int)evaluationContext.turn, + evaluationContext.movementCostByRole[HeroRole::MAIN], + evaluationContext.movementCostByRole[HeroRole::SCOUT], + goldRewardPerTurn, + evaluationContext.goldCost, + evaluationContext.armyReward, + evaluationContext.skillReward, + evaluationContext.danger, + evaluationContext.threat, + evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", + evaluationContext.strategicalValue, + evaluationContext.closestWayRatio, + evaluationContext.enemyHeroDangerRatio, + fuzzyResult); + switch (priorityTier) { case 0: //Take towns @@ -1362,33 +1382,27 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.involvesSailing && evaluationContext.movementCostByRole[HeroRole::MAIN] > 0) return 0; + if (evaluationContext.buildingCost.marketValue() > 0) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; score += evaluationContext.armyReward; score += evaluationContext.armyGrowth; - if (evaluationContext.isBuild) - { - score += 1000; - score /= evaluationContext.goldCost; - } + if (score <= 0) + return 0; else - { - if (score <= 0) - return 0; - else - score = 1000; - score *= evaluationContext.closestWayRatio; - if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) - score *= evaluationContext.armyInvolvement / evaluationContext.threat; - if (evaluationContext.movementCost > 0) - score /= evaluationContext.movementCost; - } + score = 1000; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) + score *= evaluationContext.armyInvolvement / evaluationContext.threat; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; break; } case 2: //Collect guarded stuff { - if (evaluationContext.isBuild) + if (evaluationContext.buildingCost.marketValue() > 0) return 0; if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) return 0; @@ -1413,7 +1427,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } break; } - case 3: //Pre-filter to see if anything is worth to be done at all + case 3: //For buildings and buying army { score += evaluationContext.conquestValue * 1000; score += evaluationContext.strategicalValue * 1000; @@ -1421,10 +1435,30 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; score += evaluationContext.armyReward; score += evaluationContext.armyGrowth; - if (evaluationContext.isBuild) + if (evaluationContext.buildingCost.marketValue() > 0) { + if (!evaluationContext.isTradeBuilding && ai->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && ai->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1) + { + logAi->trace("Should make sure to build market-place instead of %s", task->toString()); + for (auto town : cb->getTownsInfo()) + { + if (!town->hasBuiltSomeTradeBuilding()) + return 0; + } + } score += 1000; - score /= evaluationContext.goldCost; + auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); + auto income = ai->buildAnalyzer->getDailyIncome(); + if (resourcesAvailable < evaluationContext.buildingCost) + { + TResources needed = evaluationContext.buildingCost - resourcesAvailable; + needed.positive(); + int turnsTo = needed.div(income); + if (turnsTo == INT_MAX) + return 0; + else + score /= turnsTo; + } } break; } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index d4241efdb..2e15a4926 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -74,8 +74,9 @@ struct DLL_EXPORT EvaluationContext float threat; float armyInvolvement; bool isDefend; - bool isBuild; + TResources buildingCost; bool involvesSailing; + bool isTradeBuilding; EvaluationContext(const Nullkiller * ai); diff --git a/AI/Nullkiller/Goals/AbstractGoal.h b/AI/Nullkiller/Goals/AbstractGoal.h index b191f96a5..27adf052e 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.h +++ b/AI/Nullkiller/Goals/AbstractGoal.h @@ -104,6 +104,7 @@ namespace Goals bool isAbstract; SETTER(bool, isAbstract) int value; SETTER(int, value) ui64 goldCost; SETTER(ui64, goldCost) + TResources buildingCost; SETTER(TResources, buildingCost) int resID; SETTER(int, resID) int objid; SETTER(int, objid) int aid; SETTER(int, aid) From eb26b16823dce085aeceb94e1a13e555e3045d78 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:26:14 +0200 Subject: [PATCH 033/726] Trace level Increased AI-trace-level because I need it for debugging. --- AI/Nullkiller/Pathfinding/AINodeStorage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 12cf787d0..979f19a50 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -12,7 +12,7 @@ #define NKAI_PATHFINDER_TRACE_LEVEL 0 constexpr int NKAI_GRAPH_TRACE_LEVEL = 0; -#define NKAI_TRACE_LEVEL 0 +#define NKAI_TRACE_LEVEL 2 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" From f0f40660c4f4a0aefea93c40f2a41e022cc5cd5f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 19 Jul 2024 15:59:57 +0200 Subject: [PATCH 034/726] reverse getTotalArmyLoss()-change Due to a recent fix in army-loss calculations this method should now work correctly the way it was. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 39bc8f9f1..f7a0e6c30 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1557,7 +1557,7 @@ bool AIPath::containsHero(const CGHeroInstance * hero) const uint64_t AIPath::getTotalArmyLoss() const { - return armyLoss > targetObjectArmyLoss ? armyLoss : targetObjectArmyLoss; + return armyLoss + targetObjectArmyLoss; } std::string AIPath::toString() const From 809163b705550a0b69dcd180af380876cae5414a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 20 Jul 2024 16:10:29 +0200 Subject: [PATCH 035/726] Update SDLImage.cpp Fixed crash --- client/renderSDL/SDLImage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index c7e47cd97..6a9a61d6f 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -196,7 +196,7 @@ Point SDLImageConst::dimensions() const std::shared_ptr SDLImageConst::createImageReference(EImageBlitMode mode) { - if (surf->format->palette) + if (surf && surf->format->palette) return std::make_shared(shared_from_this(), mode); else return std::make_shared(shared_from_this(), mode); From ed82e2bf4ae24279e1f1ac87813b8f82cd2326af Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 20 Jul 2024 21:02:39 +0200 Subject: [PATCH 036/726] Warnings Removed unused variable. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 046bccf59..9ef4816b4 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -649,8 +649,6 @@ bool Nullkiller::handleTrading() if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1) return false; - int acquiredResources = 0; - int toGive; int toGet; m->getOffer(mostExpendable, mostWanted, toGive, toGet, EMarketMode::RESOURCE_RESOURCE); From 945de7c3698115900d9ce6fd5a8430893065ed5f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 20 Jul 2024 21:12:43 +0200 Subject: [PATCH 037/726] More warnings Removed unused code --- .../Behaviors/RecruitHeroBehavior.cpp | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index c0d3f8f31..837e7e91d 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -55,6 +55,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const { auto availableHeroes = ai->cb->getAvailableHeroes(town); + //TODO: Prioritize non-main-heros too by cost of their units and whether their units fit to the current town for(auto hero : availableHeroes) { auto score = ai->heroManager->evaluateHero(hero); @@ -65,24 +66,6 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const } } - int treasureSourcesCount = 0; - - for(auto obj : ai->objectClusterizer->getNearbyObjects()) - { - if((obj->ID == Obj::RESOURCE) - || obj->ID == Obj::TREASURE_CHEST - || obj->ID == Obj::CAMPFIRE - || isWeeklyRevisitable(ai, obj) - || obj->ID ==Obj::ARTIFACT) - { - auto tile = obj->visitablePos(); - auto closestTown = ai->dangerHitMap->getClosestTown(tile); - - if(town == closestTown) - treasureSourcesCount++; - } - } - if(ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh())) { From 34e4ab45ee28ecc86b4db584b22e36d99bee3505 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 15:21:17 +0200 Subject: [PATCH 038/726] Fixed what merge-conflict-handling had broken. Restored exploration-without relying on memory but with included whirlpool --- .../Behaviors/ExplorationBehavior.cpp | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp b/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp index 0aa077688..a0bf96e63 100644 --- a/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp @@ -33,33 +33,30 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const { Goals::TGoalVec tasks; - for(auto obj : ai->memory->visitableObjs) + for (auto obj : ai->memory->visitableObjs) { switch (obj->ID.num) { case Obj::REDWOOD_OBSERVATORY: case Obj::PILLAR_OF_FIRE: - { - auto rObj = dynamic_cast(obj); - if(!rObj->wasScouted(ai->playerID)) - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj)))); - break; - } + { + auto rObj = dynamic_cast(obj); + if (!rObj->wasScouted(ai->playerID)) + tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj)))); + break; + } case Obj::MONOLITH_ONE_WAY_ENTRANCE: case Obj::MONOLITH_TWO_WAY: case Obj::SUBTERRANEAN_GATE: case Obj::WHIRLPOOL: + { + auto tObj = dynamic_cast(obj); + for (auto exit : cb->getTeleportChannelExits(tObj->channel)) { - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE == ai->memory->knownTeleportChannels[tObj->channel]->passability) - break; - for(auto exit : ai->memory->knownTeleportChannels[tObj->channel]->exits) + if (exit != tObj->id) { - if (exit != tObj->id) - { - if (!cb->isVisible(cb->getObjInstance(exit))) - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); - } + if (!cb->isVisible(cb->getObjInstance(exit))) + tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); } } } From 69b64a324163bfd6f1a5ef9e51c2b68a1df2be45 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 15:40:20 +0200 Subject: [PATCH 039/726] Update ExplorationBehavior.cpp Added missing bracked and changed indenting to make it less confusing. --- .../Behaviors/ExplorationBehavior.cpp | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp b/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp index a0bf96e63..b9c6e568d 100644 --- a/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ExplorationBehavior.cpp @@ -37,26 +37,27 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * ai) const { switch (obj->ID.num) { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - { - auto rObj = dynamic_cast(obj); - if (!rObj->wasScouted(ai->playerID)) - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj)))); - break; - } - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - case Obj::WHIRLPOOL: - { - auto tObj = dynamic_cast(obj); - for (auto exit : cb->getTeleportChannelExits(tObj->channel)) + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: { - if (exit != tObj->id) + auto rObj = dynamic_cast(obj); + if (!rObj->wasScouted(ai->playerID)) + tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 200)).addNext(CaptureObject(obj)))); + break; + } + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + case Obj::WHIRLPOOL: + { + auto tObj = dynamic_cast(obj); + for (auto exit : cb->getTeleportChannelExits(tObj->channel)) { - if (!cb->isVisible(cb->getObjInstance(exit))) - tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); + if (exit != tObj->id) + { + if (!cb->isVisible(cb->getObjInstance(exit))) + tasks.push_back(sptr(Composition().addNext(ExplorationPoint(obj->visitablePos(), 50)).addNext(CaptureObject(obj)))); + } } } } From aade79720f930972c1e6f47a251908d4d93be15b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:14:27 +0200 Subject: [PATCH 040/726] Update BuildAnalyzer.cpp Allow queuing citadels and castles on other days than satur- and sunday. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 248e75cf2..6e7cee145 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -74,11 +74,11 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday) { - otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE}); otherBuildings.push_back({BuildingID::HORDE_1}); otherBuildings.push_back({BuildingID::HORDE_2}); } + otherBuildings.push_back({ BuildingID::CITADEL, BuildingID::CASTLE }); otherBuildings.push_back({ BuildingID::RESOURCE_SILO }); otherBuildings.push_back({ BuildingID::SPECIAL_1 }); otherBuildings.push_back({ BuildingID::SPECIAL_2 }); From bbb5157f74315b6eb7e63fdc5f23c8443c41faa8 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:17:04 +0200 Subject: [PATCH 041/726] Update RecruitHeroBehavior.cpp Reworked recruit-behavior to be a bit more conservative and avoid recruiting-sprees. Stuff like buying several heros in a row because the next one is always slightly better than the last but using up the whole starting-bank for that. --- .../Behaviors/RecruitHeroBehavior.cpp | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 837e7e91d..961866dcc 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -13,6 +13,7 @@ #include "../AIUtility.h" #include "../Goals/RecruitHero.h" #include "../Goals/ExecuteHeroChain.h" +#include "../lib/CHeroHandler.h" namespace NKAI { @@ -49,28 +50,45 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const if (ourHeroes.empty()) minScoreToHireMain = 0; + const CGHeroInstance* bestHeroToHire = nullptr; + const CGTownInstance* bestTownToHireFrom = nullptr; + float bestScore = 0; + bool haveCapitol = false; + for(auto town : towns) { if(ai->heroManager->canRecruitHero(town)) { auto availableHeroes = ai->cb->getAvailableHeroes(town); - //TODO: Prioritize non-main-heros too by cost of their units and whether their units fit to the current town for(auto hero : availableHeroes) { auto score = ai->heroManager->evaluateHero(hero); - if(score > minScoreToHireMain || hero->getArmyCost() > GameConstants::HERO_GOLD_COST) + if(score > minScoreToHireMain) { - tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(200))); - break; + score *= score / minScoreToHireMain; + } + score *= hero->getArmyCost(); + if (hero->type->heroClass->faction == town->getFaction()) + score *= 1.5; + score *= town->getTownLevel(); + if (score > bestScore) + { + bestScore = score; + bestHeroToHire = hero; + bestTownToHireFrom = town; } } - - if(ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 - || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh())) - { - tasks.push_back(Goals::sptr(Goals::RecruitHero(town).setpriority(3))); - } + } + if (town->hasCapitol()) + haveCapitol = true; + } + if (bestHeroToHire && bestTownToHireFrom) + { + if (ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 + || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)) + { + tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / ourHeroes.size()))); } } From 19b969406c2e9455c73020532dd9e63092f793c9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:22:14 +0200 Subject: [PATCH 042/726] Update PriorityEvaluator.cpp Delivering troops to mains is now considered a priority 0 task that should immediately be fulfilled. Defending nearby towns against nearby enemies is now also considered a priority 0 task. Priority 0 tasks are now exclusively scored by distance and armyloss has only a cut-off-point instead of lowering the score. Building-cost now has more impact on their score. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 6d8a5a122..5c91eca81 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -895,6 +895,7 @@ public: uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai); evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength()); + evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength(); evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero); } }; @@ -1338,7 +1339,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, fuzzy: %f", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1348,11 +1349,13 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) goldRewardPerTurn, evaluationContext.goldCost, evaluationContext.armyReward, + evaluationContext.armyGrowth, evaluationContext.skillReward, evaluationContext.danger, evaluationContext.threat, evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.strategicalValue, + evaluationContext.conquestValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, fuzzyResult); @@ -1361,15 +1364,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { case 0: //Take towns { - score += evaluationContext.conquestValue * 1000; + //score += evaluationContext.conquestValue * 1000; + if(evaluationContext.conquestValue > 0 || (evaluationContext.isDefend && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement)) + score = 1000; if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 0.5 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) return 0; + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; score *= evaluationContext.closestWayRatio; - if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) - score *= evaluationContext.armyInvolvement / evaluationContext.threat; if (evaluationContext.movementCost > 0) score /= evaluationContext.movementCost; - score *= (maxWillingToLose - evaluationContext.armyLossPersentage); break; } case 1: //Collect unguarded stuff @@ -1384,6 +1388,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.buildingCost.marketValue() > 0) return 0; + if (evaluationContext.closestWayRatio < 1) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; @@ -1404,8 +1410,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (evaluationContext.buildingCost.marketValue() > 0) return 0; - if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) - return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; @@ -1415,12 +1419,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage; if (score > 0) { - if (evaluationContext.enemyHeroDangerRatio > 1) - if (evaluationContext.threat > 0) - score = evaluationContext.armyInvolvement / evaluationContext.threat; score *= evaluationContext.closestWayRatio; - if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) - score *= evaluationContext.armyInvolvement / evaluationContext.threat; + if (evaluationContext.threat > 0) + score /= evaluationContext.threat; if (evaluationContext.movementCost > 0) score /= evaluationContext.movementCost; score *= (maxWillingToLose - evaluationContext.armyLossPersentage); @@ -1449,6 +1450,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score += 1000; auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); auto income = ai->buildAnalyzer->getDailyIncome(); + score /= evaluationContext.buildingCost.marketValue(); if (resourcesAvailable < evaluationContext.buildingCost) { TResources needed = evaluationContext.buildingCost - resourcesAvailable; @@ -1467,7 +1469,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, fuzzy: %f, result %f", + logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f, result %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1477,11 +1479,13 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) goldRewardPerTurn, evaluationContext.goldCost, evaluationContext.armyReward, + evaluationContext.armyGrowth, evaluationContext.skillReward, evaluationContext.danger, evaluationContext.threat, evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.strategicalValue, + evaluationContext.conquestValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, fuzzyResult, From e2e3cd281c5f97601eceef13b5cfbbf5bbe93bba Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:23:20 +0200 Subject: [PATCH 043/726] Update RecruitHero.h Multiple orders for the same hero by different towns are now handled properly instead of trying to buy the same hero several times. --- AI/Nullkiller/Goals/RecruitHero.h | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Goals/RecruitHero.h b/AI/Nullkiller/Goals/RecruitHero.h index 101588f19..2fb932ed5 100644 --- a/AI/Nullkiller/Goals/RecruitHero.h +++ b/AI/Nullkiller/Goals/RecruitHero.h @@ -44,6 +44,7 @@ namespace Goals } std::string toString() const override; + const CGHeroInstance* getHero() { return heroToBuy; } void accept(AIGateway * ai) override; }; } From 8152b003fe89e92b0be6f74590d2dd7984a388dc Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:26:53 +0200 Subject: [PATCH 044/726] Update Nullkiller.cpp Fixed a bug that caused tasks that are generated with an initial priority not being executed. Fixed an error-message wrongfully claiming a hero was locked by STARTUP, when infact the hero was locked by something else (usually a hero-chain). Recruiting-heros is now handled alongside buying army and buildings. --- AI/Nullkiller/Engine/Nullkiller.cpp | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 9ef4816b4..37a44d221 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -129,7 +129,7 @@ void TaskPlan::merge(TSubgoal task) { for(auto objid : item.affectedObjects) { - if(task == item.task || task->asTask()->isObjectAffected(objid)) + if(task == item.task || task->asTask()->isObjectAffected(objid) || task->asTask()->getHero() == item.task->asTask()->getHero()) { if(item.task->asTask()->priority >= task->asTask()->priority) return; @@ -180,8 +180,7 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const for(size_t i = r.begin(); i != r.end(); i++) { auto task = tasks[i]; - - if(task->asTask()->priority <= 0) + if (task->asTask()->priority <= 0 || priorityTier != 3) task->asTask()->priority = evaluator->evaluate(task, priorityTier); } }); @@ -329,7 +328,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const if(lockReason != HeroLockedReason::NOT_LOCKED) { #if NKAI_TRACE_LEVEL >= 1 - logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString()); + logAi->trace("Hero %s is locked by %d. Discarding %s", path.targetHero->getObjectName(), (int)lockReason, path.toString()); #endif return true; } @@ -362,8 +361,6 @@ void Nullkiller::makeTurn() { totalTownLevel += townInfo->getTownLevel(); } - logAi->info("Resources: %s Strength: %f Townlevel: %d", cb->getResourceAmount().toString(), totalHeroStrength, totalTownLevel); - resetAiState(); @@ -371,6 +368,7 @@ void Nullkiller::makeTurn() for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++) { + logAi->info("Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); auto start = std::chrono::high_resolution_clock::now(); updateAiState(i); @@ -380,12 +378,13 @@ void Nullkiller::makeTurn() { bestTasks.clear(); + decompose(bestTasks, sptr(RecruitHeroBehavior()), 1); decompose(bestTasks, sptr(BuyArmyBehavior()), 1); decompose(bestTasks, sptr(BuildingBehavior()), 1); bestTask = choseBestTask(bestTasks); - if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY) + if(bestTask->priority > 0) { logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) @@ -399,7 +398,6 @@ void Nullkiller::makeTurn() } } - decompose(bestTasks, sptr(RecruitHeroBehavior()), 1); decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1); decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH); decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH); @@ -409,11 +407,15 @@ void Nullkiller::makeTurn() if(!isOpenMap()) decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH); - auto selectedTasks = buildPlan(bestTasks, 0); - if (selectedTasks.empty() && !settings->isUseFuzzy()) - selectedTasks = buildPlan(bestTasks, 1); - if (selectedTasks.empty() && !settings->isUseFuzzy()) - selectedTasks = buildPlan(bestTasks, 2); + TTaskVec selectedTasks; + int prioOfTask = 0; + for (int prio = 0; prio <= 2; ++prio) + { + prioOfTask = prio; + selectedTasks = buildPlan(bestTasks, prio); + if (!selectedTasks.empty() || settings->isUseFuzzy()) + break; + } std::sort(selectedTasks.begin(), selectedTasks.end(), [](const TTask& a, const TTask& b) { @@ -484,7 +486,7 @@ void Nullkiller::makeTurn() continue; } - logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); + logAi->info("Performing prio %d task %s with prio: %d", prioOfTask, bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) { if(hasAnySuccess) From 1e2021fb6d67b9e6eff45563288321eee49bcc19 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:41:49 +0200 Subject: [PATCH 045/726] Update RecruitHero.h Fix warning. --- AI/Nullkiller/Goals/RecruitHero.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Goals/RecruitHero.h b/AI/Nullkiller/Goals/RecruitHero.h index 2fb932ed5..c49644948 100644 --- a/AI/Nullkiller/Goals/RecruitHero.h +++ b/AI/Nullkiller/Goals/RecruitHero.h @@ -44,7 +44,7 @@ namespace Goals } std::string toString() const override; - const CGHeroInstance* getHero() { return heroToBuy; } + const CGHeroInstance* getHero() const override { return heroToBuy; } void accept(AIGateway * ai) override; }; } From 909c308614b77d675b398b2425c744cacc2d6587 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 24 Jul 2024 21:46:18 +0200 Subject: [PATCH 046/726] Update Nullkiller.cpp Removed unused variable. --- AI/Nullkiller/Engine/Nullkiller.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 37a44d221..3276f5dbd 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -349,7 +349,6 @@ void Nullkiller::makeTurn() boost::lock_guard sharedStorageLock(AISharedStorage::locker); const int MAX_DEPTH = 10; - const float FAST_TASK_MINIMAL_PRIORITY = 0.7f; float totalHeroStrength = 0; int totalTownLevel = 0; From 38da53135bd7d944f44224bf90bad04b9e07cf02 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 25 Jul 2024 21:47:56 +0200 Subject: [PATCH 047/726] Update DefenceBehavior.cpp A town will no longer communitcate that it doesn't need defenses, when it currently has a garrisioned hero. Because otherwise the garrisoned hero would just leave and let the town undefended. --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index e23efcb1c..7487791b2 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -158,11 +158,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there - if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai)) - { - return; - } - if(!threatNode.fastestDanger.hero) { logAi->trace("No threat found for town %s", town->getNameTranslated()); From 183ce82b99a4c7c0b5f71e22376424e7193015f7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 25 Jul 2024 21:48:44 +0200 Subject: [PATCH 048/726] Update Nullkiller.cpp Readded prio-3 tasks for heroes when no prio 0-2 tasks were found. It's anything that doesn't lose more than the loss-threshold. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 3276f5dbd..5dee24025 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -408,7 +408,7 @@ void Nullkiller::makeTurn() TTaskVec selectedTasks; int prioOfTask = 0; - for (int prio = 0; prio <= 2; ++prio) + for (int prio = 0; prio <= 3; ++prio) { prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); From 07108ce03da683597d86bd0036d83dbf654a9b2b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 25 Jul 2024 21:49:17 +0200 Subject: [PATCH 049/726] Update PriorityEvaluator.cpp Allow defending in priority 2. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 5c91eca81..8389282c4 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1408,6 +1408,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case 2: //Collect guarded stuff { + if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + return 0; if (evaluationContext.buildingCost.marketValue() > 0) return 0; score += evaluationContext.strategicalValue * 1000; @@ -1430,6 +1432,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case 3: //For buildings and buying army { + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; score += evaluationContext.conquestValue * 1000; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; From b0e4551dbfdc3ba50e8dbd943e0cc7f9a3710bc9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 7 Aug 2024 01:35:17 +0200 Subject: [PATCH 050/726] Update BuyArmyBehavior.cpp No longer saving money for city-halls when city-halls cannot be built. --- AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index e988c4519..610b16bcd 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -48,7 +48,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const } } - if (ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL)) + if (ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN) { return tasks; } From ec5da0e6b3393abd1adac8b0731be82b13bd6d7a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 7 Aug 2024 01:36:27 +0200 Subject: [PATCH 051/726] Update PriorityEvaluator.cpp Further working towards heroes avoiding danger. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 8389282c4..ab9039bad 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1466,6 +1466,11 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= turnsTo; } } + else + { + if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + return 0; + } break; } } From ea4e412f9acb4d910587953d1fa42d69dc51ccc8 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 7 Aug 2024 01:37:15 +0200 Subject: [PATCH 052/726] Update Nullkiller.cpp Only actual heroes and not nullptrs will exculde each other in comparison. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 5dee24025..d843258a1 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -129,7 +129,7 @@ void TaskPlan::merge(TSubgoal task) { for(auto objid : item.affectedObjects) { - if(task == item.task || task->asTask()->isObjectAffected(objid) || task->asTask()->getHero() == item.task->asTask()->getHero()) + if(task == item.task || task->asTask()->isObjectAffected(objid) || (task->asTask()->getHero() != nullptr && task->asTask()->getHero() == item.task->asTask()->getHero())) { if(item.task->asTask()->priority >= task->asTask()->priority) return; From 730e574bef9fad9e4e3baba06ce42015c70c367b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 12:44:07 +0200 Subject: [PATCH 053/726] Fixed AI ignoring garrisioned heroes when it comes to danger-analysis. AI now considers garrisoned heros a potential threat for their heroes too. --- AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 17f69c2af..27bfe5dd0 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -89,6 +89,14 @@ void DangerHitMapAnalyzer::updateHitMap() heroes[hero->tempOwner][hero] = HeroRole::MAIN; } + if (obj->ID == Obj::TOWN) + { + auto town = dynamic_cast(obj); + auto hero = town->garrisonHero; + + if(hero) + heroes[hero->tempOwner][hero] = HeroRole::MAIN; + } } auto ourTowns = cb->getTownsInfo(); From 5907aae05771dbfbd2d88a90cd16df9dd7b3000d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 17:59:10 +0200 Subject: [PATCH 054/726] Fixed AI not taking hero-strength into account when hero is garrisoned. When evaluating their fighting-chance against towns with a garrisoned hero the AI didn't consider the attribute-boosts of the defending hero. Now it does. --- AI/Nullkiller/Engine/FuzzyHelper.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AI/Nullkiller/Engine/FuzzyHelper.cpp b/AI/Nullkiller/Engine/FuzzyHelper.cpp index 10fab5cc9..912b29e8a 100644 --- a/AI/Nullkiller/Engine/FuzzyHelper.cpp +++ b/AI/Nullkiller/Engine/FuzzyHelper.cpp @@ -73,6 +73,14 @@ ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visit } objectDanger *= ai->heroManager->getFightingStrengthCached(hero); } + if (objWithID(dangerousObject)) + { + auto town = dynamic_cast(dangerousObject); + auto hero = town->garrisonHero; + + if (hero) + objectDanger *= ai->heroManager->getFightingStrengthCached(hero); + } if(objectDanger) { From 3218363bc06aacd5fac07f6eb91d47a132caac92 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 18:13:52 +0200 Subject: [PATCH 055/726] Update googletest No idea what this is. But I guess I'll commit it. --- test/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/googletest b/test/googletest index b514bdc89..b796f7d44 160000 --- a/test/googletest +++ b/test/googletest @@ -1 +1 @@ -Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 +Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 From 958aeb3a60495798309a17d3884ba7c50fad6729 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 18:21:24 +0200 Subject: [PATCH 056/726] AI improvements Deliberately defending towns in danger will now only be performed when the town has at least a citatel. The AI will now count the buildings per town-type and add a score-bonus to dwellings of towns it already has a lot of buildings of. This shall help with getting a stronger army with less morale-issues. Scoring for mage-guild now increased the bigger the existing armies are. AI is less afraid of enemy-heros than previously. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 25 ++++++++++++++++------ AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index ab9039bad..b58d2878b 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -57,6 +57,7 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) threat(0), armyGrowth(0), armyInvolvement(0), + defenseValue(0), isDefend(false), involvesSailing(false), isTradeBuilding(false) @@ -999,6 +1000,7 @@ public: else evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue); + evaluationContext.defenseValue = town->fortLevel(); evaluationContext.isDefend = true; vstd::amax(evaluationContext.danger, defendTown.getTreat().danger); @@ -1207,6 +1209,13 @@ public: evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount); evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount; } + int sameTownBonus = 0; + for (auto town : cb->getTownsInfo()) + { + if (buildThis.town->getFaction() == town->getFaction()) + sameTownBonus+=town->getTownLevel(); + } + evaluationContext.armyReward *= sameTownBonus; } else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE) { @@ -1216,6 +1225,10 @@ public: else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5) { evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1); + for (auto hero : cb->getHeroesInfo()) + { + evaluationContext.armyInvolvement += hero->getArmyCost(); + } } if(evaluationContext.goldReward) @@ -1365,9 +1378,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) case 0: //Take towns { //score += evaluationContext.conquestValue * 1000; - if(evaluationContext.conquestValue > 0 || (evaluationContext.isDefend && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement)) + if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement)) score = 1000; - if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 0.5 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) + if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1378,7 +1391,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case 1: //Collect unguarded stuff { - if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) return 0; if (evaluationContext.isDefend && evaluationContext.enemyHeroDangerRatio == 0) return 0; @@ -1408,7 +1421,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case 2: //Collect guarded stuff { - if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) return 0; if (evaluationContext.buildingCost.marketValue() > 0) return 0; @@ -1445,7 +1458,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (!evaluationContext.isTradeBuilding && ai->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && ai->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1) { logAi->trace("Should make sure to build market-place instead of %s", task->toString()); - for (auto town : cb->getTownsInfo()) + for (auto town : ai->cb->getTownsInfo()) { if (!town->hasBuiltSomeTradeBuilding()) return 0; @@ -1468,7 +1481,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } else { - if (evaluationContext.enemyHeroDangerRatio > 0.5 && !evaluationContext.isDefend) + if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && evaluationContext.conquestValue == 0) return 0; } break; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 2e15a4926..9b935f407 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -73,6 +73,7 @@ struct DLL_EXPORT EvaluationContext float enemyHeroDangerRatio; float threat; float armyInvolvement; + int defenseValue; bool isDefend; TResources buildingCost; bool involvesSailing; From ed059393ec38b486bea9262b2f394b1a20909aa9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 18:23:02 +0200 Subject: [PATCH 057/726] No more prio 3 tasks The AI no longer performs prio 3 tasks, when no prio 0-2 tasks were found. --- AI/Nullkiller/Engine/Nullkiller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index d843258a1..4dbbe36c8 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -408,7 +408,7 @@ void Nullkiller::makeTurn() TTaskVec selectedTasks; int prioOfTask = 0; - for (int prio = 0; prio <= 3; ++prio) + for (int prio = 0; prio <= 2; ++prio) { prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); @@ -658,7 +658,7 @@ bool Nullkiller::handleTrading() if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources { cb->trade(m, EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); - logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); + logAi->debug("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); haveTraded = true; shouldTryToTrade = true; } From fba34a743ef4e7e1e02bd76ee34574ff6f562bee Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 18:23:52 +0200 Subject: [PATCH 058/726] Update DefenceBehavior.cpp Reverted previous change to defense-behavior. Both approaches have pros and cons and neither really works as I want. This still needs work. --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 7487791b2..a740d48fd 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -158,6 +158,10 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there + if (town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks, ai)) + { + return; + } if(!threatNode.fastestDanger.hero) { logAi->trace("No threat found for town %s", town->getNameTranslated()); From a7b26e237ffe23b463df2f273891a512adc883e6 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 11 Aug 2024 21:59:06 +0200 Subject: [PATCH 059/726] Fix crash Fixed a crash due to improperly trying to access "cb". --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index b58d2878b..2f57d1a02 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1210,7 +1210,7 @@ public: evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount; } int sameTownBonus = 0; - for (auto town : cb->getTownsInfo()) + for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo()) { if (buildThis.town->getFaction() == town->getFaction()) sameTownBonus+=town->getTownLevel(); @@ -1225,7 +1225,7 @@ public: else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5) { evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1); - for (auto hero : cb->getHeroesInfo()) + for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo()) { evaluationContext.armyInvolvement += hero->getArmyCost(); } From 068e3bdc59d5f619352c85e2cf057ce49f81e2b0 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 12 Aug 2024 19:47:15 +0200 Subject: [PATCH 060/726] Fix endless loop Fixed an endless-loop caused by someone removing this ", turn++". --- AI/BattleAI/BattleExchangeVariant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 0200915c9..726c33304 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -731,7 +731,7 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn { std::vector result; - for(int i = 0; i < turnOrder.size(); i++) + for(int i = 0; i < turnOrder.size(); i++, turn++) { auto & turnQueue = turnOrder[i]; HypotheticBattle turnBattle(env.get(), cb); From 6bd442e6f16dccda0969ff30e961c096ab59ab5d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 13 Aug 2024 23:43:56 +0200 Subject: [PATCH 061/726] BattleAI: Fix endless loop Fixed an issue where the part of a 2-tile-unit that is outside of the map was considered a target and caused an endless-loop in the pathfinding being unable to get there. --- AI/BattleAI/AttackPossibility.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index d663a1bf4..ced430425 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -245,7 +245,7 @@ AttackPossibility AttackPossibility::evaluate( std::vector defenderHex; if(attackInfo.shooting) - defenderHex = defender->getHexes(); + defenderHex.push_back(defender->getPosition()); else defenderHex = CStack::meleeAttackHexes(attacker, defender, hex); From c10c04779fa74d6d85844d003705be7f45bc582f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 14 Aug 2024 22:52:19 +0200 Subject: [PATCH 062/726] Adaptive Build-order When not threatened by nearby enemies the AI adds missing gold-income-buildings towards gold-pressure. This impacts the build-order in a way that they try to rush these more and get up a good economy more quickly. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index be50aa6da..8cab129aa 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -149,9 +149,17 @@ void BuildAnalyzer::update() auto towns = ai->cb->getTownsInfo(); + float economyDevelopmentCost = 0; + uint8_t closestThreat = UINT8_MAX; + ai->dangerHitMap->updateHitMap(); + for(const CGTownInstance* town : towns) { - logAi->trace("Checking town %s", town->getNameTranslated()); + for (auto threat : ai->dangerHitMap->getTownThreats(town)) + { + closestThreat = std::min(closestThreat, threat.turn); + } + logAi->trace("Checking town %s closest threat: %u", town->getNameTranslated(), (unsigned int)closestThreat); developmentInfos.push_back(TownDevelopmentInfo(town)); TownDevelopmentInfo & developmentInfo = developmentInfos.back(); @@ -161,6 +169,11 @@ void BuildAnalyzer::update() requiredResources += developmentInfo.requiredResources; totalDevelopmentCost += developmentInfo.townDevelopmentCost; + for(auto building : developmentInfo.toBuild) + { + if (building.dailyIncome[EGameResID::GOLD] > 0) + economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD]; + } armyCost += developmentInfo.armyCost; for(auto bi : developmentInfo.toBuild) @@ -169,6 +182,9 @@ void BuildAnalyzer::update() } } + if (closestThreat < 7) + economyDevelopmentCost = 0; + std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool { auto val1 = convertToGold(t1.armyCost) - convertToGold(t1.townDevelopmentCost); @@ -180,7 +196,7 @@ void BuildAnalyzer::update() updateDailyIncome(); goldPressure = ai->getLockedResources()[EGameResID::GOLD] / 5000.0f - + (float)armyCost[EGameResID::GOLD] / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f); + + ((float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f); logAi->trace("Gold pressure: %f", goldPressure); } From b7e4219fdeeac3fb7a7f4abaa57cacb205df1c7d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 14 Aug 2024 22:53:48 +0200 Subject: [PATCH 063/726] More purposeful defending Avoid defending towns that are out of reach for the enemy. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 24 ++++++++++++++-------- AI/Nullkiller/Engine/PriorityEvaluator.h | 2 ++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 2f57d1a02..7e9891317 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -59,8 +59,10 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) armyInvolvement(0), defenseValue(0), isDefend(false), + threatTurns(INT_MAX), involvesSailing(false), - isTradeBuilding(false) + isTradeBuilding(false), + isChain(false) { } @@ -1002,6 +1004,7 @@ public: evaluationContext.defenseValue = town->fortLevel(); evaluationContext.isDefend = true; + evaluationContext.threatTurns = treat.turn; vstd::amax(evaluationContext.danger, defendTown.getTreat().danger); addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength()); @@ -1027,6 +1030,7 @@ public: vstd::amax(evaluationContext.danger, path.getTotalDanger()); evaluationContext.movementCost += path.movementCost(); evaluationContext.closestWayRatio = chain.closestWayRatio; + evaluationContext.isChain = true; std::map costsPerHero; @@ -1352,7 +1356,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1365,6 +1369,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.armyGrowth, evaluationContext.skillReward, evaluationContext.danger, + evaluationContext.threatTurns, evaluationContext.threat, evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.strategicalValue, @@ -1378,7 +1383,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) case 0: //Take towns { //score += evaluationContext.conquestValue * 1000; - if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement)) + if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement && evaluationContext.threatTurns == 0)) score = 1000; if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) return 0; @@ -1391,9 +1396,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case 1: //Collect unguarded stuff { - if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) + if (evaluationContext.enemyHeroDangerRatio > 1) return 0; - if (evaluationContext.isDefend && evaluationContext.enemyHeroDangerRatio == 0) + if (evaluationContext.isDefend) return 0; if (evaluationContext.armyLossPersentage > 0) return 0; @@ -1425,6 +1430,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.buildingCost.marketValue() > 0) return 0; + if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0)) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; @@ -1435,8 +1442,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (score > 0) { score *= evaluationContext.closestWayRatio; - if (evaluationContext.threat > 0) - score /= evaluationContext.threat; + if (evaluationContext.enemyHeroDangerRatio > 1) + score /= evaluationContext.enemyHeroDangerRatio; if (evaluationContext.movementCost > 0) score /= evaluationContext.movementCost; score *= (maxWillingToLose - evaluationContext.armyLossPersentage); @@ -1491,7 +1498,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f, result %f", + logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f, result %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1504,6 +1511,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.armyGrowth, evaluationContext.skillReward, evaluationContext.danger, + evaluationContext.threatTurns, evaluationContext.threat, evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout", evaluationContext.strategicalValue, diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 9b935f407..b7c2bff9a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -75,9 +75,11 @@ struct DLL_EXPORT EvaluationContext float armyInvolvement; int defenseValue; bool isDefend; + int threatTurns; TResources buildingCost; bool involvesSailing; bool isTradeBuilding; + bool isChain; EvaluationContext(const Nullkiller * ai); From f0ca1c6112c495dedb617f36d19634e9f2d9048c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 15 Aug 2024 18:13:44 +0200 Subject: [PATCH 064/726] Update BuildAnalyzer.cpp Removed the restrictions of the greedy-playstyle. Only count forts as gold-producing prerequisites when no same- or higher-level fort exists somewhere else in the empire. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 27 ++++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 8cab129aa..648023bf3 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -150,16 +150,10 @@ void BuildAnalyzer::update() auto towns = ai->cb->getTownsInfo(); float economyDevelopmentCost = 0; - uint8_t closestThreat = UINT8_MAX; - ai->dangerHitMap->updateHitMap(); for(const CGTownInstance* town : towns) { - for (auto threat : ai->dangerHitMap->getTownThreats(town)) - { - closestThreat = std::min(closestThreat, threat.turn); - } - logAi->trace("Checking town %s closest threat: %u", town->getNameTranslated(), (unsigned int)closestThreat); + logAi->trace("Checking town %s", town->getNameTranslated()); developmentInfos.push_back(TownDevelopmentInfo(town)); TownDevelopmentInfo & developmentInfo = developmentInfos.back(); @@ -182,9 +176,6 @@ void BuildAnalyzer::update() } } - if (closestThreat < 7) - economyDevelopmentCost = 0; - std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool { auto val1 = convertToGold(t1.armyCost) - convertToGold(t1.townDevelopmentCost); @@ -254,6 +245,12 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( logAi->trace("checking %s", info.name); logAi->trace("buildInfo %s", info.toString()); + int highestFort = 0; + for (auto twn : ai->cb->getTownsInfo()) + { + highestFort = std::max(highestFort, (int)twn->fortLevel()); + } + if(!town->hasBuilt(building)) { auto canBuild = ai->cb->canBuildStructure(town, building); @@ -298,7 +295,15 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( prerequisite.baseCreatureID = info.baseCreatureID; prerequisite.prerequisitesCount++; prerequisite.armyCost = info.armyCost; - prerequisite.dailyIncome = info.dailyIncome; + bool haveSameOrBetterFort = false; + if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT) + haveSameOrBetterFort = true; + if (prerequisite.id == BuildingID::CITADEL && highestFort >= CGTownInstance::EFortLevel::CITADEL) + haveSameOrBetterFort = true; + if (prerequisite.id == BuildingID::CASTLE && highestFort >= CGTownInstance::EFortLevel::CASTLE) + haveSameOrBetterFort = true; + if(!haveSameOrBetterFort) + prerequisite.dailyIncome = info.dailyIncome; return prerequisite; } From 6193e6224f84afaeac63ed797cd8facd47cff564 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 15 Aug 2024 18:14:48 +0200 Subject: [PATCH 065/726] Update FuzzyHelper.cpp Added Multiplicative danger-modifier to citadels and castles. --- AI/Nullkiller/Engine/FuzzyHelper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Engine/FuzzyHelper.cpp b/AI/Nullkiller/Engine/FuzzyHelper.cpp index 912b29e8a..80daaca09 100644 --- a/AI/Nullkiller/Engine/FuzzyHelper.cpp +++ b/AI/Nullkiller/Engine/FuzzyHelper.cpp @@ -145,10 +145,10 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj) { auto fortLevel = town->fortLevel(); - if(fortLevel == CGTownInstance::EFortLevel::CASTLE) - danger += 10000; + if (fortLevel == CGTownInstance::EFortLevel::CASTLE) + danger = std::max(danger * 2, danger + 10000); else if(fortLevel == CGTownInstance::EFortLevel::CITADEL) - danger += 4000; + danger = std::max(ui64(danger * 1.4), danger + 4000); } return danger; From a79f76f32be284ea693d7e7fcca3ca8f13e3939a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 15 Aug 2024 18:15:29 +0200 Subject: [PATCH 066/726] Update Nullkiller.cpp Fix issue with selling/buying the same resources back and forth. But probably leads to not using the market early on. (needs more testing) --- AI/Nullkiller/Engine/Nullkiller.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 4dbbe36c8..8b3843c90 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -601,7 +601,10 @@ bool Nullkiller::handleTrading() buildAnalyzer->update(); TResources required = buildAnalyzer->getTotalResourcesRequired(); TResources income = buildAnalyzer->getDailyIncome(); - TResources available = getFreeResources(); + TResources available = cb->getResourceAmount(); + + logAi->debug("Available %s", available.toString()); + logAi->debug("Required %s", required.toString()); int mostWanted = -1; int mostExpendable = -1; @@ -610,7 +613,7 @@ bool Nullkiller::handleTrading() for (int i = 0; i < required.size(); ++i) { - if (required[i] == 0) + if (required[i] <= 0) continue; float ratio = static_cast(available[i]) / required[i]; @@ -625,6 +628,8 @@ bool Nullkiller::handleTrading() float ratio = available[i]; if (required[i] > 0) ratio = static_cast(available[i]) / required[i]; + else + ratio = available[i]; bool okToSell = false; @@ -635,7 +640,7 @@ bool Nullkiller::handleTrading() } else { - if (available[i] > required[i]) + if (required[i] <= 0) okToSell = true; } @@ -645,7 +650,7 @@ bool Nullkiller::handleTrading() } } - //logAi->info("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted); + logAi->debug("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted); if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1) return false; From 8ad6d712c0ad82b41f1f2924d5f7d143032bc26c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 15 Aug 2024 18:16:23 +0200 Subject: [PATCH 067/726] lowered aggression Being less willing to rush across half the map to attack an enemy town only to find it too well defended when arriving there. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 10 +++++++++- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 7e9891317..5b7463fd2 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -62,7 +62,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) threatTurns(INT_MAX), involvesSailing(false), isTradeBuilding(false), - isChain(false) + isChain(false), + isEnemy(false) { } @@ -1068,6 +1069,8 @@ public: evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); evaluationContext.armyInvolvement += army->getArmyCost(); + if (target->tempOwner != PlayerColor::NEUTRAL) + evaluationContext.isEnemy = true; } vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength()); @@ -1116,6 +1119,9 @@ public: evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost; evaluationContext.movementCost += objInfo.second.movementCost / boost; + if (target->tempOwner != PlayerColor::NEUTRAL) + evaluationContext.isEnemy = true; + vstd::amax(evaluationContext.turn, objInfo.second.turn / boost); boost <<= 1; @@ -1385,6 +1391,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) //score += evaluationContext.conquestValue * 1000; if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement && evaluationContext.threatTurns == 0)) score = 1000; + if (evaluationContext.isEnemy && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty()) + return 0; if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index b7c2bff9a..a7d346e17 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -80,6 +80,7 @@ struct DLL_EXPORT EvaluationContext bool involvesSailing; bool isTradeBuilding; bool isChain; + bool isEnemy; EvaluationContext(const Nullkiller * ai); From 3be25d94147ad27cc79cee532e607f93d35850f7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 09:47:05 +0200 Subject: [PATCH 068/726] Update PriorityEvaluator.cpp Defend towns 1 turn earlier. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 5b7463fd2..b11f19334 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1389,7 +1389,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) case 0: //Take towns { //score += evaluationContext.conquestValue * 1000; - if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement && evaluationContext.threatTurns == 0)) + if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement && evaluationContext.threatTurns <= 1)) score = 1000; if (evaluationContext.isEnemy && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty()) return 0; From 284f2761088541727133721be9b21241e9cca974 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 09:47:37 +0200 Subject: [PATCH 069/726] Update Nullkiller.cpp Don't trade away gold when the gold-pressure is high. --- AI/Nullkiller/Engine/Nullkiller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 8b3843c90..f9954a6d2 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -635,7 +635,7 @@ bool Nullkiller::handleTrading() if (i == 6) { - if (income[i] > 0) + if (income[i] > 0 && !buildAnalyzer->isGoldPressureHigh()) okToSell = true; } else @@ -663,7 +663,7 @@ bool Nullkiller::handleTrading() if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources { cb->trade(m, EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); - logAi->debug("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); + logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); haveTraded = true; shouldTryToTrade = true; } From ea5ee039ca60227480add95a98c33b5d064e27a8 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 09:48:16 +0200 Subject: [PATCH 070/726] Update BuildingBehavior.cpp Prioritize defensive buildings in threatened towns. --- AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 49 ++++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index 2cdc2ead3..b24ce0079 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -49,28 +49,47 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo(); auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureHigh(); + ai->dangerHitMap->updateHitMap(); + for(auto & developmentInfo : developmentInfos) { - for(auto & buildingInfo : developmentInfo.toBuild) + uint8_t closestThreat = UINT8_MAX; + for (auto threat : ai->dangerHitMap->getTownThreats(developmentInfo.town)) { - if(isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) + closestThreat = std::min(closestThreat, threat.turn); + } + int fortLevel = developmentInfo.town->fortLevel(); + for (auto& buildingInfo : developmentInfo.toBuild) + { + if (closestThreat <= 1 && developmentInfo.town->fortLevel() < BuildingID::CASTLE && !buildingInfo.notEnoughRes) { - if(buildingInfo.notEnoughRes) - { - if(ai->getLockedResources().canAfford(buildingInfo.buildCost)) - continue; - - Composition composition; - - composition.addNext(BuildThis(buildingInfo, developmentInfo)); - composition.addNext(SaveResources(buildingInfo.buildCost)); - - tasks.push_back(sptr(composition)); - } - else + if (buildingInfo.id == BuildingID::FORT || buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE) tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); } } + if (tasks.empty()) + { + for (auto& buildingInfo : developmentInfo.toBuild) + { + if (isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) + { + if (buildingInfo.notEnoughRes) + { + if (ai->getLockedResources().canAfford(buildingInfo.buildCost)) + continue; + + Composition composition; + + composition.addNext(BuildThis(buildingInfo, developmentInfo)); + composition.addNext(SaveResources(buildingInfo.buildCost)); + + tasks.push_back(sptr(composition)); + } + else + tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); + } + } + } } return tasks; From 65b85766877e28cc67960d9689a597a130d8d04e Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 09:49:02 +0200 Subject: [PATCH 071/726] Update BuyArmyBehavior.cpp Allow building army in threatened town even when it wants to save for a building. --- AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index 610b16bcd..3d15ff9e8 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -35,6 +35,8 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const return tasks; } + ai->dangerHitMap->updateHitMap(); + for(auto town : cb->getTownsInfo()) { //If we can recruit a hero that comes with more army than he costs, we are better off spending our gold on them @@ -48,7 +50,13 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const } } - if (ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN) + uint8_t closestThreat = UINT8_MAX; + for (auto threat : ai->dangerHitMap->getTownThreats(town)) + { + closestThreat = std::min(closestThreat, threat.turn); + } + + if (closestThreat >=2 && ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN) { return tasks; } From bdbb9d02fc3a597cfec39f3e77ee741727114f3c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 09:49:38 +0200 Subject: [PATCH 072/726] Update DefenceBehavior.cpp Fixed an issue where heroes that were leaving towns were still considered as defending the town. --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index a740d48fd..c0dacd8f4 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -130,7 +130,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5))); - return true; + return false; } else if(ai->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN) { @@ -141,7 +141,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa { tasks.push_back(Goals::sptr(Goals::DismissHero(heroToDismiss).setpriority(5))); - return true; + return false; } } } @@ -342,7 +342,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta } else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero)) { - if(town->garrisonHero) + if(town->garrisonHero && town->garrisonHero != path.targetHero) { if(ai->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT && town->visitingHero->getArmyStrength() < path.heroArmy->getArmyStrength() / 20) From af7d5c7f7fda798369deb6cf973b4656f906900d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 09:50:32 +0200 Subject: [PATCH 073/726] Update RecruitHeroBehavior.cpp Don't hire a hero in a town where another hero is currently defending against a threat. This would mean one of them has to stay outside and be exposed. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 961866dcc..27a784df6 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -54,9 +54,19 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const const CGTownInstance* bestTownToHireFrom = nullptr; float bestScore = 0; bool haveCapitol = false; + + ai->dangerHitMap->updateHitMap(); for(auto town : towns) { + uint8_t closestThreat = UINT8_MAX; + for (auto threat : ai->dangerHitMap->getTownThreats(town)) + { + closestThreat = std::min(closestThreat, threat.turn); + } + //Don' hire a hero in a threatened town as one would have to stay outside + if (closestThreat <= 1 && (town->visitingHero || town->garrisonHero)) + continue; if(ai->heroManager->canRecruitHero(town)) { auto availableHeroes = ai->cb->getAvailableHeroes(town); From 00e5770aa33d82a016f0fe0fd3156f8001c37c65 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 18 Aug 2024 21:22:05 +0200 Subject: [PATCH 074/726] Update PriorityEvaluator.cpp Revert change that made AI too passive. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index b08785852..0aa42144e 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1389,8 +1389,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) //score += evaluationContext.conquestValue * 1000; if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement && evaluationContext.threatTurns <= 1)) score = 1000; - if (evaluationContext.isEnemy && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty()) - return 0; if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) From e86ca49c379a9ce341f086d50d20382a5699efb3 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 19 Aug 2024 21:15:25 +0200 Subject: [PATCH 075/726] Update BuildingBehavior.cpp Fixed warning --- AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index b24ce0079..551f0d73c 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -58,7 +58,6 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const { closestThreat = std::min(closestThreat, threat.turn); } - int fortLevel = developmentInfo.town->fortLevel(); for (auto& buildingInfo : developmentInfo.toBuild) { if (closestThreat <= 1 && developmentInfo.town->fortLevel() < BuildingID::CASTLE && !buildingInfo.notEnoughRes) From 8cf99616d01f317102234ef24cb664b41dd7b537 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 19 Aug 2024 21:21:56 +0200 Subject: [PATCH 076/726] Update BuildingBehavior.cpp Fixed a warning which, in this case, was actually also a logical error! :o --- AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index 551f0d73c..cb9af8405 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -60,7 +60,7 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const } for (auto& buildingInfo : developmentInfo.toBuild) { - if (closestThreat <= 1 && developmentInfo.town->fortLevel() < BuildingID::CASTLE && !buildingInfo.notEnoughRes) + if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE && !buildingInfo.notEnoughRes) { if (buildingInfo.id == BuildingID::FORT || buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE) tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); From 85ee859b6ec3d1b16e5d132202bf2184521ea1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Wed, 21 Aug 2024 20:16:41 +0200 Subject: [PATCH 077/726] First version that works - banning objects is possible --- lib/CMakeLists.txt | 2 + .../CObjectClassesHandler.cpp | 84 +++++ .../CObjectClassesHandler.h | 8 +- lib/rmg/CRmgTemplate.cpp | 86 +++++ lib/rmg/CRmgTemplate.h | 41 +++ lib/rmg/ObjectInfo.cpp | 71 ++++ lib/rmg/ObjectInfo.h | 40 +++ lib/rmg/modificators/TreasurePlacer.cpp | 325 +++++++++++++----- lib/rmg/modificators/TreasurePlacer.h | 59 +++- 9 files changed, 612 insertions(+), 104 deletions(-) create mode 100644 lib/rmg/ObjectInfo.cpp create mode 100644 lib/rmg/ObjectInfo.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a7f4fbd67..7ed8cb697 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -181,6 +181,7 @@ set(lib_MAIN_SRCS rmg/TileInfo.cpp rmg/Zone.cpp rmg/Functions.cpp + rmg/ObjectInfo.cpp rmg/RmgMap.cpp rmg/PenroseTiling.cpp rmg/modificators/Modificator.cpp @@ -583,6 +584,7 @@ set(lib_MAIN_HEADERS rmg/RmgMap.h rmg/float3.h rmg/Functions.h + rmg/ObjectInfo.h rmg/PenroseTiling.h rmg/modificators/Modificator.h rmg/modificators/ObjectManager.h diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 62c429a09..f91a3871c 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -382,6 +382,78 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID comp return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID); } +CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const +{ + std::optional id; + if (scope.empty()) + { + id = VLC->identifiers()->getIdentifier("object", type); + } + else + { + id = VLC->identifiers()->getIdentifier(scope, "object", type); + } + + if(id) + { + const auto & object = objects.at(id.value()); + std::optional subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype); + + if (subID) + return CompoundMapObjectID(id.value(), subID.value()); + } + + std::string errorString = "Failed to get id for object of type " + type + "." + subtype; + logGlobal->error(errorString); + throw std::runtime_error(errorString); +} + +CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const +{ + // FIXME: Crash with no further log + //"core:object.creatureBank.experimentalShop", + //"core:object.creatureBank.wolfRiderPicket", + //"core:object.creatureBank.demonDomain" + + // TODO: Use existing utilities for parsing id? + JsonNode node(objectName); + auto modScope = node.getModScope(); + + std::string scope, type, subtype; + size_t firstColon = objectName.find(':'); + size_t lastDot = objectName.find_last_of('.'); + + // TODO: Ignore object class, there should not be objects with same names within one scope anyway + + if(firstColon != std::string::npos) + { + scope = objectName.substr(0, firstColon); + if(lastDot != std::string::npos && lastDot > firstColon) + { + type = objectName.substr(firstColon + 1, lastDot - firstColon - 1); + subtype = objectName.substr(lastDot + 1); + } + else + { + type = objectName.substr(firstColon + 1); + } + } + else + { + if(lastDot != std::string::npos) + { + type = objectName.substr(0, lastDot); + subtype = objectName.substr(lastDot + 1); + } + else + { + type = objectName; + } + } + + return getCompoundIdentifier(scope, type, subtype); +} + std::set CObjectClassesHandler::knownObjects() const { std::set ret; @@ -451,6 +523,18 @@ void CObjectClassesHandler::afterLoadFinalization() logGlobal->warn("No templates found for %s:%s", entry->getJsonKey(), obj->getJsonKey()); } } + + for(auto & entry : objectIdHandlers) + { + // Call function for each object id + entry.second(entry.first); + } +} + +void CObjectClassesHandler::resolveObjectCompoundId(const std::string & id, std::function callback) +{ + auto compoundId = getCompoundIdentifier(id); + objectIdHandlers.push_back(std::make_pair(compoundId, callback)); } void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container) diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.h b/lib/mapObjectConstructors/CObjectClassesHandler.h index 97521688c..ce94f67ae 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.h +++ b/lib/mapObjectConstructors/CObjectClassesHandler.h @@ -74,6 +74,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyabl /// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function std::map > handlerConstructors; + std::vector>> objectIdHandlers; + /// container with H3 templates, used only during loading, no need to serialize it using TTemplatesContainer = std::multimap, std::shared_ptr>; TTemplatesContainer legacyTemplates; @@ -110,15 +112,19 @@ public: TObjectTypeHandler getHandlerFor(MapObjectID type, MapObjectSubID subtype) const; TObjectTypeHandler getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const; TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const; + CompoundMapObjectID getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const; + CompoundMapObjectID getCompoundIdentifier(const std::string & objectName) const; std::string getObjectName(MapObjectID type, MapObjectSubID subtype) const; SObjectSounds getObjectSounds(MapObjectID type, MapObjectSubID subtype) const; + void resolveObjectCompoundId(const std::string & id, std::function callback); + /// Returns handler string describing the handler (for use in client) std::string getObjectHandlerName(MapObjectID type) const; std::string getJsonKey(MapObjectID type) const; }; -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index e5fb4836b..e512676ed 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -102,6 +102,7 @@ void ZoneOptions::CTownInfo::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("castles", castleCount, 0); handler.serializeInt("townDensity", townDensity, 0); handler.serializeInt("castleDensity", castleDensity, 0); + handler.serializeInt("sourceZone", sourceZone, NO_ZONE); } ZoneOptions::ZoneOptions(): @@ -398,6 +399,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mines[idx], 0); } } + + handler.serializeStruct("customObjects", objectConfig); } ZoneConnection::ZoneConnection(): @@ -837,6 +840,7 @@ void CRmgTemplate::afterLoad() allowedWaterContent.erase(EWaterContent::RANDOM); } +// TODO: Allow any integer size which does not match enum, as well void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName) { static const std::map sizeMapping = @@ -905,5 +909,87 @@ void CRmgTemplate::serializePlayers(JsonSerializeFormat & handler, CPlayerCountR value.fromString(encodedValue); } +void ZoneOptions::ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) +{ + // FIXME: We do not need to store the object info, just the id + + bannedObjects.push_back(objid); + + logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); +} + +void ZoneOptions::ObjectConfig::serializeJson(JsonSerializeFormat & handler) +{ + // TODO: Implement' + + auto bannedObjectData = handler.enterArray("bannedObjects"); + if (handler.saving) + { + + // FIXME: Do we even need to serialize / store banned objects? + /* + for (const auto & object : bannedObjects) + { + // TODO: Translate id back to string? + + + JsonNode node; + node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); + // TODO: Check if AI-generated code is right + + + } + // handler.serializeRaw("bannedObjects", node, std::nullopt); + + */ + } + else + { + /* + auto zonesData = handler.enterStruct("zones"); + for(const auto & idAndZone : zonesData->getCurrent().Struct()) + { + auto guard = handler.enterStruct(idAndZone.first); + auto zone = std::make_shared(); + zone->setId(decodeZoneId(idAndZone.first)); + zone->serializeJson(handler); + zones[zone->getId()] = zone; + } + */ + std::vector objectNames; + bannedObjectData.serializeArray(objectNames); + + for (const auto & objectName : objectNames) + { + VLC->objtypeh->resolveObjectCompoundId(objectName, + [this](CompoundMapObjectID objid) + { + addBannedObject(objid); + } + ); + + } + } +} + +const std::vector & ZoneOptions::getBannedObjects() const +{ + return objectConfig.getBannedObjects(); +} + +const std::vector & ZoneOptions::getCustomObjects() const +{ + return objectConfig.getCustomObjects(); +} + +const std::vector & ZoneOptions::ObjectConfig::getBannedObjects() const +{ + return bannedObjects; +} + +const std::vector & ZoneOptions::ObjectConfig::getCustomObjects() const +{ + return customObjects; +} VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 1d0382ad1..1db3b68b1 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -13,10 +13,13 @@ #include "../int3.h" #include "../GameConstants.h" #include "../ResourceSet.h" +#include "ObjectInfo.h" +#include "../mapObjectConstructors/CObjectClassesHandler.h" VCMI_LIB_NAMESPACE_BEGIN class JsonSerializeFormat; +struct CompoundMapObjectID; enum class ETemplateZoneType { @@ -131,8 +134,37 @@ public: int castleCount; int townDensity; int castleDensity; + + // TODO: Copy from another zone once its randomized + TRmgTemplateZoneId sourceZone = NO_ZONE; }; + // TODO: Store config for custom objects to spawn in this zone + // TODO: Read custom object config from zone file + class DLL_LINKAGE ObjectConfig + { + public: + //ObjectConfig() = default; + + void addBannedObject(const CompoundMapObjectID & objid); + void addCustomObject(const ObjectInfo & object); + void clearBannedObjects(); + void clearCustomObjects(); + const std::vector & getBannedObjects() const; + const std::vector & getCustomObjects() const; + + // TODO: Separate serializer + void serializeJson(JsonSerializeFormat & handler); + private: + // TODO: Add convenience method for banning objects by name + std::vector bannedObjects; + + // TODO: In what format should I store custom objects? + // Need to convert map serialization format to ObjectInfo + std::vector customObjects; + }; + // TODO: Allow to copy all custom objects config from another zone + ZoneOptions(); TRmgTemplateZoneId getId() const; @@ -182,12 +214,17 @@ public: bool areTownsSameType() const; bool isMatchTerrainToTown() const; + const std::vector & getBannedObjects() const; + const std::vector & getCustomObjects() const; + protected: TRmgTemplateZoneId id; ETemplateZoneType type; int size; ui32 maxTreasureValue; std::optional owner; + + ObjectConfig objectConfig; CTownInfo playerTowns; CTownInfo neutralTowns; bool matchTerrainToTown; @@ -276,6 +313,10 @@ private: std::set inheritTerrainType(std::shared_ptr zone, uint32_t iteration = 0); std::map inheritMineTypes(std::shared_ptr zone, uint32_t iteration = 0); std::vector inheritTreasureInfo(std::shared_ptr zone, uint32_t iteration = 0); + + // TODO: Copy custom object settings + // TODO: Copy town type after source town is actually randomized + void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName); void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName); }; diff --git a/lib/rmg/ObjectInfo.cpp b/lib/rmg/ObjectInfo.cpp new file mode 100644 index 000000000..a725ffb89 --- /dev/null +++ b/lib/rmg/ObjectInfo.cpp @@ -0,0 +1,71 @@ +/* + * ObjectInfo.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "ObjectInfo.h" + +#include "../VCMI_Lib.h" +#include "../mapObjectConstructors/CObjectClassesHandler.h" +#include "../mapObjectConstructors/AObjectTypeHandler.h" + +VCMI_LIB_NAMESPACE_BEGIN + +ObjectInfo::ObjectInfo(): + destroyObject([](CGObjectInstance * obj){}), + maxPerZone(std::numeric_limits::max()) +{ +} + +ObjectInfo::ObjectInfo(const ObjectInfo & other) +{ + templates = other.templates; + value = other.value; + probability = other.probability; + maxPerZone = other.maxPerZone; + generateObject = other.generateObject; + destroyObject = other.destroyObject; +} + +ObjectInfo & ObjectInfo::operator=(const ObjectInfo & other) +{ + if (this == &other) + return *this; + + templates = other.templates; + value = other.value; + probability = other.probability; + maxPerZone = other.maxPerZone; + generateObject = other.generateObject; + destroyObject = other.destroyObject; + return *this; +} + +void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType) +{ + auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); + if(!templHandler) + return; + + templates = templHandler->getTemplates(terrainType); +} + +/* +bool ObjectInfo::matchesId(const CompoundMapObjectID & id) const +{ + for (const auto & templ : templates) + { + if (id.primaryID == templ->id && id.secondaryID == templ->subid) + return true; + } + return false; +} +*/ + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h new file mode 100644 index 000000000..ae90ee448 --- /dev/null +++ b/lib/rmg/ObjectInfo.h @@ -0,0 +1,40 @@ +/* + * ObjectInfo.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../mapObjects/ObjectTemplate.h" +#include "../constants/EntityIdentifiers.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct CompoundMapObjectID; +class CGObjectInstance; + +struct DLL_LINKAGE ObjectInfo +{ + ObjectInfo(); + ObjectInfo(const ObjectInfo & other); + ObjectInfo & operator=(const ObjectInfo & other); + + std::vector> templates; + ui32 value = 0; + ui16 probability = 0; + ui32 maxPerZone = 1; + //ui32 maxPerMap; //unused + std::function generateObject; + std::function destroyObject; + + void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain); + + //bool matchesId(const CompoundMapObjectID & id) const; +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 13de65b39..b40d16736 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -37,15 +37,19 @@ VCMI_LIB_NAMESPACE_BEGIN -ObjectInfo::ObjectInfo(): - destroyObject([](CGObjectInstance * obj){}) -{ - -} - void TreasurePlacer::process() { + if (zone.getMaxTreasureValue() == 0) + { + //No treasures at all + return; + } + + // Get default objects addAllPossibleObjects(); + // Override with custom objects + objects.patchWithZoneConfig(zone); + auto * m = zone.getModificator(); if(m) createTreasures(*m); @@ -58,15 +62,52 @@ void TreasurePlacer::init() DEPENDENCY(ConnectionsPlacer); DEPENDENCY_ALL(PrisonHeroPlacer); DEPENDENCY(RoadPlacer); + + // Add all native creatures + for(auto const & cre : VLC->creh->objects) + { + if(!cre->special && cre->getFaction() == zone.getTownType()) + { + creatures.push_back(cre.get()); + } + } + + tierValues = generator.getConfig().pandoraCreatureValues; } void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi) { + // FIXME: It is never the case - objects must be erased or badly copied after this + if (oi.templates.empty()) + { + logGlobal->error("Attempt to add ObjectInfo with no templates! Value: %d", oi.value); + return; + } + if (!oi.generateObject) + { + logGlobal->error("Attempt to add ObjectInfo with no generateObject function! Value: %d", oi.value); + return; + } + if (!oi.maxPerZone) + { + logGlobal->warn("Attempt to add ObjectInfo with 0 maxPerZone! Value: %d", oi.value); + return; + } RecursiveLock lock(externalAccessMutex); - possibleObjects.push_back(oi); + objects.addObject(oi); } void TreasurePlacer::addAllPossibleObjects() +{ + addCommonObjects(); + addDwellings(); + addPandoraBoxes(); + addSeerHuts(); + addPrisons(); + addScrolls(); +} + +void TreasurePlacer::addCommonObjects() { ObjectInfo oi; @@ -97,7 +138,10 @@ void TreasurePlacer::addAllPossibleObjects() } } } +} +void TreasurePlacer::addPrisons() +{ //Generate Prison on water only if it has a template auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType()); if (!prisonTemplates.empty()) @@ -157,21 +201,14 @@ void TreasurePlacer::addAllPossibleObjects() addObjectToRandomPool(oi); } } +} +void TreasurePlacer::addDwellings() +{ if(zone.getType() == ETemplateZoneType::WATER) return; - //all following objects are unlimited - oi.maxPerZone = std::numeric_limits::max(); - - std::vector creatures; //native creatures for this zone - for(auto const & cre : VLC->creh->objects) - { - if(!cre->special && cre->getFaction() == zone.getTownType()) - { - creatures.push_back(cre.get()); - } - } + ObjectInfo oi; //dwellings auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4}; @@ -214,7 +251,15 @@ void TreasurePlacer::addAllPossibleObjects() } } } - +} + +void TreasurePlacer::addScrolls() +{ + if(zone.getType() == ETemplateZoneType::WATER) + return; + + ObjectInfo oi; + for(int i = 0; i < generator.getConfig().scrollValues.size(); i++) { oi.generateObject = [i, this]() -> CGObjectInstance * @@ -239,6 +284,22 @@ void TreasurePlacer::addAllPossibleObjects() addObjectToRandomPool(oi); } +} + +void TreasurePlacer::addPandoraBoxes() +{ + if(zone.getType() == ETemplateZoneType::WATER) + return; + + addPandoraBoxesWithGold(); + addPandoraBoxesWithExperience(); + addPandoraBoxesWithCreatures(); + addPandoraBoxesWithSpells(); +} + +void TreasurePlacer::addPandoraBoxesWithGold() +{ + ObjectInfo oi; //pandora box with gold for(int i = 1; i < 5; i++) { @@ -260,7 +321,11 @@ void TreasurePlacer::addAllPossibleObjects() if(!oi.templates.empty()) addObjectToRandomPool(oi); } - +} + +void TreasurePlacer::addPandoraBoxesWithExperience() +{ + ObjectInfo oi; //pandora box with experience for(int i = 1; i < 5; i++) { @@ -282,43 +347,12 @@ void TreasurePlacer::addAllPossibleObjects() if(!oi.templates.empty()) addObjectToRandomPool(oi); } - - //pandora box with creatures - const std::vector & tierValues = generator.getConfig().pandoraCreatureValues; - - auto creatureToCount = [tierValues](const CCreature * creature) -> int - { - if(!creature->getAIValue() || tierValues.empty()) //bug #2681 - return 0; //this box won't be generated - - //Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box +} - int actualTier = creature->getLevel() > tierValues.size() ? - tierValues.size() - 1 : - creature->getLevel() - 1; - float creaturesAmount = std::floor((static_cast(tierValues[actualTier])) / creature->getAIValue()); - if (creaturesAmount < 1) - { - return 0; - } - else if(creaturesAmount <= 5) - { - //No change - } - else if(creaturesAmount <= 12) - { - creaturesAmount = std::ceil(creaturesAmount / 2) * 2; - } - else if(creaturesAmount <= 50) - { - creaturesAmount = std::round(creaturesAmount / 5) * 5; - } - else - { - creaturesAmount = std::round(creaturesAmount / 10) * 10; - } - return static_cast(creaturesAmount); - }; +void TreasurePlacer::addPandoraBoxesWithCreatures() +{ + ObjectInfo oi; + //pandora box with creatures for(auto * creature : creatures) { @@ -344,7 +378,11 @@ void TreasurePlacer::addAllPossibleObjects() if(!oi.templates.empty()) addObjectToRandomPool(oi); } - +} + +void TreasurePlacer::addPandoraBoxesWithSpells() +{ + ObjectInfo oi; //Pandora with 12 spells of certain level for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++) { @@ -441,9 +479,14 @@ void TreasurePlacer::addAllPossibleObjects() oi.probability = 2; if(!oi.templates.empty()) addObjectToRandomPool(oi); - +} + +void TreasurePlacer::addSeerHuts() +{ //Seer huts with creatures or generic rewards + ObjectInfo oi; + if(zone.getConnectedZoneIds().size()) //Unlikely, but... { auto * qap = zone.getModificator(); @@ -588,12 +631,6 @@ void TreasurePlacer::addAllPossibleObjects() } } -size_t TreasurePlacer::getPossibleObjectsSize() const -{ - RecursiveLock lock(externalAccessMutex); - return possibleObjects.size(); -} - void TreasurePlacer::setMaxPrisons(size_t count) { RecursiveLock lock(externalAccessMutex); @@ -606,6 +643,40 @@ size_t TreasurePlacer::getMaxPrisons() const return maxPrisons; } +int TreasurePlacer::creatureToCount(const CCreature * creature) const +{ + if(!creature->getAIValue() || tierValues.empty()) //bug #2681 + return 0; //this box won't be generated + + //Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box + + int actualTier = creature->getLevel() > tierValues.size() ? + tierValues.size() - 1 : + creature->getLevel() - 1; + float creaturesAmount = std::floor((static_cast(tierValues[actualTier])) / creature->getAIValue()); + if (creaturesAmount < 1) + { + return 0; + } + else if(creaturesAmount <= 5) + { + //No change + } + else if(creaturesAmount <= 12) + { + creaturesAmount = std::ceil(creaturesAmount / 2) * 2; + } + else if(creaturesAmount <= 50) + { + creaturesAmount = std::round(creaturesAmount / 5) * 5; + } + else + { + creaturesAmount = std::round(creaturesAmount / 10) * 10; + } + return static_cast(creaturesAmount); +}; + bool TreasurePlacer::isGuardNeededForTreasure(int value) {// no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed return zone.monsterStrength != EMonsterStrength::ZONE_NONE && value > minGuardedValue; @@ -623,6 +694,7 @@ std::vector TreasurePlacer::prepareTreasurePile(const CTreasureInfo bool hasLargeObject = false; while(currentValue <= static_cast(desiredValue) - 100) //no objects with value below 100 are available { + // FIXME: Pointer might be invalidated after this auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject); if(!oi) //fail break; @@ -674,12 +746,21 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector accessibleArea.add(int3()); } - auto * object = oi->generateObject(); - if(oi->templates.empty()) + CGObjectInstance * object = nullptr; + if (oi->generateObject) { - logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName()); - oi->destroyObject(object); - delete object; + object = oi->generateObject(); + if(oi->templates.empty()) + { + logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName()); + oi->destroyObject(object); + delete object; + continue; + } + } + else + { + logGlobal->error("ObjectInfo has no generateObject function! Templates: %d", oi->templates.size()); continue; } @@ -785,7 +866,7 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu ui32 maxVal = desiredValue - currentValue; ui32 minValue = static_cast(0.25f * (desiredValue - currentValue)); - for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly + for(ObjectInfo & oi : objects.getPossibleObjects()) //copy constructor turned out to be costly { if(oi.value > maxVal) break; //this assumes values are sorted in ascending order @@ -859,24 +940,19 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) boost::sort(treasureInfo, valueComparator); //sort treasures by ascending value so we can stop checking treasures with too high value - boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool - { - return oi1.value < oi2.value; - }); + objects.sortPossibleObjects(); const size_t size = zone.area()->getTilesVector().size(); int totalDensity = 0; + // FIXME: No need to use iterator here for (auto t = treasureInfo.begin(); t != treasureInfo.end(); t++) { std::vector treasures; //discard objects with too high value to be ever placed - vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool - { - return oi.value > t->max; - }); + objects.discardObjectsAboveValue(t->max); totalDensity += t->density; @@ -895,7 +971,11 @@ void TreasurePlacer::createTreasures(ObjectManager& manager) continue; } - int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; }); + int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, + [](int v, const ObjectInfo* oi) + { + return v + oi->value; + }); const ui32 maxPileGenerationAttempts = 2; for (ui32 attempt = 0; attempt < maxPileGenerationAttempts; attempt++) @@ -1016,13 +1096,88 @@ char TreasurePlacer::dump(const int3 & t) return Modificator::dump(t); } -void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType) +void TreasurePlacer::ObjectPool::addObject(const ObjectInfo & info) { - auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); - if(!templHandler) - return; - - templates = templHandler->getTemplates(terrainType); + possibleObjects.push_back(info); +} + +void TreasurePlacer::ObjectPool::updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info) +{ + /* + Handle separately: + - Dwellings + - Prisons + - Seer huts (quests) + - Pandora Boxes + */ + // FIXME: This will drop all templates + customObjects[CompoundMapObjectID(id, subid)] = info; +} + +void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) +{ + // FIXME: Wycina wszystkie obiekty poza pandorami i dwellami :? + + // Copy standard objects if they are not already modified + /* + for (const auto & object : possibleObjects) + { + for (const auto & templ : object.templates) + { + // FIXME: Objects with same temmplates (Pandora boxes) are not added + CompoundMapObjectID key(templ->id, templ->subid); + if (!vstd::contains(customObjects, key)) + { + customObjects[key] = object; + } + } + } + */ + + vstd::erase_if(possibleObjects, [&zone](const ObjectInfo & object) + { + for (const auto & templ : object.templates) + { + CompoundMapObjectID key(templ->id, templ->subid); + if (vstd::contains(zone.getBannedObjects(), key)) + { + return true; + } + } + return false; + }); + + // Now copy back modified list + // FIXME: Protect with mutex as well? + /* + possibleObjects.clear(); + for (const auto & customObject : customObjects) + { + addObject(customObject.second); + } + */ + // TODO: Consider adding custom Pandora boxes with arbitrary content +} + +std::vector & TreasurePlacer::ObjectPool::getPossibleObjects() +{ + return possibleObjects; +} + +void TreasurePlacer::ObjectPool::sortPossibleObjects() +{ + boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool + { + return oi1.value < oi2.value; + }); +} + +void TreasurePlacer::ObjectPool::discardObjectsAboveValue(ui32 value) +{ + vstd::erase_if(possibleObjects, [value](const ObjectInfo& oi) -> bool + { + return oi.value > value; + }); } VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index 450c812b7..c44da2dd9 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -9,6 +9,8 @@ */ #pragma once + +#include "../ObjectInfo.h" #include "../Zone.h" #include "../../mapObjects/ObjectTemplate.h" @@ -18,21 +20,7 @@ class CGObjectInstance; class ObjectManager; class RmgMap; class CMapGenerator; - -struct ObjectInfo -{ - ObjectInfo(); - - std::vector> templates; - ui32 value = 0; - ui16 probability = 0; - ui32 maxPerZone = 1; - //ui32 maxPerMap; //unused - std::function generateObject; - std::function destroyObject; - - void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain); -}; +struct CompoundMapObjectID; class TreasurePlacer: public Modificator { @@ -45,11 +33,26 @@ public: void createTreasures(ObjectManager & manager); void addObjectToRandomPool(const ObjectInfo& oi); - void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects - size_t getPossibleObjectsSize() const; + // TODO: Can be defaulted to addAllPossibleObjects, but then each object will need to be configured + void addCommonObjects(); + void addDwellings(); + void addPandoraBoxes(); + void addPandoraBoxesWithGold(); + void addPandoraBoxesWithExperience(); + void addPandoraBoxesWithCreatures(); + void addPandoraBoxesWithSpells(); + void addSeerHuts(); + void addPrisons(); + void addScrolls(); + void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects + // TODO: Read custom object config from zone file + + /// Get all objects for this terrain + void setMaxPrisons(size_t count); size_t getMaxPrisons() const; + int creatureToCount(const CCreature * creature) const; protected: bool isGuardNeededForTreasure(int value); @@ -59,7 +62,24 @@ protected: rmg::Object constructTreasurePile(const std::vector & treasureInfos, bool densePlacement = false); protected: - std::vector possibleObjects; + class ObjectPool + { + public: + void addObject(const ObjectInfo & info); + void updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info); + std::vector & getPossibleObjects(); + void patchWithZoneConfig(const Zone & zone); + void sortPossibleObjects(); + void discardObjectsAboveValue(ui32 value); + + private: + + std::vector possibleObjects; + std::map customObjects; + + } objects; + // TODO: Need to nagivate and update these + int minGuardedValue = 0; rmg::Area treasureArea; @@ -67,6 +87,9 @@ protected: rmg::Area guards; size_t maxPrisons; + + std::vector creatures; //native creatures for this zone + std::vector tierValues; }; VCMI_LIB_NAMESPACE_END From 1d494f049d1633eaa99ba0c2d30f3789f3c313b4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 24 Aug 2024 14:54:00 +0200 Subject: [PATCH 078/726] Fix closest way ratio not initialized for ExecuteHeroChain Fix closest way ratio not initialized for ExecuteHeroChain --- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index 8fe4851b2..566427f4d 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -22,6 +22,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * { hero = path.targetHero; tile = path.targetTile(); + closestWayRatio = 1; if(obj) { From b92862c04dcf5fe35da03cec1a1caff710e18c5e Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 24 Aug 2024 14:55:26 +0200 Subject: [PATCH 079/726] New priorities Added more priority-tiers --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 28 ++++++++++++++++++---- AI/Nullkiller/Engine/PriorityEvaluator.h | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index f9954a6d2..460515ed0 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -408,7 +408,7 @@ void Nullkiller::makeTurn() TTaskVec selectedTasks; int prioOfTask = 0; - for (int prio = 0; prio <= 2; ++prio) + for (int prio = 1; prio <= 5; ++prio) { prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 0aa42144e..87841c13e 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1384,10 +1384,19 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) switch (priorityTier) { - case 0: //Take towns + case 1: //Defend immediately threatened towns + { + if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) + score = evaluationContext.armyInvolvement; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + break; + } + case 2: //Take towns { //score += evaluationContext.conquestValue * 1000; - if(evaluationContext.conquestValue > 0 || (evaluationContext.defenseValue >= CGTownInstance::EFortLevel::CITADEL && evaluationContext.turn <= 1 && evaluationContext.threat > evaluationContext.armyInvolvement && evaluationContext.threatTurns <= 1)) + if(evaluationContext.conquestValue > 0) score = 1000; if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) return 0; @@ -1398,7 +1407,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case 1: //Collect unguarded stuff + case 3: //Collect unguarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1) return 0; @@ -1428,7 +1437,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case 2: //Collect guarded stuff + case 4: //Collect guarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) return 0; @@ -1454,7 +1463,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } break; } - case 3: //For buildings and buying army + case 5: //Defend whatever if nothing else is to do + { + if (evaluationContext.isDefend) + score = evaluationContext.armyInvolvement; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + break; + } + case 0: //For buildings and buying army { if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index a7d346e17..fb7960c67 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -103,7 +103,7 @@ public: ~PriorityEvaluator(); void initVisitTile(); - float evaluate(Goals::TSubgoal task, int priorityTier = 3); + float evaluate(Goals::TSubgoal task, int priorityTier = 0); private: const Nullkiller * ai; From 64fc2e5ed0b0687a115f7365e9c112175b77c766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 24 Aug 2024 15:12:22 +0200 Subject: [PATCH 080/726] Move ObjectConfig to ObjectInfo.cpp. Preliminary version of banning object categories. --- config/schemas/template.json | 3 + .../CObjectClassesHandler.cpp | 5 +- lib/rmg/CRmgTemplate.cpp | 80 +---------- lib/rmg/CRmgTemplate.h | 27 +--- lib/rmg/ObjectInfo.cpp | 126 ++++++++++++++++-- lib/rmg/ObjectInfo.h | 45 +++++++ lib/rmg/modificators/TreasurePlacer.cpp | 74 ++++++++++ lib/rmg/modificators/TreasurePlacer.h | 3 + 8 files changed, 250 insertions(+), 113 deletions(-) diff --git a/config/schemas/template.json b/config/schemas/template.json index a1a353a6d..77aaefafa 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -49,6 +49,9 @@ }, "additionalProperties" : false } + }, + "customObjects" : { + "type" : "object" } } }, diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index f91a3871c..257e78b3f 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -411,11 +411,8 @@ CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::stri CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const { // FIXME: Crash with no further log - //"core:object.creatureBank.experimentalShop", - //"core:object.creatureBank.wolfRiderPicket", - //"core:object.creatureBank.demonDomain" - // TODO: Use existing utilities for parsing id? + // TODO: Use existing utilities for parsing id? JsonNode node(objectName); auto modScope = node.getModScope(); diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index e512676ed..f97cc6c94 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -11,9 +11,9 @@ #include "StdInc.h" #include #include + #include "CRmgTemplate.h" #include "Functions.h" - #include "../TerrainHandler.h" #include "../VCMI_Lib.h" #include "../constants/StringConstants.h" @@ -909,87 +909,19 @@ void CRmgTemplate::serializePlayers(JsonSerializeFormat & handler, CPlayerCountR value.fromString(encodedValue); } -void ZoneOptions::ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) -{ - // FIXME: We do not need to store the object info, just the id - - bannedObjects.push_back(objid); - - logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); -} - -void ZoneOptions::ObjectConfig::serializeJson(JsonSerializeFormat & handler) -{ - // TODO: Implement' - - auto bannedObjectData = handler.enterArray("bannedObjects"); - if (handler.saving) - { - - // FIXME: Do we even need to serialize / store banned objects? - /* - for (const auto & object : bannedObjects) - { - // TODO: Translate id back to string? - - - JsonNode node; - node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); - // TODO: Check if AI-generated code is right - - - } - // handler.serializeRaw("bannedObjects", node, std::nullopt); - - */ - } - else - { - /* - auto zonesData = handler.enterStruct("zones"); - for(const auto & idAndZone : zonesData->getCurrent().Struct()) - { - auto guard = handler.enterStruct(idAndZone.first); - auto zone = std::make_shared(); - zone->setId(decodeZoneId(idAndZone.first)); - zone->serializeJson(handler); - zones[zone->getId()] = zone; - } - */ - std::vector objectNames; - bannedObjectData.serializeArray(objectNames); - - for (const auto & objectName : objectNames) - { - VLC->objtypeh->resolveObjectCompoundId(objectName, - [this](CompoundMapObjectID objid) - { - addBannedObject(objid); - } - ); - - } - } -} - const std::vector & ZoneOptions::getBannedObjects() const { return objectConfig.getBannedObjects(); } +const std::vector & ZoneOptions::getBannedObjectCategories() const +{ + return objectConfig.getBannedObjectCategories(); +} + const std::vector & ZoneOptions::getCustomObjects() const { return objectConfig.getCustomObjects(); } -const std::vector & ZoneOptions::ObjectConfig::getBannedObjects() const -{ - return bannedObjects; -} - -const std::vector & ZoneOptions::ObjectConfig::getCustomObjects() const -{ - return customObjects; -} - VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 1db3b68b1..786207fae 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -139,32 +139,6 @@ public: TRmgTemplateZoneId sourceZone = NO_ZONE; }; - // TODO: Store config for custom objects to spawn in this zone - // TODO: Read custom object config from zone file - class DLL_LINKAGE ObjectConfig - { - public: - //ObjectConfig() = default; - - void addBannedObject(const CompoundMapObjectID & objid); - void addCustomObject(const ObjectInfo & object); - void clearBannedObjects(); - void clearCustomObjects(); - const std::vector & getBannedObjects() const; - const std::vector & getCustomObjects() const; - - // TODO: Separate serializer - void serializeJson(JsonSerializeFormat & handler); - private: - // TODO: Add convenience method for banning objects by name - std::vector bannedObjects; - - // TODO: In what format should I store custom objects? - // Need to convert map serialization format to ObjectInfo - std::vector customObjects; - }; - // TODO: Allow to copy all custom objects config from another zone - ZoneOptions(); TRmgTemplateZoneId getId() const; @@ -215,6 +189,7 @@ public: bool isMatchTerrainToTown() const; const std::vector & getBannedObjects() const; + const std::vector & getBannedObjectCategories() const; const std::vector & getCustomObjects() const; protected: diff --git a/lib/rmg/ObjectInfo.cpp b/lib/rmg/ObjectInfo.cpp index a725ffb89..1e469db08 100644 --- a/lib/rmg/ObjectInfo.cpp +++ b/lib/rmg/ObjectInfo.cpp @@ -9,11 +9,14 @@ */ #include "StdInc.h" +#include +#include #include "ObjectInfo.h" #include "../VCMI_Lib.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" +#include "../serializer/JsonSerializeFormat.h" VCMI_LIB_NAMESPACE_BEGIN @@ -56,16 +59,121 @@ void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainI templates = templHandler->getTemplates(terrainType); } -/* -bool ObjectInfo::matchesId(const CompoundMapObjectID & id) const +void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) { - for (const auto & templ : templates) - { - if (id.primaryID == templ->id && id.secondaryID == templ->subid) - return true; - } - return false; + // FIXME: We do not need to store the object info, just the id + + bannedObjects.push_back(objid); + + logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); +} + +void ObjectConfig::serializeJson(JsonSerializeFormat & handler) +{ + // TODO: We need serializer utility for list of enum values + + static const boost::bimap OBJECT_CATEGORY_STRINGS = boost::assign::list_of::relation> + (EObjectCategory::OTHER, "other") + (EObjectCategory::ALL, "all") + (EObjectCategory::NONE, "none") + (EObjectCategory::CREATURE_BANK, "creatureBank") + (EObjectCategory::PERMANENT_BONUS, "permanentBonus") + (EObjectCategory::NEXT_BATTLE_BONUS, "nextBattleBonus") + (EObjectCategory::DWELLING, "dwelling") + (EObjectCategory::RESOURCE, "resource") + (EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator") + (EObjectCategory::SPELL_SCROLL, "spellScroll") + (EObjectCategory::RANDOM_ARTIFACT, "randomArtifact") + (EObjectCategory::PANDORAS_BOX, "pandorasBox") + (EObjectCategory::QUEST_ARTIFACT, "questArtifact"); + + auto categories = handler.enterArray("bannedCategories"); + if (handler.saving) + { + for (const auto& category : bannedObjectCategories) + { + auto str = OBJECT_CATEGORY_STRINGS.left.at(category); + categories.serializeString(categories.size(), str); + } + } + else + { + std::vector categoryNames; + categories.serializeArray(categoryNames); + + for (const auto & categoryName : categoryNames) + { + auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName); + if (it != OBJECT_CATEGORY_STRINGS.right.end()) + { + bannedObjectCategories.push_back(it->second); + } + } + } + + auto bannedObjectData = handler.enterArray("bannedObjects"); + if (handler.saving) + { + + // FIXME: Do we even need to serialize / store banned objects? + /* + for (const auto & object : bannedObjects) + { + // TODO: Translate id back to string? + + + JsonNode node; + node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); + // TODO: Check if AI-generated code is right + + + } + // handler.serializeRaw("bannedObjects", node, std::nullopt); + + */ + } + else + { + /* + auto zonesData = handler.enterStruct("zones"); + for(const auto & idAndZone : zonesData->getCurrent().Struct()) + { + auto guard = handler.enterStruct(idAndZone.first); + auto zone = std::make_shared(); + zone->setId(decodeZoneId(idAndZone.first)); + zone->serializeJson(handler); + zones[zone->getId()] = zone; + } + */ + std::vector objectNames; + bannedObjectData.serializeArray(objectNames); + + for (const auto & objectName : objectNames) + { + VLC->objtypeh->resolveObjectCompoundId(objectName, + [this](CompoundMapObjectID objid) + { + addBannedObject(objid); + } + ); + + } + } +} + +const std::vector & ObjectConfig::getCustomObjects() const +{ + return customObjects; +} + +const std::vector & ObjectConfig::getBannedObjects() const +{ + return bannedObjects; +} + +const std::vector & ObjectConfig::getBannedObjectCategories() const +{ + return bannedObjectCategories; } -*/ VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h index ae90ee448..d421baff3 100644 --- a/lib/rmg/ObjectInfo.h +++ b/lib/rmg/ObjectInfo.h @@ -37,4 +37,49 @@ struct DLL_LINKAGE ObjectInfo //bool matchesId(const CompoundMapObjectID & id) const; }; +// TODO: Store config for custom objects to spawn in this zone +// TODO: Read custom object config from zone file +class DLL_LINKAGE ObjectConfig +{ +public: + + enum class EObjectCategory + { + OTHER = -2, + ALL = -1, + NONE = 0, + CREATURE_BANK = 1, + PERMANENT_BONUS, + NEXT_BATTLE_BONUS, + DWELLING, + RESOURCE, + RESOURCE_GENERATOR, + SPELL_SCROLL, + RANDOM_ARTIFACT, + PANDORAS_BOX, + QUEST_ARTIFACT + // TODO: Seer huts? + }; + + void addBannedObject(const CompoundMapObjectID & objid); + void addCustomObject(const ObjectInfo & object); + void clearBannedObjects(); + void clearCustomObjects(); + const std::vector & getBannedObjects() const; + const std::vector & getBannedObjectCategories() const; + const std::vector & getCustomObjects() const; + + // TODO: Separate serializer + void serializeJson(JsonSerializeFormat & handler); +private: + // TODO: Add convenience method for banning objects by name + std::vector bannedObjects; + std::vector bannedObjectCategories; + + // TODO: In what format should I store custom objects? + // Need to convert map serialization format to ObjectInfo + std::vector customObjects; +}; +// TODO: Allow to copy all custom objects config from another zone + VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index b40d16736..5ba5992b7 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "TreasurePlacer.h" +#include "../CRmgTemplate.h" #include "../CMapGenerator.h" #include "../Functions.h" #include "ObjectManager.h" @@ -1133,6 +1134,19 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) } } */ + auto bannedObjectCategories = zone.getBannedObjectCategories(); + auto categoriesSet = std::unordered_set(bannedObjectCategories.begin(), bannedObjectCategories.end()); + + vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool + { + auto category = getObjectCategory(oi.templates.front()->id); + if (categoriesSet.count(category)) + { + logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); + return true; + } + return false; + }); vstd::erase_if(possibleObjects, [&zone](const ObjectInfo & object) { @@ -1180,4 +1194,64 @@ void TreasurePlacer::ObjectPool::discardObjectsAboveValue(ui32 value) }); } +ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(MapObjectID id) +{ + auto name = VLC->objtypeh->getObjectHandlerName(id); + + if (name == "configurable") + { + // TODO: Need to check configuration here. + // Possible otions: PERMANENT_BONUS, NEXT_BATTLE_BONUS, RESOURCE + return ObjectConfig::EObjectCategory::RESOURCE; + } + else if (name == "dwelling" || name == "randomDwelling") + { + // TODO: Special handling for different tiers + return ObjectConfig::EObjectCategory::DWELLING; + } + else if (name == "bank") + return ObjectConfig::EObjectCategory::CREATURE_BANK; + else if (name == "market") + return ObjectConfig::EObjectCategory::OTHER; + else if (name == "hillFort") + return ObjectConfig::EObjectCategory::OTHER; + else if (name == "resource" || name == "randomResource") + return ObjectConfig::EObjectCategory::RESOURCE; + else if (name == "randomArtifact") //"artifact" + return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT; + else if (name == "denOfThieves") + return ObjectConfig::EObjectCategory::OTHER; + else if (name == "lighthouse") + { + // TODO: So far Lighthouse is not generated + // Also, it gives global bonus as long as owned + return ObjectConfig::EObjectCategory::PERMANENT_BONUS; + } + else if (name == "magi") + return ObjectConfig::EObjectCategory::OTHER; + else if (name == "mine") + return ObjectConfig::EObjectCategory::RESOURCE_GENERATOR; + else if (name == "pandora") + return ObjectConfig::EObjectCategory::PANDORAS_BOX; + else if (name == "prison") + { + // TODO: Prisons are configurable + return ObjectConfig::EObjectCategory::OTHER; + } + else if (name == "seerHut") + { + // quest artifacts are configurable, but what about seer huts? + return ObjectConfig::EObjectCategory::QUEST_ARTIFACT; + } + else if (name == "siren") + return ObjectConfig::EObjectCategory::NEXT_BATTLE_BONUS; + else if (name == "obelisk") + return ObjectConfig::EObjectCategory::OTHER; + + // TODO: ObjectConfig::EObjectCategory::SPELL_SCROLL + + // Not interesting for us + return ObjectConfig::EObjectCategory::NONE; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index c44da2dd9..f9ecf082e 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -20,6 +20,7 @@ class CGObjectInstance; class ObjectManager; class RmgMap; class CMapGenerator; +class ObjectConfig; struct CompoundMapObjectID; class TreasurePlacer: public Modificator @@ -72,6 +73,8 @@ protected: void sortPossibleObjects(); void discardObjectsAboveValue(ui32 value); + ObjectConfig::EObjectCategory getObjectCategory(MapObjectID id); + private: std::vector possibleObjects; From 69dc32a128eeb2d68bb474546e5975183ba4159d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 24 Aug 2024 17:15:15 +0200 Subject: [PATCH 081/726] Don't cast spells with below 0 score. The AI will no longer cast spells if the best spell's value is still below 0. --- AI/BattleAI/BattleEvaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 0c9941e2b..09da17878 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -790,7 +790,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) }; auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue); - if(castToPerform.value > cachedScore) + if(castToPerform.value > cachedScore && castToPerform.value > 0) { LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value); BattleAction spellcast; From bfe75a6a024285aed02148d2cc632de39dd8b699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 24 Aug 2024 20:18:36 +0200 Subject: [PATCH 082/726] It is now possible to copy object settings between zones --- config/schemas/template.json | 1 + lib/rmg/CRmgTemplate.cpp | 117 +++++++++++++----------- lib/rmg/CRmgTemplate.h | 28 ++++-- lib/rmg/ObjectInfo.cpp | 8 +- lib/rmg/ObjectInfo.h | 10 +- lib/rmg/modificators/TreasurePlacer.cpp | 58 +++++++++--- lib/rmg/modificators/TreasurePlacer.h | 2 +- 7 files changed, 139 insertions(+), 85 deletions(-) diff --git a/config/schemas/template.json b/config/schemas/template.json index 77aaefafa..655bcbb62 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -22,6 +22,7 @@ "minesLikeZone" : { "type" : "number" }, "terrainTypeLikeZone" : { "type" : "number" }, "treasureLikeZone" : { "type" : "number" }, + "customObjectsLikeZone" : { "type" : "number" }, "terrainTypes": {"$ref" : "#/definitions/stringArray"}, "bannedTerrains": {"$ref" : "#/definitions/stringArray"}, diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index f97cc6c94..4247d48d1 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -157,7 +157,7 @@ std::optional ZoneOptions::getOwner() const return owner; } -const std::set ZoneOptions::getTerrainTypes() const +std::set ZoneOptions::getTerrainTypes() const { if (terrainTypes.empty()) { @@ -192,7 +192,7 @@ std::set ZoneOptions::getDefaultTownTypes() const return VLC->townh->getDefaultAllowed(); } -const std::set ZoneOptions::getTownTypes() const +std::set ZoneOptions::getTownTypes() const { if (townTypes.empty()) { @@ -215,7 +215,7 @@ void ZoneOptions::setMonsterTypes(const std::set & value) monsterTypes = value; } -const std::set ZoneOptions::getMonsterTypes() const +std::set ZoneOptions::getMonsterTypes() const { return vstd::difference(monsterTypes, bannedMonsters); } @@ -251,7 +251,7 @@ void ZoneOptions::addTreasureInfo(const CTreasureInfo & value) vstd::amax(maxTreasureValue, value.max); } -const std::vector & ZoneOptions::getTreasureInfo() const +std::vector ZoneOptions::getTreasureInfo() const { return treasureInfo; } @@ -273,7 +273,22 @@ TRmgTemplateZoneId ZoneOptions::getTerrainTypeLikeZone() const TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const { - return treasureLikeZone; + return treasureLikeZone; +} + +ObjectConfig ZoneOptions::getCustomObjects() const +{ + return objectConfig; +} + +void ZoneOptions::setCustomObjects(const ObjectConfig & value) +{ + objectConfig = value; +} + +TRmgTemplateZoneId ZoneOptions::getCustomObjectsLikeZone() const +{ + return customObjectsLikeZone; } void ZoneOptions::addConnection(const ZoneConnection & connection) @@ -335,7 +350,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) SERIALIZE_ZONE_LINK(minesLikeZone); SERIALIZE_ZONE_LINK(terrainTypeLikeZone); SERIALIZE_ZONE_LINK(treasureLikeZone); - + SERIALIZE_ZONE_LINK(customObjectsLikeZone); #undef SERIALIZE_ZONE_LINK if(terrainTypeLikeZone == NO_ZONE) @@ -751,53 +766,29 @@ void CRmgTemplate::serializeJson(JsonSerializeFormat & handler) } } -std::set CRmgTemplate::inheritTerrainType(std::shared_ptr zone, uint32_t iteration /* = 0 */) +template +T CRmgTemplate::inheritZoneProperty(std::shared_ptr zone, + T (rmg::ZoneOptions::*getter)() const, + void (rmg::ZoneOptions::*setter)(const T&), + TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const, + const std::string& propertyString, + uint32_t iteration) { if (iteration >= 50) { - logGlobal->error("Infinite recursion for terrain types detected in template %s", name); - return std::set(); + logGlobal->error("Infinite recursion for %s detected in template %s", propertyString, name); + return T(); } - if (zone->getTerrainTypeLikeZone() != ZoneOptions::NO_ZONE) + + if (((*zone).*inheritFrom)() != rmg::ZoneOptions::NO_ZONE) { iteration++; - const auto otherZone = zones.at(zone->getTerrainTypeLikeZone()); - zone->setTerrainTypes(inheritTerrainType(otherZone, iteration)); + const auto otherZone = zones.at(((*zone).*inheritFrom)()); + T inheritedValue = inheritZoneProperty(otherZone, getter, setter, inheritFrom, propertyString, iteration); + ((*zone).*setter)(inheritedValue); } - //This implicitly excludes banned terrains - return zone->getTerrainTypes(); -} - -std::map CRmgTemplate::inheritMineTypes(std::shared_ptr zone, uint32_t iteration /* = 0 */) -{ - if (iteration >= 50) - { - logGlobal->error("Infinite recursion for mine types detected in template %s", name); - return std::map(); - } - if (zone->getMinesLikeZone() != ZoneOptions::NO_ZONE) - { - iteration++; - const auto otherZone = zones.at(zone->getMinesLikeZone()); - zone->setMinesInfo(inheritMineTypes(otherZone, iteration)); - } - return zone->getMinesInfo(); -} - -std::vector CRmgTemplate::inheritTreasureInfo(std::shared_ptr zone, uint32_t iteration /* = 0 */) -{ - if (iteration >= 50) - { - logGlobal->error("Infinite recursion for treasures detected in template %s", name); - return std::vector(); - } - if (zone->getTreasureLikeZone() != ZoneOptions::NO_ZONE) - { - iteration++; - const auto otherZone = zones.at(zone->getTreasureLikeZone()); - zone->setTreasureInfo(inheritTreasureInfo(otherZone, iteration)); - } - return zone->getTreasureInfo(); + + return ((*zone).*getter)(); } void CRmgTemplate::afterLoad() @@ -806,12 +797,32 @@ void CRmgTemplate::afterLoad() { auto zone = idAndZone.second; - //Inherit properties recursively. - inheritTerrainType(zone); - inheritMineTypes(zone); - inheritTreasureInfo(zone); + // Inherit properties recursively + inheritZoneProperty(zone, + &rmg::ZoneOptions::getTerrainTypes, + &rmg::ZoneOptions::setTerrainTypes, + &rmg::ZoneOptions::getTerrainTypeLikeZone, + "terrain types"); + + inheritZoneProperty(zone, + &rmg::ZoneOptions::getMinesInfo, + &rmg::ZoneOptions::setMinesInfo, + &rmg::ZoneOptions::getMinesLikeZone, + "mine types"); + + inheritZoneProperty(zone, + &rmg::ZoneOptions::getTreasureInfo, + &rmg::ZoneOptions::setTreasureInfo, + &rmg::ZoneOptions::getTreasureLikeZone, + "treasure info"); - //TODO: Inherit monster types as well + inheritZoneProperty(zone, + &rmg::ZoneOptions::getCustomObjects, + &rmg::ZoneOptions::setCustomObjects, + &rmg::ZoneOptions::getCustomObjectsLikeZone, + "custom objects"); + + //TODO: Inherit monster types as well auto monsterTypes = zone->getMonsterTypes(); if (monsterTypes.empty()) { @@ -919,9 +930,9 @@ const std::vector & ZoneOptions::getBannedObjectC return objectConfig.getBannedObjectCategories(); } -const std::vector & ZoneOptions::getCustomObjects() const +const std::vector & ZoneOptions::getConfiguredObjects() const { - return objectConfig.getCustomObjects(); + return objectConfig.getConfiguredObjects(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 786207fae..95c23d906 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -151,15 +151,15 @@ public: void setSize(int value); std::optional getOwner() const; - const std::set getTerrainTypes() const; + std::set getTerrainTypes() const; void setTerrainTypes(const std::set & value); std::set getDefaultTerrainTypes() const; const CTownInfo & getPlayerTowns() const; const CTownInfo & getNeutralTowns() const; std::set getDefaultTownTypes() const; - const std::set getTownTypes() const; - const std::set getMonsterTypes() const; + std::set getTownTypes() const; + std::set getMonsterTypes() const; void setTownTypes(const std::set & value); void setMonsterTypes(const std::set & value); @@ -169,7 +169,7 @@ public: void setTreasureInfo(const std::vector & value); void addTreasureInfo(const CTreasureInfo & value); - const std::vector & getTreasureInfo() const; + std::vector getTreasureInfo() const; ui32 getMaxTreasureValue() const; void recalculateMaxTreasureValue(); @@ -188,9 +188,15 @@ public: bool areTownsSameType() const; bool isMatchTerrainToTown() const; + // Get a group of configured objects const std::vector & getBannedObjects() const; const std::vector & getBannedObjectCategories() const; - const std::vector & getCustomObjects() const; + const std::vector & getConfiguredObjects() const; + + // Copy whole custom object config from another zone + ObjectConfig getCustomObjects() const; + void setCustomObjects(const ObjectConfig & value); + TRmgTemplateZoneId getCustomObjectsLikeZone() const; protected: TRmgTemplateZoneId id; @@ -222,6 +228,7 @@ protected: TRmgTemplateZoneId minesLikeZone; TRmgTemplateZoneId terrainTypeLikeZone; TRmgTemplateZoneId treasureLikeZone; + TRmgTemplateZoneId customObjectsLikeZone; }; } @@ -294,6 +301,15 @@ private: void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName); void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName); + + template + T inheritZoneProperty(std::shared_ptr zone, + T (rmg::ZoneOptions::*getter)() const, + void (rmg::ZoneOptions::*setter)(const T&), + TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const, + const std::string& propertyString, + uint32_t iteration = 0); + }; -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectInfo.cpp b/lib/rmg/ObjectInfo.cpp index 1e469db08..fda32a8b0 100644 --- a/lib/rmg/ObjectInfo.cpp +++ b/lib/rmg/ObjectInfo.cpp @@ -77,15 +77,15 @@ void ObjectConfig::serializeJson(JsonSerializeFormat & handler) (EObjectCategory::ALL, "all") (EObjectCategory::NONE, "none") (EObjectCategory::CREATURE_BANK, "creatureBank") - (EObjectCategory::PERMANENT_BONUS, "permanentBonus") - (EObjectCategory::NEXT_BATTLE_BONUS, "nextBattleBonus") + (EObjectCategory::BONUS, "bonus") (EObjectCategory::DWELLING, "dwelling") (EObjectCategory::RESOURCE, "resource") (EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator") (EObjectCategory::SPELL_SCROLL, "spellScroll") (EObjectCategory::RANDOM_ARTIFACT, "randomArtifact") (EObjectCategory::PANDORAS_BOX, "pandorasBox") - (EObjectCategory::QUEST_ARTIFACT, "questArtifact"); + (EObjectCategory::QUEST_ARTIFACT, "questArtifact") + (EObjectCategory::SEER_HUT, "seerHut"); auto categories = handler.enterArray("bannedCategories"); if (handler.saving) @@ -161,7 +161,7 @@ void ObjectConfig::serializeJson(JsonSerializeFormat & handler) } } -const std::vector & ObjectConfig::getCustomObjects() const +const std::vector & ObjectConfig::getConfiguredObjects() const { return customObjects; } diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h index d421baff3..cee3cf10b 100644 --- a/lib/rmg/ObjectInfo.h +++ b/lib/rmg/ObjectInfo.h @@ -49,16 +49,15 @@ public: ALL = -1, NONE = 0, CREATURE_BANK = 1, - PERMANENT_BONUS, - NEXT_BATTLE_BONUS, + BONUS, DWELLING, RESOURCE, RESOURCE_GENERATOR, SPELL_SCROLL, RANDOM_ARTIFACT, PANDORAS_BOX, - QUEST_ARTIFACT - // TODO: Seer huts? + QUEST_ARTIFACT, + SEER_HUT }; void addBannedObject(const CompoundMapObjectID & objid); @@ -67,9 +66,8 @@ public: void clearCustomObjects(); const std::vector & getBannedObjects() const; const std::vector & getBannedObjectCategories() const; - const std::vector & getCustomObjects() const; + const std::vector & getConfiguredObjects() const; - // TODO: Separate serializer void serializeJson(JsonSerializeFormat & handler); private: // TODO: Add convenience method for banning objects by name diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 5ba5992b7..8e30b5cff 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -25,6 +25,7 @@ #include "../../mapObjectConstructors/AObjectTypeHandler.h" #include "../../mapObjectConstructors/CObjectClassesHandler.h" #include "../../mapObjectConstructors/DwellingInstanceConstructor.h" +#include "../../rewardable/Info.h" #include "../../mapObjects/CGHeroInstance.h" #include "../../mapObjects/CGPandoraBox.h" #include "../../mapObjects/CQuest.h" @@ -1139,7 +1140,8 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool { - auto category = getObjectCategory(oi.templates.front()->id); + auto temp = oi.templates.front(); + auto category = getObjectCategory(CompoundMapObjectID(temp->id, temp->subid)); if (categoriesSet.count(category)) { logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); @@ -1161,10 +1163,11 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) return false; }); - // Now copy back modified list + auto configuredObjects = zone.getConfiguredObjects(); + // TODO: Overwrite or add to possibleObjects + // FIXME: Protect with mutex as well? /* - possibleObjects.clear(); for (const auto & customObject : customObjects) { addObject(customObject.second); @@ -1194,15 +1197,35 @@ void TreasurePlacer::ObjectPool::discardObjectsAboveValue(ui32 value) }); } -ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(MapObjectID id) +ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(CompoundMapObjectID id) { - auto name = VLC->objtypeh->getObjectHandlerName(id); + auto name = VLC->objtypeh->getObjectHandlerName(id.primaryID); if (name == "configurable") { - // TODO: Need to check configuration here. - // Possible otions: PERMANENT_BONUS, NEXT_BATTLE_BONUS, RESOURCE - return ObjectConfig::EObjectCategory::RESOURCE; + // TODO: Access Rewardable::Info by ID + + auto handler = VLC->objtypeh->getHandlerFor(id.primaryID, id.secondaryID); + if (!handler) + { + return ObjectConfig::EObjectCategory::NONE; + } + auto temp = handler->getTemplates().front(); + auto info = handler->getObjectInfo(temp); + if (info->givesResources()) + { + return ObjectConfig::EObjectCategory::RESOURCE; + } + else if (info->givesArtifacts()) + { + return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT; + } + else if (info->givesBonuses()) + { + return ObjectConfig::EObjectCategory::BONUS; + } + + return ObjectConfig::EObjectCategory::OTHER; } else if (name == "dwelling" || name == "randomDwelling") { @@ -1223,28 +1246,33 @@ ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(MapO return ObjectConfig::EObjectCategory::OTHER; else if (name == "lighthouse") { - // TODO: So far Lighthouse is not generated - // Also, it gives global bonus as long as owned - return ObjectConfig::EObjectCategory::PERMANENT_BONUS; + return ObjectConfig::EObjectCategory::BONUS; } else if (name == "magi") + { + // TODO: By default, both eye and hut are banned in every zone return ObjectConfig::EObjectCategory::OTHER; + } else if (name == "mine") return ObjectConfig::EObjectCategory::RESOURCE_GENERATOR; else if (name == "pandora") return ObjectConfig::EObjectCategory::PANDORAS_BOX; else if (name == "prison") { - // TODO: Prisons are configurable + // TODO: Prisons should be configurable return ObjectConfig::EObjectCategory::OTHER; } + else if (name == "questArtifact") + { + // TODO: There are no dedicated quest artifacts, needs extra logic + return ObjectConfig::EObjectCategory::QUEST_ARTIFACT; + } else if (name == "seerHut") { - // quest artifacts are configurable, but what about seer huts? - return ObjectConfig::EObjectCategory::QUEST_ARTIFACT; + return ObjectConfig::EObjectCategory::SEER_HUT; } else if (name == "siren") - return ObjectConfig::EObjectCategory::NEXT_BATTLE_BONUS; + return ObjectConfig::EObjectCategory::BONUS; else if (name == "obelisk") return ObjectConfig::EObjectCategory::OTHER; diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index f9ecf082e..c96733eb0 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -73,7 +73,7 @@ protected: void sortPossibleObjects(); void discardObjectsAboveValue(ui32 value); - ObjectConfig::EObjectCategory getObjectCategory(MapObjectID id); + ObjectConfig::EObjectCategory getObjectCategory(CompoundMapObjectID id); private: From 7ae7c139646c65fc5b315cccc0a3574b9344d309 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Wed, 28 Aug 2024 15:12:33 +0200 Subject: [PATCH 083/726] drop setting reachability for turrets --- AI/BattleAI/BattleExchangeVariant.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 04ada089e..b1b7accdb 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -484,15 +484,18 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex)); } - for(auto hex : ap.attack.attacker->getHexes()) + if(!ap.attack.attacker->isTurret()) { - auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); - for(auto unit : unitsReachingAttacker) + for(auto hex : ap.attack.attacker->getHexes()) { - if(unit->unitSide() != ap.attack.attacker->unitSide()) + auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); + for(auto unit : unitsReachingAttacker) { - allReachableUnits.push_back(unit); - result.enemyUnitsReachingAttacker.insert(unit->unitId()); + if(unit->unitSide() != ap.attack.attacker->unitSide()) + { + allReachableUnits.push_back(unit); + result.enemyUnitsReachingAttacker.insert(unit->unitId()); + } } } } From ca3d81f047962dcee38813d8d8b2e3c7381e3214 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Wed, 28 Aug 2024 15:16:33 +0200 Subject: [PATCH 084/726] fix crash on heroRoles.clear() --- AI/Nullkiller/Analyzers/HeroManager.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 44b66b46b..a67fd8ebc 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -126,20 +126,23 @@ void HeroManager::update() } std::sort(myHeroes.begin(), myHeroes.end(), scoreSort); - heroRoles.clear(); + + std::map newHeroRoles; for(auto hero : myHeroes) { if(hero->patrol.patrolling) { - heroRoles[hero] = HeroRole::MAIN; + newHeroRoles[hero] = HeroRole::MAIN; } else { - heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; + newHeroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; } } + heroRoles = std::move(newHeroRoles); + for(auto hero : myHeroes) { logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); From b8dacfc0bef79cb76682458cdff7fb26e74cf95d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 29 Aug 2024 20:49:01 +0200 Subject: [PATCH 085/726] Update Nullkiller.cpp Fixed trade no longer working and changed log-output. --- AI/Nullkiller/Engine/Nullkiller.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 460515ed0..d10e76cdb 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -365,9 +365,9 @@ void Nullkiller::makeTurn() Goals::TGoalVec bestTasks; + logAi->info("Beginning: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++) { - logAi->info("Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); auto start = std::chrono::high_resolution_clock::now(); updateAiState(i); @@ -485,7 +485,7 @@ void Nullkiller::makeTurn() continue; } - logAi->info("Performing prio %d task %s with prio: %d", prioOfTask, bestTask->toString(), bestTask->priority); + logAi->info("Pass %d: Performing prio %d task %s with prio: %d", i, prioOfTask, bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) { if(hasAnySuccess) @@ -509,6 +509,15 @@ void Nullkiller::makeTurn() logAi->warn("Maxpass exceeded. Terminating AI turn."); } } + for (auto heroInfo : cb->getHeroesInfo()) + { + totalHeroStrength += heroInfo->getTotalStrength(); + } + for (auto townInfo : cb->getTownsInfo()) + { + totalTownLevel += townInfo->getTownLevel(); + } + logAi->info("End: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); } bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const @@ -662,7 +671,7 @@ bool Nullkiller::handleTrading() //TODO trade only as much as needed if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources { - cb->trade(m, EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); + cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); haveTraded = true; shouldTryToTrade = true; From dcec5637cd5bfde197b2b99b0a006aeaddf60302 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 29 Aug 2024 21:01:06 +0200 Subject: [PATCH 086/726] Fix for defense-evaluation. Defense-evaluation didn't fill armyInvolvement but it was what created the score for it. So there was only score if it also included a HeroExchange. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 87841c13e..c83e2fcc9 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1141,6 +1141,14 @@ public: Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast(*task); const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero(); + logAi->trace("buildEvaluationContext ExchangeSwapTownHeroesContextBuilder %s affected objects: %d", swapCommand.toString(), swapCommand.getAffectedObjects().size()); + for (auto obj : swapCommand.getAffectedObjects()) + { + logAi->trace("affected object: %s", evaluationContext.evaluator.ai->cb->getObj(obj)->getObjectName()); + } + if (garrisonHero) + logAi->debug("with %s and %d", garrisonHero->getNameTranslated(), int(swapCommand.getLockingReason())); + if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE) { auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero); @@ -1149,6 +1157,9 @@ public: evaluationContext.movementCost += mpLeft; evaluationContext.movementCostByRole[defenderRole] += mpLeft; evaluationContext.heroRole = defenderRole; + evaluationContext.isDefend = true; + evaluationContext.armyInvolvement = garrisonHero->getArmyStrength(); + logAi->debug("evaluationContext.isDefend: %d", evaluationContext.isDefend); } } }; @@ -1360,7 +1371,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, isDefend: %d, fuzzy: %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1380,6 +1391,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.conquestValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, + evaluationContext.isDefend, fuzzyResult); switch (priorityTier) @@ -1389,8 +1401,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) score = evaluationContext.armyInvolvement; score *= evaluationContext.closestWayRatio; - if (evaluationContext.movementCost > 0) - score /= evaluationContext.movementCost; break; } case 2: //Take towns @@ -1468,8 +1478,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (evaluationContext.isDefend) score = evaluationContext.armyInvolvement; score *= evaluationContext.closestWayRatio; - if (evaluationContext.movementCost > 0) - score /= evaluationContext.movementCost; + score /= (evaluationContext.turn + 1); break; } case 0: //For buildings and buying army From 05d948b5825816d4587e0f63ab5a627209ee8dda Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 30 Aug 2024 16:46:36 +0200 Subject: [PATCH 087/726] Priorities Swapped priority of attacking and defending. Troop-delivery-missions will check safety of the delivering hero. --- AI/Nullkiller/Engine/Nullkiller.cpp | 4 +++- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 24 +++++++++++++--------- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index d10e76cdb..d97aeca70 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -624,6 +624,8 @@ bool Nullkiller::handleTrading() { if (required[i] <= 0) continue; + if (i != 6 && income[i] > 0) + continue; float ratio = static_cast(available[i]) / required[i]; if (ratio < minRatio) { @@ -649,7 +651,7 @@ bool Nullkiller::handleTrading() } else { - if (required[i] <= 0) + if (required[i] <= 0 && income[i] > 0) okToSell = true; } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index c83e2fcc9..f46279c52 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -63,7 +63,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) involvesSailing(false), isTradeBuilding(false), isChain(false), - isEnemy(false) + isEnemy(false), + isExchange(false) { } @@ -899,6 +900,7 @@ public: evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength()); evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength(); evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero); + evaluationContext.isExchange = true; } }; @@ -1396,19 +1398,12 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) switch (priorityTier) { - case 1: //Defend immediately threatened towns - { - if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) - score = evaluationContext.armyInvolvement; - score *= evaluationContext.closestWayRatio; - break; - } - case 2: //Take towns + case 1: //Take towns { //score += evaluationContext.conquestValue * 1000; if(evaluationContext.conquestValue > 0) score = 1000; - if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.turn > 0 && !ai->cb->getTownsInfo().empty())) + if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1417,6 +1412,13 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } + case 2: //Defend immediately threatened towns + { + if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) + score = evaluationContext.armyInvolvement; + score *= evaluationContext.closestWayRatio; + break; + } case 3: //Collect unguarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1) @@ -1475,6 +1477,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case 5: //Defend whatever if nothing else is to do { + if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange) + return 0; if (evaluationContext.isDefend) score = evaluationContext.armyInvolvement; score *= evaluationContext.closestWayRatio; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index fb7960c67..c6cef4960 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -81,6 +81,7 @@ struct DLL_EXPORT EvaluationContext bool isTradeBuilding; bool isChain; bool isEnemy; + bool isExchange; EvaluationContext(const Nullkiller * ai); From 56988e054a65ce5cf4dc6430a743e668bd24ad9a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 30 Aug 2024 18:05:47 +0200 Subject: [PATCH 088/726] New priority 1. Take / kill what is reachable in same turn 2. Defend 3. Take / kill what is further away --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 24 +++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index d97aeca70..b6aaab7fc 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -408,7 +408,7 @@ void Nullkiller::makeTurn() TTaskVec selectedTasks; int prioOfTask = 0; - for (int prio = 1; prio <= 5; ++prio) + for (int prio = 1; prio <= 6; ++prio) { prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f46279c52..f095e5919 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1398,9 +1398,10 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) switch (priorityTier) { - case 1: //Take towns + case 1: //Take towns / kill heroes in immediate reach { - //score += evaluationContext.conquestValue * 1000; + if (evaluationContext.turn > 0) + return 0; if(evaluationContext.conquestValue > 0) score = 1000; if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) @@ -1419,7 +1420,20 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score *= evaluationContext.closestWayRatio; break; } - case 3: //Collect unguarded stuff + case 3: //Take towns / kill heroes that are further away + { + if (evaluationContext.conquestValue > 0) + score = 1000; + if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) + return 0; + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + break; + } + case 4: //Collect unguarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1) return 0; @@ -1449,7 +1463,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case 4: //Collect guarded stuff + case 5: //Collect guarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) return 0; @@ -1475,7 +1489,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } break; } - case 5: //Defend whatever if nothing else is to do + case 6: //Defend whatever if nothing else is to do { if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange) return 0; From ac8e5b3711b2fd17968cd06868d1058c4eb4b6fd Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 30 Aug 2024 21:02:50 +0200 Subject: [PATCH 089/726] Update PriorityEvaluator.cpp AI should score citadels and castles higher for better developed towns so that it focuses on finishing the main-town quicker as opposed to developing several smaller towns simultaneously. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f095e5919..d1a2dcd16 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1205,10 +1205,10 @@ public: evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have evaluationContext.heroRole = HeroRole::MAIN; evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount; - int32_t cost = bi.buildCostWithPrerequisites[EGameResID::GOLD]; + int32_t cost = bi.buildCost[EGameResID::GOLD]; evaluationContext.goldCost += cost; evaluationContext.closestWayRatio = 1; - evaluationContext.buildingCost += bi.buildCostWithPrerequisites; + evaluationContext.buildingCost += bi.buildCost; if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) evaluationContext.isTradeBuilding = true; @@ -1230,13 +1230,6 @@ public: evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount); evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount; } - int sameTownBonus = 0; - for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo()) - { - if (buildThis.town->getFaction() == town->getFaction()) - sameTownBonus+=town->getTownLevel(); - } - evaluationContext.armyReward *= sameTownBonus; } else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE) { @@ -1251,6 +1244,13 @@ public: evaluationContext.armyInvolvement += hero->getArmyCost(); } } + int sameTownBonus = 0; + for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo()) + { + if (buildThis.town->getFaction() == town->getFaction()) + sameTownBonus += town->getTownLevel(); + } + evaluationContext.armyReward *= sameTownBonus; if(evaluationContext.goldReward) { From 72597b549b66602c5194f00228121f4365a300d9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 31 Aug 2024 23:00:27 +0200 Subject: [PATCH 090/726] hero spread Prefer hiring heroes at towns that don't have heroes nearby. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 8 ++++++++ AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 49ae295ca..f6e72e42b 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -148,6 +148,7 @@ void BuildAnalyzer::update() auto towns = ai->cb->getTownsInfo(); float economyDevelopmentCost = 0; + TResources nonGoldEconomyResources; for(const CGTownInstance* town : towns) { @@ -164,7 +165,10 @@ void BuildAnalyzer::update() for(auto building : developmentInfo.toBuild) { if (building.dailyIncome[EGameResID::GOLD] > 0) + { economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD]; + nonGoldEconomyResources += building.buildCostWithPrerequisites; + } } armyCost += developmentInfo.armyCost; @@ -173,6 +177,10 @@ void BuildAnalyzer::update() logAi->trace("Building preferences %s", bi.toString()); } } + nonGoldEconomyResources[EGameResID::GOLD] = 0; + //If we don't have the non-gold-resources to build a structure, we also don't need to save gold for it and can consider building something else instead + if (!ai->getFreeResources().canAfford(nonGoldEconomyResources)) + economyDevelopmentCost = 0; std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool { diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 27a784df6..f183a11a0 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -64,9 +64,15 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const { closestThreat = std::min(closestThreat, threat.turn); } - //Don' hire a hero in a threatened town as one would have to stay outside - if (closestThreat <= 1 && (town->visitingHero || town->garrisonHero)) + //Don't hire a hero where there already is one present + if (town->visitingHero || town->garrisonHero) continue; + float visitability = 0; + for (auto checkHero : ourHeroes) + { + if (ai->dangerHitMap->getClosestTown(checkHero.first.get()->pos) == town) + visitability++; + } if(ai->heroManager->canRecruitHero(town)) { auto availableHeroes = ai->cb->getAvailableHeroes(town); @@ -81,7 +87,10 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const score *= hero->getArmyCost(); if (hero->type->heroClass->faction == town->getFaction()) score *= 1.5; - score *= town->getTownLevel(); + if (visitability == 0) + score *= 30 * town->getTownLevel(); + else + score *= town->getTownLevel() / visitability; if (score > bestScore) { bestScore = score; From 0c488145b9bd0aa1165a68dde3b1a353b7ff58b4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 00:02:47 +0200 Subject: [PATCH 091/726] Update BuildAnalyzer.cpp Revert unintentionally commited changes --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index f6e72e42b..49ae295ca 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -148,7 +148,6 @@ void BuildAnalyzer::update() auto towns = ai->cb->getTownsInfo(); float economyDevelopmentCost = 0; - TResources nonGoldEconomyResources; for(const CGTownInstance* town : towns) { @@ -165,10 +164,7 @@ void BuildAnalyzer::update() for(auto building : developmentInfo.toBuild) { if (building.dailyIncome[EGameResID::GOLD] > 0) - { economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD]; - nonGoldEconomyResources += building.buildCostWithPrerequisites; - } } armyCost += developmentInfo.armyCost; @@ -177,10 +173,6 @@ void BuildAnalyzer::update() logAi->trace("Building preferences %s", bi.toString()); } } - nonGoldEconomyResources[EGameResID::GOLD] = 0; - //If we don't have the non-gold-resources to build a structure, we also don't need to save gold for it and can consider building something else instead - if (!ai->getFreeResources().canAfford(nonGoldEconomyResources)) - economyDevelopmentCost = 0; std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool { From df5d1438229c192295d4c34f7373f84690ebfedd Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 01:33:31 +0200 Subject: [PATCH 092/726] Difficulty-cheats Added the difficulty-dependent resource-cheats from the original game. --- server/processors/NewTurnProcessor.cpp | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index 4a34be20a..1078d5d33 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -234,6 +234,52 @@ ResourceSet NewTurnProcessor::generatePlayerIncome(PlayerColor playerID, bool ne for (auto obj : state.getOwnedObjects()) incomeHandicapped += obj->asOwnable()->dailyIncome(); + if (!state.isHuman()) + { + // Initialize bonuses for different resources + std::array weeklyBonuses = {}; + + // Calculate weekly bonuses based on difficulty + if (gameHandler->gameState()->getStartInfo()->difficulty == 0) + { + weeklyBonuses[EGameResID::GOLD] = static_cast(std::round(incomeHandicapped[EGameResID::GOLD] * (0.75 - 1) * 7)); + } + else if (gameHandler->gameState()->getStartInfo()->difficulty == 3) + { + weeklyBonuses[EGameResID::GOLD] = static_cast(std::round(incomeHandicapped[EGameResID::GOLD] * 0.25 * 7)); + weeklyBonuses[EGameResID::WOOD] = static_cast(std::round(incomeHandicapped[EGameResID::WOOD] * 0.39 * 7)); + weeklyBonuses[EGameResID::ORE] = static_cast(std::round(incomeHandicapped[EGameResID::ORE] * 0.39 * 7)); + weeklyBonuses[EGameResID::MERCURY] = static_cast(std::round(incomeHandicapped[EGameResID::MERCURY] * 0.14 * 7)); + weeklyBonuses[EGameResID::CRYSTAL] = static_cast(std::round(incomeHandicapped[EGameResID::CRYSTAL] * 0.14 * 7)); + weeklyBonuses[EGameResID::SULFUR] = static_cast(std::round(incomeHandicapped[EGameResID::SULFUR] * 0.14 * 7)); + weeklyBonuses[EGameResID::GEMS] = static_cast(std::round(incomeHandicapped[EGameResID::GEMS] * 0.14 * 7)); + } + else if (gameHandler->gameState()->getStartInfo()->difficulty == 4) + { + weeklyBonuses[EGameResID::GOLD] = static_cast(std::round(incomeHandicapped[EGameResID::GOLD] * 0.5 * 7)); + weeklyBonuses[EGameResID::WOOD] = static_cast(std::round(incomeHandicapped[EGameResID::WOOD] * 0.53 * 7)); + weeklyBonuses[EGameResID::ORE] = static_cast(std::round(incomeHandicapped[EGameResID::ORE] * 0.53 * 7)); + weeklyBonuses[EGameResID::MERCURY] = static_cast(std::round(incomeHandicapped[EGameResID::MERCURY] * 0.28 * 7)); + weeklyBonuses[EGameResID::CRYSTAL] = static_cast(std::round(incomeHandicapped[EGameResID::CRYSTAL] * 0.28 * 7)); + weeklyBonuses[EGameResID::SULFUR] = static_cast(std::round(incomeHandicapped[EGameResID::SULFUR] * 0.28 * 7)); + weeklyBonuses[EGameResID::GEMS] = static_cast(std::round(incomeHandicapped[EGameResID::GEMS] * 0.28 * 7)); + } + + // Distribute weekly bonuses over 7 days, depending on the current day of the week + for (int i = 0; i < GameResID::COUNT; ++i) + { + int dailyBonus = weeklyBonuses[i] / 7; + int remainderBonus = weeklyBonuses[i] % 7; + + // Apply the daily bonus for each day, and distribute the remainder accordingly + incomeHandicapped[static_cast(i)] += dailyBonus; + if (gameHandler->gameState()->getDate(Date::DAY_OF_WEEK) - 1 < remainderBonus) + { + incomeHandicapped[static_cast(i)] += 1; + } + } + } + return incomeHandicapped; } From be43c4d5f06733cc80d0bebb949231c2561e0b9f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 12:33:54 +0200 Subject: [PATCH 093/726] New hero-not acting Fixed an issue that caused newly hired heroes to do nothing on the turn they were hired under certain circumstances. --- AI/Nullkiller/Goals/RecruitHero.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index c6a6c4d4e..156f41696 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -69,6 +69,7 @@ void RecruitHero::accept(AIGateway * ai) cb->recruitHero(t, heroToHire); ai->nullkiller->heroManager->update(); + ai->nullkiller->objectClusterizer->reset(); if(t->visitingHero) ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get()); From 751f3b0e7d9cdb6d8b534127c1301f751a0b8521 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 13:46:44 +0200 Subject: [PATCH 094/726] Update BuildingBehavior.cpp Fixed an issue that prevented generating more building-tasks when there already were tasks. --- AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index cb9af8405..caed8fc1e 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -53,6 +53,7 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const for(auto & developmentInfo : developmentInfos) { + bool emergencyDefense = false; uint8_t closestThreat = UINT8_MAX; for (auto threat : ai->dangerHitMap->getTownThreats(developmentInfo.town)) { @@ -63,13 +64,17 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE && !buildingInfo.notEnoughRes) { if (buildingInfo.id == BuildingID::FORT || buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE) + { tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); + emergencyDefense = true; + } } } - if (tasks.empty()) + if (!emergencyDefense) { for (auto& buildingInfo : developmentInfo.toBuild) { + logAi->trace("Looking at %s", buildingInfo.toString()); if (isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) { if (buildingInfo.notEnoughRes) @@ -81,11 +86,14 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const composition.addNext(BuildThis(buildingInfo, developmentInfo)); composition.addNext(SaveResources(buildingInfo.buildCost)); - + logAi->trace("Generate task to build: %s", buildingInfo.toString()); tasks.push_back(sptr(composition)); } else + { tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); + logAi->trace("Generate task to build: %s", buildingInfo.toString()); + } } } } From 1ef5e8ab1b7be79c67a585d123b6879778270fd8 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 13:47:30 +0200 Subject: [PATCH 095/726] Update PriorityEvaluator.cpp Prevent building more buildings when we are saving for our favorite building. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index d1a2dcd16..d898ae1b8 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1208,7 +1208,7 @@ public: int32_t cost = bi.buildCost[EGameResID::GOLD]; evaluationContext.goldCost += cost; evaluationContext.closestWayRatio = 1; - evaluationContext.buildingCost += bi.buildCost; + evaluationContext.buildingCost += bi.buildCostWithPrerequisites; if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) evaluationContext.isTradeBuilding = true; @@ -1503,6 +1503,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; + //If we already have locked resources, we don't look at other buildings + if (ai->getLockedResources().marketValue() > 0) + return 0; score += evaluationContext.conquestValue * 1000; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; From 0e91f10bbc1c74993196eb4f47c6418081534ca7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 17:21:53 +0200 Subject: [PATCH 096/726] Update Nullkiller.cpp ResetAIState so that units realize what they can do after unlocking a cluster. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b6aaab7fc..592cec6f4 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -369,6 +369,8 @@ void Nullkiller::makeTurn() for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++) { auto start = std::chrono::high_resolution_clock::now(); + //TODO: It's only necessary to do a resetAiState when the last action was UnlockCluster + resetAiState(); updateAiState(i); Goals::TTask bestTask = taskptr(Goals::Invalid()); From 7c6f96344a600eaeb909d4306c3b175cb570e619 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 19:33:43 +0200 Subject: [PATCH 097/726] Update Nullkiller.cpp Removed resetAiState from loop cause it has too many side-effects. Such as the loop going through all passes. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 592cec6f4..b6aaab7fc 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -369,8 +369,6 @@ void Nullkiller::makeTurn() for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++) { auto start = std::chrono::high_resolution_clock::now(); - //TODO: It's only necessary to do a resetAiState when the last action was UnlockCluster - resetAiState(); updateAiState(i); Goals::TTask bestTask = taskptr(Goals::Invalid()); From 64c3fbd51928b81bcc412ced989dc8906e389236 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 1 Sep 2024 23:58:47 +0200 Subject: [PATCH 098/726] Update ExecuteHeroChain.cpp Now resetting the ObjectClusterizer as killing something might change the situation. --- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index 566427f4d..1a39ff776 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -86,6 +86,7 @@ void ExecuteHeroChain::accept(AIGateway * ai) ai->nullkiller->setActive(chainPath.targetHero, tile); ai->nullkiller->setTargetObject(objid); + ai->nullkiller->objectClusterizer->reset(); auto targetObject = ai->myCb->getObj(static_cast(objid), false); From c667ca46d1a49ed7182df9782592a7cdf72a71c6 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 2 Sep 2024 00:00:36 +0200 Subject: [PATCH 099/726] Using correct priorityTier for Clusterization Clusterizer now uses PriorityTier = 5 for evaluation, which is used to generate priority for guarded objects --- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 4 ++-- AI/Nullkiller/Engine/Nullkiller.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 5c4bb4cea..8d318badf 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -467,7 +467,7 @@ void ObjectClusterizer::clusterizeObject( heroesProcessed.insert(path.targetHero); - float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), 5); if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) continue; @@ -490,7 +490,7 @@ void ObjectClusterizer::clusterizeObject( heroesProcessed.insert(path.targetHero); - float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), 5); if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) continue; diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b6aaab7fc..9984dbf51 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -180,7 +180,7 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const for(size_t i = r.begin(); i != r.end(); i++) { auto task = tasks[i]; - if (task->asTask()->priority <= 0 || priorityTier != 3) + if (task->asTask()->priority <= 0 || priorityTier != 0) task->asTask()->priority = evaluator->evaluate(task, priorityTier); } }); @@ -385,7 +385,7 @@ void Nullkiller::makeTurn() if(bestTask->priority > 0) { - logAi->info("Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); + logAi->info("Pass %d: Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) return; From 09badeb5befd3254341331cc554b57bfc29066bd Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 2 Sep 2024 00:16:19 +0200 Subject: [PATCH 100/726] Enum for PriorityTiers In order to not confuse PriorityTiers, especially after adding new ones, now using an enum to identify them. --- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 4 ++-- AI/Nullkiller/Engine/Nullkiller.cpp | 6 +++--- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 14 +++++++------- AI/Nullkiller/Engine/PriorityEvaluator.h | 13 ++++++++++++- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 8d318badf..7e46dc713 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -467,7 +467,7 @@ void ObjectClusterizer::clusterizeObject( heroesProcessed.insert(path.targetHero); - float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), 5); + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER); if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) continue; @@ -490,7 +490,7 @@ void ObjectClusterizer::clusterizeObject( heroesProcessed.insert(path.targetHero); - float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), 5); + float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER); if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) continue; diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 9984dbf51..bc6dae208 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -180,7 +180,7 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const for(size_t i = r.begin(); i != r.end(); i++) { auto task = tasks[i]; - if (task->asTask()->priority <= 0 || priorityTier != 0) + if (task->asTask()->priority <= 0 || priorityTier != PriorityEvaluator::PriorityTier::BUILDINGS) task->asTask()->priority = evaluator->evaluate(task, priorityTier); } }); @@ -385,7 +385,7 @@ void Nullkiller::makeTurn() if(bestTask->priority > 0) { - logAi->info("Pass %d: Performing task %s with prio: %d", bestTask->toString(), bestTask->priority); + logAi->info("Pass %d: Performing task %s with prio: %d", i, bestTask->toString(), bestTask->priority); if(!executeTask(bestTask)) return; @@ -408,7 +408,7 @@ void Nullkiller::makeTurn() TTaskVec selectedTasks; int prioOfTask = 0; - for (int prio = 1; prio <= 6; ++prio) + for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio) { prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index d898ae1b8..51c2ac804 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1398,7 +1398,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) switch (priorityTier) { - case 1: //Take towns / kill heroes in immediate reach + case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach { if (evaluationContext.turn > 0) return 0; @@ -1413,14 +1413,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case 2: //Defend immediately threatened towns + case PriorityTier::INSTADEFEND: //Defend immediately threatened towns { if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) score = evaluationContext.armyInvolvement; score *= evaluationContext.closestWayRatio; break; } - case 3: //Take towns / kill heroes that are further away + case PriorityTier::KILL: //Take towns / kill heroes that are further away { if (evaluationContext.conquestValue > 0) score = 1000; @@ -1433,7 +1433,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case 4: //Collect unguarded stuff + case PriorityTier::GATHER: //Collect unguarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1) return 0; @@ -1463,7 +1463,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case 5: //Collect guarded stuff + case PriorityTier::HUNTER_GATHER: //Collect guarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) return 0; @@ -1489,7 +1489,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } break; } - case 6: //Defend whatever if nothing else is to do + case PriorityTier::DEFEND: //Defend whatever if nothing else is to do { if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange) return 0; @@ -1499,7 +1499,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= (evaluationContext.turn + 1); break; } - case 0: //For buildings and buying army + case PriorityTier::BUILDINGS: //For buildings and buying army { if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index c6cef4960..771a913e3 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -104,7 +104,18 @@ public: ~PriorityEvaluator(); void initVisitTile(); - float evaluate(Goals::TSubgoal task, int priorityTier = 0); + float evaluate(Goals::TSubgoal task, int priorityTier = BUILDINGS); + + enum PriorityTier : int32_t + { + BUILDINGS = 0, + INSTAKILL, + INSTADEFEND, + KILL, + GATHER, + HUNTER_GATHER, + DEFEND + }; private: const Nullkiller * ai; From 1176628a88fa5a8385b621a596c587d0912a2034 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 2 Sep 2024 01:37:21 +0200 Subject: [PATCH 101/726] Update PriorityEvaluator.cpp Workaround for weird -nan(ind) closestWayRatios. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 51c2ac804..144ab00d4 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1547,6 +1547,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } } result = score; + //TODO: Figure out the root cause for why evaluationContext.closestWayRatio has become -nan(ind). + if (std::isnan(result)) + return 0; } #if NKAI_TRACE_LEVEL >= 2 From 3f916ab54310bf133d5d46ceaa23bf55064c48ef Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Mon, 2 Sep 2024 14:24:22 +0300 Subject: [PATCH 102/726] BattleAI: avoid standing in moat --- AI/BattleAI/AttackPossibility.cpp | 76 +++++++++++++++++++++++++++ AI/BattleAI/AttackPossibility.h | 4 ++ AI/BattleAI/BattleEvaluator.cpp | 31 +++++++++-- AI/BattleAI/BattleExchangeVariant.cpp | 6 +++ AI/BattleAI/PotentialTargets.cpp | 1 + 5 files changed, 115 insertions(+), 3 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index e2fb50dc7..04349003e 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -12,6 +12,10 @@ #include "../../lib/CStack.h" // TODO: remove // Eventually only IBattleInfoCallback and battle::Unit should be used, // CUnitState should be private and CStack should be removed completely +#include "../../lib/spells/CSpellHandler.h" +#include "../../lib/spells/ISpellMechanics.h" +#include "../../lib/spells/ObstacleCasterProxy.h" +#include "../../lib/battle/CObstacleInstance.h" uint64_t averageDmg(const DamageRange & range) { @@ -25,9 +29,55 @@ void DamageCache::cacheDamage(const battle::Unit * attacker, const battle::Unit damageCache[attacker->unitId()][defender->unitId()] = static_cast(damage) / attacker->getCount(); } +void DamageCache::buildObstacleDamageCache(std::shared_ptr hb, BattleSide side) +{ + for(const auto & obst : hb->battleGetAllObstacles(side)) + { + auto spellObstacle = dynamic_cast(obst.get()); + + if(!spellObstacle || !obst->triggersEffects()) + continue; + + auto triggerAbility = VLC->spells()->getById(obst->getTrigger()); + auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage(); + + if(!triggerIsNegative) + continue; + + const auto * hero = hb->battleGetFightingHero(spellObstacle->casterSide); + auto caster = spells::ObstacleCasterProxy(hb->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle); + + auto affectedHexes = obst->getAffectedTiles(); + auto stacks = hb->battleGetUnitsIf([](const battle::Unit * u) -> bool { return u->alive(); }); + + for(auto stack : stacks) + { + std::shared_ptr inner = std::make_shared(hb->env, hb); + auto cast = spells::BattleCast(hb.get(), &caster, spells::Mode::PASSIVE, obst->getTrigger().toSpell()); + auto updated = inner->getForUpdate(stack->unitId()); + + spells::Target target; + target.push_back(spells::Destination(updated.get())); + + cast.castEval(inner->getServerCallback(), target); + + auto damageDealt = stack->getAvailableHealth() - updated->getAvailableHealth(); + + for(auto hex : affectedHexes) + { + obstacleDamage[hex][stack->unitId()] = damageDealt; + } + } + } +} void DamageCache::buildDamageCache(std::shared_ptr hb, BattleSide side) { + if(parent == nullptr) + { + buildObstacleDamageCache(hb, side); + } + auto stacks = hb->battleGetUnitsIf([=](const battle::Unit * u) -> bool { return u->isValidTarget(); @@ -70,6 +120,23 @@ int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); } +int64_t DamageCache::getObstacleDamage(BattleHex hex, const battle::Unit * defender) +{ + if(parent) + return parent->getObstacleDamage(hex, defender); + + auto damages = obstacleDamage.find(hex); + + if(damages == obstacleDamage.end()) + return 0; + + auto damage = damages->second.find(defender->unitId()); + + return damage == damages->second.end() + ? 0 + : damage->second; +} + int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb) { if(parent) @@ -288,6 +355,15 @@ AttackPossibility AttackPossibility::evaluate( { retaliatedUnits.push_back(attacker); } + + auto obstacleDamage = damageCache.getObstacleDamage(hex, attacker); + + if(obstacleDamage > 0) + { + ap.attackerDamageReduce += calculateDamageReduce(nullptr, attacker, obstacleDamage, damageCache, state); + + ap.attackerState->damage(obstacleDamage); + } } // ensure the defender is also affected diff --git a/AI/BattleAI/AttackPossibility.h b/AI/BattleAI/AttackPossibility.h index 990dcdb00..3ef8e1523 100644 --- a/AI/BattleAI/AttackPossibility.h +++ b/AI/BattleAI/AttackPossibility.h @@ -18,14 +18,18 @@ class DamageCache { private: std::unordered_map> damageCache; + std::map> obstacleDamage; DamageCache * parent; + void buildObstacleDamageCache(std::shared_ptr hb, BattleSide side); + public: DamageCache() : parent(nullptr) {} DamageCache(DamageCache * parent) : parent(parent) {} void cacheDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb); int64_t getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb); + int64_t getObstacleDamage(BattleHex hex, const battle::Unit * defender); int64_t getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb); void buildDamageCache(std::shared_ptr hb, BattleSide side); }; diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 8ee042f14..555865619 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -226,11 +226,36 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) { return BattleAction::makeDefend(stack); } - else + + auto enemyMellee = hb->getUnitsIf([this](const battle::Unit * u) -> bool + { + return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u); + }); + + bool isTargetOutsideFort = bestAttack.dest.getY() < GameConstants::BFIELD_WIDTH - 4; + bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER + && !bestAttack.attack.shooting + && hb->battleGetFortifications().hasMoat + && !enemyMellee.empty() + && isTargetOutsideFort; + + if(siegeDefense) { - activeActionMade = true; - return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defenderPos, bestAttack.from); + logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex); + + BattleAttackInfo bai(stack, stack, 0, false); + AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai); + + float defenseValue = scoreEvaluator.evaluateExchange(apDefend, 0, *targets, damageCache, hb); + + if((defenseValue > score && score <= 0) || (defenseValue > 2 * score && score > 0)) + { + return BattleAction::makeDefend(stack); + } } + + activeActionMade = true; + return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defenderPos, bestAttack.from); } } } diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 70c881d23..4f3b1550e 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -28,6 +28,12 @@ float BattleExchangeVariant::trackAttack( std::shared_ptr hb, DamageCache & damageCache) { + if(!ap.attackerState) + { + logAi->trace("Skipping fake ap attack"); + return 0; + } + auto attacker = hb->getForUpdate(ap.attack.attacker->unitId()); float attackValue = ap.attackValue(); diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index a341921e6..f38415ef7 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "PotentialTargets.h" #include "../../lib/CStack.h"//todo: remove +#include "../../lib/mapObjects/CGTownInstance.h" PotentialTargets::PotentialTargets( const battle::Unit * attacker, From 64fad53532d7ae3a70c8baa8b58c59495a9aba70 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 3 Sep 2024 20:51:13 +0200 Subject: [PATCH 103/726] Revert "Merge branch 'pr/4528' into develop" This reverts commit f4578c6d3ab9449a0b571857b1736515f0339670, reversing changes made to ac8e5b3711b2fd17968cd06868d1058c4eb4b6fd. --- AI/BattleAI/BattleExchangeVariant.cpp | 15 ++++++--------- AI/Nullkiller/Analyzers/HeroManager.cpp | 9 +++------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 33e1a9041..a6bd2758e 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -491,18 +491,15 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex)); } - if(!ap.attack.attacker->isTurret()) + for(auto hex : ap.attack.attacker->getHexes()) { - for(auto hex : ap.attack.attacker->getHexes()) + auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); + for(auto unit : unitsReachingAttacker) { - auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); - for(auto unit : unitsReachingAttacker) + if(unit->unitSide() != ap.attack.attacker->unitSide()) { - if(unit->unitSide() != ap.attack.attacker->unitSide()) - { - allReachableUnits.push_back(unit); - result.enemyUnitsReachingAttacker.insert(unit->unitId()); - } + allReachableUnits.push_back(unit); + result.enemyUnitsReachingAttacker.insert(unit->unitId()); } } } diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index eb95e5ebc..e2406a023 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -126,23 +126,20 @@ void HeroManager::update() } std::sort(myHeroes.begin(), myHeroes.end(), scoreSort); - - std::map newHeroRoles; + heroRoles.clear(); for(auto hero : myHeroes) { if(hero->patrol.patrolling) { - newHeroRoles[hero] = HeroRole::MAIN; + heroRoles[hero] = HeroRole::MAIN; } else { - newHeroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; + heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; } } - heroRoles = std::move(newHeroRoles); - for(auto hero : myHeroes) { logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); From dfa992951bd9fb71591febc5e7de406db13255c9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 3 Sep 2024 20:57:05 +0200 Subject: [PATCH 104/726] Revert "Merge branch 'fix-battle-ai' into develop" This reverts commit b489816d29f15a6d0a0abc5827887294bc08db03, reversing changes made to 5ee7061ab76bff92ab7f579c137dd19c2710908e. --- AI/BattleAI/AttackPossibility.cpp | 76 --------------------------- AI/BattleAI/AttackPossibility.h | 4 -- AI/BattleAI/BattleEvaluator.cpp | 31 ++--------- AI/BattleAI/BattleExchangeVariant.cpp | 6 --- AI/BattleAI/PotentialTargets.cpp | 1 - 5 files changed, 3 insertions(+), 115 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 04349003e..e2fb50dc7 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -12,10 +12,6 @@ #include "../../lib/CStack.h" // TODO: remove // Eventually only IBattleInfoCallback and battle::Unit should be used, // CUnitState should be private and CStack should be removed completely -#include "../../lib/spells/CSpellHandler.h" -#include "../../lib/spells/ISpellMechanics.h" -#include "../../lib/spells/ObstacleCasterProxy.h" -#include "../../lib/battle/CObstacleInstance.h" uint64_t averageDmg(const DamageRange & range) { @@ -29,55 +25,9 @@ void DamageCache::cacheDamage(const battle::Unit * attacker, const battle::Unit damageCache[attacker->unitId()][defender->unitId()] = static_cast(damage) / attacker->getCount(); } -void DamageCache::buildObstacleDamageCache(std::shared_ptr hb, BattleSide side) -{ - for(const auto & obst : hb->battleGetAllObstacles(side)) - { - auto spellObstacle = dynamic_cast(obst.get()); - - if(!spellObstacle || !obst->triggersEffects()) - continue; - - auto triggerAbility = VLC->spells()->getById(obst->getTrigger()); - auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage(); - - if(!triggerIsNegative) - continue; - - const auto * hero = hb->battleGetFightingHero(spellObstacle->casterSide); - auto caster = spells::ObstacleCasterProxy(hb->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle); - - auto affectedHexes = obst->getAffectedTiles(); - auto stacks = hb->battleGetUnitsIf([](const battle::Unit * u) -> bool { return u->alive(); }); - - for(auto stack : stacks) - { - std::shared_ptr inner = std::make_shared(hb->env, hb); - auto cast = spells::BattleCast(hb.get(), &caster, spells::Mode::PASSIVE, obst->getTrigger().toSpell()); - auto updated = inner->getForUpdate(stack->unitId()); - - spells::Target target; - target.push_back(spells::Destination(updated.get())); - - cast.castEval(inner->getServerCallback(), target); - - auto damageDealt = stack->getAvailableHealth() - updated->getAvailableHealth(); - - for(auto hex : affectedHexes) - { - obstacleDamage[hex][stack->unitId()] = damageDealt; - } - } - } -} void DamageCache::buildDamageCache(std::shared_ptr hb, BattleSide side) { - if(parent == nullptr) - { - buildObstacleDamageCache(hb, side); - } - auto stacks = hb->battleGetUnitsIf([=](const battle::Unit * u) -> bool { return u->isValidTarget(); @@ -120,23 +70,6 @@ int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount(); } -int64_t DamageCache::getObstacleDamage(BattleHex hex, const battle::Unit * defender) -{ - if(parent) - return parent->getObstacleDamage(hex, defender); - - auto damages = obstacleDamage.find(hex); - - if(damages == obstacleDamage.end()) - return 0; - - auto damage = damages->second.find(defender->unitId()); - - return damage == damages->second.end() - ? 0 - : damage->second; -} - int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb) { if(parent) @@ -355,15 +288,6 @@ AttackPossibility AttackPossibility::evaluate( { retaliatedUnits.push_back(attacker); } - - auto obstacleDamage = damageCache.getObstacleDamage(hex, attacker); - - if(obstacleDamage > 0) - { - ap.attackerDamageReduce += calculateDamageReduce(nullptr, attacker, obstacleDamage, damageCache, state); - - ap.attackerState->damage(obstacleDamage); - } } // ensure the defender is also affected diff --git a/AI/BattleAI/AttackPossibility.h b/AI/BattleAI/AttackPossibility.h index 3ef8e1523..990dcdb00 100644 --- a/AI/BattleAI/AttackPossibility.h +++ b/AI/BattleAI/AttackPossibility.h @@ -18,18 +18,14 @@ class DamageCache { private: std::unordered_map> damageCache; - std::map> obstacleDamage; DamageCache * parent; - void buildObstacleDamageCache(std::shared_ptr hb, BattleSide side); - public: DamageCache() : parent(nullptr) {} DamageCache(DamageCache * parent) : parent(parent) {} void cacheDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb); int64_t getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb); - int64_t getObstacleDamage(BattleHex hex, const battle::Unit * defender); int64_t getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr hb); void buildDamageCache(std::shared_ptr hb, BattleSide side); }; diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 6422b43e2..c54bc94e7 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -226,36 +226,11 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) { return BattleAction::makeDefend(stack); } - - auto enemyMellee = hb->getUnitsIf([this](const battle::Unit * u) -> bool - { - return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u); - }); - - bool isTargetOutsideFort = bestAttack.dest.getY() < GameConstants::BFIELD_WIDTH - 4; - bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER - && !bestAttack.attack.shooting - && hb->battleGetFortifications().hasMoat - && !enemyMellee.empty() - && isTargetOutsideFort; - - if(siegeDefense) + else { - logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex); - - BattleAttackInfo bai(stack, stack, 0, false); - AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai); - - float defenseValue = scoreEvaluator.evaluateExchange(apDefend, 0, *targets, damageCache, hb); - - if((defenseValue > score && score <= 0) || (defenseValue > 2 * score && score > 0)) - { - return BattleAction::makeDefend(stack); - } + activeActionMade = true; + return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defenderPos, bestAttack.from); } - - activeActionMade = true; - return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defenderPos, bestAttack.from); } } } diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index a6bd2758e..097ffb07b 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -28,12 +28,6 @@ float BattleExchangeVariant::trackAttack( std::shared_ptr hb, DamageCache & damageCache) { - if(!ap.attackerState) - { - logAi->trace("Skipping fake ap attack"); - return 0; - } - auto attacker = hb->getForUpdate(ap.attack.attacker->unitId()); float attackValue = ap.attackValue(); diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index f38415ef7..a341921e6 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "PotentialTargets.h" #include "../../lib/CStack.h"//todo: remove -#include "../../lib/mapObjects/CGTownInstance.h" PotentialTargets::PotentialTargets( const battle::Unit * attacker, From d0aefdfbe6e899042b1623bad2840a3e9ee845fe Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 3 Sep 2024 21:17:06 +0200 Subject: [PATCH 105/726] Update RecruitHero.cpp Removed a A --- AI/Nullkiller/Goals/RecruitHero.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index b78ac92bb..5ea60317f 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -76,7 +76,7 @@ void RecruitHero::accept(AIGateway * ai) ai->nullkiller->objectClusterizer->reset(); } - if(t->visitingHero)A + if(t->visitingHero) ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get()); } From db16a9d234748bcbe9da44673c50e5437e2d0169 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 4 Sep 2024 16:41:47 +0200 Subject: [PATCH 106/726] A bit of clean-up for merge Set back trace level to 0 Removed EvaluationContexts that weren't used Encapsulated many debug-messages behinde trace-levels --- AI/Nullkiller/Engine/Nullkiller.cpp | 45 +++++++++++++--------- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 11 +----- AI/Nullkiller/Engine/PriorityEvaluator.h | 2 - AI/Nullkiller/Pathfinding/AINodeStorage.h | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index bc6dae208..46d7825ab 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -350,6 +350,11 @@ void Nullkiller::makeTurn() const int MAX_DEPTH = 10; + resetAiState(); + + Goals::TGoalVec bestTasks; + +#if NKAI_TRACE_LEVEL >= 1 float totalHeroStrength = 0; int totalTownLevel = 0; for (auto heroInfo : cb->getHeroesInfo()) @@ -360,12 +365,8 @@ void Nullkiller::makeTurn() { totalTownLevel += townInfo->getTownLevel(); } - - resetAiState(); - - Goals::TGoalVec bestTasks; - logAi->info("Beginning: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); +#endif for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++) { auto start = std::chrono::high_resolution_clock::now(); @@ -385,7 +386,9 @@ void Nullkiller::makeTurn() if(bestTask->priority > 0) { +#if NKAI_TRACE_LEVEL >= 1 logAi->info("Pass %d: Performing task %s with prio: %d", i, bestTask->toString(), bestTask->priority); +#endif if(!executeTask(bestTask)) return; @@ -485,7 +488,9 @@ void Nullkiller::makeTurn() continue; } +#if NKAI_TRACE_LEVEL >= 1 logAi->info("Pass %d: Performing prio %d task %s with prio: %d", i, prioOfTask, bestTask->toString(), bestTask->priority); +#endif if(!executeTask(bestTask)) { if(hasAnySuccess) @@ -501,6 +506,17 @@ void Nullkiller::makeTurn() if(!hasAnySuccess) { logAi->trace("Nothing was done this turn. Ending turn."); +#if NKAI_TRACE_LEVEL >= 1 + for (auto heroInfo : cb->getHeroesInfo()) + { + totalHeroStrength += heroInfo->getTotalStrength(); + } + for (auto townInfo : cb->getTownsInfo()) + { + totalTownLevel += townInfo->getTownLevel(); + } + logAi->info("End: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); +#endif return; } @@ -509,15 +525,6 @@ void Nullkiller::makeTurn() logAi->warn("Maxpass exceeded. Terminating AI turn."); } } - for (auto heroInfo : cb->getHeroesInfo()) - { - totalHeroStrength += heroInfo->getTotalStrength(); - } - for (auto townInfo : cb->getTownsInfo()) - { - totalTownLevel += townInfo->getTownLevel(); - } - logAi->info("End: Strength: %f Townlevel: %d Resources: %s", totalHeroStrength, totalTownLevel, cb->getResourceAmount().toString()); } bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const @@ -611,10 +618,10 @@ bool Nullkiller::handleTrading() TResources required = buildAnalyzer->getTotalResourcesRequired(); TResources income = buildAnalyzer->getDailyIncome(); TResources available = cb->getResourceAmount(); - +#if NKAI_TRACE_LEVEL >= 2 logAi->debug("Available %s", available.toString()); logAi->debug("Required %s", required.toString()); - +#endif int mostWanted = -1; int mostExpendable = -1; float minRatio = std::numeric_limits::max(); @@ -660,9 +667,9 @@ bool Nullkiller::handleTrading() mostExpendable = i; } } - +#if NKAI_TRACE_LEVEL >= 2 logAi->debug("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted); - +#endif if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1) return false; @@ -674,7 +681,9 @@ bool Nullkiller::handleTrading() if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources { cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); +#if NKAI_TRACE_LEVEL >= 1 logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName()); +#endif haveTraded = true; shouldTryToTrade = true; } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 144ab00d4..cb282fa50 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -62,8 +62,6 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) threatTurns(INT_MAX), involvesSailing(false), isTradeBuilding(false), - isChain(false), - isEnemy(false), isExchange(false) { } @@ -1031,7 +1029,6 @@ public: vstd::amax(evaluationContext.danger, path.getTotalDanger()); evaluationContext.movementCost += path.movementCost(); evaluationContext.closestWayRatio = chain.closestWayRatio; - evaluationContext.isChain = true; std::map costsPerHero; @@ -1069,8 +1066,6 @@ public: evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); evaluationContext.armyInvolvement += army->getArmyCost(); - if (target->tempOwner != PlayerColor::NEUTRAL) - evaluationContext.isEnemy = true; } vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength()); @@ -1119,9 +1114,6 @@ public: evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost; evaluationContext.movementCost += objInfo.second.movementCost / boost; - if (target->tempOwner != PlayerColor::NEUTRAL) - evaluationContext.isEnemy = true; - vstd::amax(evaluationContext.turn, objInfo.second.turn / boost); boost <<= 1; @@ -1372,7 +1364,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; - +#if NKAI_TRACE_LEVEL >= 2 logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, isDefend: %d, fuzzy: %f", priorityTier, task->toString(), @@ -1395,6 +1387,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.enemyHeroDangerRatio, evaluationContext.isDefend, fuzzyResult); +#endif switch (priorityTier) { diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 771a913e3..5e8e8c365 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -79,8 +79,6 @@ struct DLL_EXPORT EvaluationContext TResources buildingCost; bool involvesSailing; bool isTradeBuilding; - bool isChain; - bool isEnemy; bool isExchange; EvaluationContext(const Nullkiller * ai); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 2f823f2de..cea1b0f88 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -12,7 +12,7 @@ #define NKAI_PATHFINDER_TRACE_LEVEL 0 constexpr int NKAI_GRAPH_TRACE_LEVEL = 0; -#define NKAI_TRACE_LEVEL 2 +#define NKAI_TRACE_LEVEL 0 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" From b32c9615ede64b789234a2b992834374f140d490 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 15:59:09 +0200 Subject: [PATCH 107/726] Update Nullkiller.cpp Removed unused variable. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 46d7825ab..77cac935a 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -410,10 +410,8 @@ void Nullkiller::makeTurn() decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH); TTaskVec selectedTasks; - int prioOfTask = 0; for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio) { - prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); if (!selectedTasks.empty() || settings->isUseFuzzy()) break; From 07afb2d64968b978d13872e5ecd4e14873789684 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:20:15 +0200 Subject: [PATCH 108/726] Update lib/ResourceSet.h Use suggestion. Co-authored-by: Ivan Savenko --- lib/ResourceSet.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index c080d1dff..1f1d3467b 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -159,8 +159,7 @@ public: } else { // Calculate the number of times we need to accumulate income to fulfill the need - float divisionResult = static_cast(container.at(i)) / static_cast(income[i]); - int ceiledResult = static_cast(std::ceil(divisionResult)); + int ceiledResult = vstd::divideAndCeil(container.at(i), income[i]); ret = std::max(ret, ceiledResult); } } From 23cd54c998ad3923aada46c2fdd7d7ba817963b6 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:22:25 +0200 Subject: [PATCH 109/726] Preparations for merge No longer using FuzzyEngine just to create a log-message. It's now only used when isUseFuzzy is set. Also: Removed < operator and instead use already existing "canAfford"-Method. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 58 +++++++++++----------- lib/ResourceSet.h | 11 ---- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index cb282fa50..b04ae252e 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1328,36 +1328,36 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) double result = 0; - float fuzzyResult = 0; - try - { - armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); - heroRoleVariable->setValue(evaluationContext.heroRole); - mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); - scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); - goldRewardVariable->setValue(goldRewardPerTurn); - armyRewardVariable->setValue(evaluationContext.armyReward); - armyGrowthVariable->setValue(evaluationContext.armyGrowth); - skillRewardVariable->setValue(evaluationContext.skillReward); - dangerVariable->setValue(evaluationContext.danger); - rewardTypeVariable->setValue(rewardType); - closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); - strategicalValueVariable->setValue(evaluationContext.strategicalValue); - goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure()); - goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f)); - turnVariable->setValue(evaluationContext.turn); - fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); - - engine->process(); - - fuzzyResult = value->getValue(); - } - catch (fl::Exception& fe) - { - logAi->error("evaluate VisitTile: %s", fe.getWhat()); - } if (ai->settings->isUseFuzzy()) { + float fuzzyResult = 0; + try + { + armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage); + heroRoleVariable->setValue(evaluationContext.heroRole); + mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]); + scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]); + goldRewardVariable->setValue(goldRewardPerTurn); + armyRewardVariable->setValue(evaluationContext.armyReward); + armyGrowthVariable->setValue(evaluationContext.armyGrowth); + skillRewardVariable->setValue(evaluationContext.skillReward); + dangerVariable->setValue(evaluationContext.danger); + rewardTypeVariable->setValue(rewardType); + closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); + strategicalValueVariable->setValue(evaluationContext.strategicalValue); + goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure()); + goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f)); + turnVariable->setValue(evaluationContext.turn); + fearVariable->setValue(evaluationContext.enemyHeroDangerRatio); + + engine->process(); + + fuzzyResult = value->getValue(); + } + catch (fl::Exception& fe) + { + logAi->error("evaluate VisitTile: %s", fe.getWhat()); + } result = fuzzyResult; } else @@ -1520,7 +1520,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); auto income = ai->buildAnalyzer->getDailyIncome(); score /= evaluationContext.buildingCost.marketValue(); - if (resourcesAvailable < evaluationContext.buildingCost) + if (resourcesAvailable.canAfford(evaluationContext.buildingCost)) { TResources needed = evaluationContext.buildingCost - resourcesAvailable; needed.positive(); diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index c080d1dff..0e133890b 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -189,17 +189,6 @@ public: return this->container == rhs.container; } -// WARNING: comparison operators are used for "can afford" relation: a <= b means that foreach i a[i] <= b[i] -// that doesn't work the other way: a > b doesn't mean that a cannot be afforded with b, it's still b can afford a - bool operator<(const ResourceSet &rhs) - { - for(int i = 0; i < size(); i++) - if (this->container.at(i) < rhs[i]) - return true; - - return false; - } - template void serialize(Handler &h) { h & container; From 79531c859626938ff7d24e3659e8c39e44763f73 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:30:15 +0200 Subject: [PATCH 110/726] Update ResourceSet.h Use a more descriptive method name and add comment of what it does. --- lib/ResourceSet.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index b740d5e70..f9dd6abaa 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -148,7 +148,8 @@ public: return ret; } - int div(const ResourceSet& income) { + //Returns how many items of "this" we can afford with provided income + int maxPurchasableCount(const ResourceSet& income) { int ret = 0; // Initialize to 0 because we want the maximum number of accumulations for (size_t i = 0; i < container.size(); ++i) { From 044dc272c2bdc6c5a61a2c7b8133a9bf5398cb6b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:31:44 +0200 Subject: [PATCH 111/726] Update lib/CCreatureSet.h Use a better name and add a comment to explain what it does. Co-authored-by: Ivan Savenko --- lib/CCreatureSet.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index cb2f0afca..82ec1083d 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -109,7 +109,8 @@ public: FactionID getFaction() const override; virtual ui64 getPower() const; - virtual ui64 getCost() const; + /// Returns total market value of resources needed to recruit this unit + virtual ui64 getMarketValue() const; CCreature::CreatureQuantityId getQuantityID() const; std::string getQuantityTXT(bool capitalized = true) const; virtual int getExpRank() const; From 20cfe712c9a5cd1d162eb712e625bf8e89956343 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:34:42 +0200 Subject: [PATCH 112/726] Update ResourceSet.h Rename income to availableFunds --- lib/ResourceSet.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index f9dd6abaa..9c33db5fc 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -148,19 +148,19 @@ public: return ret; } - //Returns how many items of "this" we can afford with provided income - int maxPurchasableCount(const ResourceSet& income) { + //Returns how many items of "this" we can afford with provided funds + int maxPurchasableCount(const ResourceSet& availableFunds) { int ret = 0; // Initialize to 0 because we want the maximum number of accumulations for (size_t i = 0; i < container.size(); ++i) { if (container.at(i) > 0) { // We only care about fulfilling positive needs - if (income[i] == 0) { + if (availableFunds[i] == 0) { // If income is 0 and we need a positive amount, it's impossible to fulfill return INT_MAX; } else { // Calculate the number of times we need to accumulate income to fulfill the need - int ceiledResult = vstd::divideAndCeil(container.at(i), income[i]); + int ceiledResult = vstd::divideAndCeil(container.at(i), availableFunds[i]); ret = std::max(ret, ceiledResult); } } From c186de2d521a038abc5c2390b9b00113b3d77457 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:36:07 +0200 Subject: [PATCH 113/726] Update AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp Avoid checking float against an exact value. Co-authored-by: Ivan Savenko --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index f183a11a0..00d6a4e26 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -87,7 +87,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const score *= hero->getArmyCost(); if (hero->type->heroClass->faction == town->getFaction()) score *= 1.5; - if (visitability == 0) + if (vstd::isAlmostZero(visitability)) score *= 30 * town->getTownLevel(); else score *= town->getTownLevel() / visitability; From dafc9cd8a898d7668a444906467cf2bd709da9e2 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:40:06 +0200 Subject: [PATCH 114/726] Update PriorityEvaluator.cpp Replace float-comparisons with zero by vstd::isAlmostZero --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index b04ae252e..40b3f1943 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1397,7 +1397,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if(evaluationContext.conquestValue > 0) score = 1000; - if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) + if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1417,7 +1417,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (evaluationContext.conquestValue > 0) score = 1000; - if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) + if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1524,7 +1524,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { TResources needed = evaluationContext.buildingCost - resourcesAvailable; needed.positive(); - int turnsTo = needed.div(income); + int turnsTo = needed.maxPurchasableCount(income); if (turnsTo == INT_MAX) return 0; else @@ -1533,7 +1533,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } else { - if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && evaluationContext.conquestValue == 0) + if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && vstd::isAlmostZero(evaluationContext.conquestValue)) return 0; } break; From b3115f65c5b1c50feaf3b675166bcbf22d58fbfb Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:45:45 +0200 Subject: [PATCH 115/726] Update BuildingBehavior.cpp Use std::numeric_limits::max(); instead of UINT8_MAX; and remove some leftover-trace-messages from debugging. --- AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index caed8fc1e..ab33207df 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -54,7 +54,7 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const for(auto & developmentInfo : developmentInfos) { bool emergencyDefense = false; - uint8_t closestThreat = UINT8_MAX; + uint8_t closestThreat = std::numeric_limits::max(); for (auto threat : ai->dangerHitMap->getTownThreats(developmentInfo.town)) { closestThreat = std::min(closestThreat, threat.turn); @@ -74,7 +74,6 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const { for (auto& buildingInfo : developmentInfo.toBuild) { - logAi->trace("Looking at %s", buildingInfo.toString()); if (isGoldPressureLow || buildingInfo.dailyIncome[EGameResID::GOLD] > 0) { if (buildingInfo.notEnoughRes) @@ -86,13 +85,11 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const composition.addNext(BuildThis(buildingInfo, developmentInfo)); composition.addNext(SaveResources(buildingInfo.buildCost)); - logAi->trace("Generate task to build: %s", buildingInfo.toString()); tasks.push_back(sptr(composition)); } else { tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); - logAi->trace("Generate task to build: %s", buildingInfo.toString()); } } } From d9fe8d7fa0099387382a64333dcb712077e9c1f4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 16:50:22 +0200 Subject: [PATCH 116/726] Update BuyArmyBehavior.cpp Removed pointless check for hero-army being more valuable than buying army directly as it was never the case anyways. --- AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index 3d15ff9e8..99c362bac 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -39,17 +39,6 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const for(auto town : cb->getTownsInfo()) { - //If we can recruit a hero that comes with more army than he costs, we are better off spending our gold on them - if (ai->heroManager->canRecruitHero(town)) - { - auto availableHeroes = ai->cb->getAvailableHeroes(town); - for (auto hero : availableHeroes) - { - if (hero->getArmyCost() > GameConstants::HERO_GOLD_COST) - return tasks; - } - } - uint8_t closestThreat = UINT8_MAX; for (auto threat : ai->dangerHitMap->getTownThreats(town)) { From 7c42e43fe516e9c32f4a898fa7da7584c347e67c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 17:16:06 +0200 Subject: [PATCH 117/726] Update CCreatureSet.cpp Use getMarketValue instead of getCost. --- lib/CCreatureSet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 45011035e..b340e15e4 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -370,7 +370,7 @@ ui64 CCreatureSet::getArmyCost() const { ui64 ret = 0; for (const auto& elem : stacks) - ret += elem.second->getCost(); + ret += elem.second->getMarketValue(); return ret; } @@ -866,7 +866,7 @@ ui64 CStackInstance::getPower() const return type->getAIValue() * count; } -ui64 CStackInstance::getCost() const +ui64 CStackInstance::getMarketValue() const { assert(type); return type->getFullRecruitCost().marketValue() * count; From 581a142a204f7ce27fddf5f1c510db3541796163 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 17:38:27 +0200 Subject: [PATCH 118/726] HeroStrengthForCampaign Make sure to take our magic-specialist to the next campaign-mission even if he's totally out of mana. --- lib/campaign/CampaignState.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 10 ++++++++++ lib/mapObjects/CGHeroInstance.h | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index f1fa305cb..367a00e0e 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -325,7 +325,7 @@ void CampaignState::setCurrentMapAsConquered(std::vector heroe { range::sort(heroes, [](const CGHeroInstance * a, const CGHeroInstance * b) { - return a->getHeroStrength() > b->getHeroStrength(); + return a->getHeroStrengthForCampaign() > b->getHeroStrengthForCampaign(); }); logGlobal->info("Scenario %d of campaign %s (%s) has been completed", currentMap->getNum(), getFilename(), getNameTranslated()); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 2eeeafdb9..b470517bd 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -663,11 +663,21 @@ double CGHeroInstance::getMagicStrength() const return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * mana / manaLimit()) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER) * mana / manaLimit())); } +double CGHeroInstance::getMagicStrengthForCampaign() const +{ + return sqrt((1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05 * getPrimSkillLevel(PrimarySkill::SPELL_POWER))); +} + double CGHeroInstance::getHeroStrength() const { return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0)); } +double CGHeroInstance::getHeroStrengthForCampaign() const +{ + return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrengthForCampaign(), 2.0)); +} + ui64 CGHeroInstance::getTotalStrength() const { double ret = getHeroStrength() * getArmyStrength(); diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 2ad2811a9..da4563247 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -224,8 +224,10 @@ public: int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; double getFightingStrength() const; // takes attack / defense skill into account - double getMagicStrength() const; // takes knowledge / spell power skill into account + double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account + double getMagicStrengthForCampaign() const; // takes knowledge / spell power skill into account double getHeroStrength() const; // includes fighting and magic strength + double getHeroStrengthForCampaign() const; // includes fighting and the for-campaign-version of magic strength ui64 getTotalStrength() const; // includes fighting strength and army strength TExpType calculateXp(TExpType exp) const; //apply learning skill From 5488a0a29c2b5e1c75c7807b0c2417e77691cf2c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 19:35:47 +0200 Subject: [PATCH 119/726] Removed the "GATHER"-priorityTier There was no real need for it to be a separated tier from Hunter_gather. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 30 ---------------------- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 - 2 files changed, 31 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 40b3f1943..0a4d53b30 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1426,36 +1426,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } - case PriorityTier::GATHER: //Collect unguarded stuff - { - if (evaluationContext.enemyHeroDangerRatio > 1) - return 0; - if (evaluationContext.isDefend) - return 0; - if (evaluationContext.armyLossPersentage > 0) - return 0; - if (evaluationContext.involvesSailing && evaluationContext.movementCostByRole[HeroRole::MAIN] > 0) - return 0; - if (evaluationContext.buildingCost.marketValue() > 0) - return 0; - if (evaluationContext.closestWayRatio < 1) - return 0; - score += evaluationContext.strategicalValue * 1000; - score += evaluationContext.goldReward; - score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; - score += evaluationContext.armyReward; - score += evaluationContext.armyGrowth; - if (score <= 0) - return 0; - else - score = 1000; - score *= evaluationContext.closestWayRatio; - if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend) - score *= evaluationContext.armyInvolvement / evaluationContext.threat; - if (evaluationContext.movementCost > 0) - score /= evaluationContext.movementCost; - break; - } case PriorityTier::HUNTER_GATHER: //Collect guarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 5e8e8c365..15d06ef05 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -110,7 +110,6 @@ public: INSTAKILL, INSTADEFEND, KILL, - GATHER, HUNTER_GATHER, DEFEND }; From db2416cb6b0d257e0c1396527859f5511a8fb07d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 5 Sep 2024 23:41:05 +0200 Subject: [PATCH 120/726] Update Nullkiller.cpp Readded prioOfTask because it's needed in trace-messages. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 77cac935a..46d7825ab 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -410,8 +410,10 @@ void Nullkiller::makeTurn() decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH); TTaskVec selectedTasks; + int prioOfTask = 0; for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio) { + prioOfTask = prio; selectedTasks = buildPlan(bestTasks, prio); if (!selectedTasks.empty() || settings->isUseFuzzy()) break; From e43492d8b586fac729fa397970cbe5414855066b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 6 Sep 2024 00:12:44 +0200 Subject: [PATCH 121/726] Update PriorityEvaluator.cpp Fixed affordabilitycheck not being negated. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 0a4d53b30..a81f300ba 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1490,7 +1490,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); auto income = ai->buildAnalyzer->getDailyIncome(); score /= evaluationContext.buildingCost.marketValue(); - if (resourcesAvailable.canAfford(evaluationContext.buildingCost)) + if (!resourcesAvailable.canAfford(evaluationContext.buildingCost)) { TResources needed = evaluationContext.buildingCost - resourcesAvailable; needed.positive(); From 35d8705fea1dccb4d2f6d13555c89f1fed63b03e Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 6 Sep 2024 17:20:12 +0200 Subject: [PATCH 122/726] Update Nullkiller.cpp prioOfTask-variable-usage bound to trace-level as otherwise a warning will ensue. --- AI/Nullkiller/Engine/Nullkiller.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 46d7825ab..c7f408a51 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -410,10 +410,14 @@ void Nullkiller::makeTurn() decompose(bestTasks, sptr(ExplorationBehavior()), MAX_DEPTH); TTaskVec selectedTasks; +#if NKAI_TRACE_LEVEL >= 1 int prioOfTask = 0; +#endif for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio) { +#if NKAI_TRACE_LEVEL >= 1 prioOfTask = prio; +#endif selectedTasks = buildPlan(bestTasks, prio); if (!selectedTasks.empty() || settings->isUseFuzzy()) break; From cf338e04ad45fc46bbc1f26b1bade81c9d83d8ba Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 6 Sep 2024 21:40:23 +0200 Subject: [PATCH 123/726] Update Nullkiller.cpp AI can now also buy resources that it has income for. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index c7f408a51..d12b6193c 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -635,8 +635,6 @@ bool Nullkiller::handleTrading() { if (required[i] <= 0) continue; - if (i != 6 && income[i] > 0) - continue; float ratio = static_cast(available[i]) / required[i]; if (ratio < minRatio) { From 06f894140cae3e37bd48cfacd0be815d99616bab Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 6 Sep 2024 21:42:15 +0200 Subject: [PATCH 124/726] Update BuildAnalyzer.cpp Modified goldPressure-formula to no longer use completely arbitrary part of lockedresources/5000. Lockedresources is now just divided by a factor of the free gold like everything else. --- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 49ae295ca..ce9c00f36 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -184,8 +184,7 @@ void BuildAnalyzer::update() updateDailyIncome(); - goldPressure = ai->getLockedResources()[EGameResID::GOLD] / 5000.0f - + ((float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f); + goldPressure = (ai->getLockedResources()[EGameResID::GOLD] + (float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f); logAi->trace("Gold pressure: %f", goldPressure); } From 099341e143496e9247c67c270f04db509d43a0b0 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 6 Sep 2024 22:10:14 +0200 Subject: [PATCH 125/726] Update Nullkiller.cpp Fixed incorrect trace-message at end of turn. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index d12b6193c..994e06415 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -511,6 +511,8 @@ void Nullkiller::makeTurn() { logAi->trace("Nothing was done this turn. Ending turn."); #if NKAI_TRACE_LEVEL >= 1 + totalHeroStrength = 0; + totalTownLevel = 0; for (auto heroInfo : cb->getHeroesInfo()) { totalHeroStrength += heroInfo->getTotalStrength(); From 0edc17b7d878d86aff7aeead6ef5bf9db519286c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 6 Sep 2024 22:14:59 +0200 Subject: [PATCH 126/726] Going to town when nothing to do. The StayAtTown-behavior now always creates tasks for all heroes to go and stay at a town. It will be treated differently than going to a town for mana in the sense that it is only considered at the lowest priority-tier. So it will only happen when the AI doesn't find anything else to do. It should resolve one of the two main-reasons for losing weak heros. The hunter-gather-priority-tier now goes strictly by distance for all taks that are considered above 0 in value. --- AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp | 11 +---------- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 13 +++++++------ AI/Nullkiller/Goals/StayAtTown.cpp | 5 ----- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp b/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp index 595830a66..1b2e0a04b 100644 --- a/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp @@ -39,9 +39,6 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const for(auto town : towns) { - if(!town->hasBuilt(BuildingID::MAGES_GUILD_1)) - continue; - ai->pathfinder->calculatePathInfo(paths, town->visitablePos()); for(auto & path : paths) @@ -49,14 +46,8 @@ Goals::TGoalVec StayAtTownBehavior::decompose(const Nullkiller * ai) const if(town->visitingHero && town->visitingHero.get() != path.targetHero) continue; - if(!path.targetHero->hasSpellbook() || path.targetHero->mana >= 0.75f * path.targetHero->manaLimit()) - continue; - - if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1) + if(!path.getFirstBlockedAction() && path.exchangeCount <= 1) { - if(path.targetHero->mana == path.targetHero->manaLimit()) - continue; - Composition stayAtTown; stayAtTown.addNextSequence({ diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index a81f300ba..8b6246eb9 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -944,6 +944,8 @@ public: Goals::StayAtTown & stayAtTown = dynamic_cast(*task); evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero()); + if (evaluationContext.armyReward == 0) + evaluationContext.isDefend = true; evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted(); evaluationContext.movementCost += stayAtTown.getMovementWasted(); } @@ -1365,7 +1367,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, isDefend: %d, fuzzy: %f", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, isDefend: %d", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1385,8 +1387,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.conquestValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, - evaluationContext.isDefend, - fuzzyResult); + evaluationContext.isDefend); #endif switch (priorityTier) @@ -1443,6 +1444,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage; if (score > 0) { + score = 1000; score *= evaluationContext.closestWayRatio; if (evaluationContext.enemyHeroDangerRatio > 1) score /= evaluationContext.enemyHeroDangerRatio; @@ -1457,7 +1459,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange) return 0; if (evaluationContext.isDefend) - score = evaluationContext.armyInvolvement; + score = 1000; score *= evaluationContext.closestWayRatio; score /= (evaluationContext.turn + 1); break; @@ -1516,7 +1518,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f, result %f", + logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, result %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1536,7 +1538,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.conquestValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, - fuzzyResult, result); #endif diff --git a/AI/Nullkiller/Goals/StayAtTown.cpp b/AI/Nullkiller/Goals/StayAtTown.cpp index 346b2c44d..250e16371 100644 --- a/AI/Nullkiller/Goals/StayAtTown.cpp +++ b/AI/Nullkiller/Goals/StayAtTown.cpp @@ -41,11 +41,6 @@ std::string StayAtTown::toString() const void StayAtTown::accept(AIGateway * ai) { - if(hero->visitedTown != town) - { - logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated()); - } - ai->nullkiller->lockHero(hero, HeroLockedReason::DEFENCE); } From 8c3f6fc1e2cd569ed043c5ee261e992bad99234a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 8 Sep 2024 02:19:19 +0200 Subject: [PATCH 127/726] Update RecruitHeroBehavior.cpp Fixed crash caused by mistakenly assuming that "pos" is the position of a hero on the map and not its bottom-right-corner that can be outside of the map. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 00d6a4e26..91f4f03f4 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -70,7 +70,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const float visitability = 0; for (auto checkHero : ourHeroes) { - if (ai->dangerHitMap->getClosestTown(checkHero.first.get()->pos) == town) + if (ai->dangerHitMap->getClosestTown(checkHero.first.get()->visitablePos()) == town) visitability++; } if(ai->heroManager->canRecruitHero(town)) From 5999c6d891525125e321096e712364903127a855 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 9 Sep 2024 19:54:20 +0200 Subject: [PATCH 128/726] Update BattleEvaluator.cpp Removed now unnecessary additional check for dead units. --- AI/BattleAI/BattleEvaluator.cpp | 54 --------------------------------- 1 file changed, 54 deletions(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 6422b43e2..ea5d0e7a3 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -729,60 +729,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) ps.value = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state); } - //! Some units may be dead alltogether. So if they existed before but not now, we know they were killed by the spell - for (const auto& unit : all) - { - if (!unit->isValidTarget()) - continue; - bool isDead = true; - for (const auto& remainingUnit : allUnits) - { - if (remainingUnit->unitId() == unit->unitId()) - isDead = false; - } - if (isDead) - { - auto newHealth = 0; - auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); - if (oldHealth != newHealth) - { - auto damage = std::abs(oldHealth - newHealth); - auto originalDefender = cb->getBattle(battleID)->battleGetUnitByID(unit->unitId()); - - auto dpsReduce = AttackPossibility::calculateDamageReduce( - nullptr, - originalDefender && originalDefender->alive() ? originalDefender : unit, - damage, - innerCache, - state); - - auto ourUnit = unit->unitSide() == side ? 1 : -1; - auto goodEffect = newHealth > oldHealth ? 1 : -1; - - if (ourUnit * goodEffect == 1) - { - if (ourUnit && goodEffect && (unit->isClone() || unit->isGhost())) - continue; - - ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier(); - } - else - ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier(); - -#if BATTLE_TRACE_LEVEL >= 1 - logAi->trace( - "Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", - ps.spell->getNameTranslated(), - ps.dest.at(0).hexValue.hex, - unit->creatureId().toCreature()->getNameSingularTranslated(), - unit->getCount(), - dpsReduce, - oldHealth, - newHealth); -#endif - } - } - } for(const auto & unit : allUnits) { if(!unit->isValidTarget(true)) From e7e3f6dcbe0049a2ced37889bebf779557d17cca Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 9 Sep 2024 19:55:03 +0200 Subject: [PATCH 129/726] Update DefenceBehavior.cpp Only hire heroes for defence if the enemy is already really close. (Otherwise AI hired too many heroes from defensebehavior) --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index c0dacd8f4..b5e0ab8d0 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -404,6 +404,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const { + if (threat.turn > 0) + return; + if(town->hasBuilt(BuildingID::TAVERN) && ai->cb->getResourceAmount(EGameResID::GOLD) > GameConstants::HERO_GOLD_COST) { From f8e4aa1d25d92a1eebd89098cc6a919d123374f0 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 9 Sep 2024 23:20:53 +0200 Subject: [PATCH 130/726] Update Nullkiller.cpp Use Enum for Gold. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 994e06415..e136699e4 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -655,7 +655,7 @@ bool Nullkiller::handleTrading() bool okToSell = false; - if (i == 6) + if (i == GameResID::GOLD) { if (income[i] > 0 && !buildAnalyzer->isGoldPressureHigh()) okToSell = true; From a329f607c93844e641fa77cc8bfe3d1d605bd03d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 9 Sep 2024 23:23:28 +0200 Subject: [PATCH 131/726] Update Nullkiller.cpp No more map-hack on 3rd difficulty-level. Only starting from the fourth. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index e136699e4..39054c75d 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -62,7 +62,7 @@ bool canUseOpenMap(std::shared_ptr cb, PlayerColor playerID) return false; } - return true; + return cb->getStartInfo()->difficulty >= 3; } void Nullkiller::init(std::shared_ptr cb, AIGateway * gateway) From faa5a02659ca8b9eed88353523283a3a4d77bc82 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 9 Sep 2024 23:25:09 +0200 Subject: [PATCH 132/726] Update RecruitHeroBehavior.cpp Fix potential division by zero. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 91f4f03f4..0be723a2a 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -107,7 +107,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const if (ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)) { - tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / ourHeroes.size()))); + tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1)))); } } From 37f9f939485816a81624da03a13028b65adb7306 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 9 Sep 2024 23:38:28 +0200 Subject: [PATCH 133/726] Update RecruitHeroBehavior.cpp Modified how to score what hero to hire to make it more likely to rehire fled heroes with high levels when the army-gain from the hero would be rather insignificant. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 0be723a2a..e46483fca 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -32,9 +32,11 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const auto ourHeroes = ai->heroManager->getHeroRoles(); auto minScoreToHireMain = std::numeric_limits::max(); + int currentArmyValue = 0; for(auto hero : ourHeroes) { + currentArmyValue += hero.first->getArmyCost(); if(hero.second != HeroRole::MAIN) continue; @@ -84,7 +86,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const { score *= score / minScoreToHireMain; } - score *= hero->getArmyCost(); + score *= (hero->getArmyCost() + currentArmyValue); if (hero->type->heroClass->faction == town->getFaction()) score *= 1.5; if (vstd::isAlmostZero(visitability)) From df119370c7057f3b4e088074e76ca6a6949025be Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 10 Sep 2024 00:23:17 +0200 Subject: [PATCH 134/726] Exploration Slightly adjust the value of exploring within the hunter-gather-prirority. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 7 +++++-- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 8b6246eb9..07f26f55a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -62,7 +62,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) threatTurns(INT_MAX), involvesSailing(false), isTradeBuilding(false), - isExchange(false) + isExchange(false), + isExplore(false) { } @@ -930,6 +931,7 @@ public: int tilesDiscovered = task->value; evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered); + evaluationContext.isExplore = true; } }; @@ -1444,7 +1446,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage; if (score > 0) { - score = 1000; + if(!evaluationContext.isExplore) + score = 1000; score *= evaluationContext.closestWayRatio; if (evaluationContext.enemyHeroDangerRatio > 1) score /= evaluationContext.enemyHeroDangerRatio; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 15d06ef05..c04e2477d 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -80,6 +80,7 @@ struct DLL_EXPORT EvaluationContext bool involvesSailing; bool isTradeBuilding; bool isExchange; + bool isExplore; EvaluationContext(const Nullkiller * ai); From aefe2fda3652ff0318d3fecb4bfa641e70c128bc Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 10 Sep 2024 23:42:51 +0200 Subject: [PATCH 135/726] Update BuildingBehavior.cpp No longer rush a fort in a threatened town. --- AI/Nullkiller/Behaviors/BuildingBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index ab33207df..50f800913 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -63,7 +63,7 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const { if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE && !buildingInfo.notEnoughRes) { - if (buildingInfo.id == BuildingID::FORT || buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE) + if (buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE) { tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); emergencyDefense = true; From d4fd4ed670eb2b4dd4860c1fe87e1670113ff47f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 11 Sep 2024 16:05:53 +0200 Subject: [PATCH 136/726] Update BattleEvaluator.cpp Make sure trace-message doesn't crash from accessing invalid element. --- AI/BattleAI/BattleEvaluator.cpp | 34 ++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index ea5d0e7a3..d59324880 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -768,15 +768,31 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) ps.value -= 4 * dpsReduce * scoreEvaluator.getNegativeEffectMultiplier(); #if BATTLE_TRACE_LEVEL >= 1 - logAi->trace( - "Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", - ps.spell->getNameTranslated(), - ps.dest.at(0).hexValue.hex, - unit->creatureId().toCreature()->getNameSingularTranslated(), - unit->getCount(), - dpsReduce, - oldHealth, - newHealth); + // Ensure ps.dest is not empty before accessing the first element + if (!ps.dest.empty()) + { + logAi->trace( + "Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", + ps.spell->getNameTranslated(), + ps.dest.at(0).hexValue.hex, // Safe to access .at(0) now + unit->creatureId().toCreature()->getNameSingularTranslated(), + unit->getCount(), + dpsReduce, + oldHealth, + newHealth); + } + else + { + // Handle the case where ps.dest is empty + logAi->trace( + "Spell %s has no destination, affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", + ps.spell->getNameTranslated(), + unit->creatureId().toCreature()->getNameSingularTranslated(), + unit->getCount(), + dpsReduce, + oldHealth, + newHealth); + } #endif } } From 5ed888b284ec9a19ae773056c5939e8ccac47a09 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 12 Sep 2024 20:07:22 +0200 Subject: [PATCH 137/726] Update BuyArmyBehavior.cpp Accomplish the same but with simpler code. --- AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index 99c362bac..738196e2d 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -39,11 +39,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const for(auto town : cb->getTownsInfo()) { - uint8_t closestThreat = UINT8_MAX; - for (auto threat : ai->dangerHitMap->getTownThreats(town)) - { - closestThreat = std::min(closestThreat, threat.turn); - } + uint8_t closestThreat = ai->dangerHitMap->getTileThreat(town->visitablePos()).fastestDanger.turn; if (closestThreat >=2 && ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN) { From ab64edf7dda83f78006eccc327c93c659ac0c33f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 12 Sep 2024 20:08:07 +0200 Subject: [PATCH 138/726] Update RecruitHero.cpp Remove code that, according to Ivan shouldn't do anything but cause errors. No noticable regressing in playing-strength was observed. --- AI/Nullkiller/Goals/RecruitHero.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index 5ea60317f..810a6162c 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -75,9 +75,6 @@ void RecruitHero::accept(AIGateway * ai) ai->nullkiller->heroManager->update(); ai->nullkiller->objectClusterizer->reset(); } - - if(t->visitingHero) - ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get()); } } From 4b263b6d4116d0a1782561cd256adf9ba6e2b33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Thu, 12 Sep 2024 21:04:27 +0200 Subject: [PATCH 139/726] Add specific objects and configure their frequency / value --- .../CObjectClassesHandler.cpp | 54 +++--- lib/mapObjects/ObjectTemplate.h | 2 + lib/rmg/ObjectInfo.cpp | 171 ++++++++++++------ lib/rmg/ObjectInfo.h | 3 +- lib/rmg/modificators/TreasurePlacer.cpp | 45 ++++- lib/rmg/modificators/TreasurePlacer.h | 3 +- 6 files changed, 177 insertions(+), 101 deletions(-) diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 257e78b3f..5a9df48f7 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -40,6 +40,8 @@ #include "../texts/CGeneralTextHandler.h" #include "../texts/CLegacyConfigParser.h" +#include + VCMI_LIB_NAMESPACE_BEGIN CObjectClassesHandler::CObjectClassesHandler() @@ -396,6 +398,9 @@ CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::stri if(id) { + if (subtype.empty()) + return CompoundMapObjectID(id.value(), 0); + const auto & object = objects.at(id.value()); std::optional subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype); @@ -410,45 +415,32 @@ CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::stri CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const { - // FIXME: Crash with no further log + // TODO: Use existing utilities for parsing id: + // CIdentifierStorage::ObjectCallback::fromNameAndType - // TODO: Use existing utilities for parsing id? - JsonNode node(objectName); - auto modScope = node.getModScope(); + std::string subtype = "object"; //Default for objects with no subIds + std::string type; - std::string scope, type, subtype; - size_t firstColon = objectName.find(':'); - size_t lastDot = objectName.find_last_of('.'); - - // TODO: Ignore object class, there should not be objects with same names within one scope anyway + auto scopeAndFullName = vstd::splitStringToPair(objectName, ':'); + logGlobal->debug("scopeAndFullName: %s, %s", scopeAndFullName.first, scopeAndFullName.second); - if(firstColon != std::string::npos) + auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.'); + logGlobal->debug("typeAndName: %s, %s", typeAndName.first, typeAndName.second); + + auto nameAndSubtype = vstd::splitStringToPair(typeAndName.second, '.'); + logGlobal->debug("nameAndSubtype: %s, %s", nameAndSubtype.first, nameAndSubtype.second); + + if (!nameAndSubtype.first.empty()) { - scope = objectName.substr(0, firstColon); - if(lastDot != std::string::npos && lastDot > firstColon) - { - type = objectName.substr(firstColon + 1, lastDot - firstColon - 1); - subtype = objectName.substr(lastDot + 1); - } - else - { - type = objectName.substr(firstColon + 1); - } + type = nameAndSubtype.first; + subtype = nameAndSubtype.second; } else { - if(lastDot != std::string::npos) - { - type = objectName.substr(0, lastDot); - subtype = objectName.substr(lastDot + 1); - } - else - { - type = objectName; - } + type = typeAndName.second; } - - return getCompoundIdentifier(scope, type, subtype); + + return getCompoundIdentifier(scopeAndFullName.first, type, subtype); } std::set CObjectClassesHandler::knownObjects() const diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index 96780fade..a9a5daf63 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -46,6 +46,8 @@ public: /// H3 ID/subID of this object MapObjectID id; MapObjectSubID subid; + + // TODO: get compound id /// print priority, objects with higher priority will be print first, below everything else si32 printPriority; /// animation file that should be used to display object diff --git a/lib/rmg/ObjectInfo.cpp b/lib/rmg/ObjectInfo.cpp index fda32a8b0..d9a9f3ac1 100644 --- a/lib/rmg/ObjectInfo.cpp +++ b/lib/rmg/ObjectInfo.cpp @@ -50,6 +50,15 @@ ObjectInfo & ObjectInfo::operator=(const ObjectInfo & other) return *this; } +void ObjectInfo::setAllTemplates(MapObjectID type, MapObjectSubID subtype) +{ + auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); + if(!templHandler) + return; + + templates = templHandler->getTemplates(); +} + void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType) { auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); @@ -68,6 +77,20 @@ void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); } +void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid) +{ + // FIXME: Need id / subId + + // FIXME: Add templates and possibly other info + customObjects.push_back(object); + auto & lastObject = customObjects.back(); + lastObject.setAllTemplates(objid.primaryID, objid.secondaryID); + + assert(lastObject.templates.size() > 0); + auto temp = lastObject.templates.front(); + logGlobal->info("Added custom object of type %d.%d", temp->id, temp->subid); +} + void ObjectConfig::serializeJson(JsonSerializeFormat & handler) { // TODO: We need serializer utility for list of enum values @@ -87,73 +110,105 @@ void ObjectConfig::serializeJson(JsonSerializeFormat & handler) (EObjectCategory::QUEST_ARTIFACT, "questArtifact") (EObjectCategory::SEER_HUT, "seerHut"); - auto categories = handler.enterArray("bannedCategories"); - if (handler.saving) - { - for (const auto& category : bannedObjectCategories) - { - auto str = OBJECT_CATEGORY_STRINGS.left.at(category); - categories.serializeString(categories.size(), str); - } - } - else - { - std::vector categoryNames; - categories.serializeArray(categoryNames); - for (const auto & categoryName : categoryNames) + // TODO: Separate into individual methods to enforce RAII destruction? + { + auto categories = handler.enterArray("bannedCategories"); + if (handler.saving) { - auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName); - if (it != OBJECT_CATEGORY_STRINGS.right.end()) + for (const auto& category : bannedObjectCategories) { - bannedObjectCategories.push_back(it->second); + auto str = OBJECT_CATEGORY_STRINGS.left.at(category); + categories.serializeString(categories.size(), str); } } - } - - auto bannedObjectData = handler.enterArray("bannedObjects"); - if (handler.saving) - { - - // FIXME: Do we even need to serialize / store banned objects? - /* - for (const auto & object : bannedObjects) + else { - // TODO: Translate id back to string? + std::vector categoryNames; + categories.serializeArray(categoryNames); - - JsonNode node; - node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); - // TODO: Check if AI-generated code is right - - - } - // handler.serializeRaw("bannedObjects", node, std::nullopt); - - */ - } - else - { - /* - auto zonesData = handler.enterStruct("zones"); - for(const auto & idAndZone : zonesData->getCurrent().Struct()) + for (const auto & categoryName : categoryNames) { - auto guard = handler.enterStruct(idAndZone.first); - auto zone = std::make_shared(); - zone->setId(decodeZoneId(idAndZone.first)); - zone->serializeJson(handler); - zones[zone->getId()] = zone; - } - */ - std::vector objectNames; - bannedObjectData.serializeArray(objectNames); - - for (const auto & objectName : objectNames) - { - VLC->objtypeh->resolveObjectCompoundId(objectName, - [this](CompoundMapObjectID objid) + auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName); + if (it != OBJECT_CATEGORY_STRINGS.right.end()) { - addBannedObject(objid); + bannedObjectCategories.push_back(it->second); + } + } + } + } + + // FIXME: Doesn't seem to use this field at all + + { + auto bannedObjectData = handler.enterArray("bannedObjects"); + if (handler.saving) + { + + // FIXME: Do we even need to serialize / store banned objects? + /* + for (const auto & object : bannedObjects) + { + // TODO: Translate id back to string? + + + JsonNode node; + node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); + // TODO: Check if AI-generated code is right + + + } + // handler.serializeRaw("bannedObjects", node, std::nullopt); + + */ + } + else + { + std::vector objectNames; + bannedObjectData.serializeArray(objectNames); + + for (const auto & objectName : objectNames) + { + VLC->objtypeh->resolveObjectCompoundId(objectName, + [this](CompoundMapObjectID objid) + { + addBannedObject(objid); + } + ); + + } + } + } + + auto commonObjectData = handler.getCurrent()["commonObjects"].Vector(); + if (handler.saving) + { + + //TODO? + } + else + { + for (const auto & objectConfig : commonObjectData) + { + auto objectName = objectConfig["id"].String(); + auto rmg = objectConfig["rmg"].Struct(); + + // TODO: Use common code with default rmg config + auto objectValue = rmg["value"].Integer(); + auto objectProbability = rmg["rarity"].Integer(); + auto objectMaxPerZone = rmg["zoneLimit"].Integer(); + + VLC->objtypeh->resolveObjectCompoundId(objectName, + [this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid) + { + ObjectInfo object; + + // TODO: Configure basic generateObject function + + object.value = objectValue; + object.probability = objectProbability; + object.maxPerZone = objectMaxPerZone; + addCustomObject(object, objid); } ); diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h index cee3cf10b..fa7138696 100644 --- a/lib/rmg/ObjectInfo.h +++ b/lib/rmg/ObjectInfo.h @@ -32,6 +32,7 @@ struct DLL_LINKAGE ObjectInfo std::function generateObject; std::function destroyObject; + void setAllTemplates(MapObjectID type, MapObjectSubID subtype); void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain); //bool matchesId(const CompoundMapObjectID & id) const; @@ -61,7 +62,7 @@ public: }; void addBannedObject(const CompoundMapObjectID & objid); - void addCustomObject(const ObjectInfo & object); + void addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid); void clearBannedObjects(); void clearCustomObjects(); const std::vector & getBannedObjects() const; diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 8e30b5cff..97ab2ae3c 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -50,7 +50,7 @@ void TreasurePlacer::process() // Get default objects addAllPossibleObjects(); // Override with custom objects - objects.patchWithZoneConfig(zone); + objects.patchWithZoneConfig(zone, this); auto * m = zone.getModificator(); if(m) @@ -65,6 +65,8 @@ void TreasurePlacer::init() DEPENDENCY_ALL(PrisonHeroPlacer); DEPENDENCY(RoadPlacer); + // FIXME: Starting zones get Pandoras with neutral creatures + // Add all native creatures for(auto const & cre : VLC->creh->objects) { @@ -79,7 +81,6 @@ void TreasurePlacer::init() void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi) { - // FIXME: It is never the case - objects must be erased or badly copied after this if (oi.templates.empty()) { logGlobal->error("Attempt to add ObjectInfo with no templates! Value: %d", oi.value); @@ -126,14 +127,11 @@ void TreasurePlacer::addCommonObjects() //Skip objects with per-map limit here continue; } + setBasicProperties(oi, CompoundMapObjectID(primaryID, secondaryID)); - oi.generateObject = [this, primaryID, secondaryID]() -> CGObjectInstance * - { - return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(map.mapInstance->cb, nullptr); - }; oi.value = rmgInfo.value; oi.probability = rmgInfo.rarity; - oi.setTemplates(primaryID, secondaryID, zone.getTerrainType()); + oi.maxPerZone = rmgInfo.zoneLimit; if(!oi.templates.empty()) addObjectToRandomPool(oi); @@ -142,6 +140,15 @@ void TreasurePlacer::addCommonObjects() } } +void TreasurePlacer::setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const +{ + oi.generateObject = [this, objid]() -> CGObjectInstance * + { + return VLC->objtypeh->getHandlerFor(objid)->create(map.mapInstance->cb, nullptr); + }; + oi.setTemplates(objid.primaryID, objid.secondaryID, zone.getTerrainType()); +} + void TreasurePlacer::addPrisons() { //Generate Prison on water only if it has a template @@ -1116,7 +1123,7 @@ void TreasurePlacer::ObjectPool::updateObject(MapObjectID id, MapObjectSubID sub customObjects[CompoundMapObjectID(id, subid)] = info; } -void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) +void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp) { // FIXME: Wycina wszystkie obiekty poza pandorami i dwellami :? @@ -1145,18 +1152,27 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) if (categoriesSet.count(category)) { logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); + /* FIXME: + Removing object normal from possible objects + Removing object base from possible objects + Removing object default from possible objects + */ return true; } return false; }); - vstd::erase_if(possibleObjects, [&zone](const ObjectInfo & object) + auto bannedObjects = zone.getBannedObjects(); + auto bannedObjectsSet = std::set(bannedObjects.begin(), bannedObjects.end()); + vstd::erase_if(possibleObjects, [&bannedObjectsSet](const ObjectInfo & object) { for (const auto & templ : object.templates) { CompoundMapObjectID key(templ->id, templ->subid); - if (vstd::contains(zone.getBannedObjects(), key)) + if (bannedObjectsSet.count(key)) { + // FIXME: Stopped working, nothing is banned + logGlobal->info("Banning object %s from possible objects", templ->stringID); return true; } } @@ -1164,6 +1180,15 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone) }); auto configuredObjects = zone.getConfiguredObjects(); + + // FIXME: Access TreasurePlacer from ObjectPool + for (auto & object : configuredObjects) + { + auto temp = object.templates.front(); + tp->setBasicProperties(object, CompoundMapObjectID(temp->id, temp->subid)); + addObject(object); + logGlobal->info("Added custom object of type %d.%d", temp->id, temp->subid); + } // TODO: Overwrite or add to possibleObjects // FIXME: Protect with mutex as well? diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index c96733eb0..c9cde768e 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -34,6 +34,7 @@ public: void createTreasures(ObjectManager & manager); void addObjectToRandomPool(const ObjectInfo& oi); + void setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const; // TODO: Can be defaulted to addAllPossibleObjects, but then each object will need to be configured void addCommonObjects(); @@ -69,7 +70,7 @@ protected: void addObject(const ObjectInfo & info); void updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info); std::vector & getPossibleObjects(); - void patchWithZoneConfig(const Zone & zone); + void patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp); void sortPossibleObjects(); void discardObjectsAboveValue(ui32 value); From af2df5763f29ad12dc13fe57ef74b875ac01cdcd Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 12 Sep 2024 22:53:45 +0200 Subject: [PATCH 140/726] Update PriorityEvaluator.cpp Only if there is a high gold-pressure a buildings' cost will deter from its score. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 295428a06..b6ee0b112 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1441,7 +1441,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score += 1000; auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources(); auto income = ai->buildAnalyzer->getDailyIncome(); - score /= evaluationContext.buildingCost.marketValue(); + if(ai->buildAnalyzer->isGoldPressureHigh()) + score /= evaluationContext.buildingCost.marketValue(); if (!resourcesAvailable.canAfford(evaluationContext.buildingCost)) { TResources needed = evaluationContext.buildingCost - resourcesAvailable; From ab441d8e677bb63052427b321fa21fd6437b30bd Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 13 Sep 2024 23:06:49 +0200 Subject: [PATCH 141/726] Update PriorityEvaluator.cpp AI now should no longer ignore spell-scrolls and artifacts of the treasure-class. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index b6ee0b112..f2882e124 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -261,6 +261,8 @@ static uint64_t evaluateArtifactArmyValue(const CArtifact * art) switch(art->aClass) { + case CArtifact::EartClass::ART_TREASURE: + //FALL_THROUGH case CArtifact::EartClass::ART_MINOR: classValue = 1000; break; @@ -299,6 +301,8 @@ uint64_t RewardEvaluator::getArmyReward( case Obj::CREATURE_GENERATOR3: case Obj::CREATURE_GENERATOR4: return getDwellingArmyValue(ai->cb.get(), target, checkGold); + case Obj::SPELL_SCROLL: + //FALL_THROUGH case Obj::ARTIFACT: return evaluateArtifactArmyValue(dynamic_cast(target)->storedArtifact->artType); case Obj::HERO: From 9b9a50c0aef5c6551d8c960e5d755ddc2449d86f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 14 Sep 2024 02:51:33 +0200 Subject: [PATCH 142/726] Update StayAtTown.cpp Showing mana-limit too for Stay At Town. --- AI/Nullkiller/Goals/StayAtTown.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Goals/StayAtTown.cpp b/AI/Nullkiller/Goals/StayAtTown.cpp index 250e16371..817ddbe1c 100644 --- a/AI/Nullkiller/Goals/StayAtTown.cpp +++ b/AI/Nullkiller/Goals/StayAtTown.cpp @@ -36,7 +36,8 @@ std::string StayAtTown::toString() const { return "Stay at town " + town->getNameTranslated() + " hero " + hero->getNameTranslated() - + ", mana: " + std::to_string(hero->mana); + + ", mana: " + std::to_string(hero->mana) + + " / " + std::to_string(hero->manaLimit()); } void StayAtTown::accept(AIGateway * ai) From 22222f0fba5a666c89da62a8c433f7999f49d4e7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 14 Sep 2024 02:58:23 +0200 Subject: [PATCH 143/726] Priorization-improvements Manarecoveryreward now uses float instead of unsigned int in order to avoid extremely high instead of negative scores when the hero has more mana than his mana-limit for example due to mana-vortex. Moved upgrading armies to a lower priority tier as otherwise the AI would go back to their cities all the time even though there were plenty of other things to do. Improved exploration logic by putting different kinds of exploration to different priority-tiers. Looking at the other side of a portal has high priority, visiting an observatory has medium priority and scouting by visiting nearby tiles has low priority. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 50 ++++++++++++++++++---- AI/Nullkiller/Engine/PriorityEvaluator.h | 6 ++- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 39054c75d..4a10d8b6c 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -387,7 +387,7 @@ void Nullkiller::makeTurn() if(bestTask->priority > 0) { #if NKAI_TRACE_LEVEL >= 1 - logAi->info("Pass %d: Performing task %s with prio: %d", i, bestTask->toString(), bestTask->priority); + logAi->info("Pass %d: Performing prio 0 task %s with prio: %d", i, bestTask->toString(), bestTask->priority); #endif if(!executeTask(bestTask)) return; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f2882e124..e6ad50e97 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -63,7 +63,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) involvesSailing(false), isTradeBuilding(false), isExchange(false), - isExplore(false) + isArmyUpgrade(false), + explorePriority(0) { } @@ -493,7 +494,7 @@ uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const return result; } -uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const +float RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const { return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast(hero->mana) / hero->manaLimit())); } @@ -868,6 +869,7 @@ public: evaluationContext.armyReward += upgradeValue; evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength()); + evaluationContext.isArmyUpgrade = true; } }; @@ -882,7 +884,24 @@ public: int tilesDiscovered = task->value; evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered); - evaluationContext.isExplore = true; + for (auto obj : evaluationContext.evaluator.ai->cb->getVisitableObjs(task->tile)) + { + switch (obj->ID.num) + { + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + case Obj::WHIRLPOOL: + evaluationContext.explorePriority = 1; + break; + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + evaluationContext.explorePriority = 2; + break; + } + } + if (evaluationContext.explorePriority == 0) + evaluationContext.explorePriority = 3; } }; @@ -1320,7 +1339,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) float score = 0; float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, isDefend: %d", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1340,6 +1359,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.conquestValue, evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, + evaluationContext.explorePriority, evaluationContext.isDefend); #endif @@ -1369,7 +1389,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::KILL: //Take towns / kill heroes that are further away { - if (evaluationContext.conquestValue > 0) + if (evaluationContext.conquestValue > 0 || evaluationContext.explorePriority == 1) score = 1000; if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; @@ -1388,6 +1408,10 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0)) return 0; + if (evaluationContext.explorePriority == 3) + return 0; + if (evaluationContext.isArmyUpgrade) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; @@ -1397,8 +1421,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage; if (score > 0) { - if(!evaluationContext.isExplore) - score = 1000; score *= evaluationContext.closestWayRatio; if (evaluationContext.enemyHeroDangerRatio > 1) score /= evaluationContext.enemyHeroDangerRatio; @@ -1408,11 +1430,23 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } break; } + case PriorityTier::LOW_PRIO_EXPLORE: + { + if (evaluationContext.enemyHeroDangerRatio > 1) + return 0; + if (evaluationContext.explorePriority != 3) + return 0; + score = 1000; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + break; + } case PriorityTier::DEFEND: //Defend whatever if nothing else is to do { if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange) return 0; - if (evaluationContext.isDefend) + if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade) score = 1000; score *= evaluationContext.closestWayRatio; score /= (evaluationContext.turn + 1); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index c04e2477d..4978bd28f 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -49,7 +49,7 @@ public: uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const; const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const; uint64_t townArmyGrowth(const CGTownInstance * town) const; - uint64_t getManaRecoveryArmyReward(const CGHeroInstance * hero) const; + float getManaRecoveryArmyReward(const CGHeroInstance * hero) const; }; struct DLL_EXPORT EvaluationContext @@ -80,7 +80,8 @@ struct DLL_EXPORT EvaluationContext bool involvesSailing; bool isTradeBuilding; bool isExchange; - bool isExplore; + bool isArmyUpgrade; + int explorePriority; EvaluationContext(const Nullkiller * ai); @@ -112,6 +113,7 @@ public: INSTADEFEND, KILL, HUNTER_GATHER, + LOW_PRIO_EXPLORE, DEFEND }; From e89649ec1c6b778914ddd6801c56b43df579527e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 14 Sep 2024 08:41:00 +0200 Subject: [PATCH 144/726] Move ObjectConfig to separate file --- lib/CMakeLists.txt | 2 + .../CObjectClassesHandler.cpp | 3 - lib/rmg/CRmgTemplate.cpp | 3 +- lib/rmg/CRmgTemplate.h | 1 + lib/rmg/ObjectConfig.cpp | 187 ++++++++++++++++++ lib/rmg/ObjectConfig.h | 57 ++++++ lib/rmg/ObjectInfo.cpp | 165 ---------------- lib/rmg/ObjectInfo.h | 43 ---- 8 files changed, 249 insertions(+), 212 deletions(-) create mode 100644 lib/rmg/ObjectConfig.cpp create mode 100644 lib/rmg/ObjectConfig.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7ed8cb697..28b1e0d3f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -182,6 +182,7 @@ set(lib_MAIN_SRCS rmg/Zone.cpp rmg/Functions.cpp rmg/ObjectInfo.cpp + rmg/ObjectConfig.cpp rmg/RmgMap.cpp rmg/PenroseTiling.cpp rmg/modificators/Modificator.cpp @@ -585,6 +586,7 @@ set(lib_MAIN_HEADERS rmg/float3.h rmg/Functions.h rmg/ObjectInfo.h + rmg/ObjectConfig.h rmg/PenroseTiling.h rmg/modificators/Modificator.h rmg/modificators/ObjectManager.h diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 5a9df48f7..0ac563e6d 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -415,9 +415,6 @@ CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::stri CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const { - // TODO: Use existing utilities for parsing id: - // CIdentifierStorage::ObjectCallback::fromNameAndType - std::string subtype = "object"; //Default for objects with no subIds std::string type; diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 4247d48d1..d489fa801 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -11,9 +11,9 @@ #include "StdInc.h" #include #include - #include "CRmgTemplate.h" #include "Functions.h" + #include "../TerrainHandler.h" #include "../VCMI_Lib.h" #include "../constants/StringConstants.h" @@ -351,6 +351,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) SERIALIZE_ZONE_LINK(terrainTypeLikeZone); SERIALIZE_ZONE_LINK(treasureLikeZone); SERIALIZE_ZONE_LINK(customObjectsLikeZone); + #undef SERIALIZE_ZONE_LINK if(terrainTypeLikeZone == NO_ZONE) diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 95c23d906..657e7d55b 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -14,6 +14,7 @@ #include "../GameConstants.h" #include "../ResourceSet.h" #include "ObjectInfo.h" +#include "ObjectConfig.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/ObjectConfig.cpp b/lib/rmg/ObjectConfig.cpp new file mode 100644 index 000000000..e70be399b --- /dev/null +++ b/lib/rmg/ObjectConfig.cpp @@ -0,0 +1,187 @@ +/* + * ObjectConfig.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include +#include +#include "ObjectInfo.h" +#include "ObjectConfig.h" + +#include "../VCMI_Lib.h" +#include "../mapObjectConstructors/CObjectClassesHandler.h" +#include "../mapObjectConstructors/AObjectTypeHandler.h" +#include "../serializer/JsonSerializeFormat.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) +{ + // FIXME: We do not need to store the object info, just the id + + bannedObjects.push_back(objid); + + logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); +} + +void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid) +{ + // FIXME: Need id / subId + + // FIXME: Add templates and possibly other info + customObjects.push_back(object); + auto & lastObject = customObjects.back(); + lastObject.setAllTemplates(objid.primaryID, objid.secondaryID); + + assert(lastObject.templates.size() > 0); + auto temp = lastObject.templates.front(); + logGlobal->info("Added custom object of type %d.%d", temp->id, temp->subid); +} + +void ObjectConfig::serializeJson(JsonSerializeFormat & handler) +{ + // TODO: We need serializer utility for list of enum values + + static const boost::bimap OBJECT_CATEGORY_STRINGS = boost::assign::list_of::relation> + (EObjectCategory::OTHER, "other") + (EObjectCategory::ALL, "all") + (EObjectCategory::NONE, "none") + (EObjectCategory::CREATURE_BANK, "creatureBank") + (EObjectCategory::BONUS, "bonus") + (EObjectCategory::DWELLING, "dwelling") + (EObjectCategory::RESOURCE, "resource") + (EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator") + (EObjectCategory::SPELL_SCROLL, "spellScroll") + (EObjectCategory::RANDOM_ARTIFACT, "randomArtifact") + (EObjectCategory::PANDORAS_BOX, "pandorasBox") + (EObjectCategory::QUEST_ARTIFACT, "questArtifact") + (EObjectCategory::SEER_HUT, "seerHut"); + + + // TODO: Separate into individual methods to enforce RAII destruction? + { + auto categories = handler.enterArray("bannedCategories"); + if (handler.saving) + { + for (const auto& category : bannedObjectCategories) + { + auto str = OBJECT_CATEGORY_STRINGS.left.at(category); + categories.serializeString(categories.size(), str); + } + } + else + { + std::vector categoryNames; + categories.serializeArray(categoryNames); + + for (const auto & categoryName : categoryNames) + { + auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName); + if (it != OBJECT_CATEGORY_STRINGS.right.end()) + { + bannedObjectCategories.push_back(it->second); + } + } + } + } + + // FIXME: Doesn't seem to use this field at all + + { + auto bannedObjectData = handler.enterArray("bannedObjects"); + if (handler.saving) + { + + // FIXME: Do we even need to serialize / store banned objects? + /* + for (const auto & object : bannedObjects) + { + // TODO: Translate id back to string? + + + JsonNode node; + node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); + // TODO: Check if AI-generated code is right + + + } + // handler.serializeRaw("bannedObjects", node, std::nullopt); + + */ + } + else + { + std::vector objectNames; + bannedObjectData.serializeArray(objectNames); + + for (const auto & objectName : objectNames) + { + VLC->objtypeh->resolveObjectCompoundId(objectName, + [this](CompoundMapObjectID objid) + { + addBannedObject(objid); + } + ); + + } + } + } + + auto commonObjectData = handler.getCurrent()["commonObjects"].Vector(); + if (handler.saving) + { + + //TODO? + } + else + { + for (const auto & objectConfig : commonObjectData) + { + auto objectName = objectConfig["id"].String(); + auto rmg = objectConfig["rmg"].Struct(); + + // TODO: Use common code with default rmg config + auto objectValue = rmg["value"].Integer(); + auto objectProbability = rmg["rarity"].Integer(); + auto objectMaxPerZone = rmg["zoneLimit"].Integer(); + + VLC->objtypeh->resolveObjectCompoundId(objectName, + [this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid) + { + ObjectInfo object; + + // TODO: Configure basic generateObject function + + object.value = objectValue; + object.probability = objectProbability; + object.maxPerZone = objectMaxPerZone; + addCustomObject(object, objid); + } + ); + + } + } +} + +const std::vector & ObjectConfig::getConfiguredObjects() const +{ + return customObjects; +} + +const std::vector & ObjectConfig::getBannedObjects() const +{ + return bannedObjects; +} + +const std::vector & ObjectConfig::getBannedObjectCategories() const +{ + return bannedObjectCategories; +} + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectConfig.h b/lib/rmg/ObjectConfig.h new file mode 100644 index 000000000..d4534ce47 --- /dev/null +++ b/lib/rmg/ObjectConfig.h @@ -0,0 +1,57 @@ +/* + * ObjectInfo.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../constants/EntityIdentifiers.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE ObjectConfig +{ +public: + + enum class EObjectCategory + { + OTHER = -2, + ALL = -1, + NONE = 0, + CREATURE_BANK = 1, + BONUS, + DWELLING, + RESOURCE, + RESOURCE_GENERATOR, + SPELL_SCROLL, + RANDOM_ARTIFACT, + PANDORAS_BOX, + QUEST_ARTIFACT, + SEER_HUT + }; + + void addBannedObject(const CompoundMapObjectID & objid); + void addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid); + void clearBannedObjects(); + void clearCustomObjects(); + const std::vector & getBannedObjects() const; + const std::vector & getBannedObjectCategories() const; + const std::vector & getConfiguredObjects() const; + + void serializeJson(JsonSerializeFormat & handler); +private: + // TODO: Add convenience method for banning objects by name + std::vector bannedObjects; + std::vector bannedObjectCategories; + + // TODO: In what format should I store custom objects? + // Need to convert map serialization format to ObjectInfo + std::vector customObjects; +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectInfo.cpp b/lib/rmg/ObjectInfo.cpp index d9a9f3ac1..dce0bb6ab 100644 --- a/lib/rmg/ObjectInfo.cpp +++ b/lib/rmg/ObjectInfo.cpp @@ -9,8 +9,6 @@ */ #include "StdInc.h" -#include -#include #include "ObjectInfo.h" #include "../VCMI_Lib.h" @@ -68,167 +66,4 @@ void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainI templates = templHandler->getTemplates(terrainType); } -void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) -{ - // FIXME: We do not need to store the object info, just the id - - bannedObjects.push_back(objid); - - logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID); -} - -void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid) -{ - // FIXME: Need id / subId - - // FIXME: Add templates and possibly other info - customObjects.push_back(object); - auto & lastObject = customObjects.back(); - lastObject.setAllTemplates(objid.primaryID, objid.secondaryID); - - assert(lastObject.templates.size() > 0); - auto temp = lastObject.templates.front(); - logGlobal->info("Added custom object of type %d.%d", temp->id, temp->subid); -} - -void ObjectConfig::serializeJson(JsonSerializeFormat & handler) -{ - // TODO: We need serializer utility for list of enum values - - static const boost::bimap OBJECT_CATEGORY_STRINGS = boost::assign::list_of::relation> - (EObjectCategory::OTHER, "other") - (EObjectCategory::ALL, "all") - (EObjectCategory::NONE, "none") - (EObjectCategory::CREATURE_BANK, "creatureBank") - (EObjectCategory::BONUS, "bonus") - (EObjectCategory::DWELLING, "dwelling") - (EObjectCategory::RESOURCE, "resource") - (EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator") - (EObjectCategory::SPELL_SCROLL, "spellScroll") - (EObjectCategory::RANDOM_ARTIFACT, "randomArtifact") - (EObjectCategory::PANDORAS_BOX, "pandorasBox") - (EObjectCategory::QUEST_ARTIFACT, "questArtifact") - (EObjectCategory::SEER_HUT, "seerHut"); - - - // TODO: Separate into individual methods to enforce RAII destruction? - { - auto categories = handler.enterArray("bannedCategories"); - if (handler.saving) - { - for (const auto& category : bannedObjectCategories) - { - auto str = OBJECT_CATEGORY_STRINGS.left.at(category); - categories.serializeString(categories.size(), str); - } - } - else - { - std::vector categoryNames; - categories.serializeArray(categoryNames); - - for (const auto & categoryName : categoryNames) - { - auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName); - if (it != OBJECT_CATEGORY_STRINGS.right.end()) - { - bannedObjectCategories.push_back(it->second); - } - } - } - } - - // FIXME: Doesn't seem to use this field at all - - { - auto bannedObjectData = handler.enterArray("bannedObjects"); - if (handler.saving) - { - - // FIXME: Do we even need to serialize / store banned objects? - /* - for (const auto & object : bannedObjects) - { - // TODO: Translate id back to string? - - - JsonNode node; - node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID); - // TODO: Check if AI-generated code is right - - - } - // handler.serializeRaw("bannedObjects", node, std::nullopt); - - */ - } - else - { - std::vector objectNames; - bannedObjectData.serializeArray(objectNames); - - for (const auto & objectName : objectNames) - { - VLC->objtypeh->resolveObjectCompoundId(objectName, - [this](CompoundMapObjectID objid) - { - addBannedObject(objid); - } - ); - - } - } - } - - auto commonObjectData = handler.getCurrent()["commonObjects"].Vector(); - if (handler.saving) - { - - //TODO? - } - else - { - for (const auto & objectConfig : commonObjectData) - { - auto objectName = objectConfig["id"].String(); - auto rmg = objectConfig["rmg"].Struct(); - - // TODO: Use common code with default rmg config - auto objectValue = rmg["value"].Integer(); - auto objectProbability = rmg["rarity"].Integer(); - auto objectMaxPerZone = rmg["zoneLimit"].Integer(); - - VLC->objtypeh->resolveObjectCompoundId(objectName, - [this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid) - { - ObjectInfo object; - - // TODO: Configure basic generateObject function - - object.value = objectValue; - object.probability = objectProbability; - object.maxPerZone = objectMaxPerZone; - addCustomObject(object, objid); - } - ); - - } - } -} - -const std::vector & ObjectConfig::getConfiguredObjects() const -{ - return customObjects; -} - -const std::vector & ObjectConfig::getBannedObjects() const -{ - return bannedObjects; -} - -const std::vector & ObjectConfig::getBannedObjectCategories() const -{ - return bannedObjectCategories; -} - VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h index fa7138696..409b768f9 100644 --- a/lib/rmg/ObjectInfo.h +++ b/lib/rmg/ObjectInfo.h @@ -38,47 +38,4 @@ struct DLL_LINKAGE ObjectInfo //bool matchesId(const CompoundMapObjectID & id) const; }; -// TODO: Store config for custom objects to spawn in this zone -// TODO: Read custom object config from zone file -class DLL_LINKAGE ObjectConfig -{ -public: - - enum class EObjectCategory - { - OTHER = -2, - ALL = -1, - NONE = 0, - CREATURE_BANK = 1, - BONUS, - DWELLING, - RESOURCE, - RESOURCE_GENERATOR, - SPELL_SCROLL, - RANDOM_ARTIFACT, - PANDORAS_BOX, - QUEST_ARTIFACT, - SEER_HUT - }; - - void addBannedObject(const CompoundMapObjectID & objid); - void addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid); - void clearBannedObjects(); - void clearCustomObjects(); - const std::vector & getBannedObjects() const; - const std::vector & getBannedObjectCategories() const; - const std::vector & getConfiguredObjects() const; - - void serializeJson(JsonSerializeFormat & handler); -private: - // TODO: Add convenience method for banning objects by name - std::vector bannedObjects; - std::vector bannedObjectCategories; - - // TODO: In what format should I store custom objects? - // Need to convert map serialization format to ObjectInfo - std::vector customObjects; -}; -// TODO: Allow to copy all custom objects config from another zone - VCMI_LIB_NAMESPACE_END \ No newline at end of file From 9591ce1ab4879484050f73c907d2fadb7c40a38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 14 Sep 2024 08:55:28 +0200 Subject: [PATCH 145/726] Move CompoundMapObjectID to separate file --- AI/VCAI/MapObjectsEvaluator.cpp | 1 + lib/CMakeLists.txt | 1 + .../CObjectClassesHandler.h | 23 +----------- lib/mapObjects/CompoundMapObjectID.h | 37 +++++++++++++++++++ lib/mapObjects/ObjectTemplate.cpp | 5 +++ lib/mapObjects/ObjectTemplate.h | 4 +- lib/rmg/ObjectConfig.h | 2 +- lib/rmg/ObjectInfo.h | 2 +- lib/rmg/modificators/TreasurePlacer.h | 1 - 9 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 lib/mapObjects/CompoundMapObjectID.h diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index 3d6382559..5536d8fd9 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -13,6 +13,7 @@ #include "../../lib/VCMI_Lib.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CHeroHandler.h" +#include "../../lib/mapObjects/CompoundMapObjectID.h" #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 28b1e0d3f..1893c7977 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -502,6 +502,7 @@ set(lib_MAIN_HEADERS mapObjects/IObjectInterface.h mapObjects/MapObjects.h mapObjects/MiscObjects.h + mapObjects/CompoundMapObjectID.h mapObjects/ObjectTemplate.h mapObjects/ObstacleSetHandler.h diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.h b/lib/mapObjectConstructors/CObjectClassesHandler.h index ce94f67ae..8651f08f5 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.h +++ b/lib/mapObjectConstructors/CObjectClassesHandler.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../constants/EntityIdentifiers.h" +#include "../mapObjects/CompoundMapObjectID.h" #include "../IHandlerBase.h" #include "../json/JsonNode.h" @@ -19,27 +19,6 @@ class AObjectTypeHandler; class ObjectTemplate; struct SObjectSounds; -struct DLL_LINKAGE CompoundMapObjectID -{ - si32 primaryID; - si32 secondaryID; - - CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {}; - - bool operator<(const CompoundMapObjectID& other) const - { - if(this->primaryID != other.primaryID) - return this->primaryID < other.primaryID; - else - return this->secondaryID < other.secondaryID; - } - - bool operator==(const CompoundMapObjectID& other) const - { - return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID); - } -}; - class CGObjectInstance; using TObjectTypeHandler = std::shared_ptr; diff --git a/lib/mapObjects/CompoundMapObjectID.h b/lib/mapObjects/CompoundMapObjectID.h new file mode 100644 index 000000000..4c067548b --- /dev/null +++ b/lib/mapObjects/CompoundMapObjectID.h @@ -0,0 +1,37 @@ +/* + * CompoundMapObjectID.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../constants/EntityIdentifiers.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct DLL_LINKAGE CompoundMapObjectID +{ + si32 primaryID; + si32 secondaryID; + + CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {}; + + bool operator<(const CompoundMapObjectID& other) const + { + if(this->primaryID != other.primaryID) + return this->primaryID < other.primaryID; + else + return this->secondaryID < other.secondaryID; + } + + bool operator==(const CompoundMapObjectID& other) const + { + return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID); + } +}; + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 9c32a7566..68c6a6178 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -508,6 +508,11 @@ bool ObjectTemplate::canBePlacedAt(TerrainId terrainID) const return vstd::contains(allowedTerrains, terrainID); } +CompoundMapObjectID ObjectTemplate::getCompoundID() const +{ + return CompoundMapObjectID(id, subid); +} + void ObjectTemplate::recalculate() { calculateWidth(); diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index a9a5daf63..584750f48 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -13,6 +13,7 @@ #include "../int3.h" #include "../filesystem/ResourcePath.h" #include "../serializer/Serializeable.h" +#include "../mapObjects/CompoundMapObjectID.h" VCMI_LIB_NAMESPACE_BEGIN @@ -47,7 +48,6 @@ public: MapObjectID id; MapObjectSubID subid; - // TODO: get compound id /// print priority, objects with higher priority will be print first, below everything else si32 printPriority; /// animation file that should be used to display object @@ -124,6 +124,8 @@ public: // Checks if object can be placed on specific terrain bool canBePlacedAt(TerrainId terrain) const; + CompoundMapObjectID getCompoundID() const; + ObjectTemplate(); void readTxt(CLegacyConfigParser & parser); diff --git a/lib/rmg/ObjectConfig.h b/lib/rmg/ObjectConfig.h index d4534ce47..998052944 100644 --- a/lib/rmg/ObjectConfig.h +++ b/lib/rmg/ObjectConfig.h @@ -10,7 +10,7 @@ #pragma once -#include "../constants/EntityIdentifiers.h" +#include "../mapObjects/CompoundMapObjectID.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h index 409b768f9..843c7c9ed 100644 --- a/lib/rmg/ObjectInfo.h +++ b/lib/rmg/ObjectInfo.h @@ -11,7 +11,7 @@ #pragma once #include "../mapObjects/ObjectTemplate.h" -#include "../constants/EntityIdentifiers.h" +#include "../mapObjects/CompoundMapObjectID.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index c9cde768e..f82873cd1 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -21,7 +21,6 @@ class ObjectManager; class RmgMap; class CMapGenerator; class ObjectConfig; -struct CompoundMapObjectID; class TreasurePlacer: public Modificator { From 4d4538a48dfd9e7c4d69cc07a3cedd1dfbc3372f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 14 Sep 2024 09:23:28 +0200 Subject: [PATCH 146/726] Use CompoundMapObjectID in ObjectInfo --- lib/rmg/ObjectConfig.cpp | 8 +-- lib/rmg/ObjectInfo.cpp | 18 +++++- lib/rmg/ObjectInfo.h | 6 +- lib/rmg/modificators/ObjectDistributor.cpp | 3 +- lib/rmg/modificators/TreasurePlacer.cpp | 66 +++++++++------------- 5 files changed, 54 insertions(+), 47 deletions(-) diff --git a/lib/rmg/ObjectConfig.cpp b/lib/rmg/ObjectConfig.cpp index e70be399b..1931becd7 100644 --- a/lib/rmg/ObjectConfig.cpp +++ b/lib/rmg/ObjectConfig.cpp @@ -32,16 +32,12 @@ void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid) void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid) { - // FIXME: Need id / subId - - // FIXME: Add templates and possibly other info customObjects.push_back(object); auto & lastObject = customObjects.back(); lastObject.setAllTemplates(objid.primaryID, objid.secondaryID); assert(lastObject.templates.size() > 0); - auto temp = lastObject.templates.front(); - logGlobal->info("Added custom object of type %d.%d", temp->id, temp->subid); + logGlobal->info("Added custom object of type %d.%d", objid.primaryID, objid.secondaryID); } void ObjectConfig::serializeJson(JsonSerializeFormat & handler) @@ -154,7 +150,7 @@ void ObjectConfig::serializeJson(JsonSerializeFormat & handler) VLC->objtypeh->resolveObjectCompoundId(objectName, [this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid) { - ObjectInfo object; + ObjectInfo object(objid.primaryID, objid.secondaryID); // TODO: Configure basic generateObject function diff --git a/lib/rmg/ObjectInfo.cpp b/lib/rmg/ObjectInfo.cpp index dce0bb6ab..254ff79b4 100644 --- a/lib/rmg/ObjectInfo.cpp +++ b/lib/rmg/ObjectInfo.cpp @@ -18,15 +18,24 @@ VCMI_LIB_NAMESPACE_BEGIN -ObjectInfo::ObjectInfo(): +ObjectInfo::ObjectInfo(si32 ID, si32 subID): + primaryID(ID), + secondaryID(subID), destroyObject([](CGObjectInstance * obj){}), maxPerZone(std::numeric_limits::max()) { } +ObjectInfo::ObjectInfo(CompoundMapObjectID id): + ObjectInfo(id.primaryID, id.secondaryID) +{ +} + ObjectInfo::ObjectInfo(const ObjectInfo & other) { templates = other.templates; + primaryID = other.primaryID; + secondaryID = other.secondaryID; value = other.value; probability = other.probability; maxPerZone = other.maxPerZone; @@ -40,6 +49,8 @@ ObjectInfo & ObjectInfo::operator=(const ObjectInfo & other) return *this; templates = other.templates; + primaryID = other.primaryID; + secondaryID = other.secondaryID; value = other.value; probability = other.probability; maxPerZone = other.maxPerZone; @@ -66,4 +77,9 @@ void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainI templates = templHandler->getTemplates(terrainType); } +CompoundMapObjectID ObjectInfo::getCompoundID() const +{ + return CompoundMapObjectID(primaryID, secondaryID); +} + VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/rmg/ObjectInfo.h b/lib/rmg/ObjectInfo.h index 843c7c9ed..6e414e516 100644 --- a/lib/rmg/ObjectInfo.h +++ b/lib/rmg/ObjectInfo.h @@ -20,11 +20,14 @@ class CGObjectInstance; struct DLL_LINKAGE ObjectInfo { - ObjectInfo(); + ObjectInfo(si32 ID, si32 subID); + ObjectInfo(CompoundMapObjectID id); ObjectInfo(const ObjectInfo & other); ObjectInfo & operator=(const ObjectInfo & other); std::vector> templates; + si32 primaryID; + si32 secondaryID; ui32 value = 0; ui16 probability = 0; ui32 maxPerZone = 1; @@ -35,6 +38,7 @@ struct DLL_LINKAGE ObjectInfo void setAllTemplates(MapObjectID type, MapObjectSubID subtype); void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain); + CompoundMapObjectID getCompoundID() const; //bool matchesId(const CompoundMapObjectID & id) const; }; diff --git a/lib/rmg/modificators/ObjectDistributor.cpp b/lib/rmg/modificators/ObjectDistributor.cpp index ae6c42f65..76bae9e41 100644 --- a/lib/rmg/modificators/ObjectDistributor.cpp +++ b/lib/rmg/modificators/ObjectDistributor.cpp @@ -45,7 +45,6 @@ void ObjectDistributor::init() void ObjectDistributor::distributeLimitedObjects() { - ObjectInfo oi; auto zones = map.getZones(); for (auto primaryID : VLC->objtypeh->knownObjects()) @@ -81,6 +80,8 @@ void ObjectDistributor::distributeLimitedObjects() RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand()); for (auto& zone : matchingZones) { + ObjectInfo oi(primaryID, secondaryID); + oi.generateObject = [cb=map.mapInstance->cb, primaryID, secondaryID]() -> CGObjectInstance * { return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(cb, nullptr); diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index 97ab2ae3c..ddc1ddd70 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -47,6 +47,16 @@ void TreasurePlacer::process() return; } + tierValues = generator.getConfig().pandoraCreatureValues; + // Add all native creatures + for(auto const & cre : VLC->creh->objects) + { + if(!cre->special && cre->getFaction() == zone.getTownType()) + { + creatures.push_back(cre.get()); + } + } + // Get default objects addAllPossibleObjects(); // Override with custom objects @@ -64,19 +74,6 @@ void TreasurePlacer::init() DEPENDENCY(ConnectionsPlacer); DEPENDENCY_ALL(PrisonHeroPlacer); DEPENDENCY(RoadPlacer); - - // FIXME: Starting zones get Pandoras with neutral creatures - - // Add all native creatures - for(auto const & cre : VLC->creh->objects) - { - if(!cre->special && cre->getFaction() == zone.getTownType()) - { - creatures.push_back(cre.get()); - } - } - - tierValues = generator.getConfig().pandoraCreatureValues; } void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi) @@ -112,8 +109,6 @@ void TreasurePlacer::addAllPossibleObjects() void TreasurePlacer::addCommonObjects() { - ObjectInfo oi; - for(auto primaryID : VLC->objtypeh->knownObjects()) { for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID)) @@ -127,12 +122,13 @@ void TreasurePlacer::addCommonObjects() //Skip objects with per-map limit here continue; } + ObjectInfo oi(primaryID, secondaryID); setBasicProperties(oi, CompoundMapObjectID(primaryID, secondaryID)); oi.value = rmgInfo.value; oi.probability = rmgInfo.rarity; - oi.maxPerZone = rmgInfo.zoneLimit; + if(!oi.templates.empty()) addObjectToRandomPool(oi); } @@ -172,7 +168,7 @@ void TreasurePlacer::addPrisons() size_t prisonsLeft = getMaxPrisons(); for (int i = prisonsLevels - 1; i >= 0; i--) { - ObjectInfo oi; // Create new instance which will hold destructor operation + ObjectInfo oi(Obj::PRISON, 0); // Create new instance which will hold destructor operation oi.value = generator.getConfig().prisonValues[i]; if (oi.value > zone.getMaxTreasureValue()) @@ -216,9 +212,7 @@ void TreasurePlacer::addDwellings() { if(zone.getType() == ETemplateZoneType::WATER) return; - - ObjectInfo oi; - + //dwellings auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4}; @@ -245,6 +239,9 @@ void TreasurePlacer::addDwellings() if(cre->getFaction() == zone.getTownType()) { auto nativeZonesCount = static_cast(map.getZoneCount(cre->getFaction())); + ObjectInfo oi(dwellingType, secondaryID); + setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID)); + oi.value = static_cast(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2))); oi.probability = 40; @@ -254,7 +251,6 @@ void TreasurePlacer::addDwellings() obj->tempOwner = PlayerColor::NEUTRAL; return obj; }; - oi.setTemplates(dwellingType, secondaryID, zone.getTerrainType()); if(!oi.templates.empty()) addObjectToRandomPool(oi); } @@ -267,7 +263,7 @@ void TreasurePlacer::addScrolls() if(zone.getType() == ETemplateZoneType::WATER) return; - ObjectInfo oi; + ObjectInfo oi(Obj::SPELL_SCROLL, 0); for(int i = 0; i < generator.getConfig().scrollValues.size(); i++) { @@ -308,8 +304,7 @@ void TreasurePlacer::addPandoraBoxes() void TreasurePlacer::addPandoraBoxesWithGold() { - ObjectInfo oi; - //pandora box with gold + ObjectInfo oi(Obj::PANDORAS_BOX, 0); for(int i = 1; i < 5; i++) { oi.generateObject = [this, i]() -> CGObjectInstance * @@ -334,8 +329,7 @@ void TreasurePlacer::addPandoraBoxesWithGold() void TreasurePlacer::addPandoraBoxesWithExperience() { - ObjectInfo oi; - //pandora box with experience + ObjectInfo oi(Obj::PANDORAS_BOX, 0); for(int i = 1; i < 5; i++) { oi.generateObject = [this, i]() -> CGObjectInstance * @@ -360,9 +354,7 @@ void TreasurePlacer::addPandoraBoxesWithExperience() void TreasurePlacer::addPandoraBoxesWithCreatures() { - ObjectInfo oi; - //pandora box with creatures - + ObjectInfo oi(Obj::PANDORAS_BOX, 0); for(auto * creature : creatures) { int creaturesAmount = creatureToCount(creature); @@ -391,7 +383,7 @@ void TreasurePlacer::addPandoraBoxesWithCreatures() void TreasurePlacer::addPandoraBoxesWithSpells() { - ObjectInfo oi; + ObjectInfo oi(Obj::PANDORAS_BOX, 0); //Pandora with 12 spells of certain level for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++) { @@ -494,7 +486,7 @@ void TreasurePlacer::addSeerHuts() { //Seer huts with creatures or generic rewards - ObjectInfo oi; + ObjectInfo oi(Obj::SEER_HUT, 0); if(zone.getConnectedZoneIds().size()) //Unlikely, but... { @@ -1120,7 +1112,7 @@ void TreasurePlacer::ObjectPool::updateObject(MapObjectID id, MapObjectSubID sub - Pandora Boxes */ // FIXME: This will drop all templates - customObjects[CompoundMapObjectID(id, subid)] = info; + customObjects.insert(std::make_pair(CompoundMapObjectID(id, subid), info)); } void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp) @@ -1147,8 +1139,7 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, Treasure vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool { - auto temp = oi.templates.front(); - auto category = getObjectCategory(CompoundMapObjectID(temp->id, temp->subid)); + auto category = getObjectCategory(oi.getCompoundID()); if (categoriesSet.count(category)) { logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); @@ -1168,7 +1159,7 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, Treasure { for (const auto & templ : object.templates) { - CompoundMapObjectID key(templ->id, templ->subid); + CompoundMapObjectID key = object.getCompoundID(); if (bannedObjectsSet.count(key)) { // FIXME: Stopped working, nothing is banned @@ -1184,10 +1175,9 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, Treasure // FIXME: Access TreasurePlacer from ObjectPool for (auto & object : configuredObjects) { - auto temp = object.templates.front(); - tp->setBasicProperties(object, CompoundMapObjectID(temp->id, temp->subid)); + tp->setBasicProperties(object, object.getCompoundID()); addObject(object); - logGlobal->info("Added custom object of type %d.%d", temp->id, temp->subid); + logGlobal->info("Added custom object of type %d.%d", object.primaryID, object.secondaryID); } // TODO: Overwrite or add to possibleObjects From 54e47c20b007148356225653781a98df9292da8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 14 Sep 2024 13:20:24 +0200 Subject: [PATCH 147/726] Add docs for new options --- docs/modders/Random_Map_Template.md | 31 ++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index 65bfb3724..309d84e8e 100644 --- a/docs/modders/Random_Map_Template.md +++ b/docs/modders/Random_Map_Template.md @@ -99,10 +99,13 @@ "minesLikeZone" : 1, // Treasures will have same configuration as in linked zone - "treasureLikeZone" : 1 + "treasureLikeZone" : 1, // Terrain type will have same configuration as in linked zone - "terrainTypeLikeZone" : 3 + "terrainTypeLikeZone" : 3, + + // Custom objects will have same configuration as in linked zone + "customObjectsLikeZone" : 1, // factions of monsters allowed on this zone "allowedMonsters" : ["inferno", "necropolis"] @@ -130,6 +133,28 @@ "density" : 5 } ... - ] + ], + + // Objects with different configuration than default / set by mods + "customObjects" : + { + // All of objects of this kind will be removed from zone + // Possible values: "all", "none", "creatureBank", "bonus", "dwelling", "resource", "resourceGenerator", "spellScroll", "randomArtifact", "pandorasBox", "questArtifact", "seerHut", "other + "bannedCategories" : ["all", "dwelling", "creatureBank", "other"], + // Specify object types and subtypes + "bannedObjects" :["core:object.randomArtifactRelic"], + // Configure individual common objects - overrides banned objects + "commonObjects": + [ + { + "id" : "core:object.creatureBank.dragonFlyHive", + "rmg" : { + "value" : 9000, + "rarity" : 500, + "zoneLimit" : 2 + } + } + ] + } } ``` \ No newline at end of file From c88165f90050e54cee5c6540dca7a39977959c99 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 14 Sep 2024 13:41:14 +0200 Subject: [PATCH 148/726] Update PriorityEvaluator.cpp Whirlpools are no longer explorePriority 1 as the AI would then try out all of it's 5 entries on both sides. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index e6ad50e97..c373dff6c 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -891,7 +891,6 @@ public: case Obj::MONOLITH_ONE_WAY_ENTRANCE: case Obj::MONOLITH_TWO_WAY: case Obj::SUBTERRANEAN_GATE: - case Obj::WHIRLPOOL: evaluationContext.explorePriority = 1; break; case Obj::REDWOOD_OBSERVATORY: From 1495ec56f693ae1b1660337c2b70bf7d79f137e4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 15 Sep 2024 20:46:17 +0200 Subject: [PATCH 149/726] Update AINodeStorage.cpp The node of a disembark-action can no longer be part of a hero-chain since sea-to-land-trade isn't possible and landing first eats up all movement-points. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 3f67d4b15..132928a4e 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -721,6 +721,7 @@ void HeroChainCalculationTask::calculateHeroChain( if(node->action == EPathNodeAction::BATTLE || node->action == EPathNodeAction::TELEPORT_BATTLE || node->action == EPathNodeAction::TELEPORT_NORMAL + || node->action == EPathNodeAction::DISEMBARK || node->action == EPathNodeAction::TELEPORT_BLOCKING_VISIT) { continue; From ec603f46aed24ac694ea599dcce0b0433dff257e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 21 Sep 2024 13:36:28 +0200 Subject: [PATCH 150/726] - Handle new configurable banks - Handle "all" object banned option --- lib/mapObjectConstructors/IObjectInfo.h | 3 ++ lib/rewardable/Info.cpp | 5 ++ lib/rewardable/Info.h | 2 + lib/rmg/modificators/TreasurePlacer.cpp | 65 ++++++++++++++----------- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/lib/mapObjectConstructors/IObjectInfo.h b/lib/mapObjectConstructors/IObjectInfo.h index 280402fa8..7a88b5fef 100644 --- a/lib/mapObjectConstructors/IObjectInfo.h +++ b/lib/mapObjectConstructors/IObjectInfo.h @@ -49,7 +49,10 @@ public: virtual bool givesBonuses() const { return false; } + virtual bool hasGuards() const { return false; } + virtual ~IObjectInfo() = default; + }; VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index a12d0a0fc..da5294215 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -525,6 +525,11 @@ bool Rewardable::Info::givesBonuses() const return testForKey(parameters, "bonuses"); } +bool Rewardable::Info::hasGuards() const +{ + return testForKey(parameters, "guards"); +} + const JsonNode & Rewardable::Info::getParameters() const { return parameters; diff --git a/lib/rewardable/Info.h b/lib/rewardable/Info.h index bad4f5031..ac89bdc8d 100644 --- a/lib/rewardable/Info.h +++ b/lib/rewardable/Info.h @@ -68,6 +68,8 @@ public: bool givesBonuses() const override; + bool hasGuards() const override; + void configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb) const; void init(const JsonNode & objectConfig, const std::string & objectTextID); diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index e60e043c1..d6b680b2d 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -1138,38 +1138,41 @@ void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, Treasure auto bannedObjectCategories = zone.getBannedObjectCategories(); auto categoriesSet = std::unordered_set(bannedObjectCategories.begin(), bannedObjectCategories.end()); - vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool + if (categoriesSet.count(ObjectConfig::EObjectCategory::ALL)) { - auto category = getObjectCategory(oi.getCompoundID()); - if (categoriesSet.count(category)) - { - logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); - /* FIXME: - Removing object normal from possible objects - Removing object base from possible objects - Removing object default from possible objects - */ - return true; - } - return false; - }); + possibleObjects.clear(); + } + else + { + vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool - auto bannedObjects = zone.getBannedObjects(); - auto bannedObjectsSet = std::set(bannedObjects.begin(), bannedObjects.end()); - vstd::erase_if(possibleObjects, [&bannedObjectsSet](const ObjectInfo & object) - { - for (const auto & templ : object.templates) { - CompoundMapObjectID key = object.getCompoundID(); - if (bannedObjectsSet.count(key)) + auto category = getObjectCategory(oi.getCompoundID()); + if (categoriesSet.count(category)) { - // FIXME: Stopped working, nothing is banned - logGlobal->info("Banning object %s from possible objects", templ->stringID); + logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID); return true; } - } - return false; - }); + return false; + }); + + auto bannedObjects = zone.getBannedObjects(); + auto bannedObjectsSet = std::set(bannedObjects.begin(), bannedObjects.end()); + vstd::erase_if(possibleObjects, [&bannedObjectsSet](const ObjectInfo & object) + { + for (const auto & templ : object.templates) + { + CompoundMapObjectID key = object.getCompoundID(); + if (bannedObjectsSet.count(key)) + { + // FIXME: Stopped working, nothing is banned + logGlobal->info("Banning object %s from possible objects", templ->stringID); + return true; + } + } + return false; + }); + } auto configuredObjects = zone.getConfiguredObjects(); @@ -1219,16 +1222,20 @@ ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(Comp if (name == "configurable") { - // TODO: Access Rewardable::Info by ID - auto handler = VLC->objtypeh->getHandlerFor(id.primaryID, id.secondaryID); if (!handler) { return ObjectConfig::EObjectCategory::NONE; } + auto temp = handler->getTemplates().front(); auto info = handler->getObjectInfo(temp); - if (info->givesResources()) + + if (info->hasGuards()) + { + return ObjectConfig::EObjectCategory::CREATURE_BANK; + } + else if (info->givesResources()) { return ObjectConfig::EObjectCategory::RESOURCE; } From 55e2a99154c922f49cd7f08c6e46e6c271b911fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 21 Sep 2024 13:36:51 +0200 Subject: [PATCH 151/726] Default object limit to unlimited --- lib/rmg/ObjectConfig.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/rmg/ObjectConfig.cpp b/lib/rmg/ObjectConfig.cpp index 1931becd7..9ea127edf 100644 --- a/lib/rmg/ObjectConfig.cpp +++ b/lib/rmg/ObjectConfig.cpp @@ -145,9 +145,15 @@ void ObjectConfig::serializeJson(JsonSerializeFormat & handler) // TODO: Use common code with default rmg config auto objectValue = rmg["value"].Integer(); auto objectProbability = rmg["rarity"].Integer(); + auto objectMaxPerZone = rmg["zoneLimit"].Integer(); + if (objectMaxPerZone == 0) + { + objectMaxPerZone = std::numeric_limits::max(); + } VLC->objtypeh->resolveObjectCompoundId(objectName, + [this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid) { ObjectInfo object(objid.primaryID, objid.secondaryID); From dbee8224547828c74d033840ab648bfc4b448e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 21 Sep 2024 13:52:25 +0200 Subject: [PATCH 152/726] Update json schema --- config/schemas/template.json | 44 +++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/config/schemas/template.json b/config/schemas/template.json index 47c0cd328..02f0e1996 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -52,7 +52,49 @@ } }, "customObjects" : { - "type" : "object" + "type" : "object", + "properties": { + "bannedCategories": { + "type": "array", + "items": { + "type": "string", + "enum": ["all", "dwelling", "creatureBank", "randomArtifact", "bonus", "resource", "resourceGenerator", "spellScroll", "pandorasBox", "questArtifact", "seerHut"] + } + }, + "bannedObjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "commonObjects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rmg": { + "type": "object", + "properties": { + "value": { + "type": "integer" + }, + "rarity": { + "type": "integer" + }, + "zoneLimit": { + "type": "integer" + } + }, + "required": ["value", "rarity"] + } + }, + "required": ["id", "rmg"] + } + } + } } } }, From 90d72a4458424b76645f19faa86628e6579ce5b4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 22 Sep 2024 13:06:07 +0200 Subject: [PATCH 153/726] Chase & FFA-changes The AI should no longer chase enemy heroes that are not reachable in the same turn, when there's other options as this behavior was quite exploitable. The AI should now take their overall strength into account when deciding whether to attack or not. Previously it would attack as long as their assumed army-loss was at most 25%. Now that is 50% times the ratio of their power compared to the total power of everyone. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 25 +++++++++++++++++++--- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index c373dff6c..ed0c82388 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -64,6 +64,7 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) isTradeBuilding(false), isExchange(false), isArmyUpgrade(false), + isHero(false), explorePriority(0) { } @@ -1037,6 +1038,8 @@ public: evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole); evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target)); evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target); + if (target->ID == Obj::HERO) + evaluationContext.isHero = true; evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); evaluationContext.armyInvolvement += army->getArmyCost(); } @@ -1336,9 +1339,22 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) else { float score = 0; - float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; + float myPower = 0; + float totalPower = 0; + for (auto heroInfo : ai->cb->getHeroesInfo(false)) + { + if (heroInfo->getOwner() == ai->cb->getPlayerID()) + myPower += heroInfo->getTotalStrength(); + totalPower += heroInfo->getTotalStrength(); + } + float powerRatio = 1; + if (totalPower > 0) + powerRatio = myPower / totalPower; + + float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.5 * powerRatio; + #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d powerRatio: %d", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1359,7 +1375,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, evaluationContext.explorePriority, - evaluationContext.isDefend); + evaluationContext.isDefend, + powerRatio); #endif switch (priorityTier) @@ -1388,6 +1405,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::KILL: //Take towns / kill heroes that are further away { + if (evaluationContext.turn > 0 && evaluationContext.isHero) + return 0; if (evaluationContext.conquestValue > 0 || evaluationContext.explorePriority == 1) score = 1000; if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 4978bd28f..6e1cbe5ac 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -81,6 +81,7 @@ struct DLL_EXPORT EvaluationContext bool isTradeBuilding; bool isExchange; bool isArmyUpgrade; + bool isHero; int explorePriority; EvaluationContext(const Nullkiller * ai); From e7e4d6cc72497b2661d03f11e758d1276ab2ef36 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 23 Sep 2024 01:23:18 +0200 Subject: [PATCH 154/726] Fix invincible Hellbardiers --- config/creatures/castle.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 6f53c399f..a88d5a115 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -11,10 +11,6 @@ { "type" : "CHARGE_IMMUNITY" }, - "invincible" : - { - "type" : "INVINCIBLE" - } }, "graphics" : { From 20f7751e169b02d4da0f56d638a08968e51442f4 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 23 Sep 2024 01:25:58 +0200 Subject: [PATCH 155/726] Weekend-decisions When attacking non-neutral towns that are far away, the AI now considers whether their attack would arrive in the same week. If it wouldn't, it means there's a high risk that newly bought troops might flip around who is stronger. So they now refrain from sending a hero towards an enemy town that is too far away. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 9 +++++++++ AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 10 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index ed0c82388..1d343d028 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -65,6 +65,7 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) isExchange(false), isArmyUpgrade(false), isHero(false), + isEnemy(false), explorePriority(0) { } @@ -1040,6 +1041,8 @@ public: evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target); if (target->ID == Obj::HERO) evaluationContext.isHero = true; + if (target->getOwner() != PlayerColor::NEUTRAL && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES) + evaluationContext.isEnemy = true; evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); evaluationContext.armyInvolvement += army->getArmyCost(); } @@ -1353,6 +1356,10 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.5 * powerRatio; + bool arriveNextWeek = false; + if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7) + arriveNextWeek = true; + #if NKAI_TRACE_LEVEL >= 2 logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d powerRatio: %d", priorityTier, @@ -1407,6 +1414,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (evaluationContext.turn > 0 && evaluationContext.isHero) return 0; + if (arriveNextWeek && evaluationContext.isEnemy) + return 0; if (evaluationContext.conquestValue > 0 || evaluationContext.explorePriority == 1) score = 1000; if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 6e1cbe5ac..d912130b7 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -82,6 +82,7 @@ struct DLL_EXPORT EvaluationContext bool isExchange; bool isArmyUpgrade; bool isHero; + bool isEnemy; int explorePriority; EvaluationContext(const Nullkiller * ai); From 54ba4d50d6c4777c7a296a8a5be66000ea73730b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 23 Sep 2024 17:09:46 +0200 Subject: [PATCH 156/726] Allow to ban spell scrolls --- lib/rmg/modificators/TreasurePlacer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index d6b680b2d..eb1b3c60f 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -1265,6 +1265,17 @@ ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(Comp return ObjectConfig::EObjectCategory::RESOURCE; else if (name == "randomArtifact") //"artifact" return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT; + else if (name == "artifact") + { + if (id.primaryID == Obj::SPELL_SCROLL ) // randomArtifactTreasure + { + return ObjectConfig::EObjectCategory::SPELL_SCROLL; + } + else + { + return ObjectConfig::EObjectCategory::QUEST_ARTIFACT; + } + } else if (name == "denOfThieves") return ObjectConfig::EObjectCategory::OTHER; else if (name == "lighthouse") From 71504a3140cf81c3fa636f45141522cc2d7b9bbe Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 23 Sep 2024 17:25:17 +0200 Subject: [PATCH 157/726] Hire despite hero present Only if both the garrison and the outside of a town are blocked are hires of heros being blocked. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index e46483fca..eab159597 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -67,7 +67,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const closestThreat = std::min(closestThreat, threat.turn); } //Don't hire a hero where there already is one present - if (town->visitingHero || town->garrisonHero) + if (town->visitingHero && town->garrisonHero) continue; float visitability = 0; for (auto checkHero : ourHeroes) From d87f195bc7a4157e03b558630eaab6b338c990dc Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 23 Sep 2024 18:39:18 +0200 Subject: [PATCH 158/726] Update AINodeStorage.cpp Nodes along the path are now also considered for how dangerous it is. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 360bcd5bd..247836768 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1419,6 +1419,10 @@ void AINodeStorage::calculateChainInfo(std::vector & paths, const int3 & path.heroArmy = node.actor->creatureSet; path.armyLoss = node.armyLoss; path.targetObjectDanger = ai->dangerEvaluator->evaluateDanger(pos, path.targetHero, !node.actor->allowBattle); + for (auto pathNode : path.nodes) + { + path.targetObjectDanger = std::max(ai->dangerEvaluator->evaluateDanger(pathNode.coord, path.targetHero, !node.actor->allowBattle), path.targetObjectDanger); + } if(path.targetObjectDanger > 0) { From 433c58f8b1d4125abab5a11d0fca5498a0440e7d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 23 Sep 2024 18:42:31 +0200 Subject: [PATCH 159/726] Workaround for previously masked issue A recent fix made it so that towns that weren't supposed to be defended are now no longer defended. This caused scouts with minimal army to also go after them in addition to the main-hero. Problem is when two heroes go for the same town it's a massive waste of movement-points. So for the time being only main-heroes will go for faraway captures. Better solution would be to memorize who was sent to attack what on the same turn and filter out tasks going for the same target. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 1d343d028..456f8af0d 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1412,6 +1412,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::KILL: //Take towns / kill heroes that are further away { + //TODO: This is a workaround for duplicating the same capture town-task being given to several heroes. A better solution ought to be found. + if (evaluationContext.movementCostByRole[HeroRole::MAIN] == 0) + return 0; if (evaluationContext.turn > 0 && evaluationContext.isHero) return 0; if (arriveNextWeek && evaluationContext.isEnemy) From a4392fce36267b9b3fe7c420aca48f2bd8c2a0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 23 Sep 2024 20:34:26 +0200 Subject: [PATCH 160/726] Allow mod name in camelCase --- lib/mapObjectConstructors/CObjectClassesHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 26cfda408..a6d78b2d9 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -440,7 +440,7 @@ CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::stri type = typeAndName.second; } - return getCompoundIdentifier(scopeAndFullName.first, type, subtype); + return getCompoundIdentifier(boost::to_lower_copy(scopeAndFullName.first), type, subtype); } std::set CObjectClassesHandler::knownObjects() const From b9ae7f11380892b73b5360165bb01abc384285cf Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:59:40 +0300 Subject: [PATCH 161/726] CMap put move and remove artifact method --- client/Client.h | 2 +- client/widgets/CArtifactsOfHeroBase.cpp | 4 +-- client/widgets/CArtifactsOfHeroBase.h | 2 +- client/windows/CWindowWithArtifacts.cpp | 9 ++---- lib/CArtifactInstance.cpp | 28 ++--------------- lib/CArtifactInstance.h | 7 ++--- lib/IGameCallback.h | 2 +- lib/gameState/CGameState.cpp | 2 +- lib/gameState/CGameStateCampaign.cpp | 8 ++--- lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 7 ++--- lib/mapping/CMap.cpp | 28 +++++++++++++++++ lib/mapping/CMap.h | 3 ++ lib/mapping/MapFormatH3M.cpp | 2 +- lib/networkPacks/NetPacksLib.cpp | 42 ++++++++++--------------- lib/networkPacks/PacksForClient.h | 8 ++--- server/CGameHandler.cpp | 14 ++++----- server/CGameHandler.h | 2 +- test/mock/mock_IGameCallback.h | 2 +- 19 files changed, 84 insertions(+), 90 deletions(-) diff --git a/client/Client.h b/client/Client.h index d63f70578..6c34809b4 100644 --- a/client/Client.h +++ b/client/Client.h @@ -188,7 +188,7 @@ public: bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;}; bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override {return false;}; bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override {return false;}; - bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) override {return false;}; + bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional askAssemble) override {return false;}; void removeArtifact(const ArtifactLocation & al) override {}; bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;}; diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index c44e3d01f..70f277b8e 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -137,9 +137,9 @@ void CArtifactsOfHeroBase::scrollBackpack(bool left) LOCPLINT->cb->scrollBackpackArtifacts(curHero->id, left); } -void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved) +void CArtifactsOfHeroBase::markPossibleSlots(const CArtifact * art, bool assumeDestRemoved) { - for(auto artPlace : artWorn) + for(const auto & artPlace : artWorn) artPlace.second->selectSlot(art->canBePutAt(curHero, artPlace.second->slot, assumeDestRemoved)); } diff --git a/client/widgets/CArtifactsOfHeroBase.h b/client/widgets/CArtifactsOfHeroBase.h index c4cb83e33..7aaaf5ce3 100644 --- a/client/widgets/CArtifactsOfHeroBase.h +++ b/client/widgets/CArtifactsOfHeroBase.h @@ -39,7 +39,7 @@ public: virtual void setHero(const CGHeroInstance * hero); virtual const CGHeroInstance * getHero() const; virtual void scrollBackpack(bool left); - virtual void markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved = true); + virtual void markPossibleSlots(const CArtifact * art, bool assumeDestRemoved = true); virtual void unmarkSlots(); virtual ArtPlacePtr getArtPlace(const ArtifactPosition & slot); virtual ArtPlacePtr getArtPlace(const Point & cursorPosition); diff --git a/client/windows/CWindowWithArtifacts.cpp b/client/windows/CWindowWithArtifacts.cpp index 24daa3d1c..6561f23d2 100644 --- a/client/windows/CWindowWithArtifacts.cpp +++ b/client/windows/CWindowWithArtifacts.cpp @@ -62,15 +62,12 @@ const CGHeroInstance * CWindowWithArtifacts::getHeroPickedArtifact() const const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact() const { - const CArtifactInstance * art = nullptr; - for(const auto & artSet : artSets) if(const auto pickedArt = artSet->getHero()->getArt(ArtifactPosition::TRANSITION_POS)) { - art = pickedArt; - break; + return pickedArt; } - return art; + return nullptr; } void CWindowWithArtifacts::clickPressedOnArtPlace(const CGHeroInstance * hero, const ArtifactPosition & slot, @@ -202,7 +199,7 @@ void CWindowWithArtifacts::markPossibleSlots() const continue; if(getHeroPickedArtifact() == hero || !std::dynamic_pointer_cast(artSet)) - artSet->markPossibleSlots(pickedArtInst, hero->tempOwner == LOCPLINT->playerID); + artSet->markPossibleSlots(pickedArtInst->artType, hero->tempOwner == LOCPLINT->playerID); } } } diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index 0451e2d2f..6f191e46b 100644 --- a/lib/CArtifactInstance.cpp +++ b/lib/CArtifactInstance.cpp @@ -49,13 +49,13 @@ const std::vector & CCombinedArtifactInstan return partsInfo; } -void CCombinedArtifactInstance::addPlacementMap(CArtifactSet::ArtPlacementMap & placementMap) +void CCombinedArtifactInstance::addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap) { if(!placementMap.empty()) for(auto & part : partsInfo) { - assert(placementMap.find(part.art) != placementMap.end()); - part.slot = placementMap.at(part.art); + if(placementMap.find(part.art) != placementMap.end()) + part.slot = placementMap.at(part.art); } } @@ -167,28 +167,6 @@ bool CArtifactInstance::isScroll() const return artType->isScroll(); } -void CArtifactInstance::putAt(CArtifactSet & set, const ArtifactPosition slot) -{ - auto placementMap = set.putArtifact(slot, this); - addPlacementMap(placementMap); -} - -void CArtifactInstance::removeFrom(CArtifactSet & set, const ArtifactPosition slot) -{ - set.removeArtifact(slot); - for(auto & part : partsInfo) - { - if(part.slot != ArtifactPosition::PRE_FIRST) - part.slot = ArtifactPosition::PRE_FIRST; - } -} - -void CArtifactInstance::move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot) -{ - removeFrom(srcSet, srcSlot); - putAt(dstSet, dstSlot); -} - void CArtifactInstance::deserializationFix() { setType(artType); diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index 7c100a91f..f679f8b44 100644 --- a/lib/CArtifactInstance.h +++ b/lib/CArtifactInstance.h @@ -25,7 +25,7 @@ protected: public: struct PartInfo { - ConstTransitivePtr art; + CArtifactInstance * art; ArtifactPosition slot; template void serialize(Handler & h) { @@ -39,7 +39,7 @@ public: // Checks if supposed part inst is part of this combined art inst bool isPart(const CArtifactInstance * supposedPart) const; const std::vector & getPartsInfo() const; - void addPlacementMap(CArtifactSet::ArtPlacementMap & placementMap); + void addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap); template void serialize(Handler & h) { @@ -88,9 +88,6 @@ public: bool assumeDestRemoved = false) const; bool isCombined() const; bool isScroll() const; - void putAt(CArtifactSet & set, const ArtifactPosition slot); - void removeFrom(CArtifactSet & set, const ArtifactPosition slot); - void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot); void deserializationFix(); template void serialize(Handler & h) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index a9cc7b655..c2ff9e26c 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -122,7 +122,7 @@ public: virtual bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) = 0; virtual bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) = 0; - virtual bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble = std::nullopt) = 0; + virtual bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional askAssemble = std::nullopt) = 0; virtual void removeArtifact(const ArtifactLocation& al) = 0; virtual bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) = 0; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 31bd4e2de..13d361289 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1638,7 +1638,7 @@ bool CGameState::giveHeroArtifact(CGHeroInstance * h, const ArtifactID & aid) auto slot = ArtifactUtils::getArtAnyPosition(h, aid); if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot)) { - ai->putAt(*h, slot); + map->putArtifactInstance(*h, ai, slot); return true; } else diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 8705d2d6f..703f97667 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -147,7 +147,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr if (!locked && !takeable) { logGlobal->debug("Removing artifact %s from slot %d of hero %s", art->artType->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName()); - hero.hero->getArt(al.slot)->removeFrom(*hero.hero, al.slot); + gameState->map->removeArtifactInstance(*hero.hero, al.slot); return true; } return false; @@ -327,7 +327,7 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero) CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2)); const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId()); if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot)) - scroll->putAt(*hero, slot); + gameState->map->putArtifactInstance(*hero, scroll, slot); else logGlobal->error("Cannot give starting scroll - no free slots!"); break; @@ -423,7 +423,7 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO auto * artifact = donorHero->getArt(artLocation); logGlobal->debug("Removing artifact %s from slot %d of hero %s for transfer", artifact->artType->getJsonKey(), artLocation.getNum(), donorHero->getHeroTypeName()); - artifact->removeFrom(*donorHero, artLocation); + gameState->map->removeArtifactInstance(*donorHero, artLocation); if (receiver) { @@ -431,7 +431,7 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO const auto slot = ArtifactUtils::getArtAnyPosition(receiver, artifact->getTypeId()); if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot)) - artifact->putAt(*receiver, slot); + gameState->map->putArtifactInstance(*receiver, artifact, slot); else logGlobal->error("Cannot transfer artifact - no free slots!"); } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 8cf3871b0..0a7b84596 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1201,7 +1201,7 @@ void CGHeroInstance::removeSpellbook() if(hasSpellbook()) { - getArt(ArtifactPosition::SPELLBOOK)->removeFrom(*this, ArtifactPosition::SPELLBOOK); + //VLC->arth->removeArtifactFrom(*this, ArtifactPosition::SPELLBOOK); } } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index ab5fc3d50..9c01e64aa 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -772,9 +772,8 @@ void CGArtifact::initObj(vstd::RNG & rand) { if (!storedArtifact) { - auto * a = new CArtifactInstance(); - cb->gameState()->map->addNewArtifactInstance(a); - storedArtifact = a; + storedArtifact = ArtifactUtils::createArtifact(ArtifactID()); + cb->gameState()->map->addNewArtifactInstance(storedArtifact); } if(!storedArtifact->artType) storedArtifact->setType(getArtifact().toArtifact()); @@ -901,7 +900,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const void CGArtifact::pick(const CGHeroInstance * h) const { - if(cb->putArtifact(ArtifactLocation(h->id, ArtifactPosition::FIRST_AVAILABLE), storedArtifact)) + if(cb->putArtifact(ArtifactLocation(h->id, ArtifactPosition::FIRST_AVAILABLE), storedArtifact->getId())) cb->removeObject(this, h->getOwner()); } diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index ee0948ee5..77af471b0 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -552,6 +552,34 @@ void CMap::eraseArtifactInstance(CArtifactInstance * art) artInstances[art->getId().getNum()].dellNull(); } +void CMap::moveArtifactInstance( + CArtifactSet & srcSet, const ArtifactPosition & srcSlot, + CArtifactSet & dstSet, const ArtifactPosition & dstSlot) +{ + auto art = srcSet.getArt(srcSlot); + removeArtifactInstance(srcSet, srcSlot); + putArtifactInstance(dstSet, art, dstSlot); +} + +void CMap::putArtifactInstance(CArtifactSet & set, CArtifactInstance * art, const ArtifactPosition & slot) +{ + art->addPlacementMap(set.putArtifact(slot, art)); +} + +void CMap::removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & slot) +{ + auto art = set.getArt(slot); + assert(art); + set.removeArtifact(slot); + CArtifactSet::ArtPlacementMap partsMap; + for(auto & part : art->getPartsInfo()) + { + if(part.slot != ArtifactPosition::PRE_FIRST) + partsMap.try_emplace(part.art, ArtifactPosition::PRE_FIRST); + } + art->addPlacementMap(partsMap); +} + void CMap::addNewQuestInstance(CQuest* quest) { quest->qid = static_cast(quests.size()); diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 46a4fa1e0..847f81b06 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -110,6 +110,9 @@ public: void addNewArtifactInstance(CArtifactSet & artSet); void addNewArtifactInstance(ConstTransitivePtr art); void eraseArtifactInstance(CArtifactInstance * art); + void moveArtifactInstance(CArtifactSet & srcSet, const ArtifactPosition & srcSlot, CArtifactSet & dstSet, const ArtifactPosition & dstSlot); + void putArtifactInstance(CArtifactSet & set, CArtifactInstance * art, const ArtifactPosition & slot); + void removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & slot); void addNewQuestInstance(CQuest * quest); void removeQuestInstance(CQuest * quest); diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 32e0057ac..09eeda739 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -959,7 +959,7 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot) if(ArtifactID(artifactID).toArtifact()->canBePutAt(hero, ArtifactPosition(slot))) { auto * artifact = ArtifactUtils::createArtifact(artifactID); - artifact->putAt(*hero, ArtifactPosition(slot)); + map->putArtifactInstance(*hero, artifact, slot); map->addNewArtifactInstance(artifact); } else diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 43309a8c5..ae31fdec8 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1471,8 +1471,7 @@ void NewArtifact::applyGs(CGameState *gs) { auto art = ArtifactUtils::createArtifact(artId, spellId); gs->map->addNewArtifactInstance(art); - PutArtifact pa(ArtifactLocation(artHolder, pos), false); - pa.art = art; + PutArtifact pa(art->getId(), ArtifactLocation(artHolder, pos), false); pa.applyGs(gs); } @@ -1591,14 +1590,14 @@ void RebalanceStacks::applyGs(CGameState *gs) const auto dstHero = dynamic_cast(dst.army.get()); auto srcStack = const_cast(src.getStack()); auto dstStack = const_cast(dst.getStack()); - if(auto srcArt = srcStack->getArt(ArtifactPosition::CREATURE_SLOT)) + if(srcStack->getArt(ArtifactPosition::CREATURE_SLOT)) { if(auto dstArt = dstStack->getArt(ArtifactPosition::CREATURE_SLOT)) { auto dstSlot = ArtifactUtils::getArtBackpackPosition(srcHero, dstArt->getTypeId()); if(srcHero && dstSlot != ArtifactPosition::PRE_FIRST) { - dstArt->move(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot); + gs->map->moveArtifactInstance(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot); } //else - artifact can be lost :/ else @@ -1610,12 +1609,12 @@ void RebalanceStacks::applyGs(CGameState *gs) ea.applyGs(gs); logNetwork->warn("Cannot move artifact! No free slots"); } - srcArt->move(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT); + gs->map->moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT); //TODO: choose from dialog } else //just move to the other slot before stack gets erased { - srcArt->move(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT); + gs->map->moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT); } } if (stackExp) @@ -1685,14 +1684,13 @@ void BulkSmartRebalanceStacks::applyGs(CGameState *gs) void PutArtifact::applyGs(CGameState *gs) { - // Ensure that artifact has been correctly added via NewArtifact pack - assert(vstd::contains(gs->map->artInstances, art)); + auto art = gs->getArtInstance(id); assert(!art->getParentNodes().empty()); auto hero = gs->getHero(al.artHolder); assert(hero); assert(art && art->canBePutAt(hero, al.slot)); assert(ArtifactUtils::checkIfSlotValid(*hero, al.slot)); - art->putAt(*hero, al.slot); + gs->map->putArtifactInstance(*hero, art, al.slot); } void BulkEraseArtifacts::applyGs(CGameState *gs) @@ -1731,15 +1729,13 @@ void BulkEraseArtifacts::applyGs(CGameState *gs) { logGlobal->debug("Erasing artifact %s", slotInfo->artifact->artType->getNameTranslated()); } - auto art = artSet->getArt(slot); - assert(art); - art->removeFrom(*artSet, slot); + gs->map->removeArtifactInstance(*artSet, slot); } } void BulkMoveArtifacts::applyGs(CGameState *gs) { - const auto bulkArtsRemove = [](std::vector & artsPack, CArtifactSet & artSet) + const auto bulkArtsRemove = [gs](std::vector & artsPack, CArtifactSet & artSet) { std::vector packToRemove; for(const auto & slotsPair : artsPack) @@ -1750,20 +1746,16 @@ void BulkMoveArtifacts::applyGs(CGameState *gs) }); for(const auto & slot : packToRemove) - { - auto * art = artSet.getArt(slot); - assert(art); - art->removeFrom(artSet, slot); - } + gs->map->removeArtifactInstance(artSet, slot); }; - const auto bulkArtsPut = [](std::vector & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet) + const auto bulkArtsPut = [gs](std::vector & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet) { for(const auto & slotsPair : artsPack) { auto * art = initArtSet.getArt(slotsPair.srcPos); assert(art); - art->putAt(dstArtSet, slotsPair.dstPos); + gs->map->putArtifactInstance(dstArtSet, art, slotsPair.dstPos); } }; @@ -1840,7 +1832,7 @@ void AssembledArtifact::applyGs(CGameState *gs) for(const auto slot : slotsInvolved) { const auto constituentInstance = hero->getArt(slot); - constituentInstance->removeFrom(*hero, slot); + gs->map->removeArtifactInstance(*hero, slot); if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot) combinedArt->addPart(constituentInstance, slot); @@ -1849,7 +1841,7 @@ void AssembledArtifact::applyGs(CGameState *gs) } // Put new combined artifacts - combinedArt->putAt(*hero, al.slot); + gs->map->putArtifactInstance(*hero, combinedArt, al.slot); } void DisassembledArtifact::applyGs(CGameState *gs) @@ -1859,14 +1851,14 @@ void DisassembledArtifact::applyGs(CGameState *gs) auto disassembledArt = hero->getArt(al.slot); assert(disassembledArt); - auto parts = disassembledArt->getPartsInfo(); - disassembledArt->removeFrom(*hero, al.slot); + const auto parts = disassembledArt->getPartsInfo(); + gs->map->removeArtifactInstance(*hero, al.slot); for(auto & part : parts) { // ArtifactPosition::PRE_FIRST is value of main part slot -> it'll replace combined artifact in its pos auto slot = (ArtifactUtils::isSlotEquipment(part.slot) ? part.slot : al.slot); disassembledArt->detachFrom(*part.art); - part.art->putAt(*hero, slot); + gs->map->putArtifactInstance(*hero, part.art, slot); } gs->map->eraseArtifactInstance(disassembledArt); } diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 5a81ec806..fabc5a36a 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -965,14 +965,14 @@ struct DLL_LINKAGE CArtifactOperationPack : CPackForClient struct DLL_LINKAGE PutArtifact : CArtifactOperationPack { PutArtifact() = default; - explicit PutArtifact(const ArtifactLocation & dst, bool askAssemble = true) - : al(dst), askAssemble(askAssemble) + explicit PutArtifact(const ArtifactInstanceID & id, const ArtifactLocation & dst, bool askAssemble = true) + : id(id), al(dst), askAssemble(askAssemble) { } ArtifactLocation al; bool askAssemble; - ConstTransitivePtr art; + ArtifactInstanceID id; void applyGs(CGameState * gs) override; void visitTyped(ICPackVisitor & visitor) override; @@ -981,7 +981,7 @@ struct DLL_LINKAGE PutArtifact : CArtifactOperationPack { h & al; h & askAssemble; - h & art; + h & id; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 35dde1615..8579260b3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3757,9 +3757,10 @@ bool CGameHandler::swapStacks(const StackLocation & sl1, const StackLocation & s } } -bool CGameHandler::putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) +bool CGameHandler::putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional askAssemble) { - assert(art && art->artType); + const auto artInst = getArtInstance(id); + assert(artInst && artInst->artType); ArtifactLocation dst(al.artHolder, ArtifactPosition::PRE_FIRST); dst.creature = al.creature; auto putTo = getArtSet(al); @@ -3767,11 +3768,11 @@ bool CGameHandler::putArtifact(const ArtifactLocation & al, const CArtifactInsta if(al.slot == ArtifactPosition::FIRST_AVAILABLE) { - dst.slot = ArtifactUtils::getArtAnyPosition(putTo, art->getTypeId()); + dst.slot = ArtifactUtils::getArtAnyPosition(putTo, artInst->getTypeId()); } else if(ArtifactUtils::isSlotBackpack(al.slot) && !al.creature.has_value()) { - dst.slot = ArtifactUtils::getArtBackpackPosition(putTo, art->getTypeId()); + dst.slot = ArtifactUtils::getArtBackpackPosition(putTo, artInst->getTypeId()); } else { @@ -3786,10 +3787,9 @@ bool CGameHandler::putArtifact(const ArtifactLocation & al, const CArtifactInsta askAssemble = false; } - if(art->canBePutAt(putTo, dst.slot)) + if(artInst->canBePutAt(putTo, dst.slot)) { - PutArtifact pa(dst, askAssemble.value()); - pa.art = art; + PutArtifact pa(id, dst, askAssemble.value()); sendAndApply(&pa); return true; } diff --git a/server/CGameHandler.h b/server/CGameHandler.h index cdb194377..e2aa8c4f6 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -136,7 +136,7 @@ public: bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, const SpellID & spellId, const ArtifactPosition & pos); bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override; bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override; - bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) override; + bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional askAssemble) override; void removeArtifact(const ArtifactLocation &al) override; void removeArtifact(const ObjectInstanceID & srcId, const std::vector & slotsPack); bool moveArtifact(const PlayerColor & player, const ArtifactLocation & src, const ArtifactLocation & dst) override; diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 1f2456a2f..c91118739 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -72,7 +72,7 @@ public: bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override {return false;} bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override {return false;} - bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional askAssemble) override {return false;} + bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional askAssemble) override {return false;} void removeArtifact(const ArtifactLocation &al) override {} bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;} From 586a32a6162d15ada7afc273c087aba8ef1a9c90 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:51:59 +0300 Subject: [PATCH 162/726] CArtifactSet cleanup --- client/battle/BattleWindow.cpp | 2 +- client/widgets/CArtifactsOfHeroBase.cpp | 2 +- lib/ArtifactUtils.cpp | 2 +- lib/CArtHandler.cpp | 162 +++++++++--------------- lib/CArtHandler.h | 37 ++---- lib/CCreatureSet.cpp | 4 +- lib/CCreatureSet.h | 4 +- lib/mapObjects/CGHeroInstance.cpp | 6 +- lib/mapObjects/CGHeroInstance.h | 4 +- lib/mapObjects/CQuest.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 2 +- lib/networkPacks/PacksForClient.h | 2 +- lib/rewardable/Limiter.cpp | 24 +++- server/CGameHandler.cpp | 8 +- 14 files changed, 112 insertions(+), 149 deletions(-) diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index d6e162486..25b1b2eb3 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -743,7 +743,7 @@ void BattleWindow::bSpellf() const auto artID = blockingBonus->sid.as(); //If we have artifact, put name of our hero. Otherwise assume it's the enemy. //TODO check who *really* is source of bonus - std::string heroName = myHero->hasArt(artID) ? myHero->getNameTranslated() : owner.enemyHero().name; + std::string heroName = myHero->hasArt(artID, true) ? myHero->getNameTranslated() : owner.enemyHero().name; //%s wields the %s, an ancient artifact which creates a p dead to all magic. LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683]) diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 70f277b8e..8318fdf89 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -271,7 +271,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit arts.try_emplace(combinedArt->getId(), std::vector{}); for(const auto part : combinedArt->getConstituents()) { - if(curHero->hasArt(part->getId(), false, false, false)) + if(curHero->hasArt(part->getId(), false, false)) arts.at(combinedArt->getId()).emplace_back(part->getId()); } } diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index dfc9fca20..7c95e435a 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -209,7 +209,7 @@ DLL_LINKAGE std::vector ArtifactUtils::assemblyPossibilities( for(const auto constituent : artifact->getConstituents()) //check if all constituents are available { - if(!artSet->hasArt(constituent->getId(), onlyEquiped, false, false)) + if(!artSet->hasArt(constituent->getId(), onlyEquiped, false)) { possible = false; break; diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 4f944eb70..5b3f82a0d 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -220,7 +220,7 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b auto possibleSlot = ArtifactUtils::getArtAnyPosition(&fittingSet, art->getId()); if(ArtifactUtils::isSlotEquipment(possibleSlot)) { - fittingSet.setNewArtSlot(possibleSlot, nullptr, true); + fittingSet.lockSlot(possibleSlot); } else { @@ -691,9 +691,7 @@ void CArtHandler::afterLoadFinalization() CBonusSystemNode::treeHasChanged(); } -CArtifactSet::~CArtifactSet() = default; - -const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const +CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const { if(const ArtSlotInfo * si = getSlot(pos)) { @@ -704,56 +702,34 @@ const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, boo return nullptr; } -CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) -{ - return const_cast((const_cast(this))->getArt(pos, excludeLocked)); -} - ArtifactPosition CArtifactSet::getArtPos(const ArtifactID & aid, bool onlyWorn, bool allowLocked) const { - const auto result = getAllArtPositions(aid, onlyWorn, allowLocked, false); - return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0]; -} - -std::vector CArtifactSet::getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const -{ - std::vector result; - for(const auto & slotInfo : artifactsWorn) - if(slotInfo.second.artifact->getTypeId() == aid && (allowLocked || !slotInfo.second.locked)) - result.push_back(slotInfo.first); - - if(onlyWorn) - return result; - if(!getAll && !result.empty()) - return result; - - auto backpackPositions = getBackpackArtPositions(aid); - result.insert(result.end(), backpackPositions.begin(), backpackPositions.end()); - return result; -} - -std::vector CArtifactSet::getBackpackArtPositions(const ArtifactID & aid) const -{ - std::vector result; - - si32 backpackPosition = ArtifactPosition::BACKPACK_START; - for(const auto & artInfo : artifactsInBackpack) + for(const auto & [slot, slotInfo] : artifactsWorn) { - const auto * art = artInfo.getArt(); - if(art && art->artType->getId() == aid) - result.emplace_back(backpackPosition); - backpackPosition++; + if(slotInfo.artifact->getTypeId() == aid && (allowLocked || !slotInfo.locked)) + return slot; } - return result; + if(!onlyWorn) + { + size_t backpackPositionIdx = ArtifactPosition::BACKPACK_START; + for(const auto & artInfo : artifactsInBackpack) + { + const auto art = artInfo.getArt(); + if(art && art->artType->getId() == aid) + return ArtifactPosition(backpackPositionIdx); + backpackPositionIdx++; + } + } + return ArtifactPosition::PRE_FIRST; } const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanceID & artInstId) const { - for(auto i : artifactsWorn) + for(const auto & i : artifactsWorn) if(i.second.artifact->getId() == artInstId) return i.second.artifact; - for(auto i : artifactsInBackpack) + for(const auto & i : artifactsInBackpack) if(i.artifact->getId() == artInstId) return i.artifact; @@ -779,29 +755,16 @@ ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance * artInst) cons return ArtifactPosition::PRE_FIRST; } -bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const +bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchCombinedParts) const { - return getArtPosCount(aid, onlyWorn, searchBackpackAssemblies, allowLocked) > 0; + if(searchCombinedParts && getCombinedArtWithPart(aid)) + return true; + if(getArtPos(aid, onlyWorn, searchCombinedParts) != ArtifactPosition::PRE_FIRST) + return true; + return false; } -bool CArtifactSet::hasArtBackpack(const ArtifactID & aid) const -{ - return !getBackpackArtPositions(aid).empty(); -} - -unsigned CArtifactSet::getArtPosCount(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const -{ - const auto allPositions = getAllArtPositions(aid, onlyWorn, allowLocked, true); - if(!allPositions.empty()) - return allPositions.size(); - - if(searchBackpackAssemblies && getHiddenArt(aid)) - return 1; - - return 0; -} - -CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(ArtifactPosition slot, CArtifactInstance * art) +CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & slot, CArtifactInstance * art) { ArtPlacementMap resArtPlacement; @@ -827,19 +790,38 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(ArtifactPosition slot, C assert(ArtifactUtils::isSlotEquipment(partSlot)); setNewArtSlot(partSlot, part.art, true); - resArtPlacement.emplace(std::make_pair(part.art, partSlot)); + resArtPlacement.emplace(part.art, partSlot); } else { - resArtPlacement.emplace(std::make_pair(part.art, part.slot)); + resArtPlacement.emplace(part.art, part.slot); } } } return resArtPlacement; } -void CArtifactSet::removeArtifact(ArtifactPosition slot) +void CArtifactSet::removeArtifact(const ArtifactPosition & slot) { + const auto eraseArtSlot = [this](const ArtifactPosition & slotForErase) + { + if(slotForErase == ArtifactPosition::TRANSITION_POS) + { + artifactsTransitionPos.artifact = nullptr; + } + else if(ArtifactUtils::isSlotBackpack(slotForErase)) + { + auto backpackSlot = ArtifactPosition(slotForErase - ArtifactPosition::BACKPACK_START); + + assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end()); + artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot); + } + else + { + artifactsWorn.erase(slotForErase); + } + }; + if(const auto art = getArt(slot, false)) { if(art->isCombined()) @@ -858,7 +840,7 @@ void CArtifactSet::removeArtifact(ArtifactPosition slot) } } -std::pair CArtifactSet::searchForConstituent(const ArtifactID & aid) const +const CArtifactInstance * CArtifactSet::getCombinedArtWithPart(const ArtifactID & partId) const { for(const auto & slot : artifactsInBackpack) { @@ -867,24 +849,12 @@ std::pair CArtifactSet::se { for(auto & ci : art->getPartsInfo()) { - if(ci.art->getTypeId() == aid) - { - return {art, ci.art}; - } + if(ci.art->getTypeId() == partId) + return art; } } } - return {nullptr, nullptr}; -} - -const CArtifactInstance * CArtifactSet::getHiddenArt(const ArtifactID & aid) const -{ - return searchForConstituent(aid).second; -} - -const CArtifactInstance * CArtifactSet::getAssemblyByConstituent(const ArtifactID & aid) const -{ - return searchForConstituent(aid).first; + return nullptr; } const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const @@ -905,6 +875,11 @@ const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const return nullptr; } +void CArtifactSet::lockSlot(const ArtifactPosition & pos) +{ + setNewArtSlot(pos, nullptr, true); +} + bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck) const { if(bearerType() == ArtBearer::ALTAR) @@ -916,7 +891,7 @@ bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockChe return true; //no slot means not used } -void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, ConstTransitivePtr art, bool locked) +void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked) { assert(!vstd::contains(artifactsWorn, slot)); @@ -932,31 +907,12 @@ void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, ConstTransitiveP else { auto position = artifactsInBackpack.begin() + slot - ArtifactPosition::BACKPACK_START; - slotInfo = &(*artifactsInBackpack.emplace(position, ArtSlotInfo())); + slotInfo = &(*artifactsInBackpack.emplace(position)); } slotInfo->artifact = art; slotInfo->locked = locked; } -void CArtifactSet::eraseArtSlot(const ArtifactPosition & slot) -{ - if(slot == ArtifactPosition::TRANSITION_POS) - { - artifactsTransitionPos.artifact = nullptr; - } - else if(ArtifactUtils::isSlotBackpack(slot)) - { - auto backpackSlot = ArtifactPosition(slot - ArtifactPosition::BACKPACK_START); - - assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end()); - artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot); - } - else - { - artifactsWorn.erase(slot); - } -} - void CArtifactSet::artDeserializationFix(CBonusSystemNode *node) { for(auto & elem : artifactsWorn) diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 77b7d1d1f..e82f43147 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -175,10 +175,10 @@ private: struct DLL_LINKAGE ArtSlotInfo { - ConstTransitivePtr artifact; - ui8 locked; //if locked, then artifact points to the combined artifact + CArtifactInstance * artifact; + bool locked; //if locked, then artifact points to the combined artifact - ArtSlotInfo() : locked(false) {} + ArtSlotInfo() : artifact(nullptr), locked(false) {} const CArtifactInstance * getArt() const; template void serialize(Handler & h) @@ -197,32 +197,20 @@ public: std::map artifactsWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 ArtSlotInfo artifactsTransitionPos; // Used as transition position for dragAndDrop artifact exchange - void setNewArtSlot(const ArtifactPosition & slot, ConstTransitivePtr art, bool locked); - void eraseArtSlot(const ArtifactPosition & slot); - const ArtSlotInfo * getSlot(const ArtifactPosition & pos) const; - const CArtifactInstance * getArt(const ArtifactPosition & pos, bool excludeLocked = true) const; //nullptr - no artifact - CArtifactInstance * getArt(const ArtifactPosition & pos, bool excludeLocked = true); //nullptr - no artifact - /// Looks for equipped artifact with given ID and returns its slot ID or -1 if none - /// (if more than one such artifact lower ID is returned) + void lockSlot(const ArtifactPosition & pos); + CArtifactInstance * getArt(const ArtifactPosition & pos, bool excludeLocked = true) const; + /// Looks for first artifact with given ID ArtifactPosition getArtPos(const ArtifactID & aid, bool onlyWorn = true, bool allowLocked = true) const; ArtifactPosition getArtPos(const CArtifactInstance * art) const; - std::vector getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const; - std::vector getBackpackArtPositions(const ArtifactID & aid) const; const CArtifactInstance * getArtByInstanceId(const ArtifactInstanceID & artInstId) const; - /// Search for constituents of assemblies in backpack which do not have an ArtifactPosition - const CArtifactInstance * getHiddenArt(const ArtifactID & aid) const; - const CArtifactInstance * getAssemblyByConstituent(const ArtifactID & aid) const; - /// Checks if hero possess artifact of given id (either in backack or worn) - bool hasArt(const ArtifactID & aid, bool onlyWorn = false, bool searchBackpackAssemblies = false, bool allowLocked = true) const; - bool hasArtBackpack(const ArtifactID & aid) const; + bool hasArt(const ArtifactID & aid, bool onlyWorn = false, bool searchCombinedParts = false) const; bool isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck = false) const; - unsigned getArtPosCount(const ArtifactID & aid, bool onlyWorn = true, bool searchBackpackAssemblies = true, bool allowLocked = true) const; virtual ArtBearer::ArtBearer bearerType() const = 0; - virtual ArtPlacementMap putArtifact(ArtifactPosition slot, CArtifactInstance * art); - virtual void removeArtifact(ArtifactPosition slot); - virtual ~CArtifactSet(); + virtual ArtPlacementMap putArtifact(const ArtifactPosition & slot, CArtifactInstance * art); + virtual void removeArtifact(const ArtifactPosition & slot); + virtual ~CArtifactSet() = default; template void serialize(Handler &h) { @@ -233,10 +221,11 @@ public: void artDeserializationFix(CBonusSystemNode *node); void serializeJsonArtifacts(JsonSerializeFormat & handler, const std::string & fieldName); -protected: - std::pair searchForConstituent(const ArtifactID & aid) const; + const CArtifactInstance * getCombinedArtWithPart(const ArtifactID & partId) const; private: + void setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked); + void serializeJsonHero(JsonSerializeFormat & handler); void serializeJsonCreature(JsonSerializeFormat & handler); void serializeJsonCommander(JsonSerializeFormat & handler); diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 9f74f35e5..fd1bfdc01 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -863,7 +863,7 @@ ArtBearer::ArtBearer CStackInstance::bearerType() const return ArtBearer::CREATURE; } -CStackInstance::ArtPlacementMap CStackInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art) +CStackInstance::ArtPlacementMap CStackInstance::putArtifact(const ArtifactPosition & pos, CArtifactInstance * art) { assert(!getArt(pos)); assert(art->canBePutAt(this, pos)); @@ -872,7 +872,7 @@ CStackInstance::ArtPlacementMap CStackInstance::putArtifact(ArtifactPosition pos return CArtifactSet::putArtifact(pos, art); } -void CStackInstance::removeArtifact(ArtifactPosition pos) +void CStackInstance::removeArtifact(const ArtifactPosition & pos) { assert(getArt(pos)); diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index df7ab8dd4..d20fee553 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -126,8 +126,8 @@ public: void setArmyObj(const CArmedInstance *ArmyObj); virtual void giveStackExp(TExpType exp); bool valid(bool allowUnrandomized) const; - ArtPlacementMap putArtifact(ArtifactPosition pos, CArtifactInstance * art) override;//from CArtifactSet - void removeArtifact(ArtifactPosition pos) override; + ArtPlacementMap putArtifact(const ArtifactPosition & pos, CArtifactInstance * art) override;//from CArtifactSet + void removeArtifact(const ArtifactPosition & pos) override; ArtBearer::ArtBearer bearerType() const override; //from CArtifactSet std::string nodeName() const override; //from CBonusSystemnode void deserializationFix(); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 0a7b84596..ce5653743 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1156,7 +1156,7 @@ std::string CGHeroInstance::getBiographyTextID() const return ""; //for random hero } -CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art) +CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(const ArtifactPosition & pos, CArtifactInstance * art) { assert(art->canBePutAt(this, pos)); @@ -1165,7 +1165,7 @@ CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(ArtifactPosition pos return CArtifactSet::putArtifact(pos, art); } -void CGHeroInstance::removeArtifact(ArtifactPosition pos) +void CGHeroInstance::removeArtifact(const ArtifactPosition & pos) { auto art = getArt(pos); assert(art); @@ -1201,7 +1201,7 @@ void CGHeroInstance::removeSpellbook() if(hasSpellbook()) { - //VLC->arth->removeArtifactFrom(*this, ArtifactPosition::SPELLBOOK); + cb->removeArtifact(ArtifactLocation(this->id, ArtifactPosition::SPELLBOOK)); } } diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index a4dffa8af..d07885e70 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -241,8 +241,8 @@ public: void initHero(vstd::RNG & rand); void initHero(vstd::RNG & rand, const HeroTypeID & SUBID); - ArtPlacementMap putArtifact(ArtifactPosition pos, CArtifactInstance * art) override; - void removeArtifact(ArtifactPosition pos) override; + ArtPlacementMap putArtifact(const ArtifactPosition & pos, CArtifactInstance * art) override; + void removeArtifact(const ArtifactPosition & pos) override; void initExp(vstd::RNG & rand); void initArmy(vstd::RNG & rand, IArmyDescriptor *dst = nullptr); void pushPrimSkill(PrimarySkill which, int val); diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 15a9153e8..1cdfa40e3 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -152,7 +152,7 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const } else { - const auto * assembly = h->getAssemblyByConstituent(elem); + const auto * assembly = h->getCombinedArtWithPart(elem); assert(assembly); auto parts = assembly->getPartsInfo(); diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 09eeda739..14575a5a1 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -917,7 +917,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) hero->artifactsInBackpack.clear(); while(!hero->artifactsWorn.empty()) - hero->eraseArtSlot(hero->artifactsWorn.begin()->first); + hero->removeArtifact(hero->artifactsWorn.begin()->first); } for(int i = 0; i < features.artifactSlotsCount; i++) diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index fabc5a36a..280778d3d 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -966,7 +966,7 @@ struct DLL_LINKAGE PutArtifact : CArtifactOperationPack { PutArtifact() = default; explicit PutArtifact(const ArtifactInstanceID & id, const ArtifactLocation & dst, bool askAssemble = true) - : id(id), al(dst), askAssemble(askAssemble) + : al(dst), askAssemble(askAssemble), id(id) { } diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index a5b522e4b..563db49e1 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -143,10 +143,28 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const for(const auto & elem : artifactsRequirements) { // check required amount of artifacts - if(hero->getArtPosCount(elem.first, false, true, true) < elem.second) + size_t artCnt = 0; + for(const auto & [slot, slotInfo] : hero->artifactsWorn) + if(slotInfo.artifact->getTypeId() == elem.first) + artCnt++; + + for(auto & slotInfo : hero->artifactsInBackpack) + if(slotInfo.artifact->getTypeId() == elem.first) + { + artCnt++; + } + else if(slotInfo.artifact->isCombined()) + { + for(const auto & partInfo : slotInfo.artifact->getPartsInfo()) + if(partInfo.art->getTypeId() == elem.first) + artCnt++; + } + + if(artCnt < elem.second) return false; - if(!hero->hasArt(elem.first)) - reqSlots += hero->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2; + // Check if art has no own slot. (As part of combined in backpack) + if(hero->getArtPos(elem.first, false) == ArtifactPosition::PRE_FIRST) + reqSlots += hero->getCombinedArtWithPart(elem.first)->getPartsInfo().size() - 2; } if(!ArtifactUtils::isBackpackFreeSlots(hero, reqSlots)) return false; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 8579260b3..ca0159e55 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2718,15 +2718,15 @@ bool CGameHandler::switchArtifactsCostume(const PlayerColor & player, const Obje // Second, find the necessary artifacts for the costume for(const auto & artPos : costumeArtMap) { - if(const auto availableArts = artFittingSet.getAllArtPositions(artPos.second, false, false, false); !availableArts.empty()) + if(const auto slot = artFittingSet.getArtPos(artPos.second, false, false); slot != ArtifactPosition::PRE_FIRST) { bma.artsPack0.emplace_back(BulkMoveArtifacts::LinkedSlots { - artSet->getArtPos(artFittingSet.getArt(availableArts.front())), + artSet->getArtPos(artFittingSet.getArt(slot)), artPos.first }); - artFittingSet.removeArtifact(availableArts.front()); - if(ArtifactUtils::isSlotBackpack(availableArts.front())) + artFittingSet.removeArtifact(slot); + if(ArtifactUtils::isSlotBackpack(slot)) estimateBackpackSize--; } } From 162e2ab22ed80e7c9cc11f866d28b3d4e8c32b57 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 1 Aug 2024 12:13:25 +0200 Subject: [PATCH 163/726] Additional Wooden Hill Fort message More verbose info-message for unavailable upgrades (creature levels 5 and higher). Some minor refactors. Added other statusbar message if it's standard Hill Fort --- Mods/vcmi/config/vcmi/english.json | 3 ++ Mods/vcmi/config/vcmi/polish.json | 3 ++ client/windows/GUIClasses.cpp | 80 ++++++++++++++++++++---------- client/windows/GUIClasses.h | 10 ++-- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..47f663564 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -283,6 +283,9 @@ "vcmi.adventureMap.revisitObject.hover" : "Revisit Object", "vcmi.adventureMap.revisitObject.help" : "{Revisit Object}\n\nIf a hero currently stands on a Map Object, he can revisit the location.", + "vcmi.adventureMap.miniHillFort.notAvailableUpgrade.message" : "This building cannot upgrade creatures of level 5 and higher.", + "vcmi.adventureMap.miniHillFort.statusBar.info" : "Upgrade creatures levels 1 - 5 at double the normal price", + "vcmi.adventureMap.HillFort.statusBar.info" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town", "vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately", "vcmi.battleWindow.damageEstimation.melee" : "Attack %CREATURE (%DAMAGE).", diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 2f8d0ce87..8d5d8a353 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -280,6 +280,9 @@ "vcmi.adventureMap.revisitObject.hover" : "Odwiedź obiekt ponownie", "vcmi.adventureMap.revisitObject.help" : "{Odwiedź obiekt ponownie}\n\nJeżeli bohater aktualnie stoi na polu odwiedzającym obiekt za pomocą tego przycisku może go odwiedzić ponownie.", + "vcmi.adventureMap.miniHillFort.notAvailableUpgrade.message" : "Ten budynek nie ma możliwości ulepszenia jednostek poziomu 5 i wyższych.", + "vcmi.adventureMap.miniHillFort.statusBar.info" : "Ulepsza jednostki poziomu 1 - 5 za podwójną cenę", + "vcmi.adventureMap.HillFort.statusBar.info" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 są bardziej korzystne niż w mieście", "vcmi.battleWindow.pressKeyToSkipIntro" : "Naciśnij dowolny klawisz by rozpocząć bitwę natychmiastowo", "vcmi.battleWindow.damageEstimation.melee" : "Atakuj %CREATURE (%DAMAGE).", diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index ea9e7a77f..a7652847f 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1129,6 +1129,12 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI statusbar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); garr = std::make_shared(Point(108, 60), 18, Point(), hero, nullptr); + + if(object->typeName == "miniHillFort") + statusbar->write(VLC->generaltexth->translate("vcmi.adventureMap.miniHillFort.statusBar.info")); + else if(object->typeName == "hillFort") + statusbar->write(VLC->generaltexth->translate("vcmi.adventureMap.HillFort.statusBar.info")); + updateGarrisons(); } @@ -1146,11 +1152,25 @@ void CHillFortWindow::updateGarrisons() TResources totalSum; // totalSum[resource ID] = value + auto getImgIdx = [](CHillFortWindow::State st) -> std::size_t + { + switch (st) + { + case State::EMPTY: + return 0; + case State::UNAVAILABLE: + case State::ALREADY_UPGRADED: + return 1; + default: + return static_cast(st); + } + }; + for(int i=0; icb->fillUpgradeInfo(hero, SlotID(i), info); @@ -1162,29 +1182,29 @@ void CHillFortWindow::updateGarrisons() } currState[i] = newState; - upgrade[i]->setImage(AnimationPath::builtin(currState[i] == -1 ? slotImages[0] : slotImages[currState[i]])); - upgrade[i]->block(currState[i] == -1); + upgrade[i]->setImage(AnimationPath::builtin(slotImages[getImgIdx(currState[i])])); + upgrade[i]->block(currState[i] == State::EMPTY); upgrade[i]->addHoverText(EButtonState::NORMAL, getTextForSlot(SlotID(i))); } //"Upgrade all" slot - int newState = 2; + State newState = State::MAKE_UPGRADE; { TResources myRes = LOCPLINT->cb->getResourceAmount(); bool allUpgraded = true;//All creatures are upgraded? for(int i=0; isetImage(AnimationPath::builtin(allImages[newState])); + upgradeAll->setImage(AnimationPath::builtin(allImages[static_cast(newState)])); garr->recreateSlots(); @@ -1197,7 +1217,7 @@ void CHillFortWindow::updateGarrisons() slotLabels[i][j]->setText(""); } //if can upgrade or can not afford, draw cost - if(currState[i] == 0 || currState[i] == 2) + if(currState[i] == State::UNAFORDABLE || currState[i] == State::MAKE_UPGRADE) { if(costs[i].nonZero()) { @@ -1246,16 +1266,20 @@ void CHillFortWindow::makeDeal(SlotID slot) int offset = (slot.getNum() == slotsCount)?2:0; switch(currState[slot.getNum()]) { - case 0: - LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector>(), soundBase::sound_todo); - break; - case 1: + case State::ALREADY_UPGRADED: LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[313 + offset], std::vector>(), soundBase::sound_todo); break; - case 2: - for(int i=0; ishowInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector>(), soundBase::sound_todo); + break; + case State::UNAVAILABLE: + LOCPLINT->showInfoDialog(VLC->generaltexth->translate("vcmi.adventureMap.miniHillFort.notAvailableUpgrade.message"), + std::vector>(), soundBase::sound_todo); + break; + case State::MAKE_UPGRADE: + for(int i = 0; i < slotsCount; i++) { - if(slot.getNum() ==i || ( slot.getNum() == slotsCount && currState[i] == 2 ))//this is activated slot or "upgrade all" + if(slot.getNum() == i || ( slot.getNum() == slotsCount && currState[i] == State::MAKE_UPGRADE ))//this is activated slot or "upgrade all" { UpgradeInfo info; LOCPLINT->cb->fillUpgradeInfo(hero, SlotID(i), info); @@ -1281,22 +1305,28 @@ std::string CHillFortWindow::getTextForSlot(SlotID slot) return str; } -int CHillFortWindow::getState(SlotID slot) +CHillFortWindow::State CHillFortWindow::getState(SlotID slot) { TResources myRes = LOCPLINT->cb->getResourceAmount(); - if(hero->slotEmpty(slot))//no creature here - return -1; + if(hero->slotEmpty(slot)) + return State::EMPTY; UpgradeInfo info; LOCPLINT->cb->fillUpgradeInfo(hero, slot, info); - if(!info.newID.size())//already upgraded - return 1; + if (!info.newID.size()) + { + // new Hill Fort allows upgrades level 5 and below + if (hero->getStack(slot).type->getLevel() >= 5 && hero->getCreature(slot)->hasUpgrades()) + return State::UNAVAILABLE; + + return State::ALREADY_UPGRADED; + } if(!(info.cost[0] * hero->getStackCount(slot)).canBeAfforded(myRes)) - return 0; + return State::UNAFORDABLE; - return 2;//can upgrade + return State::MAKE_UPGRADE; } CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner): diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 4c7b68a75..a652cf551 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -444,9 +444,11 @@ public: class CHillFortWindow : public CStatusbarWindow, public IGarrisonHolder { private: - static const int slotsCount = 7; + + enum class State { UNAFORDABLE, ALREADY_UPGRADED, MAKE_UPGRADE, EMPTY, UNAVAILABLE }; + static constexpr std::size_t slotsCount = 7; //todo: mithril support - static const int resCount = 7; + static constexpr std::size_t resCount = 7; const CGObjectInstance * fort; const CGHeroInstance * hero; @@ -458,7 +460,7 @@ private: std::array, resCount> totalLabels; std::array, slotsCount> upgrade;//upgrade single creature - std::array currState;//current state of slot - to avoid calls to getState or updating buttons + std::array currState;//current state of slot - to avoid calls to getState or updating buttons //there is a place for only 2 resources per slot std::array< std::array, 2>, slotsCount> slotIcons; @@ -473,7 +475,7 @@ private: std::string getTextForSlot(SlotID slot); void makeDeal(SlotID slot);//-1 for upgrading all creatures - int getState(SlotID slot); //-1 = no creature 0=can't upgrade, 1=upgraded, 2=can upgrade + State getState(SlotID slot); public: CHillFortWindow(const CGHeroInstance * visitor, const CGObjectInstance * object); void updateGarrisons() override;//update buttons after garrison changes From 5c6abb30c68135e9dc9c8f1e373941e5a198e1ce Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 1 Aug 2024 14:34:58 +0200 Subject: [PATCH 164/726] Fix typo --- client/windows/GUIClasses.cpp | 8 ++++---- client/windows/GUIClasses.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index a7652847f..f2361e759 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1200,7 +1200,7 @@ void CHillFortWindow::updateGarrisons() newState = State::ALREADY_UPGRADED; if(!totalSum.canBeAfforded(myRes)) - newState = State::UNAFORDABLE; + newState = State::UNAFFORDABLE; } currState[slotsCount] = newState; @@ -1217,7 +1217,7 @@ void CHillFortWindow::updateGarrisons() slotLabels[i][j]->setText(""); } //if can upgrade or can not afford, draw cost - if(currState[i] == State::UNAFORDABLE || currState[i] == State::MAKE_UPGRADE) + if(currState[i] == State::UNAFFORDABLE || currState[i] == State::MAKE_UPGRADE) { if(costs[i].nonZero()) { @@ -1269,7 +1269,7 @@ void CHillFortWindow::makeDeal(SlotID slot) case State::ALREADY_UPGRADED: LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[313 + offset], std::vector>(), soundBase::sound_todo); break; - case State::UNAFORDABLE: + case State::UNAFFORDABLE: LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector>(), soundBase::sound_todo); break; case State::UNAVAILABLE: @@ -1324,7 +1324,7 @@ CHillFortWindow::State CHillFortWindow::getState(SlotID slot) } if(!(info.cost[0] * hero->getStackCount(slot)).canBeAfforded(myRes)) - return State::UNAFORDABLE; + return State::UNAFFORDABLE; return State::MAKE_UPGRADE; } diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index a652cf551..70359bc7f 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -445,7 +445,7 @@ class CHillFortWindow : public CStatusbarWindow, public IGarrisonHolder { private: - enum class State { UNAFORDABLE, ALREADY_UPGRADED, MAKE_UPGRADE, EMPTY, UNAVAILABLE }; + enum class State { UNAFFORDABLE, ALREADY_UPGRADED, MAKE_UPGRADE, EMPTY, UNAVAILABLE }; static constexpr std::size_t slotsCount = 7; //todo: mithril support static constexpr std::size_t resCount = 7; From 303b0a6552e4f50c0f866ddb0334954939fcf2e8 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Wed, 7 Aug 2024 19:36:16 +0200 Subject: [PATCH 165/726] Fixes based on review remarks --- Mods/vcmi/config/vcmi/english.json | 7 +++---- Mods/vcmi/config/vcmi/polish.json | 5 +---- client/windows/GUIClasses.cpp | 13 +++++-------- config/objects/generic.json | 1 + .../HillFortInstanceConstructor.cpp | 10 ++++++++++ lib/mapObjects/MiscObjects.h | 14 ++++++++++++++ 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 47f663564..e93bf8b95 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -283,9 +283,6 @@ "vcmi.adventureMap.revisitObject.hover" : "Revisit Object", "vcmi.adventureMap.revisitObject.help" : "{Revisit Object}\n\nIf a hero currently stands on a Map Object, he can revisit the location.", - "vcmi.adventureMap.miniHillFort.notAvailableUpgrade.message" : "This building cannot upgrade creatures of level 5 and higher.", - "vcmi.adventureMap.miniHillFort.statusBar.info" : "Upgrade creatures levels 1 - 5 at double the normal price", - "vcmi.adventureMap.HillFort.statusBar.info" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town", "vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately", "vcmi.battleWindow.damageEstimation.melee" : "Attack %CREATURE (%DAMAGE).", @@ -519,7 +516,9 @@ "core.seerhut.quest.reachDate.visit.3" : "Closed till %s.", "core.seerhut.quest.reachDate.visit.4" : "Closed till %s.", "core.seerhut.quest.reachDate.visit.5" : "Closed till %s.", - + + "mapObject.core.hillFort.object.description" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town", + "core.bonus.ADDITIONAL_ATTACK.name": "Double Strike", "core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice", "core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations", diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 8d5d8a353..885caa9e5 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -280,9 +280,6 @@ "vcmi.adventureMap.revisitObject.hover" : "Odwiedź obiekt ponownie", "vcmi.adventureMap.revisitObject.help" : "{Odwiedź obiekt ponownie}\n\nJeżeli bohater aktualnie stoi na polu odwiedzającym obiekt za pomocą tego przycisku może go odwiedzić ponownie.", - "vcmi.adventureMap.miniHillFort.notAvailableUpgrade.message" : "Ten budynek nie ma możliwości ulepszenia jednostek poziomu 5 i wyższych.", - "vcmi.adventureMap.miniHillFort.statusBar.info" : "Ulepsza jednostki poziomu 1 - 5 za podwójną cenę", - "vcmi.adventureMap.HillFort.statusBar.info" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 są bardziej korzystne niż w mieście", "vcmi.battleWindow.pressKeyToSkipIntro" : "Naciśnij dowolny klawisz by rozpocząć bitwę natychmiastowo", "vcmi.battleWindow.damageEstimation.melee" : "Atakuj %CREATURE (%DAMAGE).", @@ -516,7 +513,7 @@ "core.seerhut.quest.reachDate.visit.3" : "Zamknięte do %s.", "core.seerhut.quest.reachDate.visit.4" : "Zamknięte do %s.", "core.seerhut.quest.reachDate.visit.5" : "Zamknięte do %s.", - + "mapObject.core.hillFort.object.description" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 są bardziej korzystne niż w mieście", "core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie", "core.bonus.ADDITIONAL_ATTACK.description": "Atakuje dwa razy", "core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowy odwet", diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index f2361e759..33f18f8e5 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1130,10 +1130,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI garr = std::make_shared(Point(108, 60), 18, Point(), hero, nullptr); - if(object->typeName == "miniHillFort") - statusbar->write(VLC->generaltexth->translate("vcmi.adventureMap.miniHillFort.statusBar.info")); - else if(object->typeName == "hillFort") - statusbar->write(VLC->generaltexth->translate("vcmi.adventureMap.HillFort.statusBar.info")); + statusbar->write(dynamic_cast(fort)->getDescriptionToolTip()); updateGarrisons(); } @@ -1273,7 +1270,7 @@ void CHillFortWindow::makeDeal(SlotID slot) LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector>(), soundBase::sound_todo); break; case State::UNAVAILABLE: - LOCPLINT->showInfoDialog(VLC->generaltexth->translate("vcmi.adventureMap.miniHillFort.notAvailableUpgrade.message"), + LOCPLINT->showInfoDialog(dynamic_cast(fort)->getUnavailableUpgradeMessage(), std::vector>(), soundBase::sound_todo); break; case State::MAKE_UPGRADE: @@ -1314,10 +1311,10 @@ CHillFortWindow::State CHillFortWindow::getState(SlotID slot) UpgradeInfo info; LOCPLINT->cb->fillUpgradeInfo(hero, slot, info); - if (!info.newID.size()) + if (info.newID.empty()) { - // new Hill Fort allows upgrades level 5 and below - if (hero->getStack(slot).type->getLevel() >= 5 && hero->getCreature(slot)->hasUpgrades()) + // Hill Fort may limit level of upgradeable creatures, e.g. mini Hill Fort from HOTA + if (hero->getCreature(slot)->hasUpgrades()) return State::UNAVAILABLE; return State::ALREADY_UPGRADED; diff --git a/config/objects/generic.json b/config/objects/generic.json index 23240bbd5..11802862d 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -683,6 +683,7 @@ "object" : { "index" : 0, "aiValue" : 7000, + "description" : "", "rmg" : { "zoneLimit" : 1, "value" : 7000, diff --git a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp index b325edde3..be01c9b65 100644 --- a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp @@ -11,17 +11,27 @@ #include "HillFortInstanceConstructor.h" #include "../mapObjects/MiscObjects.h" +#include "../texts/CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN void HillFortInstanceConstructor::initTypeData(const JsonNode & config) { parameters = config; + VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "unavailableUpgradeMessage"), parameters["unavailableUpgradeMessage"].String()); + VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "description"), parameters["description"].String()); } void HillFortInstanceConstructor::initializeObject(HillFort * fort) const { fort->upgradeCostPercentage = parameters["upgradeCostFactor"].convertTo>(); + fort->descriptionToolTip = VLC->generaltexth->translate(TextIdentifier(getBaseTextID(), "description").get()); + if (fort->descriptionToolTip.empty()) + fort->descriptionToolTip = parameters["description"].String(); + + fort->unavailableUpgradeMessage = VLC->generaltexth->translate(TextIdentifier(getBaseTextID(), "unavailableUpgradeMessage").get()); + if (fort->unavailableUpgradeMessage.empty()) + fort->unavailableUpgradeMessage = parameters["unavailableUpgradeMessage"].String(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 862d52596..d52ae021e 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -451,6 +451,8 @@ class DLL_LINKAGE HillFort : public CGObjectInstance, public ICreatureUpgrader friend class HillFortInstanceConstructor; std::vector upgradeCostPercentage; + std::string descriptionToolTip; + std::string unavailableUpgradeMessage; protected: void onHeroVisit(const CGHeroInstance * h) const override; @@ -459,10 +461,22 @@ protected: public: using CGObjectInstance::CGObjectInstance; + const std::string & getDescriptionToolTip() const + { + return descriptionToolTip; + } + + const std::string & getUnavailableUpgradeMessage() const + { + return unavailableUpgradeMessage; + } + template void serialize(Handler &h) { h & static_cast(*this); h & upgradeCostPercentage; + h & descriptionToolTip; + h & unavailableUpgradeMessage; } }; From f3d9a075fc3dc4cabbbc73b1c90d7e894534a0fb Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 8 Aug 2024 23:27:52 +0200 Subject: [PATCH 166/726] Fix polish description translation --- Mods/vcmi/config/vcmi/polish.json | 2 +- test/googletest | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 885caa9e5..77c830446 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -513,7 +513,7 @@ "core.seerhut.quest.reachDate.visit.3" : "Zamknięte do %s.", "core.seerhut.quest.reachDate.visit.4" : "Zamknięte do %s.", "core.seerhut.quest.reachDate.visit.5" : "Zamknięte do %s.", - "mapObject.core.hillFort.object.description" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 są bardziej korzystne niż w mieście", + "mapObject.core.hillFort.object.description" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 jest bardziej korzystny niż w mieście", "core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie", "core.bonus.ADDITIONAL_ATTACK.description": "Atakuje dwa razy", "core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowy odwet", diff --git a/test/googletest b/test/googletest index b514bdc89..b796f7d44 160000 --- a/test/googletest +++ b/test/googletest @@ -1 +1 @@ -Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 +Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 From 6cf423d97760553ff7d32f3cea814598ce8d6331 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Mon, 19 Aug 2024 01:40:44 +0200 Subject: [PATCH 167/726] Fix Hill Fort upgrade logic when there are two levels Fixes #2503 --- client/windows/GUIClasses.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 33f18f8e5..6ded1b692 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1173,7 +1173,7 @@ void CHillFortWindow::updateGarrisons() LOCPLINT->cb->fillUpgradeInfo(hero, SlotID(i), info); if(info.newID.size())//we have upgrades here - update costs { - costs[i] = info.cost[0] * hero->getStackCount(SlotID(i)); + costs[i] = info.cost.back() * hero->getStackCount(SlotID(i)); totalSum += costs[i]; } } @@ -1259,8 +1259,8 @@ void CHillFortWindow::updateGarrisons() void CHillFortWindow::makeDeal(SlotID slot) { - assert(slot.getNum()>=0); - int offset = (slot.getNum() == slotsCount)?2:0; + assert(slot.getNum() >= 0); + int offset = (slot.getNum() == slotsCount) ? 2 : 0; switch(currState[slot.getNum()]) { case State::ALREADY_UPGRADED: @@ -1280,7 +1280,7 @@ void CHillFortWindow::makeDeal(SlotID slot) { UpgradeInfo info; LOCPLINT->cb->fillUpgradeInfo(hero, SlotID(i), info); - LOCPLINT->cb->upgradeCreature(hero, SlotID(i), info.newID[0]); + LOCPLINT->cb->upgradeCreature(hero, SlotID(i), info.newID.back()); } } break; @@ -1320,7 +1320,7 @@ CHillFortWindow::State CHillFortWindow::getState(SlotID slot) return State::ALREADY_UPGRADED; } - if(!(info.cost[0] * hero->getStackCount(slot)).canBeAfforded(myRes)) + if(!(info.cost.back() * hero->getStackCount(slot)).canBeAfforded(myRes)) return State::UNAFFORDABLE; return State::MAKE_UPGRADE; From 8a4212721094f06ab7ab6e70cd74de596051682f Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Fri, 20 Sep 2024 08:37:11 +0200 Subject: [PATCH 168/726] Restore googletest module from upstream --- test/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/googletest b/test/googletest index b796f7d44..b514bdc89 160000 --- a/test/googletest +++ b/test/googletest @@ -1 +1 @@ -Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 +Subproject commit b514bdc898e2951020cbdca1304b75f5950d1f59 From edf43f5702e1cf8adf47049b25b93f83a97645fd Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Tue, 24 Sep 2024 18:28:17 +0200 Subject: [PATCH 169/726] Get and translate messages on client side --- Mods/vcmi/config/vcmi/english.json | 2 +- Mods/vcmi/config/vcmi/polish.json | 4 +++- client/windows/GUIClasses.cpp | 8 +++++--- .../HillFortInstanceConstructor.cpp | 7 ------- lib/mapObjects/MiscObjects.cpp | 10 ++++++++++ lib/mapObjects/MiscObjects.h | 15 ++------------- 6 files changed, 21 insertions(+), 25 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index e93bf8b95..52c3499af 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -517,7 +517,7 @@ "core.seerhut.quest.reachDate.visit.4" : "Closed till %s.", "core.seerhut.quest.reachDate.visit.5" : "Closed till %s.", - "mapObject.core.hillFort.object.description" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town", + "mapObject.core.hillFort.object.description" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town.", "core.bonus.ADDITIONAL_ATTACK.name": "Double Strike", "core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice", diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 77c830446..fae832498 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -513,7 +513,9 @@ "core.seerhut.quest.reachDate.visit.3" : "Zamknięte do %s.", "core.seerhut.quest.reachDate.visit.4" : "Zamknięte do %s.", "core.seerhut.quest.reachDate.visit.5" : "Zamknięte do %s.", - "mapObject.core.hillFort.object.description" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 jest bardziej korzystny niż w mieście", + + "mapObject.core.hillFort.object.description" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 jest bardziej korzystny niż w mieście.", + "core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie", "core.bonus.ADDITIONAL_ATTACK.description": "Atakuje dwa razy", "core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowy odwet", diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 6ded1b692..7dcaae593 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1130,7 +1130,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI garr = std::make_shared(Point(108, 60), 18, Point(), hero, nullptr); - statusbar->write(dynamic_cast(fort)->getDescriptionToolTip()); + statusbar->write(VLC->generaltexth->translate(dynamic_cast(fort)->getDescriptionToolTip())); updateGarrisons(); } @@ -1270,9 +1270,11 @@ void CHillFortWindow::makeDeal(SlotID slot) LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector>(), soundBase::sound_todo); break; case State::UNAVAILABLE: - LOCPLINT->showInfoDialog(dynamic_cast(fort)->getUnavailableUpgradeMessage(), - std::vector>(), soundBase::sound_todo); + { + std::string message = VLC->generaltexth->translate(dynamic_cast(fort)->getUnavailableUpgradeMessage()); + LOCPLINT->showInfoDialog(message, std::vector>(), soundBase::sound_todo); break; + } case State::MAKE_UPGRADE: for(int i = 0; i < slotsCount; i++) { diff --git a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp index be01c9b65..f71f4990e 100644 --- a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp @@ -25,13 +25,6 @@ void HillFortInstanceConstructor::initTypeData(const JsonNode & config) void HillFortInstanceConstructor::initializeObject(HillFort * fort) const { fort->upgradeCostPercentage = parameters["upgradeCostFactor"].convertTo>(); - fort->descriptionToolTip = VLC->generaltexth->translate(TextIdentifier(getBaseTextID(), "description").get()); - if (fort->descriptionToolTip.empty()) - fort->descriptionToolTip = parameters["description"].String(); - - fort->unavailableUpgradeMessage = VLC->generaltexth->translate(TextIdentifier(getBaseTextID(), "unavailableUpgradeMessage").get()); - if (fort->unavailableUpgradeMessage.empty()) - fort->unavailableUpgradeMessage = parameters["unavailableUpgradeMessage"].String(); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index ab5fc3d50..73648b77b 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1403,4 +1403,14 @@ void HillFort::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) } } +std::string HillFort::getDescriptionToolTip() const +{ + return TextIdentifier(getObjectHandler()->getBaseTextID(), "description").get(); +} + +std::string HillFort::getUnavailableUpgradeMessage() const +{ + return TextIdentifier(getObjectHandler()->getBaseTextID(), "unavailableUpgradeMessage").get(); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index d52ae021e..475122310 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -451,8 +451,6 @@ class DLL_LINKAGE HillFort : public CGObjectInstance, public ICreatureUpgrader friend class HillFortInstanceConstructor; std::vector upgradeCostPercentage; - std::string descriptionToolTip; - std::string unavailableUpgradeMessage; protected: void onHeroVisit(const CGHeroInstance * h) const override; @@ -461,22 +459,13 @@ protected: public: using CGObjectInstance::CGObjectInstance; - const std::string & getDescriptionToolTip() const - { - return descriptionToolTip; - } - - const std::string & getUnavailableUpgradeMessage() const - { - return unavailableUpgradeMessage; - } + std::string getDescriptionToolTip() const; + std::string getUnavailableUpgradeMessage() const; template void serialize(Handler &h) { h & static_cast(*this); h & upgradeCostPercentage; - h & descriptionToolTip; - h & unavailableUpgradeMessage; } }; From e4ef95f8dd347eeb7f81363e300f169f04841000 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 25 Sep 2024 16:12:53 +0200 Subject: [PATCH 170/726] Revert dynamic maxWillingToLose Out maxWillingToLose back to a static 25%. Dynamic could become too suicidal or too passive. 25% is a good sweetspot. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 456f8af0d..0dca4e1ae 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1342,26 +1342,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) else { float score = 0; - float myPower = 0; - float totalPower = 0; - for (auto heroInfo : ai->cb->getHeroesInfo(false)) - { - if (heroInfo->getOwner() == ai->cb->getPlayerID()) - myPower += heroInfo->getTotalStrength(); - totalPower += heroInfo->getTotalStrength(); - } - float powerRatio = 1; - if (totalPower > 0) - powerRatio = myPower / totalPower; - - float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.5 * powerRatio; + float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; bool arriveNextWeek = false; if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7) arriveNextWeek = true; #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d powerRatio: %d", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1382,8 +1370,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.closestWayRatio, evaluationContext.enemyHeroDangerRatio, evaluationContext.explorePriority, - evaluationContext.isDefend, - powerRatio); + evaluationContext.isDefend); #endif switch (priorityTier) From 072aa8aadd2e8987052727996a76b1d67b6f33dc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 27 Sep 2024 13:25:18 +0000 Subject: [PATCH 171/726] Remove unused method --- lib/mapObjects/CRewardableObject.cpp | 17 ++++------------- lib/mapObjects/CRewardableObject.h | 4 +--- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 90c02aacb..5f6d06739 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -95,16 +95,7 @@ std::vector CRewardableObject::loadComponents(const CGHeroInstance * return result; } -bool CRewardableObject::guardedPotentially() const -{ - for (auto const & visitInfo : configuration.info) - if (!visitInfo.reward.guards.empty()) - return true; - - return false; -} - -bool CRewardableObject::guardedPresently() const +bool CRewardableObject::isGuarded() const { return stacksCount() > 0; } @@ -117,7 +108,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const cb->sendAndApply(&cov); } - if (guardedPresently()) + if (isGuarded()) { auto guardedIndexes = getAvailableRewards(hero, Rewardable::EEventType::EVENT_GUARDED); auto guardedReward = configuration.info.at(guardedIndexes.at(0)); @@ -236,7 +227,7 @@ void CRewardableObject::battleFinished(const CGHeroInstance *hero, const BattleR void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int32_t answer) const { - if(guardedPresently()) + if(isGuarded()) { if (answer) { @@ -410,7 +401,7 @@ std::vector CRewardableObject::getPopupComponentsImpl(PlayerColor pla if (!wasScouted(player)) return {}; - if (guardedPresently()) + if (isGuarded()) { if (!cb->getSettings().getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION)) return {}; diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 39a658ad2..4619e38c2 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -47,10 +47,8 @@ protected: void doHeroVisit(const CGHeroInstance *h) const; - /// Returns true if this object might have guards present, whether they were cleared or not - bool guardedPotentially() const; /// Returns true if this object is currently guarded - bool guardedPresently() const; + bool isGuarded() const; public: /// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player) From f2c20b54d032cfb4717269ba539480e9275c2f8a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 27 Sep 2024 15:40:09 +0000 Subject: [PATCH 172/726] Unify rewardable map object and town building code --- lib/mapObjects/CRewardableObject.cpp | 160 ++---------------------- lib/mapObjects/CRewardableObject.h | 18 +-- lib/mapObjects/TownBuildingInstance.cpp | 129 ++++++------------- lib/mapObjects/TownBuildingInstance.h | 8 +- lib/rewardable/Interface.cpp | 154 ++++++++++++++++++++++- lib/rewardable/Interface.h | 19 ++- server/CGameHandler.cpp | 2 +- 7 files changed, 226 insertions(+), 264 deletions(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 5f6d06739..44ecf20a6 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -12,87 +12,30 @@ #include "CRewardableObject.h" #include "../CPlayerState.h" -#include "../GameSettings.h" #include "../IGameCallback.h" +#include "../IGameSettings.h" #include "../battle/BattleLayout.h" #include "../gameState/CGameState.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" -#include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../mapObjectConstructors/CRewardableConstructor.h" #include "../mapObjects/CGHeroInstance.h" #include "../networkPacks/PacksForClient.h" #include "../networkPacks/PacksForClientBattle.h" #include "../serializer/JsonSerializeFormat.h" -#include "../texts/CGeneralTextHandler.h" #include VCMI_LIB_NAMESPACE_BEGIN -void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const +const IObjectInterface * CRewardableObject::getObject() const { - auto vi = configuration.info.at(index); - logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); - // show message only if it is not empty or in infobox - if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty()) - { - InfoWindow iw; - iw.player = contextHero->tempOwner; - iw.text = vi.message; - vi.reward.loadComponents(iw.components, contextHero); - iw.type = configuration.infoWindowType; - if(!iw.components.empty() || !iw.text.toString().empty()) - cb->showInfoDialog(&iw); - } - // grant reward afterwards. Note that it may remove object - if(markAsVisit) - markAsVisited(contextHero); - grantReward(index, contextHero); + return this; } -void CRewardableObject::selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const +void CRewardableObject::markAsScouted(const CGHeroInstance * hero) const { - BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1); - sd.player = contextHero->tempOwner; - sd.text = dialog; - sd.components = loadComponents(contextHero, rewardIndices); - cb->showBlockingDialog(this, &sd); - -} - -void CRewardableObject::grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, bool markAsVisit) const -{ - if (rewardIndices.empty()) - return; - - for (auto index : rewardIndices) - { - // TODO: Merge all rewards of same type, with single message? - grantRewardWithMessage(contextHero, index, false); - } - // Mark visited only after all rewards were processed - if(markAsVisit) - markAsVisited(contextHero); -} - -std::vector CRewardableObject::loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const -{ - std::vector result; - - if (rewardIndices.empty()) - return result; - - if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1) - { - for (auto index : rewardIndices) - result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero)); - } - else - { - configuration.info.at(rewardIndices.front()).reward.loadComponents(result, contextHero); - } - - return result; + ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, hero->id); + cb->sendAndApply(&cov); } bool CRewardableObject::isGuarded() const @@ -127,94 +70,9 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const } } -void CRewardableObject::doHeroVisit(const CGHeroInstance *h) const -{ - if(!wasVisitedBefore(h)) - { - auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); - bool objectRemovalPossible = false; - for(auto index : rewards) - { - if(configuration.info.at(index).reward.removeObject) - objectRemovalPossible = true; - } - - logGlobal->debug("Visiting object with %d possible rewards", rewards.size()); - switch (rewards.size()) - { - case 0: // no available rewards, e.g. visiting School of War without gold - { - auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE); - if (!emptyRewards.empty()) - grantRewardWithMessage(h, emptyRewards[0], false); - else - logMod->warn("No applicable message for visiting empty object!"); - break; - } - case 1: // one reward. Just give it with message - { - if (configuration.canRefuse) - selectRewardWithMessage(h, rewards, configuration.info.at(rewards.front()).message); - else - grantRewardWithMessage(h, rewards.front(), true); - break; - } - default: // multiple rewards. Act according to select mode - { - switch (configuration.selectMode) { - case Rewardable::SELECT_PLAYER: // player must select - selectRewardWithMessage(h, rewards, configuration.onSelect); - break; - case Rewardable::SELECT_FIRST: // give first available - if (configuration.canRefuse) - selectRewardWithMessage(h, { rewards.front() }, configuration.info.at(rewards.front()).message); - else - grantRewardWithMessage(h, rewards.front(), true); - break; - case Rewardable::SELECT_RANDOM: // give random - { - ui32 rewardIndex = *RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()); - if (configuration.canRefuse) - selectRewardWithMessage(h, { rewardIndex }, configuration.info.at(rewardIndex).message); - else - grantRewardWithMessage(h, rewardIndex, true); - break; - } - case Rewardable::SELECT_ALL: // grant all possible - grantAllRewardsWithMessage(h, rewards, true); - break; - } - break; - } - } - - if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty()) - { - ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id); - cb->sendAndApply(&cov); - } - } - else - { - logGlobal->debug("Revisiting already visited object"); - - if (!wasVisited(h->getOwner())) - { - ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id); - cb->sendAndApply(&cov); - } - - auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED); - if (!visitedRewards.empty()) - grantRewardWithMessage(h, visitedRewards[0], false); - else - logMod->warn("No applicable message for visiting already visited object!"); - } -} - void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const { - grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), this, hero); + grantRewardAfterLevelup(configuration.info.at(selectedReward), this, hero); } void CRewardableObject::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const @@ -264,12 +122,12 @@ void CRewardableObject::markAsVisited(const CGHeroInstance * hero) const void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const { cb->setObjPropertyValue(id, ObjProperty::REWARD_SELECT, rewardID); - grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero); + grantRewardBeforeLevelup(configuration.info.at(rewardID), hero); // hero is not blocked by levelup dialog - grant remainder immediately if(!cb->isVisitCoveredByAnotherQuery(this, hero)) { - grantRewardAfterLevelup(cb, configuration.info.at(rewardID), this, hero); + grantRewardAfterLevelup(configuration.info.at(rewardID), this, hero); } } diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 4619e38c2..14bd52af2 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -25,28 +25,22 @@ protected: /// reward selected by player, no serialize ui16 selectedReward = 0; - void grantReward(ui32 rewardID, const CGHeroInstance * hero) const; - void markAsVisited(const CGHeroInstance * hero) const; + void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override; + void markAsVisited(const CGHeroInstance * hero) const override; + + const IObjectInterface * getObject() const override; + void markAsScouted(const CGHeroInstance * hero) const override; /// return true if this object was "cleared" before and no longer has rewards applicable to selected hero /// unlike wasVisited, this method uses information not available to player owner, for example, if object was cleared by another player before - bool wasVisitedBefore(const CGHeroInstance * contextHero) const; + bool wasVisitedBefore(const CGHeroInstance * contextHero) const override; void serializeJsonOptions(JsonSerializeFormat & handler) override; - virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const; - virtual void selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const; - - virtual void grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector& rewardIndices, bool markAsVisit) const; - - std::vector loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const; - std::string getDisplayTextImpl(PlayerColor player, const CGHeroInstance * hero, bool includeDescription) const; std::string getDescriptionMessage(PlayerColor player, const CGHeroInstance * hero) const; std::vector getPopupComponentsImpl(PlayerColor player, const CGHeroInstance * hero) const; - void doHeroVisit(const CGHeroInstance *h) const; - /// Returns true if this object is currently guarded bool isGuarded() const; public: diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index a78366f18..04d2dfbe5 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -12,14 +12,10 @@ #include "TownBuildingInstance.h" #include "CGTownInstance.h" -#include "../texts/CGeneralTextHandler.h" #include "../IGameCallback.h" -#include "../gameState/CGameState.h" #include "../mapObjects/CGHeroInstance.h" -#include "../networkPacks/PacksForClient.h" #include "../entities/building/CBuilding.h" - #include VCMI_LIB_NAMESPACE_BEGIN @@ -130,7 +126,7 @@ void TownRewardableBuildingInstance::setProperty(ObjProperty what, ObjPropertyID void TownRewardableBuildingInstance::heroLevelUpDone(const CGHeroInstance *hero) const { - grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero); + grantRewardAfterLevelup(configuration.info.at(selectedReward), town, hero); } void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const @@ -154,14 +150,12 @@ void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInstance * hero) const { - town->addHeroToStructureVisitors(hero, getBuildingType()); - - grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero); + grantRewardBeforeLevelup(configuration.info.at(rewardID), hero); // hero is not blocked by levelup dialog - grant remainder immediately if(!cb->isVisitCoveredByAnotherQuery(town, hero)) { - grantRewardAfterLevelup(cb, configuration.info.at(rewardID), town, hero); + grantRewardAfterLevelup(configuration.info.at(rewardID), town, hero); } } @@ -196,93 +190,42 @@ bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * con void TownRewardableBuildingInstance::onHeroVisit(const CGHeroInstance *h) const { - auto grantRewardWithMessage = [&](int index) -> void + assert(town->hasBuilt(getBuildingType())); + + if(town->hasBuilt(getBuildingType())) + doHeroVisit(h); +} + +const IObjectInterface * TownRewardableBuildingInstance::getObject() const +{ + return this; +} + +bool TownRewardableBuildingInstance::wasVisited(PlayerColor player) const +{ + switch (configuration.visitMode) { - auto vi = configuration.info.at(index); - logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); - - town->addHeroToStructureVisitors(h, getBuildingType()); //adding to visitors - - InfoWindow iw; - iw.player = h->tempOwner; - iw.text = vi.message; - vi.reward.loadComponents(iw.components, h); - iw.type = EInfoWindowMode::MODAL; - if(!iw.components.empty() || !iw.text.toString().empty()) - cb->showInfoDialog(&iw); - - grantReward(index, h); - }; - auto selectRewardsMessage = [&](const std::vector & rewards, const MetaString & dialog) -> void - { - BlockingDialog sd(configuration.canRefuse, rewards.size() > 1); - sd.player = h->tempOwner; - sd.text = dialog; - - if (rewards.size() > 1) - for (auto index : rewards) - sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h)); - - if (rewards.size() == 1) - configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h); - - cb->showBlockingDialog(this, &sd); - }; - - if(!town->hasBuilt(getBuildingType())) - return; - - if(!wasVisitedBefore(h)) - { - auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); - - logGlobal->debug("Visiting object with %d possible rewards", rewards.size()); - switch (rewards.size()) - { - case 0: // no available rewards, e.g. visiting School of War without gold - { - auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE); - if (!emptyRewards.empty()) - grantRewardWithMessage(emptyRewards[0]); - else - logMod->warn("No applicable message for visiting empty object!"); - break; - } - case 1: // one reward. Just give it with message - { - if (configuration.canRefuse) - selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message); - else - grantRewardWithMessage(rewards.front()); - break; - } - default: // multiple rewards. Act according to select mode - { - switch (configuration.selectMode) { - case Rewardable::SELECT_PLAYER: // player must select - selectRewardsMessage(rewards, configuration.onSelect); - break; - case Rewardable::SELECT_FIRST: // give first available - grantRewardWithMessage(rewards.front()); - break; - case Rewardable::SELECT_RANDOM: // give random - grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator())); - break; - } - break; - } - } + case Rewardable::VISIT_UNLIMITED: + case Rewardable::VISIT_BONUS: + case Rewardable::VISIT_HERO: + case Rewardable::VISIT_LIMITER: + return false; + case Rewardable::VISIT_ONCE: + case Rewardable::VISIT_PLAYER: + return !visitors.empty(); + default: + return false; } - else - { - logGlobal->debug("Revisiting already visited object"); +} - auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED); - if (!visitedRewards.empty()) - grantRewardWithMessage(visitedRewards[0]); - else - logMod->debug("No applicable message for visiting already visited object!"); - } +void TownRewardableBuildingInstance::markAsVisited(const CGHeroInstance * hero) const +{ + town->addHeroToStructureVisitors(hero, getBuildingType()); +} + +void TownRewardableBuildingInstance::markAsScouted(const CGHeroInstance * hero) const +{ + // no-op - town building is always 'scouted' by owner } diff --git a/lib/mapObjects/TownBuildingInstance.h b/lib/mapObjects/TownBuildingInstance.h index 2315566fa..8874793cf 100644 --- a/lib/mapObjects/TownBuildingInstance.h +++ b/lib/mapObjects/TownBuildingInstance.h @@ -63,10 +63,14 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance, ui16 selectedReward = 0; std::set visitors; - bool wasVisitedBefore(const CGHeroInstance * contextHero) const; - void grantReward(ui32 rewardID, const CGHeroInstance * hero) const; + bool wasVisitedBefore(const CGHeroInstance * contextHero) const override; + void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override; Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const; + const IObjectInterface * getObject() const override; + bool wasVisited(PlayerColor player) const override; + void markAsVisited(const CGHeroInstance * hero) const override; + void markAsScouted(const CGHeroInstance * hero) const override; public: void setProperty(ObjProperty what, ObjPropertyID identifier) override; void onHeroVisit(const CGHeroInstance * h) const override; diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index d86047f9c..0634ba7c0 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -25,6 +25,8 @@ #include "../networkPacks/PacksForClient.h" #include "../IGameCallback.h" +#include + VCMI_LIB_NAMESPACE_BEGIN std::vector Rewardable::Interface::getAvailableRewards(const CGHeroInstance * hero, Rewardable::EEventType event) const @@ -44,8 +46,10 @@ std::vector Rewardable::Interface::getAvailableRewards(const CGHeroInstanc return ret; } -void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CGHeroInstance * hero) const +void Rewardable::Interface::grantRewardBeforeLevelup(const Rewardable::VisitInfo & info, const CGHeroInstance * hero) const { + auto cb = getObject()->cb; + assert(hero); assert(hero->tempOwner.isValidPlayer()); assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE); @@ -129,8 +133,10 @@ void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const R cb->giveExperience(hero, expToGive); } -void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const +void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const { + auto cb = getObject()->cb; + if(info.reward.manaDiff || info.reward.manaPercentage >= 0) cb->setManaPoints(hero->id, info.reward.calculateManaPoints(hero)); @@ -216,4 +222,148 @@ void Rewardable::Interface::serializeJson(JsonSerializeFormat & handler) configuration.serializeJson(handler); } +void Rewardable::Interface::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const +{ + auto vi = configuration.info.at(index); + logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString()); + // show message only if it is not empty or in infobox + if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty()) + { + InfoWindow iw; + iw.player = contextHero->tempOwner; + iw.text = vi.message; + vi.reward.loadComponents(iw.components, contextHero); + iw.type = configuration.infoWindowType; + if(!iw.components.empty() || !iw.text.toString().empty()) + getObject()->cb->showInfoDialog(&iw); + } + // grant reward afterwards. Note that it may remove object + if(markAsVisit) + markAsVisited(contextHero); + grantReward(index, contextHero); +} + +void Rewardable::Interface::selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const +{ + BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1); + sd.player = contextHero->tempOwner; + sd.text = dialog; + sd.components = loadComponents(contextHero, rewardIndices); + getObject()->cb->showBlockingDialog(getObject(), &sd); +} + +std::vector Rewardable::Interface::loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const +{ + std::vector result; + + if (rewardIndices.empty()) + return result; + + if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1) + { + for (auto index : rewardIndices) + result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero)); + } + else + { + configuration.info.at(rewardIndices.front()).reward.loadComponents(result, contextHero); + } + + return result; +} + +void Rewardable::Interface::grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, bool markAsVisit) const +{ + if (rewardIndices.empty()) + return; + + for (auto index : rewardIndices) + { + // TODO: Merge all rewards of same type, with single message? + grantRewardWithMessage(contextHero, index, false); + } + // Mark visited only after all rewards were processed + if(markAsVisit) + markAsVisited(contextHero); +} + +void Rewardable::Interface::doHeroVisit(const CGHeroInstance *h) const +{ + if(!wasVisitedBefore(h)) + { + auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT); + bool objectRemovalPossible = false; + for(auto index : rewards) + { + if(configuration.info.at(index).reward.removeObject) + objectRemovalPossible = true; + } + + logGlobal->debug("Visiting object with %d possible rewards", rewards.size()); + switch (rewards.size()) + { + case 0: // no available rewards, e.g. visiting School of War without gold + { + auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE); + if (!emptyRewards.empty()) + grantRewardWithMessage(h, emptyRewards[0], false); + else + logMod->warn("No applicable message for visiting empty object!"); + break; + } + case 1: // one reward. Just give it with message + { + if (configuration.canRefuse) + selectRewardWithMessage(h, rewards, configuration.info.at(rewards.front()).message); + else + grantRewardWithMessage(h, rewards.front(), true); + break; + } + default: // multiple rewards. Act according to select mode + { + switch (configuration.selectMode) { + case Rewardable::SELECT_PLAYER: // player must select + selectRewardWithMessage(h, rewards, configuration.onSelect); + break; + case Rewardable::SELECT_FIRST: // give first available + if (configuration.canRefuse) + selectRewardWithMessage(h, { rewards.front() }, configuration.info.at(rewards.front()).message); + else + grantRewardWithMessage(h, rewards.front(), true); + break; + case Rewardable::SELECT_RANDOM: // give random + { + ui32 rewardIndex = *RandomGeneratorUtil::nextItem(rewards, getObject()->cb->getRandomGenerator()); + if (configuration.canRefuse) + selectRewardWithMessage(h, { rewardIndex }, configuration.info.at(rewardIndex).message); + else + grantRewardWithMessage(h, rewardIndex, true); + break; + } + case Rewardable::SELECT_ALL: // grant all possible + grantAllRewardsWithMessage(h, rewards, true); + break; + } + break; + } + } + + if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty()) + markAsScouted(h); + } + else + { + logGlobal->debug("Revisiting already visited object"); + + if (!wasVisited(h->getOwner())) + markAsScouted(h); + + auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED); + if (!visitedRewards.empty()) + grantRewardWithMessage(h, visitedRewards[0], false); + else + logMod->warn("No applicable message for visiting already visited object!"); + } +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Interface.h b/lib/rewardable/Interface.h index 7fd6fb690..5354fffaf 100644 --- a/lib/rewardable/Interface.h +++ b/lib/rewardable/Interface.h @@ -15,7 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN -class IGameCallback; +class IObjectInterface; namespace Rewardable { @@ -30,11 +30,24 @@ private: protected: /// function that must be called if hero got level-up during grantReward call - virtual void grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & reward, const CArmedInstance * army, const CGHeroInstance * hero) const; + void grantRewardAfterLevelup(const Rewardable::VisitInfo & reward, const CArmedInstance * army, const CGHeroInstance * hero) const; /// grants reward to hero - virtual void grantRewardBeforeLevelup(IGameCallback * cb, const Rewardable::VisitInfo & reward, const CGHeroInstance * hero) const; + void grantRewardBeforeLevelup(const Rewardable::VisitInfo & reward, const CGHeroInstance * hero) const; + virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const; + void selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector & rewardIndices, const MetaString & dialog) const; + void grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector& rewardIndices, bool markAsVisit) const; + std::vector loadComponents(const CGHeroInstance * contextHero, const std::vector & rewardIndices) const; + + void doHeroVisit(const CGHeroInstance *h) const; + + virtual const IObjectInterface * getObject() const = 0; + virtual bool wasVisitedBefore(const CGHeroInstance * hero) const = 0; + virtual bool wasVisited(PlayerColor player) const = 0; + virtual void markAsVisited(const CGHeroInstance * hero) const = 0; + virtual void markAsScouted(const CGHeroInstance * hero) const = 0; + virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero) const = 0; public: /// filters list of visit info and returns rewards that can be granted to current hero diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 35dde1615..d1dc63901 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1194,7 +1194,7 @@ void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vectorrewardableBuildings) { - if (!t->town->buildings.at(building.first)->manualHeroVisit) + if (!t->town->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first)) buildingsToVisit.push_back(building.first); } From 3b7834495d0f8ec87ec8638118c12d24f8276b8a Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 27 Sep 2024 19:38:07 +0200 Subject: [PATCH 173/726] Update PriorityEvaluator.cpp Fix an issue that caused AI to take their hero's attributes into consideration twice when calculating how much army they think they'll lose. Fixed an issue where offensive defending didn't take into consideration whether the hero would actually be strong enough to beat the enemy hero it was trying to dispatch. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 0dca4e1ae..dbf5d7d9a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1047,7 +1047,7 @@ public: evaluationContext.armyInvolvement += army->getArmyCost(); } - vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength()); + vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength()); addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength()); vstd::amax(evaluationContext.turn, path.turn()); } @@ -1394,6 +1394,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) score = evaluationContext.armyInvolvement; + if (evaluationContext.isEnemy) + score *= (maxWillingToLose - evaluationContext.armyLossPersentage); score *= evaluationContext.closestWayRatio; break; } From efcac3b9337e19e639b000f47b1efd2633c18031 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:16:41 +0200 Subject: [PATCH 174/726] settings --- config/gameConfig.json | 4 +++- config/schemas/gameSettings.json | 3 ++- lib/GameSettings.cpp | 1 + lib/IGameSettings.h | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config/gameConfig.json b/config/gameConfig.json index 714309b2b..3f439f223 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -311,7 +311,9 @@ // How many new building can be built in a town per day "buildingsPerTurnCap" : 1, // Chances for a town with default buildings to receive corresponding dwelling level built in start - "startingDwellingChances": [100, 50] + "startingDwellingChances": [100, 50], + // Enable spell research in mage guild + "spellResearch": false }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 0eec56189..67ef54701 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -52,7 +52,8 @@ "additionalProperties" : false, "properties" : { "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" } + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 780c79f43..4525b89c7 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -101,6 +101,7 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 9f6a8a78b..c75726a49 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -79,6 +79,7 @@ enum class EGameSettings TEXTS_TERRAIN, TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, + TOWNS_SPELL_RESEARCH, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL From a9327b3fa3fe94e743f2a704db366df24d7416a4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 27 Sep 2024 22:47:22 +0200 Subject: [PATCH 175/726] netpacks --- CCallback.cpp | 6 ++++++ CCallback.h | 2 ++ client/Client.h | 1 + client/windows/CCastleInterface.cpp | 13 +++++++----- client/windows/CCastleInterface.h | 5 ++++- config/gameConfig.json | 2 +- lib/IGameCallback.h | 1 + lib/gameState/CGameState.cpp | 2 +- lib/networkPacks/NetPackVisitor.h | 2 ++ lib/networkPacks/NetPacksLib.cpp | 16 +++++++++++++++ lib/networkPacks/PacksForClient.h | 18 ++++++++++++++++ lib/networkPacks/PacksForServer.h | 18 ++++++++++++++++ lib/serializer/RegisterTypes.h | 2 ++ server/CGameHandler.cpp | 32 +++++++++++++++++++++++++++++ server/CGameHandler.h | 3 +++ server/NetPacksServer.cpp | 5 +++++ server/ServerNetPackVisitors.h | 1 + 17 files changed, 121 insertions(+), 8 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 8d2709f3d..e5953394e 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -249,6 +249,12 @@ int CBattleCallback::sendRequest(const CPackForServer * request) return requestID; } +void CCallback::spellResearch( const CGTownInstance *town ) +{ + SpellResearch pack(town->id); + sendRequest(&pack); +} + void CCallback::swapGarrisonHero( const CGTownInstance *town ) { if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player )) diff --git a/CCallback.h b/CCallback.h index a934113ce..3e9778387 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,6 +78,7 @@ public: virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made + virtual void spellResearch(const CGTownInstance *town)=0; virtual void swapGarrisonHero(const CGTownInstance *town)=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 @@ -187,6 +188,7 @@ public: bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; void endTurn() override; + void spellResearch(const CGTownInstance *town) override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; diff --git a/client/Client.h b/client/Client.h index d63f70578..cfb00e26c 100644 --- a/client/Client.h +++ b/client/Client.h @@ -159,6 +159,7 @@ public: friend class CBattleCallback; //handling players actions void changeSpells(const CGHeroInstance * hero, bool give, const std::set & spells) override {}; + void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {}; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index e8086f830..56211c500 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1966,7 +1966,7 @@ void CFortScreen::RecruitArea::showPopupWindow(const Point & cursorPosition) } CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & imagename) - : CWindowObject(BORDERED, imagename) + : CWindowObject(BORDERED, imagename), town(owner->town) { OBJECT_CONSTRUCTION; @@ -1997,15 +1997,15 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i for(size_t j=0; jtown->mageGuildLevel() && owner->town->spells[i].size()>j) - spells.push_back(std::make_shared(positions[i][j], owner->town->spells[i][j].toSpell())); + spells.push_back(std::make_shared(positions[i][j], owner->town->spells[i][j].toSpell(), town)); else emptyScrolls.push_back(std::make_shared(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y)); } } } -CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell) - : spell(Spell) +CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, const CGTownInstance *town) + : spell(Spell), town(town) { OBJECT_CONSTRUCTION; @@ -2017,7 +2017,10 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell) void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { - LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); + if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) + LOCPLINT->cb->spellResearch(town); + else + LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); } void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition) diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 3b8fb6037..3091d48ab 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -379,9 +379,10 @@ class CMageGuildScreen : public CStatusbarWindow { const CSpell * spell; std::shared_ptr image; + const CGTownInstance *town; public: - Scroll(Point position, const CSpell *Spell); + Scroll(Point position, const CSpell *Spell, const CGTownInstance *town); void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void hover(bool on) override; @@ -393,6 +394,8 @@ class CMageGuildScreen : public CStatusbarWindow std::shared_ptr resdatabar; + const CGTownInstance *town; + public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); }; diff --git a/config/gameConfig.json b/config/gameConfig.json index 3f439f223..9e4e5a4a3 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -313,7 +313,7 @@ // Chances for a town with default buildings to receive corresponding dwelling level built in start "startingDwellingChances": [100, 50], // Enable spell research in mage guild - "spellResearch": false + "spellResearch": true }, "combat": diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index a9cc7b655..9e1e8c1ca 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -94,6 +94,7 @@ public: virtual void showInfoDialog(InfoWindow * iw) = 0; virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells)=0; + virtual void setTownSpells(const CGTownInstance * town, int level, const std::vector spells)=0; virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 31bd4e2de..fed910807 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -915,7 +915,7 @@ void CGameState::initTowns() vti->spells[s->getLevel()-1].push_back(s->id); vti->possibleSpells -= s->id; } - vti->possibleSpells.clear(); + vti->possibleSpells.clear(); //SR } } diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 475b9b5db..4cfb0e402 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -42,6 +42,7 @@ public: virtual void visitSetSecSkill(SetSecSkill & pack) {} virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {} virtual void visitChangeSpells(ChangeSpells & pack) {} + virtual void visitSetTownSpells(SetTownSpells & pack) {} virtual void visitSetMana(SetMana & pack) {} virtual void visitSetMovePoints(SetMovePoints & pack) {} virtual void visitFoWChange(FoWChange & pack) {} @@ -128,6 +129,7 @@ public: virtual void visitBuildStructure(BuildStructure & pack) {} virtual void visitVisitTownBuilding(VisitTownBuilding & pack) {} virtual void visitRazeStructure(RazeStructure & pack) {} + virtual void visitSpellResearch(SpellResearch & pack) {} virtual void visitRecruitCreatures(RecruitCreatures & pack) {} virtual void visitUpgradeCreature(UpgradeCreature & pack) {} virtual void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 43309a8c5..a2cb60448 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -162,6 +162,10 @@ void ChangeSpells::visitTyped(ICPackVisitor & visitor) visitor.visitChangeSpells(*this); } +void SetTownSpells::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSetTownSpells(*this); +} void SetMana::visitTyped(ICPackVisitor & visitor) { visitor.visitSetMana(*this); @@ -592,6 +596,11 @@ void RazeStructure::visitTyped(ICPackVisitor & visitor) visitor.visitRazeStructure(*this); } +void SpellResearch::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSpellResearch(*this); +} + void RecruitCreatures::visitTyped(ICPackVisitor & visitor) { visitor.visitRecruitCreatures(*this); @@ -930,6 +939,13 @@ void ChangeSpells::applyGs(CGameState *gs) hero->removeSpellFromSpellbook(sid); } +void SetTownSpells::applyGs(CGameState *gs) +{ + CGTownInstance *town = gs->getTown(tid); + + town->spells[level] = spells; +} + void SetMana::applyGs(CGameState *gs) { CGHeroInstance * hero = gs->getHero(hid); diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 5a81ec806..ae1d23167 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -288,6 +288,24 @@ struct DLL_LINKAGE ChangeSpells : public CPackForClient } }; +struct DLL_LINKAGE SetTownSpells : public CPackForClient +{ + void applyGs(CGameState * gs) override; + + void visitTyped(ICPackVisitor & visitor) override; + + ui8 level = 0; + ObjectInstanceID tid; + std::vector spells; + + template void serialize(Handler & h) + { + h & level; + h & tid; + h & spells; + } +}; + struct DLL_LINKAGE SetMana : public CPackForClient { void applyGs(CGameState * gs) override; diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index a909cf652..b35254f23 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -306,6 +306,24 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure void visitTyped(ICPackVisitor & visitor) override; }; +struct DLL_LINKAGE SpellResearch : public CPackForServer +{ + SpellResearch() = default; + SpellResearch(const ObjectInstanceID & TID) + : tid(TID) + { + } + ObjectInstanceID tid; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + h & tid; + } +}; + struct DLL_LINKAGE RecruitCreatures : public CPackForServer { RecruitCreatures() = default; diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 94663357e..6fcbc5e20 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -288,6 +288,8 @@ void registerTypes(Serializer &s) s.template registerType(238); s.template registerType(239); s.template registerType(240); + s.template registerType(241); + s.template registerType(242); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 35dde1615..c14248b54 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1235,6 +1235,15 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st sendAndApply(&cs); } +void CGameHandler::setTownSpells(const CGTownInstance * town, int level, const std::vector spells) +{ + SetTownSpells cs; + cs.tid = town->id; + cs.spells = spells; + cs.level = level; + sendAndApply(&cs); +} + void CGameHandler::giveHeroBonus(GiveBonus * bonus) { sendAndApply(bonus); @@ -2233,6 +2242,29 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } +bool CGameHandler::spellResearch(ObjectInstanceID tid) +{ + if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) + return false; + + CGTownInstance *t = gs->getTown(tid); + auto spells = t->spells.at(1); + auto spell = SpellID(SpellID::FLY); + spells.at(0) = spell; + setTownSpells(t, 1, spells); + spellResearchFinished(tid); + return true; +} + +void CGameHandler::spellResearchFinished(ObjectInstanceID tid) +{ + const CGTownInstance * t = getTown(tid); + if(t->visitingHero) + giveSpells(t, t->visitingHero); + if(t->garrisonHero) + giveSpells(t, t->garrisonHero); +} + bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl, PlayerColor player) { const CGDwelling * dwelling = dynamic_cast(getObj(objid)); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index cdb194377..b1540b636 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -107,6 +107,7 @@ public: //from IGameCallback //do sth void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override; + void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; void giveExperience(const CGHeroInstance * hero, TExpType val) override; @@ -218,6 +219,8 @@ public: bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); + bool spellResearch(ObjectInstanceID tid); + void spellResearchFinished(ObjectInstanceID tid); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 2a7b493d4..90e8f2062 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -138,6 +138,11 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) result = gh.buildStructure(pack.tid, pack.bid); } +void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) +{ + result = gh.spellResearch(pack.tid); +} + void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) { gh.throwIfWrongOwner(&pack, pack.tid); diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index 34593c1da..ba94157cb 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -41,6 +41,7 @@ public: void visitBulkSmartSplitStack(BulkSmartSplitStack & pack) override; void visitDisbandCreature(DisbandCreature & pack) override; void visitBuildStructure(BuildStructure & pack) override; + void visitSpellResearch(SpellResearch & pack) override; void visitVisitTownBuilding(VisitTownBuilding & pack) override; void visitRecruitCreatures(RecruitCreatures & pack) override; void visitUpgradeCreature(UpgradeCreature & pack) override; From 0b016b9b14a7da034e6c53e30904b14ff020f22b Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 27 Sep 2024 23:02:40 +0200 Subject: [PATCH 176/726] Update RecruitHeroBehavior.cpp If the AI is very rich it will buy more heroes even if it doesn't have a capitol. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index eab159597..010c8df78 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -107,7 +107,8 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const if (bestHeroToHire && bestTownToHireFrom) { if (ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 - || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)) + || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol) + || (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh())) { tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1)))); } From ea535b211ad3c6b35484849659693ecec4e36a47 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 27 Sep 2024 23:03:09 +0200 Subject: [PATCH 177/726] Update Nullkiller.h Fixed issues caused by running buildPlan with wrong default-priority-tier. --- AI/Nullkiller/Engine/Nullkiller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 5166421fb..6a442dc80 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -127,7 +127,7 @@ private: void updateAiState(int pass, bool fast = false); void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const; Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const; - Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier = 3) const; + Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier = PriorityEvaluator::PriorityTier::HUNTER_GATHER) const; bool executeTask(Goals::TTask task); bool areAffectedObjectsPresent(Goals::TTask task) const; HeroRole getTaskRole(Goals::TTask task) const; From 8d93c0c9c9902c36b3e692d1effaa11db2107a15 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 27 Sep 2024 23:03:35 +0200 Subject: [PATCH 178/726] Update PriorityEvaluator.cpp Removed workaround that was likely necessitated by other issues. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index dbf5d7d9a..f588406fb 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1401,9 +1401,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::KILL: //Take towns / kill heroes that are further away { - //TODO: This is a workaround for duplicating the same capture town-task being given to several heroes. A better solution ought to be found. - if (evaluationContext.movementCostByRole[HeroRole::MAIN] == 0) - return 0; if (evaluationContext.turn > 0 && evaluationContext.isHero) return 0; if (arriveNextWeek && evaluationContext.isEnemy) From 857b2e9a352ae8a4ce54213a03c9762d19e92987 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:52:33 +0200 Subject: [PATCH 179/726] spell replacement works --- client/ClientNetPackVisitors.h | 1 + client/NetPacksClient.cpp | 7 +++++++ client/windows/CCastleInterface.cpp | 28 +++++++++++++++++++++------- client/windows/CCastleInterface.h | 7 ++++--- server/CGameHandler.cpp | 12 ++++-------- server/CGameHandler.h | 1 - 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index 82cb72d53..7b67d4b72 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -37,6 +37,7 @@ public: void visitHeroVisitCastle(HeroVisitCastle & pack) override; void visitSetMana(SetMana & pack) override; void visitSetMovePoints(SetMovePoints & pack) override; + void visitSetTownSpells(SetTownSpells & pack) override; void visitFoWChange(FoWChange & pack) override; void visitChangeStackCount(ChangeStackCount & pack) override; void visitSetStackType(SetStackType & pack) override; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index cda16c4b3..e26516078 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -14,6 +14,7 @@ #include "CPlayerInterface.h" #include "CGameInfo.h" #include "windows/GUIClasses.h" +#include "windows/CCastleInterface.h" #include "mapView/mapHandler.h" #include "adventureMap/AdventureMapInterface.h" #include "adventureMap/CInGameConsole.h" @@ -172,6 +173,12 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h); } +void ApplyClientNetPackVisitor::visitSetTownSpells(SetTownSpells & pack) +{ + for(const auto & win : GH.windows().findWindows()) + win->update(); +} + void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack) { for(auto &i : cl.playerint) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 56211c500..7ee8dfeb1 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1966,7 +1966,7 @@ void CFortScreen::RecruitArea::showPopupWindow(const Point & cursorPosition) } CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & imagename) - : CWindowObject(BORDERED, imagename), town(owner->town) + : CWindowObject(BORDERED, imagename), townId(owner->town->id) { OBJECT_CONSTRUCTION; @@ -1982,6 +1982,12 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN); + update(); +} + +void CMageGuildScreen::update() +{ + OBJECT_CONSTRUCTION; static const std::vector > positions = { {Point(222,445), Point(312,445), Point(402,445), Point(520,445), Point(610,445), Point(700,445)}, @@ -1991,21 +1997,28 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i {Point(491,325), Point(591,325)} }; - for(size_t i=0; itown->town->mageLevel; i++) + spells.clear(); + emptyScrolls.clear(); + + const CGTownInstance * town = LOCPLINT->cb->getTown(townId); + + for(size_t i=0; itown->mageLevel; i++) { - size_t spellCount = owner->town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? + size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? for(size_t j=0; jtown->mageGuildLevel() && owner->town->spells[i].size()>j) - spells.push_back(std::make_shared(positions[i][j], owner->town->spells[i][j].toSpell(), town)); + if(imageGuildLevel() && town->spells[i].size()>j) + spells.push_back(std::make_shared(positions[i][j], town->spells[i][j].toSpell(), townId)); else emptyScrolls.push_back(std::make_shared(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y)); } } + + redraw(); } -CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, const CGTownInstance *town) - : spell(Spell), town(town) +CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId) + : spell(Spell), townId(townId) { OBJECT_CONSTRUCTION; @@ -2017,6 +2030,7 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, const CGTo void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { + const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) LOCPLINT->cb->spellResearch(town); else diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 3091d48ab..a0d338555 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -379,10 +379,10 @@ class CMageGuildScreen : public CStatusbarWindow { const CSpell * spell; std::shared_ptr image; - const CGTownInstance *town; + ObjectInstanceID townId; public: - Scroll(Point position, const CSpell *Spell, const CGTownInstance *town); + Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId); void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void hover(bool on) override; @@ -394,10 +394,11 @@ class CMageGuildScreen : public CStatusbarWindow std::shared_ptr resdatabar; - const CGTownInstance *town; + ObjectInstanceID townId; public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); + void update(); }; /// The blacksmith window where you can buy available in town war machine diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c14248b54..4c28225a9 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2248,21 +2248,17 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid) return false; CGTownInstance *t = gs->getTown(tid); - auto spells = t->spells.at(1); + auto spells = t->spells.at(0); auto spell = SpellID(SpellID::FLY); spells.at(0) = spell; - setTownSpells(t, 1, spells); - spellResearchFinished(tid); - return true; -} + setTownSpells(t, 0, spells); -void CGameHandler::spellResearchFinished(ObjectInstanceID tid) -{ - const CGTownInstance * t = getTown(tid); if(t->visitingHero) giveSpells(t, t->visitingHero); if(t->garrisonHero) giveSpells(t, t->garrisonHero); + + return true; } bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dstid, CreatureID crid, ui32 cram, si32 fromLvl, PlayerColor player) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index b1540b636..e446aec7c 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -220,7 +220,6 @@ public: bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); bool spellResearch(ObjectInstanceID tid); - void spellResearchFinished(ObjectInstanceID tid); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); From 5b2aa4dc717b2a530f65d99989c9997884c9f5aa Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 00:34:25 +0200 Subject: [PATCH 180/726] swapping spells --- CCallback.cpp | 4 ++-- CCallback.h | 4 ++-- client/windows/CCastleInterface.cpp | 2 +- lib/networkPacks/PacksForServer.h | 6 ++++-- server/CGameHandler.cpp | 22 +++++++++++++++++----- server/CGameHandler.h | 2 +- server/NetPacksServer.cpp | 2 +- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index e5953394e..a126475d3 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -249,9 +249,9 @@ int CBattleCallback::sendRequest(const CPackForServer * request) return requestID; } -void CCallback::spellResearch( const CGTownInstance *town ) +void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot ) { - SpellResearch pack(town->id); + SpellResearch pack(town->id, spellAtSlot); sendRequest(&pack); } diff --git a/CCallback.h b/CCallback.h index 3e9778387..74f9b0fda 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,7 +78,7 @@ public: virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made - virtual void spellResearch(const CGTownInstance *town)=0; + virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot)=0; virtual void swapGarrisonHero(const CGTownInstance *town)=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 @@ -188,7 +188,7 @@ public: bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; void endTurn() override; - void spellResearch(const CGTownInstance *town) override; + void spellResearch(const CGTownInstance *town, SpellID spellAtSlot) override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 7ee8dfeb1..457871a3a 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2032,7 +2032,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) - LOCPLINT->cb->spellResearch(town); + LOCPLINT->cb->spellResearch(town, spell->id); else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); } diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index b35254f23..059205973 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -309,11 +309,12 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure struct DLL_LINKAGE SpellResearch : public CPackForServer { SpellResearch() = default; - SpellResearch(const ObjectInstanceID & TID) - : tid(TID) + SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot) + : tid(TID), spellAtSlot(spellAtSlot) { } ObjectInstanceID tid; + SpellID spellAtSlot; void visitTyped(ICPackVisitor & visitor) override; @@ -321,6 +322,7 @@ struct DLL_LINKAGE SpellResearch : public CPackForServer { h & static_cast(*this); h & tid; + h & spellAtSlot; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4c28225a9..607fdf620 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2242,16 +2242,28 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } -bool CGameHandler::spellResearch(ObjectInstanceID tid) +bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) { if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) return false; CGTownInstance *t = gs->getTown(tid); - auto spells = t->spells.at(0); - auto spell = SpellID(SpellID::FLY); - spells.at(0) = spell; - setTownSpells(t, 0, spells); + + int level = -1; + for(int i = 0; i < t->spells.size(); i++) + if(vstd::find_pos(t->spells[i], spellAtSlot) != -1) + level = i; + + if(level == -1 && complain("Spell for replacement not found!")) + return false; + + auto spells = t->spells.at(level); + + std::swap(spells.at(t->spellsAtLevel(level, false)), spells.at(vstd::find_pos(spells, spellAtSlot))); + auto it = spells.begin() + t->spellsAtLevel(level, false); + std::rotate(it, it + 1, spells.end()); // move to end + + setTownSpells(t, level, spells); if(t->visitingHero) giveSpells(t, t->visitingHero); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e446aec7c..fabba227e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -219,7 +219,7 @@ public: bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); - bool spellResearch(ObjectInstanceID tid); + bool spellResearch(ObjectInstanceID tid, SpellID spellAtSlot); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 90e8f2062..b8f71167b 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -140,7 +140,7 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) { - result = gh.spellResearch(pack.tid); + result = gh.spellResearch(pack.tid, pack.spellAtSlot); } void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) From 7707adc44f29c802b7a62e36d461261b75ef5fb2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:18:10 +0200 Subject: [PATCH 181/726] checks on server --- lib/mapObjects/CGTownInstance.cpp | 3 ++- lib/mapObjects/CGTownInstance.h | 4 ++++ lib/serializer/ESerializationVersion.h | 3 ++- server/CGameHandler.cpp | 18 ++++++++++++++++++ server/NetPacksServer.cpp | 3 +++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 2c040da6b..500ce19b0 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -268,7 +268,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): built(0), destroyed(0), identifier(0), - alignmentToPlayer(PlayerColor::NEUTRAL) + alignmentToPlayer(PlayerColor::NEUTRAL), + lastSpellResearchDay(0) { this->setNodeType(CBonusSystemNode::TOWN); } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fab98714e..e14802fd4 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -73,6 +73,7 @@ public: std::vector > spells; //spells[level] -> vector of spells, first will be available in guild std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); + int lastSpellResearchDay; ////////////////////////////////////////////////////////////////////////// template void serialize(Handler &h) @@ -93,6 +94,9 @@ public: h & spells; h & events; + if (h.version >= Handler::Version::SPELL_RESEARCH) + h & lastSpellResearchDay; + if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS) { h & rewardableBuildings; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index dd0deb6b0..b4be54223 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -61,6 +61,7 @@ enum class ESerializationVersion : int32_t CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects REGION_LABEL, // 864 - labels for campaign regions + SPELL_RESEARCH, // 865 - spell research - CURRENT = REGION_LABEL + CURRENT = SPELL_RESEARCH }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 607fdf620..fb9625012 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2256,6 +2256,24 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) if(level == -1 && complain("Spell for replacement not found!")) return false; + + int daysSinceLastResearch = gs->getDate(Date::DAY) - t->lastSpellResearchDay; + if(!daysSinceLastResearch && complain("Already researched today!")) + return false; + + TResources cost; + cost[EGameResID::GOLD] = 1000; + cost[EGameResID::MERCURY] = (level + 1) * 2; + cost[EGameResID::SULFUR] = (level + 1) * 2; + cost[EGameResID::CRYSTAL] = (level + 1) * 2; + cost[EGameResID::GEMS] = (level + 1) * 2; + + if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) + return false; + + giveResources(t->getOwner(), -cost); + + t->lastSpellResearchDay = gs->getDate(Date::DAY); auto spells = t->spells.at(level); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index b8f71167b..b4227f1ea 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -140,6 +140,9 @@ void ApplyGhNetPackVisitor::visitBuildStructure(BuildStructure & pack) void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) { + gh.throwIfWrongOwner(&pack, pack.tid); + gh.throwIfPlayerNotActive(&pack); + result = gh.spellResearch(pack.tid, pack.spellAtSlot); } From 3559f9f9236c54b54973b5ba9873a015852dfa2f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 01:47:32 +0200 Subject: [PATCH 182/726] HMI for spell research --- Mods/vcmi/config/vcmi/english.json | 4 ++++ Mods/vcmi/config/vcmi/german.json | 4 ++++ client/windows/CCastleInterface.cpp | 32 ++++++++++++++++++++++++++++- config/gameConfig.json | 2 +- lib/gameState/CGameState.cpp | 2 +- lib/networkPacks/NetPacksLib.cpp | 1 + server/CGameHandler.cpp | 2 -- 7 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..692b19b46 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -59,6 +59,10 @@ "vcmi.spellBook.search" : "search...", + "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", + "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", + "vcmi.spellResearch.pay" : "Would you like to research a new spell and replace this?", + "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", "vcmi.mainMenu.serverConnectionFailed" : "Failed to connect", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index efe786220..6ef4f4353 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -58,6 +58,10 @@ "vcmi.spellBook.search" : "suchen...", + "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", + "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", + "vcmi.spellResearch.pay" : "Möchtet Ihr einen neuen Zauberspruch erforschen und diesen ersetzen?", + "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", "vcmi.mainMenu.serverConnectionFailed" : "Verbindung fehlgeschlagen", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 457871a3a..045e4fb3a 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2032,7 +2032,37 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) - LOCPLINT->cb->spellResearch(town, spell->id); + { + int daysSinceLastResearch = LOCPLINT->cb->getDate(Date::DAY) - town->lastSpellResearchDay; + if(!daysSinceLastResearch) + { + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); + return; + } + + int level = -1; + for(int i = 0; i < town->spells.size(); i++) + if(vstd::find_pos(town->spells[i], spell->id) != -1) + level = i; + + TResources cost; + cost[EGameResID::GOLD] = 1000; + cost[EGameResID::MERCURY] = (level + 1) * 2; + cost[EGameResID::SULFUR] = (level + 1) * 2; + cost[EGameResID::CRYSTAL] = (level + 1) * 2; + cost[EGameResID::GEMS] = (level + 1) * 2; + + std::vector> resComps; + for(TResources::nziterator i(cost); i.valid(); i++) + { + resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal)); + } + + if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) + LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id); }, nullptr, resComps); + else + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); + } else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); } diff --git a/config/gameConfig.json b/config/gameConfig.json index 9e4e5a4a3..3f439f223 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -313,7 +313,7 @@ // Chances for a town with default buildings to receive corresponding dwelling level built in start "startingDwellingChances": [100, 50], // Enable spell research in mage guild - "spellResearch": true + "spellResearch": false }, "combat": diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index fed910807..31bd4e2de 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -915,7 +915,7 @@ void CGameState::initTowns() vti->spells[s->getLevel()-1].push_back(s->id); vti->possibleSpells -= s->id; } - vti->possibleSpells.clear(); //SR + vti->possibleSpells.clear(); } } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index a2cb60448..89207fa1c 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -944,6 +944,7 @@ void SetTownSpells::applyGs(CGameState *gs) CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; + town->lastSpellResearchDay = gs->getDate(Date::DAY); } void SetMana::applyGs(CGameState *gs) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index fb9625012..6620f0c74 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2273,8 +2273,6 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) giveResources(t->getOwner(), -cost); - t->lastSpellResearchDay = gs->getDate(Date::DAY); - auto spells = t->spells.at(level); std::swap(spells.at(t->spellsAtLevel(level, false)), spells.at(vstd::find_pos(spells, spellAtSlot))); From afb90c076d4a60decd2ac67bea592374b132509f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:34:18 +0200 Subject: [PATCH 183/726] better UI --- Mods/vcmi/config/vcmi/english.json | 2 +- Mods/vcmi/config/vcmi/german.json | 2 +- client/widgets/CComponent.cpp | 4 +++- client/widgets/CComponent.h | 1 + client/windows/CCastleInterface.cpp | 4 +++- test/mock/mock_IGameCallback.h | 1 + 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 692b19b46..cc936dbf9 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -61,7 +61,7 @@ "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", - "vcmi.spellResearch.pay" : "Would you like to research a new spell and replace this?", + "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one?", "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 6ef4f4353..26deaf5c0 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -60,7 +60,7 @@ "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", - "vcmi.spellResearch.pay" : "Möchtet Ihr einen neuen Zauberspruch erforschen und diesen ersetzen?", + "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen?", "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index 2ece8d515..f02ba67b9 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -70,6 +70,7 @@ void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optiona customSubtitle = ValText; size = imageSize; font = fnt; + newLine = false; assert(size < sizeInvalid); @@ -471,7 +472,8 @@ void CComponentBox::placeComponents(bool selectable) //start next row if ((pos.w != 0 && rows.back().width + comp->pos.w + distance > pos.w) // row is full - || rows.back().comps >= componentsInRow) + || rows.back().comps >= componentsInRow + || (prevComp && prevComp->newLine)) { prevComp = nullptr; rows.push_back (RowData (0,0,0)); diff --git a/client/widgets/CComponent.h b/client/widgets/CComponent.h index f4d360460..d4c1acc3f 100644 --- a/client/widgets/CComponent.h +++ b/client/widgets/CComponent.h @@ -52,6 +52,7 @@ public: std::string customSubtitle; ESize size; //component size. EFonts font; //Font size of label + bool newLine; //Line break after component std::string getDescription() const; std::string getSubtitle() const; diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 045e4fb3a..6a5ce592f 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2053,9 +2053,11 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) cost[EGameResID::GEMS] = (level + 1) * 2; std::vector> resComps; + resComps.push_back(std::make_shared(ComponentType::SPELL_SCROLL, town->spells[level].at(town->spellsAtLevel(level, false)))); + resComps.back()->newLine = true; for(TResources::nziterator i(cost); i.valid(); i++) { - resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal)); + resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 1f2456a2f..1cec76e76 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -44,6 +44,7 @@ public: void showInfoDialog(InfoWindow * iw) override {} void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override {} + void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {} bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {} void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} From 5bb29732d0e2a39175de0ee2b19b9ffbe8103cad Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:53:57 +0200 Subject: [PATCH 184/726] spell description, not spell roll --- client/windows/CCastleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 6a5ce592f..8fd68dbe2 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2053,7 +2053,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) cost[EGameResID::GEMS] = (level + 1) * 2; std::vector> resComps; - resComps.push_back(std::make_shared(ComponentType::SPELL_SCROLL, town->spells[level].at(town->spellsAtLevel(level, false)))); + resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); resComps.back()->newLine = true; for(TResources::nziterator i(cost); i.valid(); i++) { From 1558cbdfa91fb204117464362b5769125996111b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:45:04 +0200 Subject: [PATCH 185/726] turn timer and simturn change on load --- lib/gameState/CGameState.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 31bd4e2de..d5f147362 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -267,6 +267,8 @@ void CGameState::updateOnLoad(StartInfo * si) for(auto & i : si->playerInfos) gs->players[i.first].human = i.second.isControlledByHuman(); scenarioOps->extraOptionsInfo = si->extraOptionsInfo; + scenarioOps->turnTimerInfo = si->turnTimerInfo; + scenarioOps->simturnsInfo = si->simturnsInfo; } void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking) From 2052a2603175951477b586745433a0b5b4b17981 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:25:11 +0200 Subject: [PATCH 186/726] code review --- client/NetPacksClient.cpp | 2 +- client/windows/CCastleInterface.cpp | 15 ++++++--------- client/windows/CCastleInterface.h | 2 +- config/gameConfig.json | 6 +++++- config/schemas/gameSettings.json | 8 +++++--- lib/GameSettings.cpp | 2 ++ lib/IGameSettings.h | 2 ++ lib/mapObjects/CGTownInstance.cpp | 3 ++- lib/mapObjects/CGTownInstance.h | 1 + lib/mapping/MapFormatH3M.cpp | 5 +---- server/CGameHandler.cpp | 15 ++++++--------- 11 files changed, 32 insertions(+), 29 deletions(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index e26516078..1ae453d63 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -176,7 +176,7 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) void ApplyClientNetPackVisitor::visitSetTownSpells(SetTownSpells & pack) { for(const auto & win : GH.windows().findWindows()) - win->update(); + win->updateSpells(); } void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 8fd68dbe2..b0751e5a9 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1982,10 +1982,10 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN); - update(); + updateSpells(); } -void CMageGuildScreen::update() +void CMageGuildScreen::updateSpells() { OBJECT_CONSTRUCTION; static const std::vector > positions = @@ -2031,7 +2031,7 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInst void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) { const CGTownInstance * town = LOCPLINT->cb->getTown(townId); - if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH)) + if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && town->spellResearchAllowed) { int daysSinceLastResearch = LOCPLINT->cb->getDate(Date::DAY) - town->lastSpellResearchDay; if(!daysSinceLastResearch) @@ -2045,12 +2045,9 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) if(vstd::find_pos(town->spells[i], spell->id) != -1) level = i; - TResources cost; - cost[EGameResID::GOLD] = 1000; - cost[EGameResID::MERCURY] = (level + 1) * 2; - cost[EGameResID::SULFUR] = (level + 1) * 2; - cost[EGameResID::CRYSTAL] = (level + 1) * 2; - cost[EGameResID::GEMS] = (level + 1) * 2; + auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); + auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); + auto cost = costBase + costPerLevel * (level + 1); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index a0d338555..b5fd3d7ab 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -398,7 +398,7 @@ class CMageGuildScreen : public CStatusbarWindow public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); - void update(); + void updateSpells(); }; /// The blacksmith window where you can buy available in town war machine diff --git a/config/gameConfig.json b/config/gameConfig.json index 3f439f223..ae79b22ba 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -313,7 +313,11 @@ // Chances for a town with default buildings to receive corresponding dwelling level built in start "startingDwellingChances": [100, 50], // Enable spell research in mage guild - "spellResearch": false + "spellResearch": false, + // Base cost for an spell research + "spellResearchCostBase": { "gold": 1000 }, + // Costs depends on level for an spell research + "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 } }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 67ef54701..e98bf8a0e 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -51,9 +51,11 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" }, - "spellResearch" : { "type" : "boolean" } + "buildingsPerTurnCap" : { "type" : "number" }, + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" }, + "spellResearchCostBase" : { "type" : "object" }, + "spellResearchCostPerLevel" : { "type" : "object" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 4525b89c7..2a6077928 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -102,6 +102,8 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index c75726a49..5b3b81a9d 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -80,6 +80,8 @@ enum class EGameSettings TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, TOWNS_SPELL_RESEARCH, + TOWNS_SPELL_RESEARCH_COST_BASE, + TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 500ce19b0..c8ccb57f2 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -269,7 +269,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): destroyed(0), identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), - lastSpellResearchDay(0) + lastSpellResearchDay(0), + spellResearchAllowed(true) { this->setNodeType(CBonusSystemNode::TOWN); } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index e14802fd4..204bdb51e 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -74,6 +74,7 @@ public: std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); int lastSpellResearchDay; + bool spellResearchAllowed; ////////////////////////////////////////////////////////////////////////// template void serialize(Handler &h) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 32e0057ac..4d46aba86 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2235,10 +2235,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt } if(features.levelHOTA1) - { - // TODO: HOTA support - [[maybe_unused]] bool spellResearchAvailable = reader->readBool(); - } + object->spellResearchAllowed = reader->readBool(); // Read castle events uint32_t eventsCount = reader->readUInt32(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 6620f0c74..47233d034 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2244,11 +2244,11 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) { - if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) - return false; - CGTownInstance *t = gs->getTown(tid); + if(!(getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && t->spellResearchAllowed) && complain("Spell research not allowed!")) + return false; + int level = -1; for(int i = 0; i < t->spells.size(); i++) if(vstd::find_pos(t->spells[i], spellAtSlot) != -1) @@ -2261,12 +2261,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) if(!daysSinceLastResearch && complain("Already researched today!")) return false; - TResources cost; - cost[EGameResID::GOLD] = 1000; - cost[EGameResID::MERCURY] = (level + 1) * 2; - cost[EGameResID::SULFUR] = (level + 1) * 2; - cost[EGameResID::CRYSTAL] = (level + 1) * 2; - cost[EGameResID::GEMS] = (level + 1) * 2; + auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); + auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); + auto cost = costBase + costPerLevel * (level + 1); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; From d929bfb9d1fa09e457d7622c3327eba7962f03a5 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 15:05:13 +0200 Subject: [PATCH 187/726] rename; introduce factor --- CCallback.cpp | 4 +- CCallback.h | 4 +- client/Client.h | 2 +- client/ClientNetPackVisitors.h | 2 +- client/NetPacksClient.cpp | 2 +- client/windows/CCastleInterface.cpp | 4 +- config/gameConfig.json | 6 +- config/schemas/gameSettings.json | 11 +-- lib/GameSettings.cpp | 135 ++++++++++++++-------------- lib/IGameCallback.h | 2 +- lib/IGameSettings.h | 1 + lib/mapObjects/CGTownInstance.cpp | 1 + lib/mapObjects/CGTownInstance.h | 5 ++ lib/networkPacks/NetPackVisitor.h | 2 +- lib/networkPacks/NetPacksLib.cpp | 8 +- lib/networkPacks/PacksForClient.h | 4 +- lib/networkPacks/PacksForServer.h | 4 +- lib/serializer/RegisterTypes.h | 2 +- server/CGameHandler.cpp | 11 +-- server/CGameHandler.h | 4 +- server/NetPacksServer.cpp | 2 +- test/mock/mock_IGameCallback.h | 2 +- 22 files changed, 118 insertions(+), 100 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index a126475d3..7a4e2a3f5 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -249,9 +249,9 @@ int CBattleCallback::sendRequest(const CPackForServer * request) return requestID; } -void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot ) +void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot, bool accepted ) { - SpellResearch pack(town->id, spellAtSlot); + SpellResearch pack(town->id, spellAtSlot, accepted); sendRequest(&pack); } diff --git a/CCallback.h b/CCallback.h index 74f9b0fda..1bf7d1d7d 100644 --- a/CCallback.h +++ b/CCallback.h @@ -78,7 +78,7 @@ public: virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0; virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0; virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made - virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot)=0; + virtual void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted)=0; virtual void swapGarrisonHero(const CGTownInstance *town)=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 @@ -188,7 +188,7 @@ public: bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; void endTurn() override; - void spellResearch(const CGTownInstance *town, SpellID spellAtSlot) override; + void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override; void swapGarrisonHero(const CGTownInstance *town) override; void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; diff --git a/client/Client.h b/client/Client.h index cfb00e26c..5e5723338 100644 --- a/client/Client.h +++ b/client/Client.h @@ -159,7 +159,7 @@ public: friend class CBattleCallback; //handling players actions void changeSpells(const CGHeroInstance * hero, bool give, const std::set & spells) override {}; - void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {}; + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override {}; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index 7b67d4b72..3198f74fe 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -37,7 +37,7 @@ public: void visitHeroVisitCastle(HeroVisitCastle & pack) override; void visitSetMana(SetMana & pack) override; void visitSetMovePoints(SetMovePoints & pack) override; - void visitSetTownSpells(SetTownSpells & pack) override; + void visitSetResearchedSpells(SetResearchedSpells & pack) override; void visitFoWChange(FoWChange & pack) override; void visitChangeStackCount(ChangeStackCount & pack) override; void visitSetStackType(SetStackType & pack) override; diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 1ae453d63..75618089f 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -173,7 +173,7 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h); } -void ApplyClientNetPackVisitor::visitSetTownSpells(SetTownSpells & pack) +void ApplyClientNetPackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack) { for(const auto & win : GH.windows().findWindows()) win->updateSpells(); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index b0751e5a9..776192009 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2047,7 +2047,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = costBase + costPerLevel * (level + 1); + auto cost = (costBase + costPerLevel * (level + 1)) * (town->spellResearchCounter + 1); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); @@ -2058,7 +2058,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) } if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) - LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id); }, nullptr, resComps); + LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }, nullptr, resComps); else LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); } diff --git a/config/gameConfig.json b/config/gameConfig.json index ae79b22ba..1848e15af 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -302,7 +302,7 @@ "backpackSize" : -1, // if heroes are invitable in tavern "tavernInvite" : false, - // minimai primary skills for heroes + // minimal primary skills for heroes "minimalPrimarySkills": [ 0, 0, 1, 1] }, @@ -317,7 +317,9 @@ // Base cost for an spell research "spellResearchCostBase": { "gold": 1000 }, // Costs depends on level for an spell research - "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 } + "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, + // Factor for increasing cost for each research + "spellResearchCostFactorPerResearch": 2.0 }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index e98bf8a0e..166b27961 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -51,11 +51,12 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" }, - "spellResearch" : { "type" : "boolean" }, - "spellResearchCostBase" : { "type" : "object" }, - "spellResearchCostPerLevel" : { "type" : "object" } + "buildingsPerTurnCap" : { "type" : "number" }, + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" }, + "spellResearchCostBase" : { "type" : "object" }, + "spellResearchCostPerLevel" : { "type" : "object" }, + "spellResearchCostFactorPerResearch" : { "type" : "number" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 2a6077928..3fe782a58 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -37,73 +37,74 @@ GameSettings::GameSettings() = default; GameSettings::~GameSettings() = default; const std::vector GameSettings::settingProperties = { - {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, - {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, - {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, - {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, - {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, - {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, - {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, - {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, - {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, - {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, - {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, - {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, - {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, - {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, - {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, - {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"}, - {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit"}, - {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, - {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, - {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, - {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, - {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, - {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, - {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, - {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, - {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, - {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, - {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, - {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, - {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, - {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, - {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, - {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, - {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, - {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, - {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, - {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, - {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, - {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, - {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, - {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, - {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, - {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, - {EGameSettings::TEXTS_FACTION, "textData", "faction" }, - {EGameSettings::TEXTS_HERO, "textData", "hero" }, - {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, - {EGameSettings::TEXTS_OBJECT, "textData", "object" }, - {EGameSettings::TEXTS_RIVER, "textData", "river" }, - {EGameSettings::TEXTS_ROAD, "textData", "road" }, - {EGameSettings::TEXTS_SPELL, "textData", "spell" }, - {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, - {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, - {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, - {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, + {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, + {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, + {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, + {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, + {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, + {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, + {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, + {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, + {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, + {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, + {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, + {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, + {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, + {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, + {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles" }, + {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit" }, + {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, + {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, + {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, + {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, + {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, + {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, + {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, + {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, + {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, + {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, + {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, + {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, + {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, + {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, + {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, + {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, + {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, + {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, + {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, + {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, + {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, + {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, + {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, + {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, + {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, + {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, + {EGameSettings::TEXTS_FACTION, "textData", "faction" }, + {EGameSettings::TEXTS_HERO, "textData", "hero" }, + {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, + {EGameSettings::TEXTS_OBJECT, "textData", "object" }, + {EGameSettings::TEXTS_RIVER, "textData", "river" }, + {EGameSettings::TEXTS_ROAD, "textData", "road" }, + {EGameSettings::TEXTS_SPELL, "textData", "spell" }, + {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, + {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, + {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, "towns", "spellResearchCostFactorPerResearch" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 9e1e8c1ca..3d44b451e 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -94,7 +94,7 @@ public: virtual void showInfoDialog(InfoWindow * iw) = 0; virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells)=0; - virtual void setTownSpells(const CGTownInstance * town, int level, const std::vector spells)=0; + virtual void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted)=0; virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 5b3b81a9d..11683ed20 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -82,6 +82,7 @@ enum class EGameSettings TOWNS_SPELL_RESEARCH, TOWNS_SPELL_RESEARCH_COST_BASE, TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, + TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index c8ccb57f2..fbd2a76c6 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -270,6 +270,7 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), lastSpellResearchDay(0), + spellResearchCounter(0), spellResearchAllowed(true) { this->setNodeType(CBonusSystemNode::TOWN); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 204bdb51e..fb5eb0af7 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -74,6 +74,7 @@ public: std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); int lastSpellResearchDay; + int spellResearchCounter; bool spellResearchAllowed; ////////////////////////////////////////////////////////////////////////// @@ -96,7 +97,11 @@ public: h & events; if (h.version >= Handler::Version::SPELL_RESEARCH) + { h & lastSpellResearchDay; + h & spellResearchCounter; + h & spellResearchAllowed; + } if (h.version >= Handler::Version::NEW_TOWN_BUILDINGS) { diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 4cfb0e402..007f30fa6 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -42,7 +42,7 @@ public: virtual void visitSetSecSkill(SetSecSkill & pack) {} virtual void visitHeroVisitCastle(HeroVisitCastle & pack) {} virtual void visitChangeSpells(ChangeSpells & pack) {} - virtual void visitSetTownSpells(SetTownSpells & pack) {} + virtual void visitSetResearchedSpells(SetResearchedSpells & pack) {} virtual void visitSetMana(SetMana & pack) {} virtual void visitSetMovePoints(SetMovePoints & pack) {} virtual void visitFoWChange(FoWChange & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 89207fa1c..785052430 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -162,9 +162,9 @@ void ChangeSpells::visitTyped(ICPackVisitor & visitor) visitor.visitChangeSpells(*this); } -void SetTownSpells::visitTyped(ICPackVisitor & visitor) +void SetResearchedSpells::visitTyped(ICPackVisitor & visitor) { - visitor.visitSetTownSpells(*this); + visitor.visitSetResearchedSpells(*this); } void SetMana::visitTyped(ICPackVisitor & visitor) { @@ -939,12 +939,14 @@ void ChangeSpells::applyGs(CGameState *gs) hero->removeSpellFromSpellbook(sid); } -void SetTownSpells::applyGs(CGameState *gs) +void SetResearchedSpells::applyGs(CGameState *gs) { CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; town->lastSpellResearchDay = gs->getDate(Date::DAY); + if(accepted) + town->spellResearchCounter++; } void SetMana::applyGs(CGameState *gs) diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index ae1d23167..e992e4e21 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -288,7 +288,7 @@ struct DLL_LINKAGE ChangeSpells : public CPackForClient } }; -struct DLL_LINKAGE SetTownSpells : public CPackForClient +struct DLL_LINKAGE SetResearchedSpells : public CPackForClient { void applyGs(CGameState * gs) override; @@ -297,12 +297,14 @@ struct DLL_LINKAGE SetTownSpells : public CPackForClient ui8 level = 0; ObjectInstanceID tid; std::vector spells; + bool accepted; template void serialize(Handler & h) { h & level; h & tid; h & spells; + h & accepted; } }; diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index 059205973..202d1bb7b 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -309,12 +309,13 @@ struct DLL_LINKAGE RazeStructure : public BuildStructure struct DLL_LINKAGE SpellResearch : public CPackForServer { SpellResearch() = default; - SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot) + SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot, bool accepted) : tid(TID), spellAtSlot(spellAtSlot) { } ObjectInstanceID tid; SpellID spellAtSlot; + bool accepted; void visitTyped(ICPackVisitor & visitor) override; @@ -323,6 +324,7 @@ struct DLL_LINKAGE SpellResearch : public CPackForServer h & static_cast(*this); h & tid; h & spellAtSlot; + h & accepted; } }; diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 6fcbc5e20..f716c7be4 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -289,7 +289,7 @@ void registerTypes(Serializer &s) s.template registerType(239); s.template registerType(240); s.template registerType(241); - s.template registerType(242); + s.template registerType(242); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 47233d034..9518078b7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1235,12 +1235,13 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st sendAndApply(&cs); } -void CGameHandler::setTownSpells(const CGTownInstance * town, int level, const std::vector spells) +void CGameHandler::setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) { - SetTownSpells cs; + SetResearchedSpells cs; cs.tid = town->id; cs.spells = spells; cs.level = level; + cs.accepted = accepted; sendAndApply(&cs); } @@ -2242,7 +2243,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) return true; } -bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) +bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool accepted) { CGTownInstance *t = gs->getTown(tid); @@ -2263,7 +2264,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = costBase + costPerLevel * (level + 1); + auto cost = (costBase + costPerLevel * (level + 1)) * (t->spellResearchCounter + 1); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; @@ -2276,7 +2277,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot) auto it = spells.begin() + t->spellsAtLevel(level, false); std::rotate(it, it + 1, spells.end()); // move to end - setTownSpells(t, level, spells); + setResearchedSpells(t, level, spells, accepted); if(t->visitingHero) giveSpells(t, t->visitingHero); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index fabba227e..69a83928c 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -107,7 +107,7 @@ public: //from IGameCallback //do sth void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override; - void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override; + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; void giveExperience(const CGHeroInstance * hero, TExpType val) override; @@ -219,7 +219,7 @@ public: bool buildStructure(ObjectInstanceID tid, BuildingID bid, bool force=false);//force - for events: no cost, no checkings bool visitTownBuilding(ObjectInstanceID tid, BuildingID bid); bool razeStructure(ObjectInstanceID tid, BuildingID bid); - bool spellResearch(ObjectInstanceID tid, SpellID spellAtSlot); + bool spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool accepted); bool disbandCreature( ObjectInstanceID id, SlotID pos ); bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); bool bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index b4227f1ea..9713810fe 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -143,7 +143,7 @@ void ApplyGhNetPackVisitor::visitSpellResearch(SpellResearch & pack) gh.throwIfWrongOwner(&pack, pack.tid); gh.throwIfPlayerNotActive(&pack); - result = gh.spellResearch(pack.tid, pack.spellAtSlot); + result = gh.spellResearch(pack.tid, pack.spellAtSlot, pack.accepted); } void ApplyGhNetPackVisitor::visitVisitTownBuilding(VisitTownBuilding & pack) diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 1cec76e76..8f67f41a6 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -44,7 +44,7 @@ public: void showInfoDialog(InfoWindow * iw) override {} void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override {} - void setTownSpells(const CGTownInstance * town, int level, const std::vector spells) override {} + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override {} bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {} void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} From f94f0a3274f330a900d4cbf2b190973b49986689 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 15:51:53 +0200 Subject: [PATCH 188/726] new dialog --- Mods/vcmi/config/vcmi/english.json | 5 +- Mods/vcmi/config/vcmi/german.json | 5 +- client/windows/CCastleInterface.cpp | 29 +++++- config/gameConfig.json | 4 +- config/schemas/gameSettings.json | 12 +-- lib/GameSettings.cpp | 136 ++++++++++++++-------------- lib/IGameSettings.h | 2 +- lib/networkPacks/PacksForServer.h | 2 +- server/CGameHandler.cpp | 16 +++- 9 files changed, 123 insertions(+), 88 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index cc936dbf9..11ed66eaa 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -61,7 +61,10 @@ "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", - "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one?", + "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one or skip this spell?", + "vcmi.spellResearch.research" : "Research this Spell", + "vcmi.spellResearch.skip" : "Skip this Spell", + "vcmi.spellResearch.abort" : "Abort", "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 26deaf5c0..38f5428e8 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -60,7 +60,10 @@ "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", - "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen?", + "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen oder diesen überspringen?", + "vcmi.spellResearch.research" : "Erforsche diesen Zauberspruch", + "vcmi.spellResearch.skip" : "Überspringe diesen Zauberspruch", + "vcmi.spellResearch.abort" : "Abbruch", "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 776192009..8bc898ddc 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2047,7 +2047,8 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = (costBase + costPerLevel * (level + 1)) * (town->spellResearchCounter + 1); + auto costExponent = LOCPLINT->cb->getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); + auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(town->spellResearchCounter + 1, costExponent); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); @@ -2057,10 +2058,28 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } - if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) - LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.spellResearch.pay"), [this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }, nullptr, resComps); - else - LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); + auto showSpellResearchDialog = [this, resComps, town, cost](){ + std::vector>> pom; + pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); + pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); + pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + auto temp = std::make_shared(CGI->generaltexth->translate("vcmi.spellResearch.pay"), LOCPLINT->playerID, resComps, pom); + + temp->buttons[0]->addCallback([this, resComps, town, cost](){ + if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) + LOCPLINT->cb->spellResearch(town, spell->id, true); + else + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); + }); + temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); + temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); + temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); + temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); + + GH.windows().pushWindow(temp); + }; + + showSpellResearchDialog(); } else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); diff --git a/config/gameConfig.json b/config/gameConfig.json index 1848e15af..08f2ce659 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -318,8 +318,8 @@ "spellResearchCostBase": { "gold": 1000 }, // Costs depends on level for an spell research "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, - // Factor for increasing cost for each research - "spellResearchCostFactorPerResearch": 2.0 + // Exponent for increasing cost for each research + "spellResearchCostExponentPerResearch": 1.5 }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 166b27961..da113f7f1 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -51,12 +51,12 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "buildingsPerTurnCap" : { "type" : "number" }, - "startingDwellingChances" : { "type" : "array" }, - "spellResearch" : { "type" : "boolean" }, - "spellResearchCostBase" : { "type" : "object" }, - "spellResearchCostPerLevel" : { "type" : "object" }, - "spellResearchCostFactorPerResearch" : { "type" : "number" } + "buildingsPerTurnCap" : { "type" : "number" }, + "startingDwellingChances" : { "type" : "array" }, + "spellResearch" : { "type" : "boolean" }, + "spellResearchCostBase" : { "type" : "object" }, + "spellResearchCostPerLevel" : { "type" : "object" }, + "spellResearchCostExponentPerResearch" : { "type" : "number" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 3fe782a58..fee70455e 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -37,74 +37,74 @@ GameSettings::GameSettings() = default; GameSettings::~GameSettings() = default; const std::vector GameSettings::settingProperties = { - {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, - {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, - {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, - {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, - {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, - {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, - {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, - {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, - {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, - {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, - {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, - {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, - {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, - {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, - {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, - {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, - {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, - {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, - {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles" }, - {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit" }, - {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, - {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, - {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, - {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, - {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, - {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, - {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, - {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, - {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, - {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, - {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, - {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, - {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, - {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, - {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, - {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, - {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, - {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, - {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, - {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, - {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, - {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, - {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, - {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, - {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, - {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, - {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, - {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, - {EGameSettings::TEXTS_FACTION, "textData", "faction" }, - {EGameSettings::TEXTS_HERO, "textData", "hero" }, - {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, - {EGameSettings::TEXTS_OBJECT, "textData", "object" }, - {EGameSettings::TEXTS_RIVER, "textData", "river" }, - {EGameSettings::TEXTS_ROAD, "textData", "road" }, - {EGameSettings::TEXTS_SPELL, "textData", "spell" }, - {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, - {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, - {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, - {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, "towns", "spellResearchCostFactorPerResearch" }, + {EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" }, + {EGameSettings::BONUSES_GLOBAL, "bonuses", "global" }, + {EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" }, + {EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX, "combat", "areaShotCanTargetEmptyHex" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" }, + {EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP, "combat", "attackPointDamageFactorCap" }, + {EGameSettings::COMBAT_BAD_LUCK_DICE, "combat", "badLuckDice" }, + {EGameSettings::COMBAT_BAD_MORALE_DICE, "combat", "badMoraleDice" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR, "combat", "defensePointDamageFactor" }, + {EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" }, + {EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" }, + {EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" }, + {EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" }, + {EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" }, + {EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" }, + {EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" }, + {EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" }, + {EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" }, + {EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" }, + {EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" }, + {EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles" }, + {EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit" }, + {EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" }, + {EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" }, + {EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" }, + {EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" }, + {EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" }, + {EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" }, + {EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" }, + {EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" }, + {EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" }, + {EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" }, + {EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" }, + {EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" }, + {EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" }, + {EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" }, + {EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" }, + {EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" }, + {EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" }, + {EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" }, + {EGameSettings::MODULE_COMMANDERS, "modules", "commanders" }, + {EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" }, + {EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" }, + {EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" }, + {EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" }, + {EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" }, + {EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" }, + {EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" }, + {EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" }, + {EGameSettings::TEXTS_CREATURE, "textData", "creature" }, + {EGameSettings::TEXTS_FACTION, "textData", "faction" }, + {EGameSettings::TEXTS_HERO, "textData", "hero" }, + {EGameSettings::TEXTS_HERO_CLASS, "textData", "heroClass" }, + {EGameSettings::TEXTS_OBJECT, "textData", "object" }, + {EGameSettings::TEXTS_RIVER, "textData", "river" }, + {EGameSettings::TEXTS_ROAD, "textData", "road" }, + {EGameSettings::TEXTS_SPELL, "textData", "spell" }, + {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, + {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, + {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, "towns", "spellResearchCostExponentPerResearch" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 11683ed20..12dc351c7 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -82,7 +82,7 @@ enum class EGameSettings TOWNS_SPELL_RESEARCH, TOWNS_SPELL_RESEARCH_COST_BASE, TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, - TOWNS_SPELL_RESEARCH_COST_FACTOR_PER_RESEARCH, + TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index 202d1bb7b..d72be5265 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -310,7 +310,7 @@ struct DLL_LINKAGE SpellResearch : public CPackForServer { SpellResearch() = default; SpellResearch(const ObjectInstanceID & TID, SpellID spellAtSlot, bool accepted) - : tid(TID), spellAtSlot(spellAtSlot) + : tid(TID), spellAtSlot(spellAtSlot), accepted(accepted) { } ObjectInstanceID tid; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9518078b7..b295a897d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2257,22 +2257,32 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool if(level == -1 && complain("Spell for replacement not found!")) return false; + + auto spells = t->spells.at(level); int daysSinceLastResearch = gs->getDate(Date::DAY) - t->lastSpellResearchDay; if(!daysSinceLastResearch && complain("Already researched today!")) return false; + if(!accepted) + { + auto it = spells.begin() + t->spellsAtLevel(level, false); + std::rotate(it, it + 1, spells.end()); // move to end + setResearchedSpells(t, level, spells, accepted); + return true; + } + auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto cost = (costBase + costPerLevel * (level + 1)) * (t->spellResearchCounter + 1); + auto costExponent = getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); + + auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(t->spellResearchCounter + 1, costExponent); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; giveResources(t->getOwner(), -cost); - auto spells = t->spells.at(level); - std::swap(spells.at(t->spellsAtLevel(level, false)), spells.at(vstd::find_pos(spells, spellAtSlot))); auto it = spells.begin() + t->spellsAtLevel(level, false); std::rotate(it, it + 1, spells.end()); // move to end From 8461189e9501971288dcc645c27b8832312b5d10 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:10:03 +0200 Subject: [PATCH 189/726] code review + text --- client/NetPacksClient.cpp | 2 +- client/windows/CCastleInterface.cpp | 18 +++++++++--------- client/windows/CCastleInterface.h | 2 +- config/gameConfig.json | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 75618089f..d403120a7 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -176,7 +176,7 @@ void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) void ApplyClientNetPackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack) { for(const auto & win : GH.windows().findWindows()) - win->updateSpells(); + win->updateSpells(pack.tid); } void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 8bc898ddc..bd129ea4a 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1982,11 +1982,14 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->allTexts[593]), [&](){ close(); }, EShortcut::GLOBAL_RETURN); - updateSpells(); + updateSpells(townId); } -void CMageGuildScreen::updateSpells() +void CMageGuildScreen::updateSpells(ObjectInstanceID tID) { + if(tID != townId) + return; + OBJECT_CONSTRUCTION; static const std::vector > positions = { @@ -2063,15 +2066,12 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); - auto temp = std::make_shared(CGI->generaltexth->translate("vcmi.spellResearch.pay"), LOCPLINT->playerID, resComps, pom); + auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); + auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); - temp->buttons[0]->addCallback([this, resComps, town, cost](){ - if(LOCPLINT->cb->getResourceAmount().canAfford(cost)) - LOCPLINT->cb->spellResearch(town, spell->id, true); - else - LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.canNotAfford"), resComps); - }); + temp->buttons[0]->addCallback([this, resComps, town, cost](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); + temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index b5fd3d7ab..717c1a748 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -398,7 +398,7 @@ class CMageGuildScreen : public CStatusbarWindow public: CMageGuildScreen(CCastleInterface * owner, const ImagePath & image); - void updateSpells(); + void updateSpells(ObjectInstanceID tID); }; /// The blacksmith window where you can buy available in town war machine diff --git a/config/gameConfig.json b/config/gameConfig.json index 08f2ce659..a2330ec2b 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -319,7 +319,7 @@ // Costs depends on level for an spell research "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, // Exponent for increasing cost for each research - "spellResearchCostExponentPerResearch": 1.5 + "spellResearchCostExponentPerResearch": 1.25 }, "combat": From 3813db83abe133e222d60557691613e7cd309750 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:16:34 +0200 Subject: [PATCH 190/726] make ci happy --- client/windows/CCastleInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index bd129ea4a..53705a146 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2069,7 +2069,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); - temp->buttons[0]->addCallback([this, resComps, town, cost](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); + temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); From 63b711758f9034aeb9e1b7c059f6df451a15896e Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 28 Sep 2024 17:14:41 +0200 Subject: [PATCH 191/726] Fix chain lightning wasting effect on creatures immune to magic --- lib/spells/effects/UnitEffect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index 657f98e9f..3598fbbb8 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -197,7 +197,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool { - return isValidTarget(m, unit); + return isReceptive(m, unit) && isValidTarget(m, unit); }); for(const auto *unit : possibleTargets) From e6cb87abacde3a8f0b5d319291abe7654d914121 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:32:53 +0200 Subject: [PATCH 192/726] cast only on self --- config/schemas/spell.json | 4 ++++ docs/modders/Entities_Format/Spell_Format.md | 3 +++ include/vcmi/spells/Spell.h | 1 + lib/spells/BattleSpellMechanics.cpp | 23 ++++++++++++-------- lib/spells/CSpellHandler.cpp | 8 +++++++ lib/spells/CSpellHandler.h | 2 ++ test/mock/mock_spells_Spell.h | 1 + 7 files changed, 33 insertions(+), 9 deletions(-) diff --git a/config/schemas/spell.json b/config/schemas/spell.json index d4b00487b..4aac0ca66 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -171,6 +171,10 @@ "type" : "boolean", "description" : "If used as creature spell, unit can cast this spell on itself" }, + "canCastOnlyOnSelf" : { + "type" : "boolean", + "description" : "If used as creature spell, unit can cast this spell only on itself" + }, "canCastWithoutSkip" : { "type" : "boolean", "description" : "If used the creature will not skip the turn after casting a spell." diff --git a/docs/modders/Entities_Format/Spell_Format.md b/docs/modders/Entities_Format/Spell_Format.md index 41461b606..105dba65d 100644 --- a/docs/modders/Entities_Format/Spell_Format.md +++ b/docs/modders/Entities_Format/Spell_Format.md @@ -64,6 +64,9 @@ // If true, then creature capable of casting this spell can cast this spell on itself // If false, then creature can only cast this spell on other units "canCastOnSelf" : false, + + // If true, then creature capable of casting this spell can cast this spell only on itself + "canCastOnlyOnSelf" : false, // If true the creature will not skip the turn after casting a spell "canCastWithoutSkip": false, diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index 6b40bf258..7a3852a5b 100644 --- a/include/vcmi/spells/Spell.h +++ b/include/vcmi/spells/Spell.h @@ -45,6 +45,7 @@ public: virtual bool hasSchool(SpellSchool school) const = 0; virtual bool canCastOnSelf() const = 0; + virtual bool canCastOnlyOnSelf() const = 0; virtual bool canCastWithoutSkip() const = 0; virtual void forEachSchool(const SchoolCallback & cb) const = 0; virtual int32_t getCost(const int32_t skillLevel) const = 0; diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 4d79fd0e3..fa486002c 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -217,23 +217,28 @@ bool BattleSpellMechanics::canBeCastAt(const Target & target, Problem & problem) const battle::Unit * mainTarget = nullptr; - if (!getSpell()->canCastOnSelf()) + if(spellTarget.front().unitValue) { - if(spellTarget.front().unitValue) - { - mainTarget = target.front().unitValue; - } - else if(spellTarget.front().hexValue.isValid()) - { - mainTarget = battle()->battleGetUnitByPos(target.front().hexValue, true); - } + mainTarget = target.front().unitValue; + } + else if(spellTarget.front().hexValue.isValid()) + { + mainTarget = battle()->battleGetUnitByPos(target.front().hexValue, true); + } + if (!getSpell()->canCastOnSelf() && !getSpell()->canCastOnlyOnSelf()) + { if(mainTarget && mainTarget == caster) return false; // can't cast on self if(mainTarget && mainTarget->hasBonusOfType(BonusType::INVINCIBLE) && !getSpell()->getPositiveness()) return false; } + else if(getSpell()->canCastOnlyOnSelf()) + { + if(mainTarget && mainTarget != caster) + return false; // can't cast on others + } return effects->applicable(problem, this, target, spellTarget); } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 845c2cbf9..454c8080d 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -79,6 +79,8 @@ CSpell::CSpell(): combat(false), creatureAbility(false), castOnSelf(false), + castOnlyOnSelf(false), + castWithoutSkip(false), positiveness(ESpellPositiveness::NEUTRAL), defaultProbability(0), rising(false), @@ -298,6 +300,11 @@ bool CSpell::canCastOnSelf() const return castOnSelf; } +bool CSpell::canCastOnlyOnSelf() const +{ + return castOnlyOnSelf; +} + bool CSpell::canCastWithoutSkip() const { return castWithoutSkip; @@ -788,6 +795,7 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c } spell->castOnSelf = json["canCastOnSelf"].Bool(); + spell->castOnlyOnSelf = json["canCastOnlyOnSelf"].Bool(); spell->castWithoutSkip = json["canCastWithoutSkip"].Bool(); spell->level = static_cast(json["level"].Integer()); spell->power = static_cast(json["power"].Integer()); diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index c16e10ceb..30154ed4c 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -167,6 +167,7 @@ public: bool hasSchool(SpellSchool school) const override; bool canCastOnSelf() const override; + bool canCastOnlyOnSelf() const override; bool canCastWithoutSkip() const override; /** @@ -297,6 +298,7 @@ private: bool combat; //is this spell combat (true) or adventure (false) bool creatureAbility; //if true, only creatures can use this spell bool castOnSelf; // if set, creature caster can cast this spell on itself + bool castOnlyOnSelf; // if set, creature caster can cast this spell on itself bool castWithoutSkip; // if set the creature will not skip the turn after casting a spell si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative diff --git a/test/mock/mock_spells_Spell.h b/test/mock/mock_spells_Spell.h index 2d0ae31d1..512649dae 100644 --- a/test/mock/mock_spells_Spell.h +++ b/test/mock/mock_spells_Spell.h @@ -47,6 +47,7 @@ public: MOCK_CONST_METHOD0(isSpecial, bool()); MOCK_CONST_METHOD0(isMagical, bool()); MOCK_CONST_METHOD0(canCastOnSelf, bool()); + MOCK_CONST_METHOD0(canCastOnlyOnSelf, bool()); MOCK_CONST_METHOD0(canCastWithoutSkip, bool()); MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool)); MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &)); From ac380b0f4c8e8e70368d6f480de24a6654f2dc3d Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sat, 28 Sep 2024 19:12:32 +0200 Subject: [PATCH 193/726] Remove block of conflux creatures idle animations --- config/creatures/conflux.json | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index 397d9e154..b7cc45ffe 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -55,10 +55,6 @@ "upgrades": ["stormElemental"], "graphics" : { - "animationTime" : - { - "idle" : 0 - }, "animation": "CAELEM.DEF" }, "sound" : @@ -122,10 +118,6 @@ "upgrades": ["magmaElemental"], "graphics" : { - "animationTime" : - { - "idle" : 0 - }, "animation": "CEELEM.DEF" }, "sound" : @@ -185,10 +177,6 @@ "upgrades": ["energyElemental"], "graphics" : { - "animationTime" : - { - "idle" : 0 - }, "animation": "CFELEM.DEF" }, "sound" : @@ -273,10 +261,6 @@ "upgrades": ["iceElemental"], "graphics" : { - "animationTime" : - { - "idle" : 0 - }, "animation": "CWELEM.DEF" }, "sound" : @@ -472,10 +456,6 @@ "graphics" : { "animation": "CICEE.DEF", - "animationTime" : - { - "idle" : 0 - }, "missile" : { "projectile": "PICEE.DEF" @@ -554,10 +534,6 @@ }, "graphics" : { - "animationTime" : - { - "idle" : 0 - }, "animation": "CSTONE.DEF" }, "sound" : @@ -635,10 +611,6 @@ "graphics" : { "animation": "CSTORM.DEF", - "animationTime" : - { - "idle" : 0 - }, "missile" : { "projectile": "CPRGTIX.DEF" @@ -717,10 +689,6 @@ }, "graphics" : { - "animationTime" : - { - "idle" : 0 - }, "animation": "CNRG.DEF" }, "sound" : From 532ed9b12cfdd71963321a763fc64057008bc846 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Sep 2024 19:29:49 +0000 Subject: [PATCH 194/726] Adjusted ttf font sizes to better match H3 fonts --- config/fonts.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/fonts.json b/config/fonts.json index 0ba3f076f..e40da48f6 100644 --- a/config/fonts.json +++ b/config/fonts.json @@ -29,14 +29,14 @@ // "noShadow" - if set, this font will not drop any shadow "trueType": { - "BIGFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 19, 39, 58, 78] }, + "BIGFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 18, 38, 57, 76] }, "CALLI10R" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 12, 24, 36, 48] }, // TODO: find better matching font? This is likely non-free 'Callisto MT' font "CREDITS" : { "file" : "NotoSerif-Black.ttf", "size" : [ 22, 44, 66, 88], "outline" : true }, "HISCORE" : { "file" : "NotoSerif-Black.ttf", "size" : [ 18, 36, 54, 72], "outline" : true }, - "MEDFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 15, 31, 46, 62] }, - "SMALFONT" : { "file" : "NotoSerif-Medium.ttf", "size" : [ 12, 24, 36, 48] }, + "MEDFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 13, 26, 39, 52] }, + "SMALFONT" : { "file" : "NotoSerif-Medium.ttf", "size" : [ 11, 22, 33, 44] }, "TIMES08R" : { "file" : "NotoSerif-Medium.ttf", "size" : [ 8, 16, 24, 32] }, - "TINY" : { "file" : "NotoSans-Medium.ttf", "size" : [ 9, 19, 28, 38], "noShadow" : true }, // The only H3 font without shadow + "TINY" : { "file" : "NotoSans-Medium.ttf", "size" : [ 9, 18, 28, 38], "noShadow" : true }, // The only H3 font without shadow "VERD10B" : { "file" : "NotoSans-Medium.ttf", "size" : [ 13, 26, 39, 52] } } } From 51848ced3b4a1c89e1aca2c43b33d27483332bf8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Sep 2024 19:30:26 +0000 Subject: [PATCH 195/726] Add font loading tracking to log --- client/renderSDL/CBitmapFont.cpp | 6 ++++++ client/renderSDL/CTrueTypeFont.cpp | 8 ++++++++ client/renderSDL/RenderHandler.cpp | 2 ++ 3 files changed, 16 insertions(+) diff --git a/client/renderSDL/CBitmapFont.cpp b/client/renderSDL/CBitmapFont.cpp index 53a09460b..13de8dcdd 100644 --- a/client/renderSDL/CBitmapFont.cpp +++ b/client/renderSDL/CBitmapFont.cpp @@ -212,6 +212,12 @@ CBitmapFont::CBitmapFont(const std::string & filename): SDL_FreeSurface(atlasImage); atlasImage = scaledSurface; } + + logGlobal->debug("Loaded BMP font: '%s', height %d, ascent %d", + filename, + getLineHeightScaled(), + getFontAscentScaled() + ); } CBitmapFont::~CBitmapFont() diff --git a/client/renderSDL/CTrueTypeFont.cpp b/client/renderSDL/CTrueTypeFont.cpp index 2119ba14a..dcf3661d6 100644 --- a/client/renderSDL/CTrueTypeFont.cpp +++ b/client/renderSDL/CTrueTypeFont.cpp @@ -73,6 +73,14 @@ CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig): TTF_SetFontStyle(font.get(), getFontStyle(fontConfig)); TTF_SetFontHinting(font.get(),TTF_HINTING_MONO); + logGlobal->debug("Loaded TTF font: '%s', point size %d, height %d, ascent %d, descent %d, line skip %d", + fontConfig["file"].String(), + getPointSize(fontConfig["size"]), + TTF_FontHeight(font.get()), + TTF_FontAscent(font.get()), + TTF_FontDescent(font.get()), + TTF_FontLineSkip(font.get()) + ); } CTrueTypeFont::~CTrueTypeFont() = default; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index be0d5db3b..f1afc8255 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -342,6 +342,8 @@ std::shared_ptr RenderHandler::loadFont(EFonts font) return fonts.at(font); const int8_t index = static_cast(font); + logGlobal->debug("Loading font %d", static_cast(index)); + auto configList = CResourceHandler::get()->getResourcesWithName(JsonPath::builtin("config/fonts.json")); std::shared_ptr loadedFont = std::make_shared(); std::string bitmapPath; From fecfdd705656c992bc4f954f0629395c5cf917b7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Sep 2024 19:30:50 +0000 Subject: [PATCH 196/726] Fix centering of multi-line labels --- client/lobby/OptionsTab.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 674bb81ee..3b56e8433 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -1037,11 +1037,11 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con } const auto & font = GH.renderHandler().loadFont(FONT_SMALL); - labelWhoCanPlay = std::make_shared(Rect(6, 23, 45, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); + labelWhoCanPlay = std::make_shared(Rect(6, 23, 45, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; }; std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString(); - labelHandicap = std::make_shared(Rect(57, 24, 47, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, labelHandicapText); + labelHandicap = std::make_shared(Rect(57, 24, 47, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, labelHandicapText); handicap = std::make_shared(Rect(56, 24, 49, font->getLineHeight()*2), [](){ if(!CSH->isHost()) return; From 4ed478b6e5b8197e75c1ae8acf75f4a3736b1ecb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 28 Sep 2024 19:31:16 +0000 Subject: [PATCH 197/726] Improve font mode auto-selection for languages like Chinese --- client/renderSDL/FontChain.cpp | 22 ++++++++++++++++++++-- client/renderSDL/FontChain.h | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/client/renderSDL/FontChain.cpp b/client/renderSDL/FontChain.cpp index 44f71097f..3c1e6446e 100644 --- a/client/renderSDL/FontChain.cpp +++ b/client/renderSDL/FontChain.cpp @@ -13,8 +13,13 @@ #include "CTrueTypeFont.h" #include "CBitmapFont.h" +#include "../CGameInfo.h" + #include "../../lib/CConfigHandler.h" +#include "../../lib/modding/CModHandler.h" #include "../../lib/texts/TextOperations.h" +#include "../../lib/texts/CGeneralTextHandler.h" +#include "../../lib/texts/Languages.h" void FontChain::renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const { @@ -39,7 +44,7 @@ size_t FontChain::getFontAscentScaled() const return maxHeight; } -bool FontChain::bitmapFontsPrioritized() const +bool FontChain::bitmapFontsPrioritized(const std::string & bitmapFontName) const { const std::string & fontType = settings["video"]["fontsType"].String(); if (fontType == "original") @@ -55,6 +60,19 @@ bool FontChain::bitmapFontsPrioritized() const if (!vstd::isAlmostEqual(1.0, settings["video"]["fontScalingFactor"].Float())) return false; // If player requested non-100% scaling - use scalable fonts + std::string modName = CGI->modh->findResourceOrigin(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT)); + std::string fontLanguage = CGI->modh->getModLanguage(modName); + std::string gameLanguage = CGI->generaltexth->getPreferredLanguage(); + std::string fontEncoding = Languages::getLanguageOptions(fontLanguage).encoding; + std::string gameEncoding = Languages::getLanguageOptions(gameLanguage).encoding; + + // player uses language with different encoding than his bitmap fonts + // for example, Polish language with English fonts or Chinese language which can't use H3 fonts at all + // this may result in unintended mixing of ttf and bitmap fonts, which may have a bit different look + // so in this case prefer ttf fonts that are likely to cover target language better than H3 fonts + if (fontEncoding != gameEncoding) + return false; + return true; // else - use original bitmap fonts } @@ -65,7 +83,7 @@ void FontChain::addTrueTypeFont(const JsonNode & trueTypeConfig) void FontChain::addBitmapFont(const std::string & bitmapFilename) { - if (bitmapFontsPrioritized()) + if (bitmapFontsPrioritized(bitmapFilename)) chain.insert(chain.begin(), std::make_unique(bitmapFilename)); else chain.push_back(std::make_unique(bitmapFilename)); diff --git a/client/renderSDL/FontChain.h b/client/renderSDL/FontChain.h index b07902cd4..b66d5cce2 100644 --- a/client/renderSDL/FontChain.h +++ b/client/renderSDL/FontChain.h @@ -29,7 +29,7 @@ class FontChain final : public IFont void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override; size_t getFontAscentScaled() const override; - bool bitmapFontsPrioritized() const; + bool bitmapFontsPrioritized(const std::string & bitmapFontName) const; public: FontChain() = default; From c2ddf8e06a566f3ecc9ae9ab3dc92ad61b72e405 Mon Sep 17 00:00:00 2001 From: PolishUser Date: Sat, 28 Sep 2024 23:54:51 +0200 Subject: [PATCH 198/726] Add 'newBuilding' sound upon hero recruitment in towns --- client/CPlayerInterface.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 162aada4c..d15cabb2e 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -427,6 +427,8 @@ void CPlayerInterface::heroCreated(const CGHeroInstance * hero) EVENT_HANDLER_CALLED_BY_CLIENT; localState->addWanderingHero(hero); adventureInt->onHeroChanged(hero); + if(castleInt) + CCS->soundh->playSound(soundBase::newBuilding); } void CPlayerInterface::openTownWindow(const CGTownInstance * town) { From 769268cfe34a50f38e789b600afb24e316583ad2 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 29 Sep 2024 01:15:09 +0200 Subject: [PATCH 199/726] Eternal Garrison Fixed an issue that caused heroes to stay garrisoned for ever when hero-cap was reached. --- AI/Nullkiller/Analyzers/HeroManager.cpp | 5 ++--- AI/Nullkiller/Analyzers/HeroManager.h | 2 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 594b2e394..7e88bcdce 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -189,10 +189,9 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const return evaluateFightingStrength(hero); } -bool HeroManager::heroCapReached() const +bool HeroManager::heroCapReached(bool includeGarrisoned) const { - const bool includeGarnisoned = true; - int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned); + int heroCount = cb->getHeroCount(ai->playerID, includeGarrisoned); return heroCount >= ALLOWED_ROAMING_HEROES || heroCount >= ai->settings->getMaxRoamingHeroes() diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 675357626..9d7f6c4e0 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -56,7 +56,7 @@ public: float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const; float evaluateHero(const CGHeroInstance * hero) const; bool canRecruitHero(const CGTownInstance * t = nullptr) const; - bool heroCapReached() const; + bool heroCapReached(bool includeGarrisoned = true) const; const CGHeroInstance * findHeroWithGrail() const; const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const; float getMagicStrength(const CGHeroInstance * hero) const; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 247836768..fe8912c7a 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -962,7 +962,7 @@ void AINodeStorage::setHeroes(std::map heroes) // do not allow our own heroes in garrison to act on map if(hero.first->getOwner() == ai->playerID && hero.first->inTownGarrison - && (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached())) + && (ai->isHeroLocked(hero.first) || ai->heroManager->heroCapReached(false))) { continue; } From 4a5ecdf25e85481212e05aa06aa51436b1e6477d Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 29 Sep 2024 01:15:53 +0200 Subject: [PATCH 200/726] Update Nullkiller.h Removed pointless default-parameter. --- AI/Nullkiller/Engine/Nullkiller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 6a442dc80..941e71f16 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -127,7 +127,7 @@ private: void updateAiState(int pass, bool fast = false); void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const; Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const; - Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier = PriorityEvaluator::PriorityTier::HUNTER_GATHER) const; + Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const; bool executeTask(Goals::TTask task); bool areAffectedObjectsPresent(Goals::TTask task) const; HeroRole getTaskRole(Goals::TTask task) const; From f7a961793ab9983cb0dc48f3ef1f0471fca2ebd6 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 29 Sep 2024 01:23:13 +0200 Subject: [PATCH 201/726] Update PriorityEvaluator.cpp AI is more careful when gathering stuff near enemies. The wasted movement-points are no longer considered when calculating which own city to fall back to when there's nothing better to do. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f588406fb..3275ba9aa 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -909,18 +909,21 @@ public: class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder { public: - void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + void buildEvaluationContext(EvaluationContext& evaluationContext, Goals::TSubgoal task) const override { - if(task->goalType != Goals::STAY_AT_TOWN) + if (task->goalType != Goals::STAY_AT_TOWN) return; - Goals::StayAtTown & stayAtTown = dynamic_cast(*task); + Goals::StayAtTown& stayAtTown = dynamic_cast(*task); evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero()); if (evaluationContext.armyReward == 0) evaluationContext.isDefend = true; - evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted(); - evaluationContext.movementCost += stayAtTown.getMovementWasted(); + else + { + evaluationContext.movementCost += stayAtTown.getMovementWasted(); + evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted(); + } } }; @@ -1428,6 +1431,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.isArmyUpgrade) return 0; + if (evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) + return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05; @@ -1438,8 +1443,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (score > 0) { score *= evaluationContext.closestWayRatio; - if (evaluationContext.enemyHeroDangerRatio > 1) - score /= evaluationContext.enemyHeroDangerRatio; + score /= (1 + evaluationContext.enemyHeroDangerRatio); if (evaluationContext.movementCost > 0) score /= evaluationContext.movementCost; score *= (maxWillingToLose - evaluationContext.armyLossPersentage); From a2904584d3ef04e7a9c65deb921a0c9b478fd1c1 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 29 Sep 2024 02:01:09 +0200 Subject: [PATCH 202/726] Update Nullkiller.cpp Build and hire-tasks no longer eat into the pass-depth. --- AI/Nullkiller/Engine/Nullkiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 4a10d8b6c..e4db13bbc 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -374,7 +374,7 @@ void Nullkiller::makeTurn() Goals::TTask bestTask = taskptr(Goals::Invalid()); - for(;i <= settings->getMaxPass(); i++) + while(true) { bestTasks.clear(); From cfe4d7592a587d63a138a0d537ed2dc1b76c50ef Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 29 Sep 2024 03:13:21 +0200 Subject: [PATCH 203/726] Update DefenceBehavior.cpp Heroes will no longer rush to defend towns that have a standing garrison that they can't merge their armies with. --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index b5e0ab8d0..bc5a11bbe 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -249,6 +249,16 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta continue; } + if (!path.targetHero->canBeMergedWith(*town)) + { +#if NKAI_TRACE_LEVEL >= 1 + logAi->trace("Can't merge armies of hero %s and town %s", + path.targetHero->getObjectName(), + town->getObjectName()); +#endif + continue; + } + if(path.targetHero == town->visitingHero.get() && path.exchangeCount == 1) { #if NKAI_TRACE_LEVEL >= 1 From 3c9ec0c30ee1256f9881a2a9b0de4a264fa7b0ac Mon Sep 17 00:00:00 2001 From: Dydzio Date: Sun, 29 Sep 2024 15:44:24 +0200 Subject: [PATCH 204/726] Change neutral AI to BattleAI --- config/schemas/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 3cc99dc28..da312e19d 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -509,7 +509,7 @@ }, "neutralAI" : { "type" : "string", - "default" : "StupidAI" + "default" : "BattleAI" }, "enemyAI" : { "type" : "string", From 713fcd65436e634a2b06e547975d1ba027dc125e Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:40:28 +0200 Subject: [PATCH 205/726] research per day & seperate config --- client/windows/CCastleInterface.cpp | 21 ++++++++++----------- config/gameConfig.json | 18 ++++++++++++------ config/schemas/gameSettings.json | 6 +++--- lib/GameSettings.cpp | 4 ++-- lib/IGameSettings.h | 4 ++-- lib/mapObjects/CGTownInstance.cpp | 1 - lib/mapObjects/CGTownInstance.h | 4 ++-- lib/networkPacks/NetPacksLib.cpp | 2 +- server/CGameHandler.cpp | 13 ++++++------- 9 files changed, 38 insertions(+), 35 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 53705a146..95c01ce58 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2036,22 +2036,21 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) const CGTownInstance * town = LOCPLINT->cb->getTown(townId); if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && town->spellResearchAllowed) { - int daysSinceLastResearch = LOCPLINT->cb->getDate(Date::DAY) - town->lastSpellResearchDay; - if(!daysSinceLastResearch) + int level = -1; + for(int i = 0; i < town->spells.size(); i++) + if(vstd::find_pos(town->spells[i], spell->id) != -1) + level = i; + + int today = LOCPLINT->cb->getDate(Date::DAY); + if(town->spellResearchActionsPerDay.find(today) == town->spellResearchActionsPerDay.end() || town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); return; } - int level = -1; - for(int i = 0; i < town->spells.size(); i++) - if(vstd::find_pos(town->spells[i], spell->id) != -1) - level = i; - - auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); - auto costPerLevel = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto costExponent = LOCPLINT->cb->getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); - auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(town->spellResearchCounter + 1, costExponent); + auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); + auto costExponent = LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); + auto cost = costBase * std::pow(town->spellResearchCounter + 1, costExponent); std::vector> resComps; resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); diff --git a/config/gameConfig.json b/config/gameConfig.json index a2330ec2b..a7a80d2a0 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -314,12 +314,18 @@ "startingDwellingChances": [100, 50], // Enable spell research in mage guild "spellResearch": false, - // Base cost for an spell research - "spellResearchCostBase": { "gold": 1000 }, - // Costs depends on level for an spell research - "spellResearchCostPerLevel": { "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, - // Exponent for increasing cost for each research - "spellResearchCostExponentPerResearch": 1.25 + // Cost for an spell research (array index is spell tier) + "spellResearchCost": [ + { "gold": 1000, "wood" : 2, "mercury": 2, "ore": 2, "sulfur": 2, "crystal": 2, "gems": 2 }, + { "gold": 1000, "wood" : 4, "mercury": 4, "ore": 4, "sulfur": 4, "crystal": 4, "gems": 4 }, + { "gold": 1000, "wood" : 6, "mercury": 6, "ore": 6, "sulfur": 6, "crystal": 6, "gems": 6 }, + { "gold": 1000, "wood" : 8, "mercury": 8, "ore": 8, "sulfur": 8, "crystal": 8, "gems": 8 }, + { "gold": 1000, "wood" : 10, "mercury": 10, "ore": 10, "sulfur": 10, "crystal": 10, "gems": 10 } + ], + // How much researchs/skips per day are possible? (array index is spell tier) + "spellResearchPerDay": [ 2, 2, 2, 2, 1 ], + // Exponent for increasing cost for each research (factor 1 disables this; array index is spell tier) + "spellResearchCostExponentPerResearch": [ 1.25, 1.25, 1.25, 1.25, 1.25 ] }, "combat": diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index da113f7f1..6d0fd4670 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -54,9 +54,9 @@ "buildingsPerTurnCap" : { "type" : "number" }, "startingDwellingChances" : { "type" : "array" }, "spellResearch" : { "type" : "boolean" }, - "spellResearchCostBase" : { "type" : "object" }, - "spellResearchCostPerLevel" : { "type" : "object" }, - "spellResearchCostExponentPerResearch" : { "type" : "number" } + "spellResearchCost" : { "type" : "array" }, + "spellResearchPerDay" : { "type" : "array" }, + "spellResearchCostExponentPerResearch" : { "type" : "array" } } }, "combat": { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index fee70455e..8ea0228b1 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -102,8 +102,8 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, {EGameSettings::TOWNS_SPELL_RESEARCH, "towns", "spellResearch" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE, "towns", "spellResearchCostBase" }, - {EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, "towns", "spellResearchCostPerLevel" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_COST, "towns", "spellResearchCost" }, + {EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY, "towns", "spellResearchPerDay" }, {EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, "towns", "spellResearchCostExponentPerResearch" }, }; diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 12dc351c7..fdde0da27 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -80,8 +80,8 @@ enum class EGameSettings TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, TOWNS_SPELL_RESEARCH, - TOWNS_SPELL_RESEARCH_COST_BASE, - TOWNS_SPELL_RESEARCH_COST_PER_LEVEL, + TOWNS_SPELL_RESEARCH_COST, + TOWNS_SPELL_RESEARCH_PER_DAY, TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH, OPTIONS_COUNT, diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index fbd2a76c6..76dc2a254 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -269,7 +269,6 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): destroyed(0), identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), - lastSpellResearchDay(0), spellResearchCounter(0), spellResearchAllowed(true) { diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fb5eb0af7..6e4bc7af3 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -73,7 +73,7 @@ public: std::vector > spells; //spells[level] -> vector of spells, first will be available in guild std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); - int lastSpellResearchDay; + std::map spellResearchActionsPerDay; int spellResearchCounter; bool spellResearchAllowed; @@ -98,7 +98,7 @@ public: if (h.version >= Handler::Version::SPELL_RESEARCH) { - h & lastSpellResearchDay; + h & spellResearchActionsPerDay; h & spellResearchCounter; h & spellResearchAllowed; } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 785052430..fb37de483 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -944,7 +944,7 @@ void SetResearchedSpells::applyGs(CGameState *gs) CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; - town->lastSpellResearchDay = gs->getDate(Date::DAY); + town->spellResearchActionsPerDay[gs->getDate(Date::DAY)]++; if(accepted) town->spellResearchCounter++; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b295a897d..44fe7e06d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2260,8 +2260,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto spells = t->spells.at(level); - int daysSinceLastResearch = gs->getDate(Date::DAY) - t->lastSpellResearchDay; - if(!daysSinceLastResearch && complain("Already researched today!")) + int today = getDate(Date::DAY); + bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) == t->spellResearchActionsPerDay.end() || t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); + if(researchLimitExceeded && complain("Already researched today!")) return false; if(!accepted) @@ -2272,11 +2273,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool return true; } - auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_BASE)); - auto costPerLevel = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_PER_LEVEL)); - auto costExponent = getSettings().getDouble(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH); - - auto cost = (costBase + costPerLevel * (level + 1)) * std::pow(t->spellResearchCounter + 1, costExponent); + auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); + auto costExponent = getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); + auto cost = costBase * std::pow(t->spellResearchCounter + 1, costExponent); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; From e2b49bbf79f9557dc7900402b12e02a167db973d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:46:45 +0200 Subject: [PATCH 206/726] fix condition --- client/windows/CCastleInterface.cpp | 2 +- server/CGameHandler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 95c01ce58..abb4fa036 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2042,7 +2042,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) level = i; int today = LOCPLINT->cb->getDate(Date::DAY); - if(town->spellResearchActionsPerDay.find(today) == town->spellResearchActionsPerDay.end() || town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) + if(town->spellResearchActionsPerDay.find(today) != town->spellResearchActionsPerDay.end() && town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); return; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 44fe7e06d..8d1046398 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2261,7 +2261,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto spells = t->spells.at(level); int today = getDate(Date::DAY); - bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) == t->spellResearchActionsPerDay.end() || t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); + bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) != t->spellResearchActionsPerDay.end() && t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); if(researchLimitExceeded && complain("Already researched today!")) return false; From d59a1fe9e9f518f9397a20709c5c68f8bf05aff0 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 30 Sep 2024 17:57:41 +0200 Subject: [PATCH 207/726] Fix case of "Got false in applying struct CastAdvSpell" Heroes now leave the garrison before trying (and failing) to cast adventure-map-spells. --- AI/Nullkiller/Goals/AdventureSpellCast.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 1868d7c60..8e8df0241 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -53,6 +53,9 @@ void AdventureSpellCast::accept(AIGateway * ai) throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); } + if (hero->inTownGarrison) + ai->myCb->swapGarrisonHero(hero->visitedTown); + auto wait = cb->waitTillRealize; cb->waitTillRealize = true; From 31f87cb6eddcc265d365090998ce6e724db42677 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:15:13 +0200 Subject: [PATCH 208/726] improve ui --- Mods/vcmi/config/vcmi/english.json | 4 ++-- Mods/vcmi/config/vcmi/german.json | 4 ++-- client/windows/CCastleInterface.cpp | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 11ed66eaa..d6c27e5b0 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -59,9 +59,9 @@ "vcmi.spellBook.search" : "search...", - "vcmi.spellResearch.canNotAfford" : "You can't afford to research a spell.", + "vcmi.spellResearch.canNotAfford" : "You can't afford to replace {%SPELL1} with {%SPELL2}. But you can still discard this spell and continue spell research.", "vcmi.spellResearch.comeAgain" : "Research has already been done today. Come back tomorrow.", - "vcmi.spellResearch.pay" : "Would you like to research this new spell and replace the old one or skip this spell?", + "vcmi.spellResearch.pay" : "Would you like to replace {%SPELL1} with {%SPELL2}? Or discard this spell and continue spell research?", "vcmi.spellResearch.research" : "Research this Spell", "vcmi.spellResearch.skip" : "Skip this Spell", "vcmi.spellResearch.abort" : "Abort", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 38f5428e8..e1934f370 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -58,9 +58,9 @@ "vcmi.spellBook.search" : "suchen...", - "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, einen Zauberspruch zu erforschen.", + "vcmi.spellResearch.canNotAfford" : "Ihr könnt es Euch nicht leisten, {%SPELL1} durch {%SPELL2} zu ersetzen. Aber Ihr könnt diesen Zauberspruch trotzdem verwerfen und die Zauberspruchforschung fortsetzen.", "vcmi.spellResearch.comeAgain" : "Die Forschung wurde heute bereits abgeschlossen. Kommt morgen wieder.", - "vcmi.spellResearch.pay" : "Möchtet Ihr diesen neuen Zauberspruch erforschen und den alten ersetzen oder diesen überspringen?", + "vcmi.spellResearch.pay" : "Möchtet Ihr {%SPELL1} durch {%SPELL2} ersetzen? Oder diesen Zauberspruch verwerfen und die Zauberspruchforschung fortsetzen?", "vcmi.spellResearch.research" : "Erforsche diesen Zauberspruch", "vcmi.spellResearch.skip" : "Überspringe diesen Zauberspruch", "vcmi.spellResearch.abort" : "Abbruch", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index abb4fa036..9bc5e1ae6 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2053,19 +2053,24 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto cost = costBase * std::pow(town->spellResearchCounter + 1, costExponent); std::vector> resComps; - resComps.push_back(std::make_shared(ComponentType::SPELL, town->spells[level].at(town->spellsAtLevel(level, false)))); + auto newSpell = town->spells[level].at(town->spellsAtLevel(level, false)); + resComps.push_back(std::make_shared(ComponentType::SPELL, spell->id)); + resComps.push_back(std::make_shared(ComponentType::SPELL, newSpell)); resComps.back()->newLine = true; for(TResources::nziterator i(cost); i.valid(); i++) { resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } - auto showSpellResearchDialog = [this, resComps, town, cost](){ + auto showSpellResearchDialog = [this, resComps, town, cost, newSpell](){ std::vector>> pom; pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); + boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated()); + boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated()); auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); From 74f3aedcc9286e0a21f5c08d17f61bfdc831ce60 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 30 Sep 2024 19:32:27 +0200 Subject: [PATCH 209/726] TP onto friend attempt Fixed an issue that caused the AI to think it can townportal onto heroes of other factions, for example their allies. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index fe8912c7a..c22ef912f 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1197,6 +1197,10 @@ void AINodeStorage::calculateTownPortal( continue; } + if (targetTown->visitingHero + && targetTown->visitingHero.get()->getFaction() != actor->hero->getFaction()) + continue; + auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown); if(nodeOptional) From 73e7d3f5bb6ba09c6e3c8171f50175acacb6326c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 30 Sep 2024 19:41:39 +0200 Subject: [PATCH 210/726] Another reason not to try to town-portal Even if the hero blocking a town is from the own faction, the town must not become a target if the city has stashed armies because in that case the hero ontop of it won't be able to go into garrison for the TP. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index c22ef912f..45824c9ca 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1198,7 +1198,8 @@ void AINodeStorage::calculateTownPortal( } if (targetTown->visitingHero - && targetTown->visitingHero.get()->getFaction() != actor->hero->getFaction()) + && (targetTown->visitingHero.get()->getFaction() != actor->hero->getFaction() + || targetTown->getUpperArmy()->stacksCount())) continue; auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown); From 844b07848284f50e484c97ae22241fd0c89dd607 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:46:53 +0200 Subject: [PATCH 211/726] color shift parameter to config --- client/render/AssetGenerator.cpp | 23 ++++++++++++++--------- client/render/ColorFilter.cpp | 7 +++++++ client/render/ColorFilter.h | 1 + config/gameConfig.json | 16 ++++++++++++++++ config/schemas/gameSettings.json | 7 +++++++ lib/GameSettings.cpp | 1 + lib/IGameSettings.h | 1 + 7 files changed, 47 insertions(+), 9 deletions(-) diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index ca03969f5..697efb6be 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -18,6 +18,10 @@ #include "../render/IRenderHandler.h" #include "../lib/filesystem/Filesystem.h" +#include "../lib/GameSettings.h" +#include "../lib/IGameSettings.h" +#include "../lib/json/JsonNode.h" +#include "../lib/VCMI_Lib.h" void AssetGenerator::generateAll() { @@ -138,16 +142,17 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) std::shared_ptr texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); - // Color transform to make color of brown DIBOX.PCX texture match color of specified player + // transform to make color of brown DIBOX.PCX texture match color of specified player + auto filterSettings = VLC->settingsHandler->getFullConfig()["general"]["playerColoredBackground"]; static const std::array filters = { - ColorFilter::genRangeShifter( 0.25, 0, 0, 1.25, 0.00, 0.00 ), // red - ColorFilter::genRangeShifter( 0, 0, 0, 0.45, 1.20, 4.50 ), // blue - ColorFilter::genRangeShifter( 0.40, 0.27, 0.23, 1.10, 1.20, 1.15 ), // tan - ColorFilter::genRangeShifter( -0.27, 0.10, -0.27, 0.70, 1.70, 0.70 ), // green - ColorFilter::genRangeShifter( 0.47, 0.17, -0.27, 1.60, 1.20, 0.70 ), // orange - ColorFilter::genRangeShifter( 0.12, -0.1, 0.25, 1.15, 1.20, 2.20 ), // purple - ColorFilter::genRangeShifter( -0.13, 0.23, 0.23, 0.90, 1.20, 2.20 ), // teal - ColorFilter::genRangeShifter( 0.44, 0.15, 0.25, 1.00, 1.00, 1.75 ) // pink + ColorFilter::genRangeShifter( filterSettings["red" ].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["blue" ].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["tan" ].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["green" ].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["orange"].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["purple"].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["teal" ].convertTo>() ), + ColorFilter::genRangeShifter( filterSettings["pink" ].convertTo>() ) }; assert(player.isValidPlayer()); diff --git a/client/render/ColorFilter.cpp b/client/render/ColorFilter.cpp index 8b2288b4e..9e530009f 100644 --- a/client/render/ColorFilter.cpp +++ b/client/render/ColorFilter.cpp @@ -70,6 +70,13 @@ ColorFilter ColorFilter::genRangeShifter( float minR, float minG, float minB, fl 1.f); } +ColorFilter ColorFilter::genRangeShifter( std::vector parameters ) +{ + assert(std::size(parameters) == 6); + + return genRangeShifter(parameters[0], parameters[1], parameters[2], parameters[3], parameters[4], parameters[5]); +} + ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a ) { return ColorFilter(r, g, b, a); diff --git a/client/render/ColorFilter.h b/client/render/ColorFilter.h index 5df63ec67..2a0268fe0 100644 --- a/client/render/ColorFilter.h +++ b/client/render/ColorFilter.h @@ -44,6 +44,7 @@ public: /// Generates object that transforms each channel independently static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB ); + static ColorFilter genRangeShifter( std::vector parameters ); /// Generates object that performs arbitrary mixing between any channels static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a ); diff --git a/config/gameConfig.json b/config/gameConfig.json index 714309b2b..f999c74af 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -555,6 +555,22 @@ "valueType" : "BASE_NUMBER" } } + }, + + "general" : + { + // Color transform to make color of brown DIBOX.PCX texture match color of specified player + "playerColoredBackground" : + { + "red" : [ 0.25, 0, 0, 1.25, 0.00, 0.00 ], + "blue" : [ 0, 0, 0, 0.45, 1.20, 4.50 ], + "tan" : [ 0.40, 0.27, 0.23, 1.10, 1.20, 1.15 ], + "green" : [ -0.27, 0.10, -0.27, 0.70, 1.70, 0.70 ], + "orange" : [ 0.47, 0.17, -0.27, 1.60, 1.20, 0.70 ], + "purple" : [ 0.12, -0.1, 0.25, 1.15, 1.20, 2.20 ], + "teal" : [ -0.13, 0.23, 0.23, 0.90, 1.20, 2.20 ], + "pink" : [ 0.44, 0.15, 0.25, 1.00, 1.00, 1.75 ] + } } } } diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 0eec56189..6e2663d01 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -148,5 +148,12 @@ "perHero" : { "type" : "object" } } }, + "general": { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "playerColoredBackground" : { "type" : "object" } + } + } } } diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index 780c79f43..a904e1995 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -101,6 +101,7 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, + {EGameSettings::GENERAL_PLAYER_COLORED_BACKGROUND, "general", "playerColoredBackground" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index 9f6a8a78b..e7256a9bc 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -79,6 +79,7 @@ enum class EGameSettings TEXTS_TERRAIN, TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, + GENERAL_PLAYER_COLORED_BACKGROUND, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL From 7a3ff4a43370779b309bc10670e584b44c967ae1 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:51:40 +0200 Subject: [PATCH 212/726] Create swedish.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I have finally translated swedish.json 🥳 --- Mods/vcmi/config/vcmi/swedish.json | 672 +++++++++++++++++++++++++++++ 1 file changed, 672 insertions(+) create mode 100644 Mods/vcmi/config/vcmi/swedish.json diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json new file mode 100644 index 000000000..1bef307dc --- /dev/null +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -0,0 +1,672 @@ +{ + "vcmi.adventureMap.monsterThreat.title" : "\n\nHotnivå: ", + "vcmi.adventureMap.monsterThreat.levels.0" : "Utan ansträngning", + "vcmi.adventureMap.monsterThreat.levels.1" : "Väldigt svag", + "vcmi.adventureMap.monsterThreat.levels.2" : "Svag", + "vcmi.adventureMap.monsterThreat.levels.3" : "Lite svagare", + "vcmi.adventureMap.monsterThreat.levels.4" : "Jämbördig", + "vcmi.adventureMap.monsterThreat.levels.5" : "Lite starkare", + "vcmi.adventureMap.monsterThreat.levels.6" : "Stark", + "vcmi.adventureMap.monsterThreat.levels.7" : "Väldigt stark", + "vcmi.adventureMap.monsterThreat.levels.8" : "Utmanande", + "vcmi.adventureMap.monsterThreat.levels.9" : "Överväldigande", + "vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig", + "vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig", + "vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN", + + "vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?", + "vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!", + "vcmi.adventureMap.noTownWithTavern" : "Det finns inga tillgängliga städer med värdshus!", + "vcmi.adventureMap.spellUnknownProblem" : "Det finns ett okänt problem med den här formeln! Ingen mer information är tillgänglig.", + "vcmi.adventureMap.playerAttacked" : "Spelare har blivit attackerad: %s", + "vcmi.adventureMap.moveCostDetails" : "Förflyttningspoängs-kostnad: %TURNS tur(er) + %POINTS poäng - Återstående poäng: %REMAINING", + "vcmi.adventureMap.moveCostDetailsNoTurns" : "Förflyttningspoängs-kostnad: %POINTS poäng - Återstående poäng: %REMAINING", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!", + + "vcmi.capitalColors.0" : "Röd", + "vcmi.capitalColors.1" : "Blå", + "vcmi.capitalColors.2" : "Ljusbrun", + "vcmi.capitalColors.3" : "Grön", + "vcmi.capitalColors.4" : "Orange", + "vcmi.capitalColors.5" : "Lila", + "vcmi.capitalColors.6" : "Grönblå", + "vcmi.capitalColors.7" : "Rosa", + + "vcmi.heroOverview.startingArmy" : "Startarmé", + "vcmi.heroOverview.warMachine" : "Krigsmaskiner", + "vcmi.heroOverview.secondarySkills" : "Sekundärförmågor", + "vcmi.heroOverview.spells" : "Trollformler", + + "vcmi.radialWheel.mergeSameUnit" : "Slå samman samma varelser", + "vcmi.radialWheel.fillSingleUnit" : "Fyll på med enstaka varelser", + "vcmi.radialWheel.splitSingleUnit" : "Dela av en enda varelse", + "vcmi.radialWheel.splitUnitEqually" : "Dela upp varelser lika", + "vcmi.radialWheel.moveUnit" : "Flytta varelser till en annan armé", + "vcmi.radialWheel.splitUnit" : "Dela upp varelse till en annan ruta", + + "vcmi.radialWheel.heroGetArmy" : "Hämta armé från annan hjälte", + "vcmi.radialWheel.heroSwapArmy" : "Byt armé med annan hjälte", + "vcmi.radialWheel.heroExchange" : "Öppna hjälteutbyte", + "vcmi.radialWheel.heroGetArtifacts" : "Hämta artefakter från annan hjälte", + "vcmi.radialWheel.heroSwapArtifacts" : "Byt artefakter med annan hjälte", + "vcmi.radialWheel.heroDismiss" : "Avfärda hjälten", + + "vcmi.radialWheel.moveTop" : "Flytta till toppen", + "vcmi.radialWheel.moveUp" : "Flytta upp", + "vcmi.radialWheel.moveDown" : "Flytta nedåt", + "vcmi.radialWheel.moveBottom" : "Flytta till botten", + + "vcmi.spellBook.search" : "sök...", + + "vcmi.mainMenu.serverConnecting" : "Ansluter...", + "vcmi.mainMenu.serverAddressEnter" : "Ange adress:", + "vcmi.mainMenu.serverConnectionFailed" : "Misslyckades med att ansluta", + "vcmi.mainMenu.serverClosing" : "Avslutar...", + "vcmi.mainMenu.hostTCP" : "Spela som värd (TCP/IP)", + "vcmi.mainMenu.joinTCP" : "Anslut till värd (TCP/IP)", + + "vcmi.lobby.filepath" : "Filsökväg", + "vcmi.lobby.creationDate" : "Skapelsedatum", + "vcmi.lobby.scenarioName" : "Namn på scenariot", + "vcmi.lobby.mapPreview" : "Förhandsgranskning av karta", + "vcmi.lobby.noPreview" : "ingen förhandsgranskning", + "vcmi.lobby.noUnderground" : "ingen underjord", + "vcmi.lobby.sortDate" : "Sorterar kartor efter ändringsdatum", + "vcmi.lobby.backToLobby" : "Återgå till lobbyn", + "vcmi.lobby.author" : "Skaparen av lobbyn", + "vcmi.lobby.handicap" : "Handikapp", + "vcmi.lobby.handicap.resource" : "Ger spelarna lämpliga resurser att börja med utöver de normala startresurserna. Negativa värden är tillåtna men är begränsade till 0 totalt (spelaren börjar aldrig med negativa resurser).", + "vcmi.lobby.handicap.income" : "Ändrar spelarens olika inkomster i procent (resultaten avrundas uppåt).", + "vcmi.lobby.handicap.growth" : "Ändrar tillväxttakten för varelser i de städer som ägs av spelaren (resultaten avrundas uppåt).", + + "vcmi.lobby.login.title" : "VCMI Online Lobby", + "vcmi.lobby.login.username" : "Användarnamn:", + "vcmi.lobby.login.connecting" : "Ansluter...", + "vcmi.lobby.login.error" : "Anslutningsfel: %s", + "vcmi.lobby.login.create" : "Nytt konto", + "vcmi.lobby.login.login" : "Logga in", + "vcmi.lobby.login.as" : "Logga in som %s", + "vcmi.lobby.header.rooms" : "Spelrum - %d", + "vcmi.lobby.header.channels" : "Chattkanaler", + "vcmi.lobby.header.chat.global" : "Global spelchatt - %s", // %s -> språknamn + "vcmi.lobby.header.chat.match" : "Chatt från föregående spel på %s", // %s -> datum och tid för spelstart + "vcmi.lobby.header.chat.player" : "Privat chatt med %s", // %s -> smeknamn på en annan spelare + "vcmi.lobby.header.history" : "Dina tidigare spel", + "vcmi.lobby.header.players" : "Spelare online - %d", + "vcmi.lobby.match.solo" : "Spel för en spelare", + "vcmi.lobby.match.duel" : "Spel med %s", // %s -> smeknamn på en annan spelare + "vcmi.lobby.match.multi" : "%d spelare", + "vcmi.lobby.room.create" : "Skapa nytt rum", + "vcmi.lobby.room.players.limit" : "Begränsning av spelare", + "vcmi.lobby.room.description.public" : "Alla spelare kan gå med i det offentliga rummet.", + "vcmi.lobby.room.description.private" : "Endast inbjudna spelare kan gå med i ett privat rum.", + "vcmi.lobby.room.description.new" : "För att starta spelet, välj ett scenario eller skapa en slumpmässig karta.", + "vcmi.lobby.room.description.load" : "Använd ett av dina sparade spel för att starta spelet.", + "vcmi.lobby.room.description.limit" : "Upp till %d spelare kan komma in i ditt rum (dig inkluderad).", + "vcmi.lobby.invite.header" : "Bjud in spelare", + "vcmi.lobby.invite.notification" : "Spelaren har bjudit in dig till sitt spelrum. Du kan nu gå med i deras privata rum.", + "vcmi.lobby.preview.title" : "Gå med i spelrummet", + "vcmi.lobby.preview.subtitle" : "Spel på karta/RMG-mall: %s - Värdens smeknamn: %s", //TL Notering: 1) namn på karta eller RMG-mall 2) smeknamn på spelvärden + "vcmi.lobby.preview.version" : "Spelversion:", + "vcmi.lobby.preview.players" : "Spelare:", + "vcmi.lobby.preview.mods" : "Moddar som används:", + "vcmi.lobby.preview.allowed" : "Gå med i spelrummet?", + "vcmi.lobby.preview.error.header" : "Det går inte att gå med i det här rummet.", + "vcmi.lobby.preview.error.playing" : "Du måste lämna ditt nuvarande spel först.", + "vcmi.lobby.preview.error.full" : "Rummet är redan fullt.", + "vcmi.lobby.preview.error.busy" : "Rummet tar inte längre emot nya spelare.", + "vcmi.lobby.preview.error.invite" : "Du blev inte inbjuden till det här rummet.", + "vcmi.lobby.preview.error.mods" : "Du använder en annan uppsättning moddar.", + "vcmi.lobby.preview.error.version" : "Du använder en annan version av VCMI.", + "vcmi.lobby.room.new" : "Nytt spel", + "vcmi.lobby.room.load" : "Ladda spel", + "vcmi.lobby.room.type" : "Rumstyp", + "vcmi.lobby.room.mode" : "Spelläge", + "vcmi.lobby.room.state.public" : "Offentligt", + "vcmi.lobby.room.state.private" : "Privat", + "vcmi.lobby.room.state.busy" : "I spel", + "vcmi.lobby.room.state.invited" : "Inbjuden", + "vcmi.lobby.mod.state.compatible" : "Kompatibel", + "vcmi.lobby.mod.state.disabled" : "Måste vara aktiverat", + "vcmi.lobby.mod.state.version" : "Versioner matchar inte", + "vcmi.lobby.mod.state.excessive" : "Måste vara inaktiverat", + "vcmi.lobby.mod.state.missing" : "Ej installerad", + "vcmi.lobby.pvp.coin.hover" : "Mynt", + "vcmi.lobby.pvp.coin.help" : "Singla slant", + "vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad", + "vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpmässig stad i chatten", + "vcmi.lobby.pvp.randomTownVs.hover" : "Slumpmässig stad vs.", + "vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpmässiga städer i chatten", + "vcmi.lobby.pvp.versus" : "vs.", + + "vcmi.client.errors.invalidMap" : "{Ogiltig karta eller kampanj}\n\nStartade inte spelet! Vald karta eller kampanj kan vara ogiltig eller skadad. Orsak:\n%s", + "vcmi.client.errors.missingCampaigns" : "{Saknade datafiler}\n\nKampanjernas datafiler hittades inte! Du kanske använder ofullständiga eller skadade Heroes 3-datafiler. Vänligen installera om speldata.", + "vcmi.server.errors.disconnected" : "{Nätverksfel}\n\nAnslutningen till spelservern har förlorats!", + "vcmi.server.errors.existingProcess" : "En annan VCMI-serverprocess är igång. Vänligen avsluta den innan du startar ett nytt spel.", + "vcmi.server.errors.modsToEnable" : "{Följande modd(ar) krävs}", + "vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}", + "vcmi.server.errors.modNoDependency" : "Misslyckades med att ladda modd {'%s'}!\n Den är beroende av modd {'%s'} som inte är aktiverad!\n", + "vcmi.server.errors.modConflict" : "Misslyckades med att ladda modd {'%s'}!\n Konflikter med aktiverad modd {'%s'}!\n", + "vcmi.server.errors.unknownEntity" : "Misslyckades med att ladda sparat spel! Okänd enhet '%s' hittades i sparat spel! Sparningen kanske inte är kompatibel med den aktuella versionen av moddarna!", + + "vcmi.dimensionDoor.seaToLandError" : "Det går inte att teleportera sig från hav till land eller tvärtom med trollformeln 'Dimensionsdörr'.", + + "vcmi.settingsMainWindow.generalTab.hover" : "Allmänt", + "vcmi.settingsMainWindow.generalTab.help" : "Växlar till fliken/menyn med allmänna spelklients-inställningar relaterade till allmänt beteende för spelklienten.", + "vcmi.settingsMainWindow.battleTab.hover" : "Strid", + "vcmi.settingsMainWindow.battleTab.help" : "Växlar till fliken/menyn med strids-inställningar där man kan konfigurera spelets beteende under strider.", + "vcmi.settingsMainWindow.adventureTab.hover" : "Äventyrskarta", + "vcmi.settingsMainWindow.adventureTab.help" : "Växlar till fliken/menyn med inställningar som har med äventyrskartan att göra (äventyrskartan är den del av spelet där spelarna kan styra sina hjältars förflyttning på land, vatten och nere i underjorden).", + + "vcmi.systemOptions.videoGroup" : "Bild-inställningar", + "vcmi.systemOptions.audioGroup" : "Ljud-inställningar", + "vcmi.systemOptions.otherGroup" : "Andra inställningar", // unused right now + "vcmi.systemOptions.townsGroup" : "By-/Stads-skärm", + + "vcmi.statisticWindow.statistics" : "Statistik", + "vcmi.statisticWindow.tsvCopy" : "Statistik-data till urklipp", + "vcmi.statisticWindow.selectView" : "Välj vy", + "vcmi.statisticWindow.value" : "Värde", + "vcmi.statisticWindow.title.overview" : "Översikt", + "vcmi.statisticWindow.title.resources" : "Resurser", + "vcmi.statisticWindow.title.income" : "Inkomst", + "vcmi.statisticWindow.title.numberOfHeroes" : "Antal hjältar", + "vcmi.statisticWindow.title.numberOfTowns" : "Antal städer/byar", + "vcmi.statisticWindow.title.numberOfArtifacts" : "Antal artefakter", + "vcmi.statisticWindow.title.numberOfDwellings" : "Antal varelse-bon", + "vcmi.statisticWindow.title.numberOfMines" : "Antal gruvor", + "vcmi.statisticWindow.title.armyStrength" : "Arméns styrka", + "vcmi.statisticWindow.title.experience" : "Erfarenhet", + "vcmi.statisticWindow.title.resourcesSpentArmy" : "Armé-kostnader", + "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Byggnadskostnader", + "vcmi.statisticWindow.title.mapExplored" : "Kart-utforskningsratio", + "vcmi.statisticWindow.param.playerName" : "Spelarens namn", + "vcmi.statisticWindow.param.daysSurvived" : "Överlevda dagar", + "vcmi.statisticWindow.param.maxHeroLevel" : "Den högsta hjältenivån", + "vcmi.statisticWindow.param.battleWinRatioHero" : "Vinstkvot (gentemot hjältar)", + "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Vinstkvot (gentemot neutrala)", + "vcmi.statisticWindow.param.battlesHero" : "Strider (gentemot hjältar)", + "vcmi.statisticWindow.param.battlesNeutral" : "Strider (gentemot neutrala)", + "vcmi.statisticWindow.param.maxArmyStrength" : "Största totala arméstyrkan", + "vcmi.statisticWindow.param.tradeVolume" : "Handelsvolym", + "vcmi.statisticWindow.param.obeliskVisited" : "Obelisker besökta", + "vcmi.statisticWindow.icon.townCaptured" : "Städer/byar erövrade", + "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Den starkaste motståndarhjälten som blivit besegrad", + "vcmi.statisticWindow.icon.grailFound" : "Graal funnen", + "vcmi.statisticWindow.icon.defeated" : "Besegrad", + + "vcmi.systemOptions.fullscreenBorderless.hover" : "Helskärm (kantlös)", + "vcmi.systemOptions.fullscreenBorderless.help" : "{Kantlös helskärm}\n\nI kantlöst helskärmsläge kommer spelet alltid att använda samma bildskärmsupplösning som valts i operativsystemet (bildskärmsupplösningen som valts i VCMI kommer ignoreras).", + "vcmi.systemOptions.fullscreenExclusive.hover" : "Exklusivt helskärmsläge", + "vcmi.systemOptions.fullscreenExclusive.help" : "{Helskärm}\n\nI exklusivt helskärmsläge kommer spelet att ändra bildskärmsupplösningen till det som valts i VCMI.", + "vcmi.systemOptions.resolutionButton.hover" : "Bildskärmsupplösning: %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{Bildskärmsupplösning}\n\nÄndrar bildskärmens upplösning i spelet.", + "vcmi.systemOptions.resolutionMenu.hover" : "Välj bildskärmsupplösningen i spelet", + "vcmi.systemOptions.resolutionMenu.help" : "Ändrar bildskärmsupplösning i spelet.", + "vcmi.systemOptions.scalingButton.hover" : "Gränssnittsskalning: %p%", + "vcmi.systemOptions.scalingButton.help" : "{Gränssnittsskalning}\n\nÄndrar storleken av de olika gränssnitten som finns i spelet.", + "vcmi.systemOptions.scalingMenu.hover" : "Välj gränssnittsskalning", + "vcmi.systemOptions.scalingMenu.help" : "Förstorar eller förminskar olika gränssnitt i spelet.", + "vcmi.systemOptions.longTouchButton.hover" : "Fördröjt tryckintervall: %d ms", // Översättningsnot: ’ms’ = "millisekunder" + "vcmi.systemOptions.longTouchButton.help" : "{Fördröjt tryckintervall}\n\nNär du använder dig av en pekskärm och vill komma åt spelalternativ behöver du göra en fördröjd pekskärmsberöring under en specifikt angiven tid (i millisekunder) för att en popup-meny skall synas.", + "vcmi.systemOptions.longTouchMenu.hover" : "Välj tidsintervall för fördröjd pekskärmsberöringsmeny", + "vcmi.systemOptions.longTouchMenu.help" : "Ändra varaktighetsintervallet för fördröjd beröring.", + "vcmi.systemOptions.longTouchMenu.entry" : "%d millisekunder","%d millisekunder", + "vcmi.systemOptions.framerateButton.hover" : "Visar FPS (skärmbilder per sekund)", + "vcmi.systemOptions.framerateButton.help" : "{Visa FPS}\n\nVisar räknaren för bildrutor per sekund i hörnet av spelfönstret.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptisk återkoppling", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Haptisk feedback}\n\nÄndrar den haptiska feedbacken för berörings-input.", + "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Förbättringar av användargränssnittet", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Gränssnittsförbättringar}\n\nVälj mellan olika förbättringar av användargränssnittet. Till exempel en lättåtkomlig ryggsäcksknapp med mera. Avaktivera för att få en mer klassisk spelupplevelse.", + "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Stor trollformelsbok", + "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Stor trollformelsbok}\n\nAktiverar en större trollformelsbok som rymmer fler trollformler per sida (animeringen av sidbyte i den större trollformelsboken fungerar inte).", + "vcmi.systemOptions.audioMuteFocus.hover" : "Stänger av ljudet vid inaktivitet", + "vcmi.systemOptions.audioMuteFocus.help" : "{Stäng av ljud vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny tur/omgång.", + + "vcmi.adventureOptions.infoBarPick.hover" : "Visar textmeddelanden i infopanelen", + "vcmi.adventureOptions.infoBarPick.help" : "{Infopanelsmeddelanden}\n\nNär det är möjligt kommer spelmeddelanden från besökande kartobjekt att visas i infopanelen istället för att dyka upp i ett separat fönster.", + "vcmi.adventureOptions.numericQuantities.hover" : "Numeriska antal varelser", + "vcmi.adventureOptions.numericQuantities.help" : "{Numerisk varelsemängd}\n\nVisa ungefärliga mängder av fiendevarelser i det numeriska A-B-formatet.", + "vcmi.adventureOptions.forceMovementInfo.hover" : "Visa alltid förflyttningskostnad", + "vcmi.adventureOptions.forceMovementInfo.help" : "{Visa alltid förflyttningskostnad}\n\nVisar alltid förflyttningspoäng i statusfältet (istället för att bara visa dem när du håller ned ALT-tangenten).", + "vcmi.adventureOptions.showGrid.hover" : "Visa rutnät", + "vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.", + "vcmi.adventureOptions.borderScroll.hover" : "Kantrullning", + "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantering av varelser i infopanelen i nedre högra hörnet", + "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Dra kartan med vänster musknapp", + "vcmi.adventureOptions.leftButtonDrag.help" : "{Vänsterklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med vänster musknapp nedtryckt.", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Dra kartan med höger musknapp", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Högerklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med höger musknapp nedtryckt.", + "vcmi.adventureOptions.smoothDragging.hover" : "Mjuk kartdragning", + "vcmi.adventureOptions.smoothDragging.help" : "{Mjuk kartdragning}\n\nVid aktivering så har kartdragningen en modern rullningseffekt.", + "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Skippar övertoningseffekter", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Skippa övertoningseffekter}\n\nHoppar över ut- och intoningseffekten och liknande effekter av kartobjekt (resursinsamling, ombordning och avbordning av skepp osv.). Gör användargränssnittet mer reaktivt i vissa fall på bekostnad av estetiken. Speciellt användbart i PvP-spel. När maximal förflyttningshastighet är valt så är skippning av effekter aktiverat oavsett vad du har valt här.", + "vcmi.adventureOptions.mapScrollSpeed1.hover" : "", + "vcmi.adventureOptions.mapScrollSpeed5.hover" : "", + "vcmi.adventureOptions.mapScrollSpeed6.hover" : "", + "vcmi.adventureOptions.mapScrollSpeed1.help" : "Ställ in kartans rullningshastighet på 'Mycket långsam'.", + "vcmi.adventureOptions.mapScrollSpeed5.help" : "Ställ in kartans rullningshastighet på 'Mycket snabb'.", + "vcmi.adventureOptions.mapScrollSpeed6.help" : "Ställ in kartans rullningshastighet till 'Omedelbar'.", + "vcmi.adventureOptions.hideBackground.hover" : "Dölj bakgrund", + "vcmi.adventureOptions.hideBackground.help" : "{Dölj bakgrund}\n\nDöljer äventyrskartan i bakgrunden och visar en textur istället.", + + "vcmi.battleOptions.queueSizeLabel.hover" : "Visa turordningskö", + "vcmi.battleOptions.queueSizeNoneButton.hover" : "AV", + "vcmi.battleOptions.queueSizeAutoButton.hover" : "AUTO", + "vcmi.battleOptions.queueSizeSmallButton.hover" : "LITEN", + "vcmi.battleOptions.queueSizeBigButton.hover" : "STOR", + "vcmi.battleOptions.queueSizeNoneButton.help" : "Visa inte turordningskö.", + "vcmi.battleOptions.queueSizeAutoButton.help" : "Justera automatiskt storleken på turordningskön baserat på spelets skärmbildsupplösning ('LITEN' används när det är mindre än 700 pixlar i höjd, 'STOR' används annars).", + "vcmi.battleOptions.queueSizeSmallButton.help" : "Ställer in storleksinställningen på turordningskön till 'LITEN'.", + "vcmi.battleOptions.queueSizeBigButton.help" : "Ställer in storleksinställningen på turordningskön till 'STOR' (bildskärmsupplösningen måste överstiga 700 pixlar i höjd).", + "vcmi.battleOptions.animationsSpeed1.hover" : "", + "vcmi.battleOptions.animationsSpeed5.hover" : "", + "vcmi.battleOptions.animationsSpeed6.hover" : "", + "vcmi.battleOptions.animationsSpeed1.help" : "Ställ in animationshastigheten till mycket långsam.", + "vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.", + "vcmi.battleOptions.animationsSpeed6.help" : "Ställ in animationshastigheten till omedelbar.", + "vcmi.battleOptions.movementHighlightOnHover.hover" : "Muspeka (hovra) för att avslöja förflyttningsräckvidd", + "vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Avslöja skyttars räckvidd", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help" : "{Muspeka för att avslöja skyttars räckvidd}\n\nVisar hur långt en enhets distansattack sträcker sig över slagfältet när du håller muspekaren över dem.", + "vcmi.battleOptions.showStickyHeroInfoWindows.hover" : "Visa fönster med hjältars primärförmågor", + "vcmi.battleOptions.showStickyHeroInfoWindows.help" : "{Visa fönster med hjältars primärförmågor}\n\nKommer alltid att visa ett fönster där du kan se dina hjältars primärförmågor (anfall, försvar, trollkonst, kunskap och trollformelpoäng).", + "vcmi.battleOptions.skipBattleIntroMusic.hover" : "Hoppa över intromusik", + "vcmi.battleOptions.skipBattleIntroMusic.help" : "{Hoppa över intromusik}\n\nTillåt åtgärder under intromusiken som spelas i början av varje strid.", + "vcmi.battleOptions.endWithAutocombat.hover" : "Slutför striden så fort som möjligt", + "vcmi.battleOptions.endWithAutocombat.help" : "{Slutför strid}\n\nAuto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.", + "vcmi.battleOptions.showQuickSpell.hover" : "Snabb åtkomst till dina trollformler", + "vcmi.battleOptions.showQuickSpell.help" : "{Visa snabbtrollformels-panelen}\n\nVisar en snabbvalspanel vid sidan av stridsfönstret där du har snabb åtkomst till några av dina trollformler", + + "vcmi.adventureMap.revisitObject.hover" : "Gör ett återbesök", + "vcmi.adventureMap.revisitObject.help" : "{Återbesök kartobjekt}\n\nEn hjälte som för närvarande står på ett kartobjekt kan göra ett återbesök (utan att först behöva avlägsna sig från kartobjektet för att sedan göra ett återbesök).", + + "vcmi.battleWindow.pressKeyToSkipIntro" : "Tryck på valfri tangent för att starta striden omedelbart", + "vcmi.battleWindow.damageEstimation.melee" : "Attackera %CREATURE (%DAMAGE).", + "vcmi.battleWindow.damageEstimation.meleeKills" : "Attackera %CREATURE (%DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.ranged" : "Skjut %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Skjut %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d skott kvar", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d skott kvar", + "vcmi.battleWindow.damageEstimation.damage" : "%d skada", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d skada", + "vcmi.battleWindow.damageEstimation.kills" : "%d kommer att förgås", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d kommer att förgås", + + "vcmi.battleWindow.damageRetaliation.will" : "Kommer att retaliera ", + "vcmi.battleWindow.damageRetaliation.may" : "Kommer kanske att retaliera ", + "vcmi.battleWindow.damageRetaliation.never" : "Kommer inte att retaliera.", + "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", + "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", + + "vcmi.battleWindow.killed" : "Dödad", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s dödades av träffsäkra skott!", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s dödades med ett träffsäkert skott!", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s dödades av träffsäkra skott!", + "vcmi.battleWindow.endWithAutocombat" : "Är du säker på att du vill slutföra striden med auto-strid?", + + "vcmi.battleResultsWindow.applyResultsLabel" : "Acceptera stridsresultat?", + + "vcmi.tutorialWindow.title" : "Pekskärmsintroduktion", + "vcmi.tutorialWindow.decription.RightClick" : "Rör vid och håll kvar det element som du vill högerklicka på. Tryck på det fria området för att stänga.", + "vcmi.tutorialWindow.decription.MapPanning" : "Tryck och dra med ett finger för att flytta kartan.", + "vcmi.tutorialWindow.decription.MapZooming" : "Nyp med två fingrar för att ändra kartans zoom.", + "vcmi.tutorialWindow.decription.RadialWheel" : "Genom att svepa öppnas ett radiellt hjul för olika åtgärder t.ex. hantering av varelser/hjältar och stadsåtgärder.", + "vcmi.tutorialWindow.decription.BattleDirection" : "För att attackera från en viss riktning sveper du i den riktning från vilken attacken ska göras.", + "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesten för attackriktning kan avbrytas om du sveper tillräckligt långt bort.", + "vcmi.tutorialWindow.decription.AbortSpell" : "Tryck och håll för att avbryta en vald trollformel.", + + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Visar tillgängliga varelser att rekrytera", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Visa tillgängliga varelser}\n\nVisa antalet varelser som finns tillgängliga att rekrytera istället för deras veckovisa förökning i stadsöversikten (nedre vänstra hörnet av stadsskärmen).", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Visar den veckovisa varelseförökningen", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Visa veckovis varelseförökning}\n\nVisa varelsers veckovisa förökning istället för antalet tillgängliga varelser i stadsöversikten (nedre vänstra hörnet av stadsskärmen).", + "vcmi.otherOptions.compactTownCreatureInfo.hover" : "Visa mindre varelse-info", + "vcmi.otherOptions.compactTownCreatureInfo.help" : "{Kompakt varelse-info}\n\nVisa mindre information för stadens varelser i stadsöversikten (nedre vänstra hörnet av stadsskärmen).", + + "vcmi.townHall.missingBase" : "Basbyggnaden '%s' måste byggas först", + "vcmi.townHall.noCreaturesToRecruit" : "Det finns inga varelser att rekrytera!", + + "vcmi.townStructure.bank.borrow" : "Du går in i banken. En bankman ser dig och säger: \"Vi har gjort ett specialerbjudande till dig. Du kan ta ett lån på 2500 guld från oss i 5 dagar. Du måste återbetala 500 guld varje dag.\"", + "vcmi.townStructure.bank.payBack" : "Du går in i banken. En bankman ser dig och säger: \"Du har redan fått ditt lån. Betala tillbaka det innan du tar ett nytt.\"", + + "vcmi.logicalExpressions.anyOf" : "Något av följande:", + "vcmi.logicalExpressions.allOf" : "Alla följande:", + "vcmi.logicalExpressions.noneOf" : "Inget av följande:", + + "vcmi.heroWindow.openCommander.hover" : "Öppna befälhavarens informationsfönster", + "vcmi.heroWindow.openCommander.help" : "Visar detaljer om befälhavaren för den här hjälten.", + "vcmi.heroWindow.openBackpack.hover" : "Öppna artefaktryggsäcksfönster", + "vcmi.heroWindow.openBackpack.help" : "Öppnar fönster som gör det enklare att hantera artefaktryggsäcken.", + + "vcmi.tavernWindow.inviteHero" : "Bjud in hjälte", + + "vcmi.commanderWindow.artifactMessage" : "Vill du återlämna denna artefakt till hjälten?", + + "vcmi.creatureWindow.showBonuses.hover" : "Byt till bonusvy", + "vcmi.creatureWindow.showBonuses.help" : "Visa befälhavarens aktiva bonusar.", + "vcmi.creatureWindow.showSkills.hover" : "Byt till färdighetsvy", + "vcmi.creatureWindow.showSkills.help" : "Visa befälhavarens inlärda färdigheter.", + "vcmi.creatureWindow.returnArtifact.hover" : "Återlämna artefakt", + "vcmi.creatureWindow.returnArtifact.help" : "Klicka på den här knappen för att lägga tillbaka artefakten i hjältens ryggsäck.", + + "vcmi.questLog.hideComplete.hover" : "Gömmer alla slutförda uppdrag", + "vcmi.questLog.hideComplete.help" : "Dölj alla slutförda uppdrag.", + + "vcmi.randomMapTab.widgets.randomTemplate" : "(Slumpmässig)", + "vcmi.randomMapTab.widgets.templateLabel" : "Mall", + "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Ställ in...", + "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Laggruppering", + "vcmi.randomMapTab.widgets.roadTypesLabel" : "Vägtyper", + + "vcmi.optionsTab.turnOptions.hover" : "Turomgångsalternativ", + "vcmi.optionsTab.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer", + + "vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern", + "vcmi.optionsTab.chessFieldTurn.hover" : "Turomgångs-timern", + "vcmi.optionsTab.chessFieldBattle.hover" : "Strids-timern", + "vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-timern", + "vcmi.optionsTab.chessFieldBase.help" : "Används när {Turomgångs-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).", + "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern} till nästa turomgång.", + "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid går förlorad.", + "vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PVP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PVP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PVP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", + + "vcmi.optionsTab.accumulate" : "Ackumulera", + + "vcmi.optionsTab.simturnsTitle" : "Simultana turomgångar", + "vcmi.optionsTab.simturnsMin.hover" : "Åtminstone i", + "vcmi.optionsTab.simturnsMax.hover" : "Som mest i", + "vcmi.optionsTab.simturnsAI.hover" : "(Experimentell) Simultana AI-turomgångar", + "vcmi.optionsTab.simturnsMin.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar. Kontakter mellan spelare under denna period är blockerade", + "vcmi.optionsTab.simturnsMax.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar eller tills en tillräckligt nära kontakt inträffar med en annan spelare", + "vcmi.optionsTab.simturnsAI.help" : "{Simultana AI-turomgångar}\nExperimentellt alternativ. Tillåter AI-spelare att agera samtidigt som den mänskliga spelaren när simultana turomgångar är aktiverade.", + + "vcmi.optionsTab.turnTime.select" : "Turtids-förinställningar", + "vcmi.optionsTab.turnTime.unlimited" : "Obegränsat med tid", + "vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut", + "vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter", + "vcmi.optionsTab.turnTime.classic.5" : "Klassisk timer: 5 minuter", + "vcmi.optionsTab.turnTime.classic.10" : "Klassisk timer: 10 minuter", + "vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter", + "vcmi.optionsTab.turnTime.classic.30" : "Klassisk timer: 30 minuter", + "vcmi.optionsTab.turnTime.chess.20" : "Schack-timer: 20:00 + 10:00 + 02:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.16" : "Schack-timer: 16:00 + 08:00 + 01:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.8" : "Schack-timer: 08:00 + 04:00 + 01:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.4" : "Schack-timer: 04:00 + 02:00 + 00:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.2" : "Schack-timer: 02:00 + 01:00 + 00:15 + 00:00", + "vcmi.optionsTab.turnTime.chess.1" : "Schack-timer: 01:00 + 01:00 + 00:00 + 00:00", + + "vcmi.optionsTab.simturns.select" : "Välj förinställning för simultana/samtidiga turer", + "vcmi.optionsTab.simturns.none" : "Inga simultana/samtidiga turer", + "vcmi.optionsTab.simturns.tillContactMax" : "Simultantur: Fram till kontakt", + "vcmi.optionsTab.simturns.tillContact1" : "Simultantur: 1 vecka, bryt vid kontakt", + "vcmi.optionsTab.simturns.tillContact2" : "Simultantur: 2 veckor, bryt vid kontakt", + "vcmi.optionsTab.simturns.tillContact4" : "Simultantur: 1 månad, bryt vid kontakt", + "vcmi.optionsTab.simturns.blocked1" : "Simultantur: 1 vecka, kontakter blockerade", + "vcmi.optionsTab.simturns.blocked2" : "Simultantur: 2 veckor, kontakter blockerade", + "vcmi.optionsTab.simturns.blocked4" : "Simultantur: 1 månad, kontakter blockerade", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d dagar", + "vcmi.optionsTab.simturns.days.1" : " %d dag", + "vcmi.optionsTab.simturns.days.2" : " %d dagar", + "vcmi.optionsTab.simturns.weeks.0" : " %d veckor", + "vcmi.optionsTab.simturns.weeks.1" : " %d vecka", + "vcmi.optionsTab.simturns.weeks.2" : " %d veckor", + "vcmi.optionsTab.simturns.months.0" : " %d månader", + "vcmi.optionsTab.simturns.months.1" : " %d månad", + "vcmi.optionsTab.simturns.months.2" : " %d månader", + + "vcmi.optionsTab.extraOptions.hover" : "Extra-inställningar", + "vcmi.optionsTab.extraOptions.help" : "Ytterligare spelinställningar", + + "vcmi.optionsTab.cheatAllowed.hover" : "Tillåter fusk i spelet", + "vcmi.optionsTab.unlimitedReplay.hover" : "Obegränsade omspelningar av strider", + "vcmi.optionsTab.cheatAllowed.help" : "{Tillåt fusk}\nTillåter inmatning av fuskkoder under spelets gång.", + "vcmi.optionsTab.unlimitedReplay.help" : "{Obegränsade stridsomspelningar}\nIngen begränsning för hur många gånger man kan spela om sina strider.", + + // Custom victory conditions for H3 campaigns and HotA maps + "vcmi.map.victoryCondition.daysPassed.toOthers" : "Fienden har lyckats överleva fram till denna dag. Segern är deras!", + "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulerar! Du har lyckats överleva. Segern är er!", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Fienden har besegrat alla monster som plågade detta land och utropar seger!", + "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulerar! Du har besegrat alla monster som plågade detta land och kan utropa seger!", + "vcmi.map.victoryCondition.collectArtifacts.message" : "Förvärva tre artefakter", + "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulerar! Alla dina fiender har besegrats och du har 'Änglaalliansen'! Segern är din!", + "vcmi.map.victoryCondition.angelicAlliance.message" : "Besegra alla fiender och sätt ihop 'Änglaalliansen'", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Ack, du har förlorat en del av 'Änglaalliansen'. Allt är förlorat.", + + // few strings from WoG used by vcmi + "vcmi.stackExperience.description" : "» V a r e l s e f ö r b a n d s d e t a l j e r «\n\nVarelsetyp ................... : %s\nErfarenhetsrank ................. : %s (%i)\nErfarenhetspoäng ............... : %i\nErfarenhetspoäng till nästa rank .. : %i\nMaximal erfarenhet per strid ... : %i%% (%i)\nAntal varelser i förbandet .... : %i\nMaximalt antal nya rekryter\n utan att förlora nuvarande rank .... : %i\nErfarenhetsmultiplikator ........... : %.2f\nUppgradera erfarenhetsmultiplikator .............. : %.2f\nErfarenhet efter rank 10 ........ : %i\nMaximalt antal nyrekryteringar för att stanna kvar på\n rank 10 vid maximal erfarenhet : %i", + "vcmi.stackExperience.rank.0" : "Grundläggande", + "vcmi.stackExperience.rank.1" : "Novis", + "vcmi.stackExperience.rank.2" : "Tränad", + "vcmi.stackExperience.rank.3" : "Erfaren", + "vcmi.stackExperience.rank.4" : "Beprövad", + "vcmi.stackExperience.rank.5" : "Veteran", + "vcmi.stackExperience.rank.6" : "Adept", + "vcmi.stackExperience.rank.7" : "Expert", + "vcmi.stackExperience.rank.8" : "Elit", + "vcmi.stackExperience.rank.9" : "Mästare", + "vcmi.stackExperience.rank.10" : "Äss", + + // Strings for HotA Seer Hut / Quest Guards + "core.seerhut.quest.heroClass.complete.0" : "Ah, du är %s. Här är en gåva till dig. Tar du emot den?", + "core.seerhut.quest.heroClass.complete.1" : "Ah, du är %s. Här är en gåva till dig. Tar du emot den?", + "core.seerhut.quest.heroClass.complete.2" : "Ah, du är %s. Här är en gåva till dig. Tar du emot den?", + "core.seerhut.quest.heroClass.complete.3" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?", + "core.seerhut.quest.heroClass.complete.4" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?", + "core.seerhut.quest.heroClass.complete.5" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?", + "core.seerhut.quest.heroClass.description.0" : "Skicka %s till %s", + "core.seerhut.quest.heroClass.description.1" : "Skicka %s till %s", + "core.seerhut.quest.heroClass.description.2" : "Skicka %s till %s", ’Skicka %s till %s’, + "core.seerhut.quest.heroClass.description.3" : "Skicka %s för att öppna grinden", + "core.seerhut.quest.heroClass.description.4" : "Skicka %s till öppen grind", + "core.seerhut.quest.heroClass.description.5" : "Skicka %s till öppen grind", + "core.seerhut.quest.heroClass.hover.0" : "(söker hjälte i %s klass)", + "core.seerhut.quest.heroClass.hover.1" : "(söker hjälte i %s klass)", + "core.seerhut.quest.heroClass.hover.2" : "(söker hjälte i %s klass)", + "core.seerhut.quest.heroClass.hover.3" : "(söker hjälte i %s klass)", + "core.seerhut.quest.heroClass.hover.4" : "(söker hjälte i %s klass)", + "core.seerhut.quest.heroClass.hover.5" : "(söker hjälte i %s klass)", + "core.seerhut.quest.heroClass.receive.0" : "Jag har en gåva till %s.", + "core.seerhut.quest.heroClass.receive.1" : "Jag har en gåva till %s.", + "core.seerhut.quest.heroClass.receive.2" : "Jag har en gåva till %s.", + "core.seerhut.quest.heroClass.receive.3" : "Vakterna här säger att de bara kommer att låta %s passera.", + "core.seerhut.quest.heroClass.receive.4" : "Vakterna här säger att de bara kommer att låta %s passera.", + "core.seerhut.quest.heroClass.receive.5" : "Vakterna här säger att de bara kommer att låta %s passera.", + "core.seerhut.quest.heroClass.visit.0" : "Du är inte %s. Det finns inget här för dig att hämta. Försvinn!", + "core.seerhut.quest.heroClass.visit.1" : "Du är inte %s. Det finns inget här för dig att hämta. Försvinn!", + "core.seerhut.quest.heroClass.visit.2" : "Du är inte %s. Det finns inget här för dig att hämta. Försvinn!", + "core.seerhut.quest.heroClass.visit.3" : "Vakterna här kommer bara att låta %s passera.", + "core.seerhut.quest.heroClass.visit.4" : "Vakterna här kommer bara att låta %s passera.", + "core.seerhut.quest.heroClass.visit.5" : "Vakterna här kommer bara att låta %s passera.", + + "core.seerhut.quest.reachDate.complete.0" : "Jag är fri nu. Det här är vad jag har att erbjuda dig. Accepterar du?", + "core.seerhut.quest.reachDate.complete.1" : "Jag är fri nu. Det här är vad jag har att erbjuda dig. Accepterar du?", + "core.seerhut.quest.reachDate.complete.2" : "Jag är fri nu. Det här är vad jag har att erbjuda dig. Accepterar du?", + "core.seerhut.quest.reachDate.complete.3" : "Du är fri att gå igenom nu. Önskar ni att passera?", + "core.seerhut.quest.reachDate.complete.4" : "Du är fri att gå igenom nu. Önskar ni att passera?", + "core.seerhut.quest.reachDate.complete.5" : "Du är fri att gå igenom nu. Önskar ni att passera?", + "core.seerhut.quest.reachDate.description.0" : "Vänta tills %s för %s", + "core.seerhut.quest.reachDate.description.1" : "Vänta tills %s för %s", + "core.seerhut.quest.reachDate.description.2" : "Vänta tills %s för %s", + "core.seerhut.quest.reachDate.description.3" : "Vänta tills %s öppnar grinden", + "core.seerhut.quest.reachDate.description.4" : "Vänta tills %s öppnar grinden", + "core.seerhut.quest.reachDate.description.5" : "Vänta tills %s öppnar grinden", + "core.seerhut.quest.reachDate.hover.0" : "(Återvänd inte före %s)", + "core.seerhut.quest.reachDate.hover.1" : "(Återvänd inte före %s)", + "core.seerhut.quest.reachDate.hover.2" : "(Återvänd inte före %s)", + "core.seerhut.quest.reachDate.hover.3" : "(Återvänd inte före %s)", + "core.seerhut.quest.reachDate.hover.4" : "(Återvänd inte före %s)", + "core.seerhut.quest.reachDate.hover.5" : "(Återvänd inte före %s)", + "core.seerhut.quest.reachDate.receive.0" : "Jag är upptagen. Kom inte tillbaka före %s", + "core.seerhut.quest.reachDate.receive.1" : "Jag är upptagen. Kom inte tillbaka före %s", + "core.seerhut.quest.reachDate.receive.2" : "Jag är upptagen. Kom inte tillbaka före %s", + "core.seerhut.quest.reachDate.receive.3" : "Stängt fram till %s.", + "core.seerhut.quest.reachDate.receive.4" : "Stängt fram till %s.", + "core.seerhut.quest.reachDate.receive.5" : "Stängt fram till %s.", + "core.seerhut.quest.reachDate.visit.0" : "Jag är upptagen. Kom inte tillbaka före %s.", + "core.seerhut.quest.reachDate.visit.1" : "Jag är upptagen. Kom inte tillbaka före %s.", + "core.seerhut.quest.reachDate.visit.2" : "Jag är upptagen. Kom inte tillbaka före %s.", + "core.seerhut.quest.reachDate.visit.3" : "Stängt fram till %s.", + "core.seerhut.quest.reachDate.visit.4" : "Stängt fram till %s.", + "core.seerhut.quest.reachDate.visit.5" : "Stängt fram till %s.", + + "core.bonus.ADDITIONAL_ATTACK.name" : "Dubbelslag", + "core.bonus.ADDITIONAL_ATTACK.description" : "Attackerar två gånger", + "core.bonus.ADDITIONAL_RETALIATION.name" : "Ytterligare motattacker", + "core.bonus.ADDITIONAL_RETALIATION.description" : "Kan slå tillbaka ${val} extra gånger", + "core.bonus.AIR_IMMUNITY.name" : "Luft-immunitet", + "core.bonus.AIR_IMMUNITY.description" : "Immun mot alla trollformler från skolan för luftmagi", + "core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring", + "core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender", + "core.bonus.BLOCKS_RETALIATION.name" : "Ingen motattack", + "core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera", + "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Ingen motattack på avstånd", + "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte göra en motattack/retaliering på avstånd genom att använda en distansattack", + "core.bonus.CATAPULT.name" : "Katapult", + "core.bonus.CATAPULT.description" : "Attackerar belägringsmurar", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description" : "Minskar trollformelkostnaden för hjälten med ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name" : "Magisk dämpare (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Ökar trollformelkostnaden för fiendens trollformler med ${val}", + "core.bonus.CHARGE_IMMUNITY.name" : "Galoppanfalls-immunitet", + "core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall", + "core.bonus.DARKNESS.name" : "I skydd av mörkret", + "core.bonus.DARKNESS.description" : "Skapar ett hölje av mörker med en ${val}-rutorsradie", + "core.bonus.DEATH_STARE.name" : "Dödsblick (${val}%)", + "core.bonus.DEATH_STARE.description" : "Varje förbandsenhet med 'Dödsblick' har ${val}% chans att döda den översta enheten i ett fiendeförband", + "core.bonus.DEFENSIVE_STANCE.name" : "Försvarshållning", + "core.bonus.DEFENSIVE_STANCE.description" : "Ger ytterligare +${val} till enhetens försvarsförmåga när du väljer att försvarar dig", + "core.bonus.DESTRUCTION.name" : "Förintelse", + "core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack", + "core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Dödsstöt", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att ge dubbel basskada vid attack", + "core.bonus.DRAGON_NATURE.name" : "Drake", + "core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur", + "core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet", + "core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi", + "core.bonus.ENCHANTER.name" : "Förtrollare", + "core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje tur/omgång", + "core.bonus.ENCHANTED.name" : "Förtrollad", + "core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}", + "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description" : "När du blir attackerad ignoreras ${val}% av angriparens attack", + "core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Förbigå försvar (${val}%)", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "När du attackerar ignoreras ${val}% av försvararens försvar", + "core.bonus.FIRE_IMMUNITY.name" : "Eld-immunitet", + "core.bonus.FIRE_IMMUNITY.description" : "Immun mot alla trollformler från skolan för eldmagi", + "core.bonus.FIRE_SHIELD.name" : "Eldsköld (${val}%)", + "core.bonus.FIRE_SHIELD.description" : "Reflekterar en del av närstridsskadorna", + "core.bonus.FIRST_STRIKE.name" : "Första slaget", + "core.bonus.FIRST_STRIKE.description" : "Denna varelse gör en motattack innan den blir attackerad", + "core.bonus.FEAR.name" : "Rädsla", + "core.bonus.FEAR.description" : "Orsakar rädsla på ett fiendeförband", + "core.bonus.FEARLESS.name" : "Orädd", + "core.bonus.FEARLESS.description" : "Immun mot rädsla", + "core.bonus.FEROCITY.name" : "Vildsint", + "core.bonus.FEROCITY.description" : "Attackerar ${val} extra gång(er) om någon dödas", + "core.bonus.FLYING.name" : "Flygande", + "core.bonus.FLYING.description" : "Flyger vid förflyttning (ignorerar hinder)", + "core.bonus.FREE_SHOOTING.name" : "Skjut på nära håll", + "core.bonus.FREE_SHOOTING.description" : "Kan använda distansattacker på närstridsavstånd", + "core.bonus.GARGOYLE.name" : "Stenfigur", + "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas", + "core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar fysisk skada från både distans- och närstridsattacker", + "core.bonus.HATE.name" : "Hatar ${subtyp.varelse}", + "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}", + "core.bonus.HEALER.name" : "Helare", + "core.bonus.HEALER.description" : "Helar/läker allierade enheter", + "core.bonus.HP_REGENERATION.name" : "Självläkande", + "core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} träffpoäng (hälsa) varje runda", + "core.bonus.JOUSTING.name" : "Galopperande ridanfall", + "core.bonus.JOUSTING.description" : "Orsakar +${val}% extra skada för varje ruta som enheten förflyttas innan attack", + "core.bonus.KING.name" : "Kung", + "core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre", + "core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Förtrollningsimmunitet 1-${val}", + "core.bonus.LEVEL_SPELL_IMMUNITY.description" : "Immun mot trollformler på nivå 1-${val}", + "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad räckvidd för skjutning", + "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Kan inte sikta på enheter längre bort än ${val} rutor", + "core.bonus.LIFE_DRAIN.name" : "Dränerar livskraft (${val}%)", + "core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% träffpoäng (hälsa) av utdelad skada", + "core.bonus.MANA_CHANNELING.name" : "Kanalisera trollformelspoäng ${val}%", + "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av den mängd trollformelspoäng som fienden spenderar per trollformel i strid", + "core.bonus.MANA_DRAIN.name" : "Dränera trollformelspoäng", + "core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur", + "core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)", + "core.bonus.MAGIC_MIRROR.description" : "Har ${val}% chans att reflektera (omdirigera) en offensiv trollformel på en fiendeenhet", + "core.bonus.MAGIC_RESISTANCE.name" : "Magiskt motstånd (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel", + "core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar", + "core.bonus.MIND_IMMUNITY.description" : "Immun mot förtrollningar som påverkar dina sinnen", + "core.bonus.NO_DISTANCE_PENALTY.name" : "Ingen avståndsbestraffning", + "core.bonus.NO_DISTANCE_PENALTY.description" : "Gör full skada på vilket avstånd som helst i strid", + "core.bonus.NO_MELEE_PENALTY.name" : "Ingen närstridsbestraffning", + "core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har ingen närstridsbestraffning", + "core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan", + "core.bonus.NO_MORALE.description" : "Varelsen är immun mot moraliska effekter och har alltid neutral moral", + "core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning", + "core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur", + "core.bonus.NON_LIVING.name" : "Icke levande", + "core.bonus.NON_LIVING.description" : "Immunitet mot många effekter som annars bara påverkar levande och odöda varelser", + "core.bonus.RANDOM_SPELLCASTER.name" : "Slumpmässig besvärjare", + "core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt", + "core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd", + "core.bonus.RANGED_RETALIATION.description" : "Kan retaliera/motattackera på avstånd", + "core.bonus.RECEPTIVE.name" : "Mottaglig", + "core.bonus.RECEPTIVE.description" : "Ingen immunitet mot vänliga besvärjelser", + "core.bonus.REBIRTH.name" : "Återfödelse (${val}%)", + "core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden", + "core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid", + "core.bonus.RETURN_AFTER_STRIKE.description" : "Efter att ha attackerat en fiendeenhet i närstrid återvänder enheten till rutan som den var placerad på innan den utförde sin närstridsattack", + "core.bonus.REVENGE.name" : "Hämnd", + "core.bonus.REVENGE.description" : "Orsakar extra skada baserat på angriparens förlorade träffpoäng (hälsa) i strid", + "core.bonus.SHOOTER.name" : "Distans-attack", + "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd", + "core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten", + "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Denna varelses distans-attacker drabbar alla mål i ett litet område", + "core.bonus.SOUL_STEAL.name" : "Själtjuv", + "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna enheter för varje dödad fiendeenhet", + "core.bonus.SPELLCASTER.name" : "Besvärjare", + "core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}", + "core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack", + "core.bonus.SPELL_AFTER_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} efter att den har attackerat", + "core.bonus.SPELL_BEFORE_ATTACK.name" : "Besvärja före attack", + "core.bonus.SPELL_BEFORE_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} innan den attackerar", + "core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Trolldoms-resistens", + "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.", + "core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet", + "core.bonus.SPELL_IMMUNITY.description" : "Immun mot ${subtype.spell}", + "core.bonus.SPELL_LIKE_ATTACK.name" : "Trolldomsliknande attack", + "core.bonus.SPELL_LIKE_ATTACK.description" : "Attackerar med ${subtype.spell}", + "core.bonus.SPELL_RESISTANCE_AURA.name" : "Motståndsaura", + "core.bonus.SPELL_RESISTANCE_AURA.description" : "Närbelägna förband får ${val}% magi-resistens", + "core.bonus.SUMMON_GUARDIANS.name" : "Åkalla väktare", + "core.bonus.SUMMON_GUARDIANS.description" : "I början av striden åkallas ${subtype.creature} (${val}%)", + "core.bonus.SYNERGY_TARGET.name" : "Synergibar", + "core.bonus.SYNERGY_TARGET.description" : "Denna varelse är sårbar för synergieffekt", + "core.bonus.TWO_HEX_ATTACK_BREATH.name" : "Dödlig andedräkt", + "core.bonus.TWO_HEX_ATTACK_BREATH.description" : "Andningsattack (2 rutors räckvidd)", + "core.bonus.THREE_HEADED_ATTACK.name" : "Trehövdad attack", + "core.bonus.THREE_HEADED_ATTACK.description" : "Attackerar tre angränsande enheter", + "core.bonus.TRANSMUTATION.name" : "Transmutation", + "core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ", + "core.bonus.UNDEAD.name" : "Odöd", + "core.bonus.UNDEAD.description" : "Varelsen är odöd", + "core.bonus.UNLIMITED_RETALIATIONS.name" : "Obegränsat antal motattacker", + "core.bonus.UNLIMITED_RETALIATIONS.description" : "Kan slå tillbaka mot ett obegränsat antal attacker varje omgång", + "core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet", + "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagi-skolan", + "core.bonus.WIDE_BREATH.name" : "Bred dödlig andedräkt", + "core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)", + "core.bonus.DISINTEGRATE.name" : "Desintegrerar", + "core.bonus.DISINTEGRATE.description" : "Ingen fysisk kropp finns kvar efter att enheten blivit besegrad i strid", + "core.bonus.INVINCIBLE.name" : "Oövervinnerlig", + "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting" +} From c1b1685cc119c982c059f4a5b1c08c42567a721a Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:34:38 +0200 Subject: [PATCH 213/726] Update Mods/vcmi/config/vcmi/swedish.json Co-authored-by: Laserlicht <13953785+Laserlicht@users.noreply.github.com> --- Mods/vcmi/config/vcmi/swedish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index 1bef307dc..64a2cb13c 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -212,7 +212,7 @@ "vcmi.systemOptions.longTouchButton.help" : "{Fördröjt tryckintervall}\n\nNär du använder dig av en pekskärm och vill komma åt spelalternativ behöver du göra en fördröjd pekskärmsberöring under en specifikt angiven tid (i millisekunder) för att en popup-meny skall synas.", "vcmi.systemOptions.longTouchMenu.hover" : "Välj tidsintervall för fördröjd pekskärmsberöringsmeny", "vcmi.systemOptions.longTouchMenu.help" : "Ändra varaktighetsintervallet för fördröjd beröring.", - "vcmi.systemOptions.longTouchMenu.entry" : "%d millisekunder","%d millisekunder", + "vcmi.systemOptions.longTouchMenu.entry" : "%d millisekunder", "vcmi.systemOptions.framerateButton.hover" : "Visar FPS (skärmbilder per sekund)", "vcmi.systemOptions.framerateButton.help" : "{Visa FPS}\n\nVisar räknaren för bildrutor per sekund i hörnet av spelfönstret.", "vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptisk återkoppling", From 265e9a69e4fa306e954e7b09ec95c6e2b17dc2ca Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:38:39 +0200 Subject: [PATCH 214/726] Update Mods/vcmi/config/vcmi/swedish.json Co-authored-by: Laserlicht <13953785+Laserlicht@users.noreply.github.com> --- Mods/vcmi/config/vcmi/swedish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index 64a2cb13c..8998f5700 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -463,7 +463,7 @@ "core.seerhut.quest.heroClass.complete.5" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?", "core.seerhut.quest.heroClass.description.0" : "Skicka %s till %s", "core.seerhut.quest.heroClass.description.1" : "Skicka %s till %s", - "core.seerhut.quest.heroClass.description.2" : "Skicka %s till %s", ’Skicka %s till %s’, + "core.seerhut.quest.heroClass.description.2" : "Skicka %s till %s", "core.seerhut.quest.heroClass.description.3" : "Skicka %s för att öppna grinden", "core.seerhut.quest.heroClass.description.4" : "Skicka %s till öppen grind", "core.seerhut.quest.heroClass.description.5" : "Skicka %s till öppen grind", From a01e84214f124d0c766a5f9e8b623cba4251b318 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 30 Sep 2024 21:00:50 +0200 Subject: [PATCH 215/726] Fixed errors AI no longer tries to access tiles it cannot see while clusterizing objects. --- AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index e24f0a757..5b9e0a12b 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -97,9 +97,10 @@ std::optional ObjectClusterizer::getBlocker(const AIPa { auto guardPos = ai->cb->getGuardingCreaturePosition(node.coord); - blockers = ai->cb->getVisitableObjs(node.coord); + if (ai->cb->isVisible(node.coord)) + blockers = ai->cb->getVisitableObjs(node.coord); - if(guardPos.valid()) + if(guardPos.valid() && ai->cb->isVisible(guardPos)) { auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node.coord)); From 05d7b55209e078c98144c9bafa1da5f1fe62e9b3 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:07:45 +0200 Subject: [PATCH 216/726] Update mod.json Added code for the Swedish language. --- Mods/vcmi/mod.json | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index baf74106d..13ccbda7d 100644 --- a/Mods/vcmi/mod.json +++ b/Mods/vcmi/mod.json @@ -77,17 +77,6 @@ ] }, - "ukrainian" : { - "name" : "VCMI - ключові файли", - "description" : "Ключові файли необхідні для повноцінної роботи VCMI", - "author" : "Команда VCMI", - - "skipValidation" : true, - "translations" : [ - "config/vcmi/ukrainian.json" - ] - }, - "spanish" : { "name" : "VCMI - ficheros necesarios", "description" : "Ficheros necesarios para ejecutar VCMI correctamente", @@ -99,6 +88,28 @@ ] }, + "swedish" : { + "name" : "Nödvändiga VCMI-filer", + "description" : "Filer som behövs för att köra VCMI korrekt", + "author" : "Maurycy (XCOM-HUB on GitHub)", + + "skipValidation" : true, + "translations" : [ + "config/vcmi/swedish.json" + ] + }, + + "ukrainian" : { + "name" : "VCMI - ключові файли", + "description" : "Ключові файли необхідні для повноцінної роботи VCMI", + "author" : "Команда VCMI", + + "skipValidation" : true, + "translations" : [ + "config/vcmi/ukrainian.json" + ] + }, + "vietnamese": { "name": "VCMI essential files", "description": "Các tập tin cần thiết để chạy VCMI", From 9c52e3a0b2db623b7f56a744f4de891809dfb636 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 1 Oct 2024 00:42:01 +0200 Subject: [PATCH 217/726] Fix for inconsistency in planned and performed army-merge. Removed the usage of BonusModifiers because depending on the case the function was sometimes called with and sometimes without an actual hero as first parameter. This lead to inconsistencies between planned and performed army-merge and got the AI stuck in a loop where it ordered an army-merge over and over that then would not conclude. The inclusion of bonuses of the hero for determining which army is better on them is unnecessarily convoluted and just causes issues. It took me like 4 hours to figure out why the AI didn't act. --- AI/Nullkiller/Analyzers/ArmyManager.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index 5c2a7f4a8..9c70410b5 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -152,16 +152,6 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, uint64_t armyValue = 0; TemporaryArmy newArmyInstance; - auto bonusModifiers = armyCarrier->getBonuses(Selector::type()(BonusType::MORALE)); - - for(auto bonus : *bonusModifiers) - { - // army bonuses will change and object bonuses are temporary - if(bonus->source != BonusSource::ARMY && bonus->source != BonusSource::OBJECT_INSTANCE && bonus->source != BonusSource::OBJECT_TYPE) - { - newArmyInstance.addNewBonus(std::make_shared(*bonus)); - } - } while(allowedFactions.size() < alignmentMap.size()) { From 2d783211cea896642caa65edb3db0b57f30f3299 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 1 Oct 2024 01:11:17 +0200 Subject: [PATCH 218/726] Fixed a crash Fixed a crash from trying to access nonexisting map-element. --- AI/Nullkiller/Analyzers/HeroManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 7e88bcdce..e305f761c 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -148,7 +148,10 @@ void HeroManager::update() HeroRole HeroManager::getHeroRole(const HeroPtr & hero) const { - return heroRoles.at(hero); + if (heroRoles.find(hero) != heroRoles.end()) + return heroRoles.at(hero); + else + return HeroRole::SCOUT; } const std::map & HeroManager::getHeroRoles() const From be20469dc167daf55e2f2e928af04a23acd8a204 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Mon, 30 Sep 2024 22:35:19 -0300 Subject: [PATCH 219/726] Update Portuguese Translation --- Mods/vcmi/config/vcmi/portuguese.json | 2 +- launcher/translation/portuguese.ts | 106 +++++++++++++------------- mapeditor/translation/portuguese.ts | 8 +- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index f2b6e377e..cbf548f07 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -79,7 +79,7 @@ "vcmi.lobby.handicap.resource" : "Fornece aos jogadores recursos apropriados para começar, além dos recursos iniciais normais. Valores negativos são permitidos, mas são limitados a 0 no total (o jogador nunca começa com recursos negativos).", "vcmi.lobby.handicap.income" : "Altera as várias rendas do jogador em porcentagem. Arredondado para cima.", "vcmi.lobby.handicap.growth" : "Altera a taxa de produção das criaturas nas cidades possuídas pelo jogador. Arredondado para cima.", - + "vcmi.lobby.login.title" : "Sala de Espera Online do VCMI", "vcmi.lobby.login.username" : "Nome de usuário:", "vcmi.lobby.login.connecting" : "Conectando...", diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index dfe5d75c8..0f4108637 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -442,17 +442,17 @@ Gog files - + Arquivos GOG All files (*.*) - + Todos os arquivos (*.*) Select files (configs, mods, maps, campaigns, gog files) to install... - + Selecione arquivos (configurações, mods, mapas, campanhas, arquivos gog) para instalar... @@ -483,7 +483,7 @@ Encountered errors: Não foi possível baixar todos os arquivos. -Encontrados os seguintes erros: +Erros encontrados: @@ -494,12 +494,12 @@ Encontrados os seguintes erros: Install successfully downloaded? -Instalar o download realizado com sucesso? +O download da instalação foi bem-sucedido? Installing chronicles - + Instalando crônicas @@ -641,12 +641,12 @@ Instalar o download realizado com sucesso? Artificial Intelligence - Inteligência Artificial + Inteligência artificial Interface Scaling - Escala da Interface + Escala da interface @@ -666,12 +666,12 @@ Instalar o download realizado com sucesso? Adventure Map Allies - Aliados do Mapa de Aventura + Aliados do mapa de aventura Online Lobby port - Porta da Sala de Espera On-line + Porta da sala de espera on-line @@ -681,22 +681,22 @@ Instalar o download realizado com sucesso? Sticks Sensitivity - Sensibilidade dos Analógicos + Sensibilidade dos analógicos Automatic (Linear) - Automático (Linear) + Automático (linear) Haptic Feedback - Resposta Tátil + Resposta tátil Software Cursor - Cursor por Software + Cursor por software @@ -723,25 +723,30 @@ Instalar o download realizado com sucesso? xBRZ x4 xBRZ x4 + + + Use scalable fonts + Usar fontes escaláveis + Online Lobby address - Endereço da Sala de Espera On-line + Endereço da sala de espera on-line Upscaling Filter - Filtro de Aumento de Escala + Filtro de aumento de escala Use Relative Pointer Mode - Usar Modo de Ponteiro Relativo + Usar modo de ponteiro relativo Nearest - Mais Próximo + Mais próximo @@ -751,28 +756,23 @@ Instalar o download realizado com sucesso? Input - Touchscreen - Entrada - Tela de Toque + Entrada - tela de toque Adventure Map Enemies - Inimigos do Mapa de Aventura + Inimigos do mapa de aventura Show Tutorial again - Mostrar o Tutorial novamente + Mostrar o tutorial novamente Reset Redefinir - - - Use scalable fonts - Usar Fontes Escaláveis - Network @@ -786,12 +786,12 @@ Instalar o download realizado com sucesso? Relative Pointer Speed - Velocidade do Ponteiro Relativo + Velocidade do ponteiro relativo Music Volume - Volume da Música + Volume da música @@ -801,12 +801,12 @@ Instalar o download realizado com sucesso? Input - Mouse - Entrada - Mouse + Entrada - mouse Long Touch Duration - Duração do Toque Longo + Duração do toque longo @@ -816,22 +816,22 @@ Instalar o download realizado com sucesso? Controller Click Tolerance - Tolerância de Clique do Controle + Tolerância de clique do controle Touch Tap Tolerance - Tolerância de Toque Tátil + Tolerância de toque tátil Input - Controller - Entrada - Controle + Entrada - controle Sound Volume - Volume do Som + Volume do som @@ -856,12 +856,12 @@ Instalar o download realizado com sucesso? Downscaling Filter - Filtro de Redução de Escala + Filtro de redução de escala Framerate Limit - Limite de Taxa de Quadros + Limite de taxa de quadros @@ -871,12 +871,12 @@ Instalar o download realizado com sucesso? Mouse Click Tolerance - Tolerância de Clique do Mouse + Tolerância de clique do mouse Sticks Acceleration - Aceleração dos Analógicos + Aceleração dos analógicos @@ -1003,7 +1003,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu Not Installed - Não Instalado + Não instalado @@ -1016,35 +1016,35 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu File cannot opened - + Não foi possível abrir o arquivo Invalid file selected - Arquivo selecionado inválido + Arquivo selecionado inválido You have to select an gog installer file! - + Você precisa selecionar um arquivo de instalação do GOG! You have to select an chronicle installer file! - + Você precisa selecionar um arquivo de instalação do Chronicles! Extracting error! - Erro ao extrair! + Erro ao extrair! Heroes Chronicles - + Heroes Chronicles @@ -1090,7 +1090,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu Mods Preset - Predefinição de Mod + Predefinição de mod @@ -1220,7 +1220,7 @@ O instalador offline consiste em duas partes, .exe e .bin. Certifique-se de baix Manual Installation - Instalação Manual + Instalação manual @@ -1246,7 +1246,7 @@ O instalador offline consiste em duas partes, .exe e .bin. Certifique-se de baix Install VCMI Mod Preset - Instalar Predefinição de Mod do VCMI + Instalar predefinição de mod do VCMI @@ -1370,7 +1370,7 @@ Por favor, selecione o diretório com Heroes III: Complete Edition ou Heroes III Image Viewer - Visualizador de Imagens + Visualizador de imagens @@ -1379,18 +1379,18 @@ Por favor, selecione o diretório com Heroes III: Complete Edition ou Heroes III Stream error while extracting files! error reason: - Erro de fluxo ao extrair arquivos! + Erro de fluxo ao extrair arquivos! Motivo do erro: Not a supported Inno Setup installer! - Instalador Inno Setup não suportado! + Instalador Inno Setup não suportado! VCMI was compiled without innoextract support, which is needed to extract exe files! - + O VCMI foi compilado sem suporte ao innoextract, que é necessário para extrair arquivos EXE! @@ -1506,7 +1506,7 @@ Motivo do erro: Map Editor - Editor de Mapas + Editor de mapas diff --git a/mapeditor/translation/portuguese.ts b/mapeditor/translation/portuguese.ts index 668d74a12..80599e202 100644 --- a/mapeditor/translation/portuguese.ts +++ b/mapeditor/translation/portuguese.ts @@ -2052,22 +2052,22 @@ Roads - Estradas + Estradas Dirt - + Terra Gravel - + Cascalho Cobblestone - + Paralelepípedo From be31041a488815ec1f11fb0438e75f565915b0d0 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:09:22 +0200 Subject: [PATCH 220/726] "general" -> "interface" --- client/render/AssetGenerator.cpp | 2 +- config/gameConfig.json | 2 +- config/schemas/gameSettings.json | 2 +- lib/GameSettings.cpp | 2 +- lib/IGameSettings.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 697efb6be..3d0d346e2 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -143,7 +143,7 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) std::shared_ptr texture = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); // transform to make color of brown DIBOX.PCX texture match color of specified player - auto filterSettings = VLC->settingsHandler->getFullConfig()["general"]["playerColoredBackground"]; + auto filterSettings = VLC->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"]; static const std::array filters = { ColorFilter::genRangeShifter( filterSettings["red" ].convertTo>() ), ColorFilter::genRangeShifter( filterSettings["blue" ].convertTo>() ), diff --git a/config/gameConfig.json b/config/gameConfig.json index f999c74af..91b021437 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -557,7 +557,7 @@ } }, - "general" : + "interface" : { // Color transform to make color of brown DIBOX.PCX texture match color of specified player "playerColoredBackground" : diff --git a/config/schemas/gameSettings.json b/config/schemas/gameSettings.json index 6e2663d01..9b014ba70 100644 --- a/config/schemas/gameSettings.json +++ b/config/schemas/gameSettings.json @@ -148,7 +148,7 @@ "perHero" : { "type" : "object" } } }, - "general": { + "interface": { "type" : "object", "additionalProperties" : false, "properties" : { diff --git a/lib/GameSettings.cpp b/lib/GameSettings.cpp index a904e1995..e0c5fff56 100644 --- a/lib/GameSettings.cpp +++ b/lib/GameSettings.cpp @@ -101,7 +101,7 @@ const std::vector GameSettings::settingProperties = {EGameSettings::TEXTS_TERRAIN, "textData", "terrain" }, {EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" }, {EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" }, - {EGameSettings::GENERAL_PLAYER_COLORED_BACKGROUND, "general", "playerColoredBackground" }, + {EGameSettings::INTERFACE_PLAYER_COLORED_BACKGROUND, "interface", "playerColoredBackground" }, }; void GameSettings::loadBase(const JsonNode & input) diff --git a/lib/IGameSettings.h b/lib/IGameSettings.h index e7256a9bc..83b2289ed 100644 --- a/lib/IGameSettings.h +++ b/lib/IGameSettings.h @@ -79,7 +79,7 @@ enum class EGameSettings TEXTS_TERRAIN, TOWNS_BUILDINGS_PER_TURN_CAP, TOWNS_STARTING_DWELLING_CHANCES, - GENERAL_PLAYER_COLORED_BACKGROUND, + INTERFACE_PLAYER_COLORED_BACKGROUND, OPTIONS_COUNT, OPTIONS_BEGIN = BONUSES_GLOBAL From 9c6bd201594eac73d67877fad231dcba0161c24a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:32:28 +0200 Subject: [PATCH 221/726] code review --- client/windows/CCastleInterface.cpp | 5 ++--- lib/mapObjects/CGTownInstance.cpp | 3 ++- lib/mapObjects/CGTownInstance.h | 8 ++++---- lib/networkPacks/NetPacksLib.cpp | 7 +++++-- server/CGameHandler.cpp | 9 +++++---- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 9bc5e1ae6..d8f069e56 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2041,8 +2041,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) if(vstd::find_pos(town->spells[i], spell->id) != -1) level = i; - int today = LOCPLINT->cb->getDate(Date::DAY); - if(town->spellResearchActionsPerDay.find(today) != town->spellResearchActionsPerDay.end() && town->spellResearchActionsPerDay.at(today) >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) + if(town->spellResearchCounterDay >= LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float()) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.comeAgain")); return; @@ -2050,7 +2049,7 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto costBase = TResources(LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); auto costExponent = LOCPLINT->cb->getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); - auto cost = costBase * std::pow(town->spellResearchCounter + 1, costExponent); + auto cost = costBase * std::pow(town->spellResearchAcceptedCounter + 1, costExponent); std::vector> resComps; auto newSpell = town->spells[level].at(town->spellsAtLevel(level, false)); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 76dc2a254..fc968ffb5 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -269,7 +269,8 @@ CGTownInstance::CGTownInstance(IGameCallback *cb): destroyed(0), identifier(0), alignmentToPlayer(PlayerColor::NEUTRAL), - spellResearchCounter(0), + spellResearchCounterDay(0), + spellResearchAcceptedCounter(0), spellResearchAllowed(true) { this->setNodeType(CBonusSystemNode::TOWN); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 6e4bc7af3..fe143c6fd 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -73,8 +73,8 @@ public: std::vector > spells; //spells[level] -> vector of spells, first will be available in guild std::vector events; std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond, factory = save debts); - std::map spellResearchActionsPerDay; - int spellResearchCounter; + int spellResearchCounterDay; + int spellResearchAcceptedCounter; bool spellResearchAllowed; ////////////////////////////////////////////////////////////////////////// @@ -98,8 +98,8 @@ public: if (h.version >= Handler::Version::SPELL_RESEARCH) { - h & spellResearchActionsPerDay; - h & spellResearchCounter; + h & spellResearchCounterDay; + h & spellResearchAcceptedCounter; h & spellResearchAllowed; } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index fb37de483..623437b03 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -944,9 +944,9 @@ void SetResearchedSpells::applyGs(CGameState *gs) CGTownInstance *town = gs->getTown(tid); town->spells[level] = spells; - town->spellResearchActionsPerDay[gs->getDate(Date::DAY)]++; + town->spellResearchCounterDay++; if(accepted) - town->spellResearchCounter++; + town->spellResearchAcceptedCounter++; } void SetMana::applyGs(CGameState *gs) @@ -1941,7 +1941,10 @@ void NewTurn::applyGs(CGameState *gs) creatureSet.applyGs(gs); for(CGTownInstance* t : gs->map->towns) + { t->built = 0; + t->spellResearchCounterDay = 0; + } if(newRumor) gs->currentRumor = *newRumor; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 8d1046398..8448ca6fc 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2247,7 +2247,9 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool { CGTownInstance *t = gs->getTown(tid); - if(!(getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && t->spellResearchAllowed) && complain("Spell research not allowed!")) + if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!")) + return false; + if (!t->spellResearchAllowed && complain("Spell research not allowed in this town!")) return false; int level = -1; @@ -2260,8 +2262,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto spells = t->spells.at(level); - int today = getDate(Date::DAY); - bool researchLimitExceeded = t->spellResearchActionsPerDay.find(today) != t->spellResearchActionsPerDay.end() && t->spellResearchActionsPerDay.at(today) >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); + bool researchLimitExceeded = t->spellResearchCounterDay >= getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_PER_DAY).Vector()[level].Float(); if(researchLimitExceeded && complain("Already researched today!")) return false; @@ -2275,7 +2276,7 @@ bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool auto costBase = TResources(getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST).Vector()[level]); auto costExponent = getSettings().getValue(EGameSettings::TOWNS_SPELL_RESEARCH_COST_EXPONENT_PER_RESEARCH).Vector()[level].Float(); - auto cost = costBase * std::pow(t->spellResearchCounter + 1, costExponent); + auto cost = costBase * std::pow(t->spellResearchAcceptedCounter + 1, costExponent); if(!getPlayerState(t->getOwner())->resources.canAfford(cost) && complain("Spell replacement cannot be afforded!")) return false; From f2b8b40925b879d8d433124039c6874c5664ce1c Mon Sep 17 00:00:00 2001 From: Xilmi Date: Tue, 1 Oct 2024 17:20:54 +0200 Subject: [PATCH 222/726] Being less dismissive Reduced the amount of circumstances under which the AI dismisses heroes. Among others to prevent a loop through all passes where it repeatedly hires and dismisses heroes. --- AI/Nullkiller/Analyzers/HeroManager.cpp | 3 ++- AI/Nullkiller/Analyzers/HeroManager.h | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 21 ++++++--------------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index e305f761c..e559bd264 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -284,7 +284,7 @@ const CGHeroInstance * HeroManager::findHeroWithGrail() const return nullptr; } -const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) const +const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit, const CGTownInstance* townToSpare) const { const CGHeroInstance * weakestHero = nullptr; auto myHeroes = ai->cb->getHeroesInfo(); @@ -295,6 +295,7 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) co || existingHero->getArmyStrength() >armyLimit || getHeroRole(existingHero) == HeroRole::MAIN || existingHero->movementPointsRemaining() + || (townToSpare != nullptr && existingHero->visitedTown == townToSpare) || existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1)) { continue; diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 9d7f6c4e0..383f3c13a 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -58,7 +58,7 @@ public: bool canRecruitHero(const CGTownInstance * t = nullptr) const; bool heroCapReached(bool includeGarrisoned = true) const; const CGHeroInstance * findHeroWithGrail() const; - const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const; + const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit, const CGTownInstance * townToSpare = nullptr) const; float getMagicStrength(const CGHeroInstance * hero) const; float getFightingStrengthCached(const CGHeroInstance * hero) const; diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index bc5a11bbe..158fd8989 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -354,21 +354,12 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { if(town->garrisonHero && town->garrisonHero != path.targetHero) { - if(ai->heroManager->getHeroRole(town->visitingHero.get()) == HeroRole::SCOUT - && town->visitingHero->getArmyStrength() < path.heroArmy->getArmyStrength() / 20) - { - if(path.turn() == 0) - sequence.push_back(sptr(DismissHero(town->visitingHero.get()))); - } - else - { #if NKAI_TRACE_LEVEL >= 1 - logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero", - path.targetHero->getObjectName(), - town->getObjectName()); + logAi->trace("Cancel moving %s to defend town %s as the town has garrison hero", + path.targetHero->getObjectName(), + town->getObjectName()); #endif - continue; - } + continue; } else if(path.turn() == 0) { @@ -414,7 +405,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitMapInfo & threat, const CGTownInstance * town, const Nullkiller * ai) const { - if (threat.turn > 0) + if (threat.turn > 0 || town->garrisonHero || town->visitingHero) return; if(town->hasBuilt(BuildingID::TAVERN) @@ -463,7 +454,7 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM } else if(ai->heroManager->heroCapReached()) { - heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength()); + heroToDismiss = ai->heroManager->findWeakHeroToDismiss(hero->getArmyStrength(), town); if(!heroToDismiss) continue; From e497a5137310ccabd3fbdce592db295314202df9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 1 Oct 2024 15:25:58 +0000 Subject: [PATCH 223/726] Use swresample (part of FFmpeg) to de-planarize audio into format that can be consumed by SDL --- CMakeLists.txt | 6 +-- client/media/CVideoHandler.cpp | 77 ++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9918c0a3..31bd3b8f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -486,11 +486,7 @@ if(NOT FORCE_BUNDLED_MINIZIP) endif() if (ENABLE_CLIENT) - set(FFMPEG_COMPONENTS avutil swscale avformat avcodec) - if(APPLE_IOS AND NOT USING_CONAN) - list(APPEND FFMPEG_COMPONENTS swresample) - endif() - find_package(ffmpeg COMPONENTS ${FFMPEG_COMPONENTS}) + find_package(ffmpeg COMPONENTS avutil swscale avformat avcodec swresample) find_package(SDL2 REQUIRED) find_package(SDL2_image REQUIRED) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 51e17ff10..9428c7428 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -33,7 +33,9 @@ extern "C" { #include #include #include +#include #include +#include } // Define a set of functions to read data @@ -501,32 +503,71 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide int numChannels = codecpar->ch_layout.nb_channels; #endif - samples.reserve(44100 * 5); // arbitrary 5-second buffer + samples.reserve(44100 * 5); // arbitrary 5-second buffer to reduce reallocations - for (;;) + if (formatProperties.isPlanar && numChannels > 1) { - decodeNextFrame(); - const AVFrame * frame = getCurrentFrame(); + // Format is 'planar', which is not supported by wav / SDL + // Use swresample part of ffmpeg to deplanarize audio into format supported by wav / SDL - if (!frame) - break; + auto sourceFormat = static_cast(codecpar->format); + auto targetFormat = av_get_alt_sample_fmt(sourceFormat, false); - int samplesToRead = frame->nb_samples * numChannels; - int bytesToRead = samplesToRead * formatProperties.sampleSizeBytes; + SwrContext * swr_ctx = swr_alloc(); - if (formatProperties.isPlanar && numChannels > 1) +#if (LIBAVUTIL_VERSION_MAJOR < 58) + av_opt_set_channel_layout(swr_ctx, "in_chlayout", codecpar->channel_layout, 0); + av_opt_set_channel_layout(swr_ctx, "out_chlayout", codecpar->channel_layout, 0); +#else + av_opt_set_chlayout(swr_ctx, "in_chlayout", &codecpar->ch_layout, 0); + av_opt_set_chlayout(swr_ctx, "out_chlayout", &codecpar->ch_layout, 0); +#endif + av_opt_set_int(swr_ctx, "in_sample_rate", codecpar->sample_rate, 0); + av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", sourceFormat, 0); + av_opt_set_int(swr_ctx, "out_sample_rate", codecpar->sample_rate, 0); + av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", targetFormat, 0); + + int initResult = swr_init(swr_ctx); + if (initResult < 0) + throwFFmpegError(initResult); + + std::vector frameSamplesBuffer; + for (;;) { - // Workaround for lack of resampler - // Currently, ffmpeg on conan systems is built without sws resampler - // Because of that, and because wav format does not supports 'planar' formats from ffmpeg - // we need to de-planarize it and convert to "normal" (non-planar / interleaved) stream - samples.reserve(samples.size() + bytesToRead); - for (int sm = 0; sm < frame->nb_samples; ++sm) - for (int ch = 0; ch < numChannels; ++ch) - samples.insert(samples.end(), frame->data[ch] + sm * formatProperties.sampleSizeBytes, frame->data[ch] + (sm+1) * formatProperties.sampleSizeBytes ); + decodeNextFrame(); + const AVFrame * frame = getCurrentFrame(); + + if (!frame) + break; + + size_t samplesToRead = frame->nb_samples * numChannels; + size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes; + frameSamplesBuffer.resize(std::max(frameSamplesBuffer.size(), bytesToRead)); + uint8_t * frameSamplesPtr = frameSamplesBuffer.data(); + + int result = swr_convert(swr_ctx, &frameSamplesPtr, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples); + + if (result < 0) + throwFFmpegError(result); + + size_t samplesToCopy = result * numChannels; + size_t bytesToCopy = samplesToCopy * formatProperties.sampleSizeBytes; + samples.insert(samples.end(), frameSamplesBuffer.begin(), frameSamplesBuffer.begin() + bytesToCopy); } - else + swr_free(&swr_ctx); + } + else + { + for (;;) { + decodeNextFrame(); + const AVFrame * frame = getCurrentFrame(); + + if (!frame) + break; + + size_t samplesToRead = frame->nb_samples * numChannels; + size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes; samples.insert(samples.end(), frame->data[0], frame->data[0] + bytesToRead); } } From d04f369d85c1fc4184bd29b62df9903507eca18a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 18:15:32 +0200 Subject: [PATCH 224/726] search basic implementation --- client/adventureMap/AdventureMapShortcuts.cpp | 37 ++++++++++++++++++- client/adventureMap/AdventureMapShortcuts.h | 1 + client/gui/Shortcut.h | 1 + client/gui/ShortcutHandler.cpp | 1 + config/shortcutsConfig.json | 1 + 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index fe7253cb0..9497bf1df 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -36,6 +36,7 @@ #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapping/CMap.h" #include "../../lib/pathfinder/CGPathNode.h" +#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h" AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner) : owner(owner) @@ -109,7 +110,8 @@ std::vector AdventureMapShortcuts::getShortcuts() { EShortcut::ADVENTURE_MOVE_HERO_EE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, 0}); } }, { EShortcut::ADVENTURE_MOVE_HERO_NW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, -1}); } }, { EShortcut::ADVENTURE_MOVE_HERO_NN, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, -1}); } }, - { EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } } + { EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } }, + { EShortcut::ADVENTURE_SEARCH, optionSidePanelActive(),[this]() { this->search(); } } }; return result; } @@ -457,6 +459,39 @@ void AdventureMapShortcuts::zoom( int distance) owner.hotkeyZoom(distance, false); } +void AdventureMapShortcuts::search() +{ + LOCPLINT->showInfoDialog("search"); + + std::vector visitableObjInstances; + int3 mapSizes = LOCPLINT->cb->getMapSize(); + for(int x = 0; x < mapSizes.x; x++) + for(int y = 0; y < mapSizes.y; y++) + for(int z = 0; z < mapSizes.z; z++) + for(auto & obj : LOCPLINT->cb->getVisitableObjs(int3(x, y, z), false)) + visitableObjInstances.push_back(obj->id); + + // count of elements for each group + std::map mapObjCount; + for(auto & obj : visitableObjInstances) + mapObjCount[LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex()]++; + + // sort by name + std::vector> mapObjCountList; + for (auto itr = mapObjCount.begin(); itr != mapObjCount.end(); ++itr) + mapObjCountList.push_back(*itr); + std::sort(mapObjCountList.begin(), mapObjCountList.end(), + [=](std::pair& a, std::pair& b) + { + return VLC->objtypeh->getObjectName(a.first, 0) < VLC->objtypeh->getObjectName(b.first, 0); + } + ); + + //TODO show table as popup + for(auto & obj : mapObjCountList) + std::cout << VLC->objtypeh->getObjectName(obj.first, 0) << " " << obj.second << "\n"; +} + void AdventureMapShortcuts::nextObject() { const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index b32f3ea29..cfe07b781 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -71,6 +71,7 @@ class AdventureMapShortcuts void nextTown(); void nextObject(); void zoom( int distance); + void search(); void moveHeroDirectional(const Point & direction); public: diff --git a/client/gui/Shortcut.h b/client/gui/Shortcut.h index 66019bd77..3bf0502d8 100644 --- a/client/gui/Shortcut.h +++ b/client/gui/Shortcut.h @@ -161,6 +161,7 @@ enum class EShortcut ADVENTURE_RESTART_GAME, ADVENTURE_TO_MAIN_MENU, ADVENTURE_QUIT_GAME, + ADVENTURE_SEARCH, // Move hero one tile in specified direction. Bound to cursors & numpad buttons ADVENTURE_MOVE_HERO_SW, diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index a4cffb8c4..e64d9350d 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -209,6 +209,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"adventureZoomIn", EShortcut::ADVENTURE_ZOOM_IN }, {"adventureZoomOut", EShortcut::ADVENTURE_ZOOM_OUT }, {"adventureZoomReset", EShortcut::ADVENTURE_ZOOM_RESET }, + {"adventureSearch", EShortcut::ADVENTURE_SEARCH }, {"battleToggleHeroesStats", EShortcut::BATTLE_TOGGLE_HEROES_STATS}, {"battleToggleQueue", EShortcut::BATTLE_TOGGLE_QUEUE }, {"battleUseCreatureSpell", EShortcut::BATTLE_USE_CREATURE_SPELL }, diff --git a/config/shortcutsConfig.json b/config/shortcutsConfig.json index 41ee66f22..111026765 100644 --- a/config/shortcutsConfig.json +++ b/config/shortcutsConfig.json @@ -56,6 +56,7 @@ "adventureZoomIn": "Keypad +", "adventureZoomOut": "Keypad -", "adventureZoomReset": "Backspace", + "adventureSearch": "Ctrl+F", "battleAutocombat": "A", "battleAutocombatEnd": "Q", "battleCastSpell": "C", From 5ea9063a3b5d6948e8c37c0448298e9d43d4fa73 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:47:10 +0200 Subject: [PATCH 225/726] search with CObjectListWindow --- Mods/vcmi/config/vcmi/english.json | 2 ++ Mods/vcmi/config/vcmi/german.json | 2 ++ client/adventureMap/AdventureMapShortcuts.cpp | 28 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..032f6ac2e 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -13,6 +13,8 @@ "vcmi.adventureMap.monsterThreat.levels.10" : "Deadly", "vcmi.adventureMap.monsterThreat.levels.11" : "Impossible", "vcmi.adventureMap.monsterLevel" : "\n\nLevel %LEVEL %TOWN unit", + "vcmi.adventureMap.search.hover" : "Search map object", + "vcmi.adventureMap.search.help" : "Select object to search on map.", "vcmi.adventureMap.confirmRestartGame" : "Are you sure you want to restart the game?", "vcmi.adventureMap.noTownWithMarket" : "There are no available marketplaces!", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index efe786220..0ca8f9748 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -13,6 +13,8 @@ "vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich", "vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich", "vcmi.adventureMap.monsterLevel" : "\n\nStufe %LEVEL %TOWN-Einheit", + "vcmi.adventureMap.search.hover" : "Suche Kartenobjekt", + "vcmi.adventureMap.search.help" : "Wähle Objekt das gesucht werden soll.", "vcmi.adventureMap.confirmRestartGame" : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?", "vcmi.adventureMap.noTownWithMarket" : "Kein Marktplatz verfügbar!", diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 9497bf1df..0980e6634 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -24,6 +24,7 @@ #include "../windows/CKingdomInterface.h" #include "../windows/CSpellWindow.h" #include "../windows/CMarketWindow.h" +#include "../windows/GUIClasses.h" #include "../windows/settings/SettingsMainWindow.h" #include "AdventureMapInterface.h" #include "AdventureOptions.h" @@ -461,8 +462,6 @@ void AdventureMapShortcuts::zoom( int distance) void AdventureMapShortcuts::search() { - LOCPLINT->showInfoDialog("search"); - std::vector visitableObjInstances; int3 mapSizes = LOCPLINT->cb->getMapSize(); for(int x = 0; x < mapSizes.x; x++) @@ -477,19 +476,36 @@ void AdventureMapShortcuts::search() mapObjCount[LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex()]++; // sort by name - std::vector> mapObjCountList; + std::vector> mapObjCountList; for (auto itr = mapObjCount.begin(); itr != mapObjCount.end(); ++itr) mapObjCountList.push_back(*itr); std::sort(mapObjCountList.begin(), mapObjCountList.end(), - [=](std::pair& a, std::pair& b) + [=](std::pair& a, std::pair& b) { return VLC->objtypeh->getObjectName(a.first, 0) < VLC->objtypeh->getObjectName(b.first, 0); } ); - //TODO show table as popup + std::vector texts; for(auto & obj : mapObjCountList) - std::cout << VLC->objtypeh->getObjectName(obj.first, 0) << " " << obj.second << "\n"; + texts.push_back(VLC->objtypeh->getObjectName(obj.first, 0) + " (" + std::to_string(obj.second) + ")"); + + GH.windows().createAndPushWindow(texts, nullptr, + CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), + CGI->generaltexth->translate("vcmi.adventureMap.search.help"), + [this, mapObjCountList, visitableObjInstances](int index) + { + auto selObj = mapObjCountList[index].first; + for(auto & obj : visitableObjInstances) + { + auto objInst = LOCPLINT->cb->getObjInstance(obj); + if(selObj == objInst->getObjGroupIndex()) + owner.centerOnObject(objInst); + } + + + }, + 0); } void AdventureMapShortcuts::nextObject() From bd58caac132fc3bda66e2555ab040d424bd6b548 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:20:19 +0200 Subject: [PATCH 226/726] search feature working --- client/adventureMap/AdventureMapShortcuts.cpp | 44 ++++++++++++------- client/adventureMap/AdventureMapShortcuts.h | 5 +++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 0980e6634..e4b4b3284 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -43,6 +43,8 @@ AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner) : owner(owner) , state(EAdventureState::NOT_INITIALIZED) , mapLevel(0) + , searchLast(MapObjectID::NO_OBJ) + , searchPos(0) {} void AdventureMapShortcuts::setState(EAdventureState newState) @@ -462,6 +464,7 @@ void AdventureMapShortcuts::zoom( int distance) void AdventureMapShortcuts::search() { + // get all relevant objects std::vector visitableObjInstances; int3 mapSizes = LOCPLINT->cb->getMapSize(); for(int x = 0; x < mapSizes.x; x++) @@ -486,26 +489,37 @@ void AdventureMapShortcuts::search() } ); + // get pos of last selection + int lastSel = 0; + for(int i = 0; i < mapObjCountList.size(); i++) + if(mapObjCountList[i].first == searchLast) + lastSel = i; + + // create texts std::vector texts; for(auto & obj : mapObjCountList) texts.push_back(VLC->objtypeh->getObjectName(obj.first, 0) + " (" + std::to_string(obj.second) + ")"); - GH.windows().createAndPushWindow(texts, nullptr, - CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), - CGI->generaltexth->translate("vcmi.adventureMap.search.help"), - [this, mapObjCountList, visitableObjInstances](int index) - { - auto selObj = mapObjCountList[index].first; - for(auto & obj : visitableObjInstances) - { - auto objInst = LOCPLINT->cb->getObjInstance(obj); - if(selObj == objInst->getObjGroupIndex()) - owner.centerOnObject(objInst); - } - + GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), + [this, mapObjCountList, visitableObjInstances](int index) + { + auto selObj = mapObjCountList[index].first; - }, - 0); + // filter for matching objects + std::vector selVisitableObjInstances; + for(auto & obj : visitableObjInstances) + if(selObj == LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex()) + selVisitableObjInstances.push_back(obj); + + if(searchPos + 1 < selVisitableObjInstances.size() && searchLast == selObj) + searchPos++; + else + searchPos = 0; + + auto objInst = LOCPLINT->cb->getObjInstance(selVisitableObjInstances[searchPos]); + owner.centerOnObject(objInst); + searchLast = objInst->getObjGroupIndex(); + }, lastSel); } void AdventureMapShortcuts::nextObject() diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index cfe07b781..e0c8146b1 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -10,6 +10,8 @@ #pragma once +#include "../../lib/constants/EntityIdentifiers.h" + VCMI_LIB_NAMESPACE_BEGIN class Point; class Rect; @@ -33,6 +35,9 @@ class AdventureMapShortcuts EAdventureState state; int mapLevel; + MapObjectID searchLast; + int searchPos; + void showOverview(); void worldViewBack(); void worldViewScale1x(); From e85e938865e97d6f3e4df3590b33ee2ea48ec73b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:44:08 +0200 Subject: [PATCH 227/726] use subid --- client/adventureMap/AdventureMapShortcuts.cpp | 21 ++++++++++--------- client/adventureMap/AdventureMapShortcuts.h | 2 +- .../CObjectClassesHandler.cpp | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index e4b4b3284..42eabc096 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -43,7 +43,7 @@ AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner) : owner(owner) , state(EAdventureState::NOT_INITIALIZED) , mapLevel(0) - , searchLast(MapObjectID::NO_OBJ) + , searchLast({MapObjectID::NO_OBJ, 0}) , searchPos(0) {} @@ -471,21 +471,22 @@ void AdventureMapShortcuts::search() for(int y = 0; y < mapSizes.y; y++) for(int z = 0; z < mapSizes.z; z++) for(auto & obj : LOCPLINT->cb->getVisitableObjs(int3(x, y, z), false)) - visitableObjInstances.push_back(obj->id); + if(obj->ID != MapObjectID::MONSTER && obj->ID != MapObjectID::EVENT && obj->ID != MapObjectID::HERO) + visitableObjInstances.push_back(obj->id); // count of elements for each group - std::map mapObjCount; + std::map, int> mapObjCount; for(auto & obj : visitableObjInstances) - mapObjCount[LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex()]++; + mapObjCount[{ LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex() }]++; // sort by name - std::vector> mapObjCountList; + std::vector, int>> mapObjCountList; for (auto itr = mapObjCount.begin(); itr != mapObjCount.end(); ++itr) mapObjCountList.push_back(*itr); std::sort(mapObjCountList.begin(), mapObjCountList.end(), - [=](std::pair& a, std::pair& b) + [=](std::pair, int>& a, std::pair, int>& b) { - return VLC->objtypeh->getObjectName(a.first, 0) < VLC->objtypeh->getObjectName(b.first, 0); + return VLC->objtypeh->getObjectName(a.first.first, a.first.second) < VLC->objtypeh->getObjectName(b.first.first, b.first.second); } ); @@ -498,7 +499,7 @@ void AdventureMapShortcuts::search() // create texts std::vector texts; for(auto & obj : mapObjCountList) - texts.push_back(VLC->objtypeh->getObjectName(obj.first, 0) + " (" + std::to_string(obj.second) + ")"); + texts.push_back(VLC->objtypeh->getObjectName(obj.first.first, obj.first.second) + " (" + std::to_string(obj.second) + ")"); GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), [this, mapObjCountList, visitableObjInstances](int index) @@ -508,7 +509,7 @@ void AdventureMapShortcuts::search() // filter for matching objects std::vector selVisitableObjInstances; for(auto & obj : visitableObjInstances) - if(selObj == LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex()) + if(selObj == std::pair{ LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex() }) selVisitableObjInstances.push_back(obj); if(searchPos + 1 < selVisitableObjInstances.size() && searchLast == selObj) @@ -518,7 +519,7 @@ void AdventureMapShortcuts::search() auto objInst = LOCPLINT->cb->getObjInstance(selVisitableObjInstances[searchPos]); owner.centerOnObject(objInst); - searchLast = objInst->getObjGroupIndex(); + searchLast = { objInst->getObjGroupIndex(), objInst->getObjTypeIndex() }; }, lastSel); } diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index e0c8146b1..6e3a03572 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -35,7 +35,7 @@ class AdventureMapShortcuts EAdventureState state; int mapLevel; - MapObjectID searchLast; + std::pair searchLast; int searchPos; void showOverview(); diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index e45332abb..2ba61627c 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -353,7 +353,7 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObj return mapObjectTypes.front()->objectTypeHandlers.front(); auto subID = subtype.getNum(); - if (type == Obj::PRISON || type == Obj::HERO_PLACEHOLDER) + if (type == Obj::PRISON || type == Obj::HERO_PLACEHOLDER || type == Obj::SPELL_SCROLL) subID = 0; auto result = mapObjectTypes.at(type.getNum())->objectTypeHandlers.at(subID); From 07aa7bac3c787c3258818c641fc85eba59ab6b25 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:19:42 +0200 Subject: [PATCH 228/726] search for texts --- client/adventureMap/AdventureMapShortcuts.cpp | 32 +++++++++---------- client/adventureMap/AdventureMapShortcuts.h | 2 +- client/widgets/ObjectLists.cpp | 3 ++ client/windows/GUIClasses.cpp | 2 ++ 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 42eabc096..01347f453 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -43,7 +43,7 @@ AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner) : owner(owner) , state(EAdventureState::NOT_INITIALIZED) , mapLevel(0) - , searchLast({MapObjectID::NO_OBJ, 0}) + , searchLast("") , searchPos(0) {} @@ -475,41 +475,41 @@ void AdventureMapShortcuts::search() visitableObjInstances.push_back(obj->id); // count of elements for each group - std::map, int> mapObjCount; + std::map mapObjCount; for(auto & obj : visitableObjInstances) - mapObjCount[{ LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex() }]++; + mapObjCount[{ VLC->objtypeh->getObjectName(LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex()) }]++; // sort by name - std::vector, int>> mapObjCountList; + std::vector> textCountList; for (auto itr = mapObjCount.begin(); itr != mapObjCount.end(); ++itr) - mapObjCountList.push_back(*itr); - std::sort(mapObjCountList.begin(), mapObjCountList.end(), - [=](std::pair, int>& a, std::pair, int>& b) + textCountList.push_back(*itr); + std::sort(textCountList.begin(), textCountList.end(), + [=](std::pair& a, std::pair& b) { - return VLC->objtypeh->getObjectName(a.first.first, a.first.second) < VLC->objtypeh->getObjectName(b.first.first, b.first.second); + return a.first < b.first; } ); // get pos of last selection int lastSel = 0; - for(int i = 0; i < mapObjCountList.size(); i++) - if(mapObjCountList[i].first == searchLast) + for(int i = 0; i < textCountList.size(); i++) + if(textCountList[i].first == searchLast) lastSel = i; // create texts std::vector texts; - for(auto & obj : mapObjCountList) - texts.push_back(VLC->objtypeh->getObjectName(obj.first.first, obj.first.second) + " (" + std::to_string(obj.second) + ")"); + for(auto & obj : textCountList) + texts.push_back(obj.first + " (" + std::to_string(obj.second) + ")"); GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), - [this, mapObjCountList, visitableObjInstances](int index) + [this, textCountList, visitableObjInstances](int index) { - auto selObj = mapObjCountList[index].first; + auto selObj = textCountList[index].first; // filter for matching objects std::vector selVisitableObjInstances; for(auto & obj : visitableObjInstances) - if(selObj == std::pair{ LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex() }) + if(selObj == VLC->objtypeh->getObjectName(LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex())) selVisitableObjInstances.push_back(obj); if(searchPos + 1 < selVisitableObjInstances.size() && searchLast == selObj) @@ -519,7 +519,7 @@ void AdventureMapShortcuts::search() auto objInst = LOCPLINT->cb->getObjInstance(selVisitableObjInstances[searchPos]); owner.centerOnObject(objInst); - searchLast = { objInst->getObjGroupIndex(), objInst->getObjTypeIndex() }; + searchLast = VLC->objtypeh->getObjectName(objInst->getObjGroupIndex(), objInst->getObjTypeIndex()); }, lastSel); } diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index 6e3a03572..7a5bff54d 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -35,7 +35,7 @@ class AdventureMapShortcuts EAdventureState state; int mapLevel; - std::pair searchLast; + std::string searchLast; int searchPos; void showOverview(); diff --git a/client/widgets/ObjectLists.cpp b/client/widgets/ObjectLists.cpp index d2ad5c1ae..d9a257923 100644 --- a/client/widgets/ObjectLists.cpp +++ b/client/widgets/ObjectLists.cpp @@ -185,6 +185,9 @@ void CListBox::scrollTo(size_t which) //scroll down else if (first + items.size() <= which && which < totalSize) moveToPos(which - items.size() + 1); + + if(slider) + slider->scrollTo(which); } void CListBox::moveToPos(size_t which) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 46a9775e5..9ab54fe39 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1494,6 +1494,7 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, std::share } init(titleWidget_, _title, _descr); + list->scrollTo(initialSelection); } CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images) @@ -1509,6 +1510,7 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, st items.push_back(std::make_pair(int(i), _items[i])); init(titleWidget_, _title, _descr); + list->scrollTo(initialSelection); } void CObjectListWindow::init(std::shared_ptr titleWidget_, std::string _title, std::string _descr) From e86b694b22fcab986b13ca28c5c8c47f3ccd365c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:36:45 +0200 Subject: [PATCH 229/726] fast search --- client/adventureMap/AdventureMapShortcuts.cpp | 16 +++++++++++----- client/adventureMap/AdventureMapShortcuts.h | 2 +- client/gui/Shortcut.h | 1 + client/gui/ShortcutHandler.cpp | 1 + config/shortcutsConfig.json | 1 + 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index 01347f453..fce6ec5fe 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -114,7 +114,8 @@ std::vector AdventureMapShortcuts::getShortcuts() { EShortcut::ADVENTURE_MOVE_HERO_NW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, -1}); } }, { EShortcut::ADVENTURE_MOVE_HERO_NN, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, -1}); } }, { EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } }, - { EShortcut::ADVENTURE_SEARCH, optionSidePanelActive(),[this]() { this->search(); } } + { EShortcut::ADVENTURE_SEARCH, optionSidePanelActive(),[this]() { this->search(false); } }, + { EShortcut::ADVENTURE_SEARCH_CONTINUE, optionSidePanelActive(),[this]() { this->search(true); } } }; return result; } @@ -462,7 +463,7 @@ void AdventureMapShortcuts::zoom( int distance) owner.hotkeyZoom(distance, false); } -void AdventureMapShortcuts::search() +void AdventureMapShortcuts::search(bool next) { // get all relevant objects std::vector visitableObjInstances; @@ -501,8 +502,8 @@ void AdventureMapShortcuts::search() for(auto & obj : textCountList) texts.push_back(obj.first + " (" + std::to_string(obj.second) + ")"); - GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), - [this, textCountList, visitableObjInstances](int index) + // function to center element from list on map + auto selectObjOnMap = [this, textCountList, visitableObjInstances](int index) { auto selObj = textCountList[index].first; @@ -520,7 +521,12 @@ void AdventureMapShortcuts::search() auto objInst = LOCPLINT->cb->getObjInstance(selVisitableObjInstances[searchPos]); owner.centerOnObject(objInst); searchLast = VLC->objtypeh->getObjectName(objInst->getObjGroupIndex(), objInst->getObjTypeIndex()); - }, lastSel); + }; + + if(next) + selectObjOnMap(lastSel); + else + GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), [selectObjOnMap](int index){ selectObjOnMap(index); }, lastSel); } void AdventureMapShortcuts::nextObject() diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index 7a5bff54d..96315cda5 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -76,7 +76,7 @@ class AdventureMapShortcuts void nextTown(); void nextObject(); void zoom( int distance); - void search(); + void search(bool next); void moveHeroDirectional(const Point & direction); public: diff --git a/client/gui/Shortcut.h b/client/gui/Shortcut.h index 3bf0502d8..bd8c57a26 100644 --- a/client/gui/Shortcut.h +++ b/client/gui/Shortcut.h @@ -162,6 +162,7 @@ enum class EShortcut ADVENTURE_TO_MAIN_MENU, ADVENTURE_QUIT_GAME, ADVENTURE_SEARCH, + ADVENTURE_SEARCH_CONTINUE, // Move hero one tile in specified direction. Bound to cursors & numpad buttons ADVENTURE_MOVE_HERO_SW, diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index e64d9350d..19c3cc728 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -210,6 +210,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"adventureZoomOut", EShortcut::ADVENTURE_ZOOM_OUT }, {"adventureZoomReset", EShortcut::ADVENTURE_ZOOM_RESET }, {"adventureSearch", EShortcut::ADVENTURE_SEARCH }, + {"adventureSearchContinue", EShortcut::ADVENTURE_SEARCH_CONTINUE }, {"battleToggleHeroesStats", EShortcut::BATTLE_TOGGLE_HEROES_STATS}, {"battleToggleQueue", EShortcut::BATTLE_TOGGLE_QUEUE }, {"battleUseCreatureSpell", EShortcut::BATTLE_USE_CREATURE_SPELL }, diff --git a/config/shortcutsConfig.json b/config/shortcutsConfig.json index 111026765..e6c91b12c 100644 --- a/config/shortcutsConfig.json +++ b/config/shortcutsConfig.json @@ -57,6 +57,7 @@ "adventureZoomOut": "Keypad -", "adventureZoomReset": "Backspace", "adventureSearch": "Ctrl+F", + "adventureSearchContinue": "Alt+F", "battleAutocombat": "A", "battleAutocombatEnd": "Q", "battleCastSpell": "C", From 8e079cf0167b3f239259c939177fcfb3e19906d0 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 22:53:05 +0200 Subject: [PATCH 230/726] add search box --- client/adventureMap/AdventureMapShortcuts.cpp | 2 +- client/windows/GUIClasses.cpp | 53 ++++++++++++++----- client/windows/GUIClasses.h | 14 +++-- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index fce6ec5fe..f3ab04933 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -526,7 +526,7 @@ void AdventureMapShortcuts::search(bool next) if(next) selectObjOnMap(lastSel); else - GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), [selectObjOnMap](int index){ selectObjOnMap(index); }, lastSel); + GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), [selectObjOnMap](int index){ selectObjOnMap(index); }, lastSel, std::vector>(), true); } void AdventureMapShortcuts::nextObject() diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 9ab54fe39..4c98c2044 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1479,7 +1479,7 @@ void CObjectListWindow::CItem::showPopupWindow(const Point & cursorPosition) parent->onPopup(index); } -CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images) +CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images, bool searchBoxEnabled) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("TPGATE")), onSelect(Callback), selected(initialSelection), @@ -1489,15 +1489,14 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, std::share items.reserve(_items.size()); for(int id : _items) - { items.push_back(std::make_pair(id, LOCPLINT->cb->getObjInstance(ObjectInstanceID(id))->getObjectName())); - } + itemsVisible = items; - init(titleWidget_, _title, _descr); + init(titleWidget_, _title, _descr, searchBoxEnabled); list->scrollTo(initialSelection); } -CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images) +CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images, bool searchBoxEnabled) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("TPGATE")), onSelect(Callback), selected(initialSelection), @@ -1508,12 +1507,13 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, st for(size_t i=0; i<_items.size(); i++) items.push_back(std::make_pair(int(i), _items[i])); + itemsVisible = items; - init(titleWidget_, _title, _descr); + init(titleWidget_, _title, _descr, searchBoxEnabled); list->scrollTo(initialSelection); } -void CObjectListWindow::init(std::shared_ptr titleWidget_, std::string _title, std::string _descr) +void CObjectListWindow::init(std::shared_ptr titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled) { titleWidget = titleWidget_; @@ -1528,24 +1528,51 @@ void CObjectListWindow::init(std::shared_ptr titleWidget_, std::stri titleWidget->pos.y =75 + pos.y - titleWidget->pos.h/2; } list = std::make_shared(std::bind(&CObjectListWindow::genItem, this, _1), - Point(14, 151), Point(0, 25), 9, items.size(), 0, 1, Rect(262, -32, 256, 256) ); + Point(14, 151), Point(0, 25), 9, itemsVisible.size(), 0, 1, Rect(262, -32, 256, 256) ); list->setRedrawParent(true); ok = std::make_shared(Point(15, 402), AnimationPath::builtin("IOKAY.DEF"), CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), EShortcut::GLOBAL_ACCEPT); ok->block(!list->size()); + + if(!searchBoxEnabled) + return; + + Rect r(50, 90, pos.w - 100, 16); + const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75); + const ColorRGBA borderColor = ColorRGBA(128, 100, 75); + const ColorRGBA grayedColor = ColorRGBA(158, 130, 105); + searchBoxRectangle = std::make_shared(r.resize(1), rectangleColor, borderColor); + searchBoxDescription = std::make_shared(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search")); + + searchBox = std::make_shared(r, FONT_SMALL, ETextAlignment::CENTER, true); + searchBox->setCallback([this](const std::string & text){ + searchBoxDescription->setEnabled(text.empty()); + + itemsVisible.clear(); + for(auto & item : items) + if(boost::algorithm::contains(boost::algorithm::to_lower_copy(item.second), boost::algorithm::to_lower_copy(text))) + itemsVisible.push_back(item); + + selected = 0; + list->resize(itemsVisible.size()); + list->scrollTo(0); + ok->block(!itemsVisible.size()); + + redraw(); + }); } std::shared_ptr CObjectListWindow::genItem(size_t index) { - if(index < items.size()) - return std::make_shared(this, index, items[index].second); + if(index < itemsVisible.size()) + return std::make_shared(this, index, itemsVisible[index].second); return std::shared_ptr(); } void CObjectListWindow::elementSelected() { std::function toCall = onSelect;//save - int where = items[selected].first; //required variables + int where = itemsVisible[selected].first; //required variables close();//then destroy window toCall(where);//and send selected object } @@ -1601,13 +1628,13 @@ void CObjectListWindow::keyPressed(EShortcut key) sel = 0; break; case EShortcut::MOVE_LAST: - sel = static_cast(items.size()); + sel = static_cast(itemsVisible.size()); break; default: return; } - vstd::abetween(sel, 0, items.size()-1); + vstd::abetween(sel, 0, itemsVisible.size()-1); list->scrollTo(sel); changeSelection(sel); } diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 5e58ad6d2..6484b11ed 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -45,6 +45,7 @@ class IImage; class VideoWidget; class VideoWidgetOnce; class GraphicalPrimitiveCanvas; +class TransparentFilledRectangle; enum class EUserEvent; @@ -186,9 +187,14 @@ class CObjectListWindow : public CWindowObject std::shared_ptr ok; std::shared_ptr exit; - std::vector< std::pair > items;//all items present in list + std::shared_ptr searchBox; + std::shared_ptr searchBoxRectangle; + std::shared_ptr searchBoxDescription; - void init(std::shared_ptr titleWidget_, std::string _title, std::string _descr); + std::vector< std::pair > items; //all items present in list + std::vector< std::pair > itemsVisible; //visible items present in list + + void init(std::shared_ptr titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled); void exitPressed(); public: size_t selected;//index of currently selected item @@ -200,8 +206,8 @@ public: /// Callback will be called when OK button is pressed, returns id of selected item. initState = initially selected item /// Image can be nullptr ///item names will be taken from map objects - CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}); - CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}); + CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}, bool searchBoxEnabled = false); + CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}, bool searchBoxEnabled = false); std::shared_ptr genItem(size_t index); void elementSelected();//call callback and close this window From a4b60aca10e1f6fe0681665c03d1b16b9e24fccc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 1 Oct 2024 23:37:27 +0200 Subject: [PATCH 231/726] cleanup --- client/adventureMap/AdventureMapShortcuts.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index 96315cda5..b314e7bbd 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -10,8 +10,6 @@ #pragma once -#include "../../lib/constants/EntityIdentifiers.h" - VCMI_LIB_NAMESPACE_BEGIN class Point; class Rect; From c19d885603cafb114e92f42e61707012fb3f70d7 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 2 Oct 2024 00:10:45 +0200 Subject: [PATCH 232/726] Subterranean Gateway fix Fixed an issue that caused pathfinding for player and AI not working when a subterranean gate was too close to the edge of the map. Fixed another issue that played into pathfinding for AI not working when a subterranean gate was too close to the edge of the map. --- lib/CGameInfoCallback.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index de5a77433..8e404e34c 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -964,7 +964,7 @@ std::vector CGameInfoCallback::getVisibleTeleportObjects(std:: vstd::erase_if(ids, [&](const ObjectInstanceID & id) -> bool { const auto * obj = getObj(id, false); - return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player)); + return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->visitablePos(), player)); }); return ids; } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 9c01e64aa..054b4fcff 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -602,13 +602,13 @@ void CGSubterraneanGate::postInit(IGameCallback * cb) //matches subterranean gat auto * hlp = dynamic_cast(cb->gameState()->getObjInstance(obj->id)); if(hlp) - gatesSplit[hlp->pos.z].push_back(hlp); + gatesSplit[hlp->visitablePos().z].push_back(hlp); } //sort by position std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b) { - return a->pos < b->pos; + return a->visitablePos() < b->visitablePos(); }); auto assignToChannel = [&](CGSubterraneanGate * obj) @@ -631,7 +631,7 @@ void CGSubterraneanGate::postInit(IGameCallback * cb) //matches subterranean gat CGSubterraneanGate *checked = gatesSplit[1][j]; if(checked->channel != TeleportChannelID()) continue; - si32 hlp = checked->pos.dist2dSQ(objCurrent->pos); + si32 hlp = checked->visitablePos().dist2dSQ(objCurrent->visitablePos()); if(hlp < best.second) { best.first = j; From 2c16e6b4e1806688703927f784dd42aea8753c36 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Tue, 1 Oct 2024 20:20:48 -0300 Subject: [PATCH 233/726] Update Portuguese Translation --- launcher/translation/portuguese.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index 0f4108637..1281d69a2 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -653,6 +653,11 @@ O download da instalação foi bem-sucedido? Neutral AI in battles IA neutra nas batalhas + + + Font Scaling (experimental) + Escala da fonte (experimental) + Enemy AI in battles @@ -963,7 +968,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu VSync - VSync + Sincronização vertical (VSync) From 5aebc83bca8ed181e0af1a901c97fd869cefb6f2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 2 Oct 2024 09:24:53 +0000 Subject: [PATCH 234/726] Fixes pathfinding via subterranean gates located on right edge of map In this case, 'pos' is actually outside of map borders, so never visible to a player --- lib/CGameInfoCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index de5a77433..8e404e34c 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -964,7 +964,7 @@ std::vector CGameInfoCallback::getVisibleTeleportObjects(std:: vstd::erase_if(ids, [&](const ObjectInstanceID & id) -> bool { const auto * obj = getObj(id, false); - return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->pos, player)); + return player != PlayerColor::UNFLAGGABLE && (!obj || !isVisible(obj->visitablePos(), player)); }); return ids; } From e22d15b1d8f3f629c59d785935d51fc29f27d8f7 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:47:26 +0300 Subject: [PATCH 235/726] manageBackpackArtifacts --- CCallback.cpp | 18 ++++++++ CCallback.h | 6 +++ client/windows/CHeroBackpackWindow.cpp | 7 +++ client/windows/CHeroBackpackWindow.h | 1 + server/CGameHandler.cpp | 64 ++++++++++++++++++++++---- server/CGameHandler.h | 3 +- server/NetPacksServer.cpp | 17 +------ 7 files changed, 89 insertions(+), 27 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 8d2709f3d..b63ab9dc9 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -192,6 +192,24 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left) sendRequest(&mba); } +void CCallback::sortBackpackArtifactsBySlot(const ObjectInstanceID hero) +{ + ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT); + sendRequest(&mba); +} + +void CCallback::sortBackpackArtifactsByCost(const ObjectInstanceID hero) +{ + ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_COST); + sendRequest(&mba); +} + +void CCallback::sortBackpackArtifactsByClass(const ObjectInstanceID hero) +{ + ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS); + sendRequest(&mba); +} + void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) { ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume); diff --git a/CCallback.h b/CCallback.h index a934113ce..c2a439595 100644 --- a/CCallback.h +++ b/CCallback.h @@ -92,6 +92,9 @@ public: //virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0; virtual void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) = 0; + virtual void sortBackpackArtifactsBySlot(const ObjectInstanceID hero) = 0; + virtual void sortBackpackArtifactsByCost(const ObjectInstanceID hero) = 0; + virtual void sortBackpackArtifactsByClass(const ObjectInstanceID hero) = 0; virtual void manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) = 0; virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0; virtual void eraseArtifactByClient(const ArtifactLocation & al)=0; @@ -179,6 +182,9 @@ public: void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override; void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped = true, bool backpack = true) override; void scrollBackpackArtifacts(ObjectInstanceID hero, bool left) override; + void sortBackpackArtifactsBySlot(const ObjectInstanceID hero) override; + void sortBackpackArtifactsByCost(const ObjectInstanceID hero) override; + void sortBackpackArtifactsByClass(const ObjectInstanceID hero) override; void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override; void eraseArtifactByClient(const ArtifactLocation & al) override; bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override; diff --git a/client/windows/CHeroBackpackWindow.cpp b/client/windows/CHeroBackpackWindow.cpp index 361350a72..ee07f2f60 100644 --- a/client/windows/CHeroBackpackWindow.cpp +++ b/client/windows/CHeroBackpackWindow.cpp @@ -20,6 +20,8 @@ #include "render/Canvas.h" #include "CPlayerInterface.h" +#include "../../CCallback.h" + #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/networkPacks/ArtifactLocation.h" @@ -41,11 +43,16 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero, const std: }; addSet(arts); arts->setHero(hero); + quitButton = std::make_shared(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), [this]() { WindowBase::close(); }, EShortcut::GLOBAL_RETURN); pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin; pos.h = stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin; quitButton->moveTo(Point(pos.x + pos.w / 2 - quitButton->pos.w / 2, pos.y + arts->pos.h + 2 * windowMargin)); + + sortBySlot = std::make_shared(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), + [hero]() { LOCPLINT->cb->sortBackpackArtifactsBySlot(hero->id); }, EShortcut::GLOBAL_RETURN); + sortBySlot->moveTo(Point(pos.x + windowMargin, quitButton->pos.y)); statusbar = CGStatusBar::create(0, pos.h, ImagePath::builtin("ADROLLVR.bmp"), pos.w); pos.h += statusbar->pos.h; diff --git a/client/windows/CHeroBackpackWindow.h b/client/windows/CHeroBackpackWindow.h index 5dcd2ab1c..e9a443477 100644 --- a/client/windows/CHeroBackpackWindow.h +++ b/client/windows/CHeroBackpackWindow.h @@ -21,6 +21,7 @@ public: protected: std::shared_ptr arts; std::shared_ptr quitButton; + std::shared_ptr sortBySlot; std::shared_ptr stretchedBackground; const int windowMargin = 5; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 383e3bbe3..b8ab1abf7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2655,22 +2655,66 @@ bool CGameHandler::bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceI return true; } -bool CGameHandler::scrollBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, bool left) +bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, const ManageBackpackArtifacts::ManageCmd & sortType) { const auto artSet = getArtSet(heroID); - COMPLAIN_RET_FALSE_IF(artSet == nullptr, "scrollBackpackArtifacts: wrong hero's ID"); + COMPLAIN_RET_FALSE_IF(artSet == nullptr, "manageBackpackArtifacts: wrong hero's ID"); BulkMoveArtifacts bma(player, heroID, heroID, false); - - const auto backpackEnd = ArtifactPosition(ArtifactPosition::BACKPACK_START + artSet->artifactsInBackpack.size() - 1); - if(backpackEnd > ArtifactPosition::BACKPACK_START) + const auto makeSortBackpackRequest = [artSet, &bma](const std::function & getSortId) { - if(left) - bma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(backpackEnd, ArtifactPosition::BACKPACK_START)); - else - bma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(ArtifactPosition::BACKPACK_START, backpackEnd)); - sendAndApply(&bma); + std::map> packsSorted; + ArtifactPosition backpackSlot = ArtifactPosition::BACKPACK_START; + for(const auto & backpackSlotInfo : artSet->artifactsInBackpack) + packsSorted.try_emplace(getSortId(backpackSlotInfo)).first->second.emplace_back(backpackSlot++, ArtifactPosition::PRE_FIRST); + + for(auto & [sortId, pack] : packsSorted) + { + // Each pack of artifacts is also sorted by ArtifactID + std::sort(pack.begin(), pack.end(), [artSet](const auto & slots0, const auto & slots1) -> bool + { + return artSet->getArt(slots0.srcPos)->getTypeId().num > artSet->getArt(slots1.srcPos)->getTypeId().num; + }); + bma.artsPack0.insert(bma.artsPack0.end(), pack.begin(), pack.end()); + } + backpackSlot = ArtifactPosition::BACKPACK_START; + for(auto & slots : bma.artsPack0) + slots.dstPos = backpackSlot++; + }; + + if(sortType == ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT) + { + makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t + { + return inf.getArt()->artType->getPossibleSlots().at(ArtBearer::HERO).front().num; + }); } + else if(sortType == ManageBackpackArtifacts::ManageCmd::SORT_BY_COST) + { + makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t + { + return inf.getArt()->artType->getPrice(); + }); + } + else if(sortType == ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS) + { + makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t + { + return inf.getArt()->artType->aClass; + }); + } + else + { + const auto backpackEnd = ArtifactPosition(ArtifactPosition::BACKPACK_START + artSet->artifactsInBackpack.size() - 1); + if(backpackEnd > ArtifactPosition::BACKPACK_START) + { + if(sortType == ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT) + bma.artsPack0.emplace_back(backpackEnd, ArtifactPosition::BACKPACK_START); + else + bma.artsPack0.emplace_back(ArtifactPosition::BACKPACK_START, backpackEnd); + } + } + sendAndApply(&bma); return true; } diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e2aa8c4f6..44bc9d57e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -15,6 +15,7 @@ #include "../lib/LoadProgress.h" #include "../lib/ScriptHandler.h" #include "../lib/gameState/GameStatistics.h" +#include "../lib/networkPacks/PacksForServer.h" VCMI_LIB_NAMESPACE_BEGIN @@ -141,7 +142,7 @@ public: void removeArtifact(const ObjectInstanceID & srcId, const std::vector & slotsPack); bool moveArtifact(const PlayerColor & player, const ArtifactLocation & src, const ArtifactLocation & dst) override; bool bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceID srcId, ObjectInstanceID dstId, bool swap, bool equipped, bool backpack); - bool scrollBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, bool left); + bool manageBackpackArtifacts(const PlayerColor & player, const ObjectInstanceID heroID, const ManageBackpackArtifacts::ManageCmd & sortType); bool saveArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx); bool switchArtifactsCostume(const PlayerColor & player, const ObjectInstanceID heroID, uint32_t costumeIdx); bool eraseArtifactByClient(const ArtifactLocation & al); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 2a7b493d4..b8d315932 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -197,22 +197,7 @@ void ApplyGhNetPackVisitor::visitManageBackpackArtifacts(ManageBackpackArtifacts gh.throwIfPlayerNotActive(&pack); if(gh.getPlayerRelations(pack.player, gh.getOwner(pack.artHolder)) != PlayerRelations::ENEMIES) - { - if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT) - result = gh.scrollBackpackArtifacts(pack.player, pack.artHolder, true); - else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT) - result = gh.scrollBackpackArtifacts(pack.player, pack.artHolder, false); - else - { - gh.throwIfWrongOwner(&pack, pack.artHolder); - if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS) - result = true; - else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SORT_BY_COST) - result = true; - else if(pack.cmd == ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT) - result = true; - } - } + result = gh.manageBackpackArtifacts(pack.player, pack.artHolder, pack.cmd); } void ApplyGhNetPackVisitor::visitManageEquippedArtifacts(ManageEquippedArtifacts & pack) From e6f4a63951286f729e4079936e1688d8ffd392d9 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:26:16 +0300 Subject: [PATCH 236/726] add sort buttons --- Mods/vcmi/config/vcmi/english.json | 6 +++++ client/windows/CHeroBackpackWindow.cpp | 32 +++++++++++++++++++------- client/windows/CHeroBackpackWindow.h | 4 ++-- server/CGameHandler.cpp | 8 +++++-- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..852e9b7e2 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -340,6 +340,12 @@ "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero.", "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Sort by cost", + "vcmi.heroWindow.sortBackpackByCost.help" : "Sort artifacts in backpack by cost.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Sort by slot", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Sort artifacts in backpack by equipped slot.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Sort by class", + "vcmi.heroWindow.sortBackpackByClass.help" : "Sort artifacts in backpack by artifact class. Treasure, Minor, Major, Relic", "vcmi.tavernWindow.inviteHero" : "Invite hero", diff --git a/client/windows/CHeroBackpackWindow.cpp b/client/windows/CHeroBackpackWindow.cpp index ee07f2f60..c184b7ed8 100644 --- a/client/windows/CHeroBackpackWindow.cpp +++ b/client/windows/CHeroBackpackWindow.cpp @@ -44,21 +44,37 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero, const std: addSet(arts); arts->setHero(hero); - quitButton = std::make_shared(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), - [this]() { WindowBase::close(); }, EShortcut::GLOBAL_RETURN); + buttons.emplace_back(std::make_unique(Point(), AnimationPath::builtin("ALTFILL.DEF"), + CButton::tooltipLocalized("vcmi.heroWindow.sortBackpackByCost"), + [hero]() { LOCPLINT->cb->sortBackpackArtifactsByCost(hero->id); })); + buttons.emplace_back(std::make_unique(Point(), AnimationPath::builtin("ALTFILL.DEF"), + CButton::tooltipLocalized("vcmi.heroWindow.sortBackpackBySlot"), + [hero]() { LOCPLINT->cb->sortBackpackArtifactsBySlot(hero->id); })); + buttons.emplace_back(std::make_unique(Point(), AnimationPath::builtin("ALTFILL.DEF"), + CButton::tooltipLocalized("vcmi.heroWindow.sortBackpackByClass"), + [hero]() { LOCPLINT->cb->sortBackpackArtifactsByClass(hero->id); })); + pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin; - pos.h = stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin; - quitButton->moveTo(Point(pos.x + pos.w / 2 - quitButton->pos.w / 2, pos.y + arts->pos.h + 2 * windowMargin)); + pos.h = stretchedBackground->pos.h = arts->pos.h + buttons.back()->pos.h + 3 * windowMargin; - sortBySlot = std::make_shared(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), - [hero]() { LOCPLINT->cb->sortBackpackArtifactsBySlot(hero->id); }, EShortcut::GLOBAL_RETURN); - sortBySlot->moveTo(Point(pos.x + windowMargin, quitButton->pos.y)); + auto buttonPos = Point(pos.x + windowMargin, pos.y + arts->pos.h + 2 * windowMargin); + for(const auto & button : buttons) + { + button->moveTo(buttonPos); + buttonPos += Point(button->pos.w + 10, 0); + } + statusbar = CGStatusBar::create(0, pos.h, ImagePath::builtin("ADROLLVR.bmp"), pos.w); pos.h += statusbar->pos.h; - + addUsedEvents(LCLICK); center(); } +void CHeroBackpackWindow::notFocusedClick() +{ + close(); +} + void CHeroBackpackWindow::showAll(Canvas & to) { CIntObject::showAll(to); diff --git a/client/windows/CHeroBackpackWindow.h b/client/windows/CHeroBackpackWindow.h index e9a443477..239a3fa0a 100644 --- a/client/windows/CHeroBackpackWindow.h +++ b/client/windows/CHeroBackpackWindow.h @@ -17,11 +17,11 @@ class CHeroBackpackWindow : public CStatusbarWindow, public CWindowWithArtifacts { public: CHeroBackpackWindow(const CGHeroInstance * hero, const std::vector & artsSets); + void notFocusedClick() override; protected: std::shared_ptr arts; - std::shared_ptr quitButton; - std::shared_ptr sortBySlot; + std::vector> buttons; std::shared_ptr stretchedBackground; const int windowMargin = 5; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b8ab1abf7..b11e6a1d7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2670,10 +2670,14 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj for(auto & [sortId, pack] : packsSorted) { - // Each pack of artifacts is also sorted by ArtifactID + // Each pack of artifacts is also sorted by ArtifactID. Scrolls by SpellID std::sort(pack.begin(), pack.end(), [artSet](const auto & slots0, const auto & slots1) -> bool { - return artSet->getArt(slots0.srcPos)->getTypeId().num > artSet->getArt(slots1.srcPos)->getTypeId().num; + const auto art0 = artSet->getArt(slots0.srcPos); + const auto art1 = artSet->getArt(slots1.srcPos); + if(art0->isScroll() && art1->isScroll()) + return art0->getScrollSpellID() > art1->getScrollSpellID(); + return art0->getTypeId().num > art1->getTypeId().num; }); bma.artsPack0.insert(bma.artsPack0.end(), pack.begin(), pack.end()); } From d8d652783a43027f3d568accf7f34aea1e6c10d5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 2 Oct 2024 11:04:40 +0000 Subject: [PATCH 237/726] Fix crash on casting spell by a random spellcaster (e.g. Master Genie) --- server/battles/BattleFlowProcessor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index ce70d21fd..7510b519a 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -571,7 +571,8 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const assert(activeStack != nullptr); assert(actedStack != nullptr); - if(actedStack->castSpellThisTurn && SpellID(ba.spell).toSpell()->canCastWithoutSkip()) + // NOTE: in case of random spellcaster, (e.g. Master Genie) spell has been selected by server and was not present in action received from player + if(actedStack->castSpellThisTurn && ba.spell.hasValue() && ba.spell.toSpell()->canCastWithoutSkip()) { setActiveStack(battle, actedStack); return; From 4896b51a5d2ff691b8b9cf146d7ad6cba37315ae Mon Sep 17 00:00:00 2001 From: kdmcser Date: Wed, 2 Oct 2024 19:08:40 +0800 Subject: [PATCH 238/726] display attack type(melee/ranged) in popup window --- Mods/vcmi/config/vcmi/chinese.json | 4 +++- Mods/vcmi/config/vcmi/english.json | 4 +++- lib/mapObjects/CGCreature.cpp | 25 +++++++++++++++++++------ lib/mapObjects/CGCreature.h | 2 +- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 9ac1d1952..a36a923ce 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -12,7 +12,9 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "压倒性的", "vcmi.adventureMap.monsterThreat.levels.10" : "致命的", "vcmi.adventureMap.monsterThreat.levels.11" : "无法取胜", - "vcmi.adventureMap.monsterLevel" : "\n\n%TOWN%LEVEL级生物", + "vcmi.adventureMap.monsterLevel" : "\n\n%TOWN%LEVEL级%ATTACK_TYPE生物", + "vcmi.adventureMap.monsterMeleeType" : "近战", + "vcmi.adventureMap.monsterRangedType" : "远程", "vcmi.adventureMap.confirmRestartGame" : "你想要重新开始游戏吗?", "vcmi.adventureMap.noTownWithMarket" : "没有足够的市场。", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..04479e321 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -12,7 +12,9 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Overpowering", "vcmi.adventureMap.monsterThreat.levels.10" : "Deadly", "vcmi.adventureMap.monsterThreat.levels.11" : "Impossible", - "vcmi.adventureMap.monsterLevel" : "\n\nLevel %LEVEL %TOWN unit", + "vcmi.adventureMap.monsterLevel" : "\n\nLevel %LEVEL %TOWN %ATTACK_TYPE unit", + "vcmi.adventureMap.monsterMeleeType" : "melee", + "vcmi.adventureMap.monsterRangedType" : "ranged", "vcmi.adventureMap.confirmRestartGame" : "Are you sure you want to restart the game?", "vcmi.adventureMap.noTownWithMarket" : "There are no available marketplaces!", diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index d34b5792c..d74b55a00 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -66,6 +66,18 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const } } +std::string CGCreature::getMonsterLevelText() const +{ + std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel"); + bool isRanged = VLC->creatures()->getById(getCreature())->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER); + std::string attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType"; + std::string attackType = VLC->generaltexth->translate(attackTypeKey); + boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated()); + boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel())); + boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType); + return monsterLevel; +} + std::string CGCreature::getPopupText(const CGHeroInstance * hero) const { std::string hoverName; @@ -102,11 +114,7 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const if (settings["general"]["enableUiEnhancements"].Bool()) { - std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel"); - boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated()); - boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel())); - hoverName += monsterLevel; - + hoverName += getMonsterLevelText(); hoverName += VLC->generaltexth->translate("vcmi.adventureMap.monsterThreat.title"); int choice; @@ -131,7 +139,12 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const std::string CGCreature::getPopupText(PlayerColor player) const { - return getHoverText(player); + std::string hoverName = getHoverText(player); + if (settings["general"]["enableUiEnhancements"].Bool()) + { + hoverName += getMonsterLevelText(); + } + return hoverName; } std::vector CGCreature::getPopupComponents(PlayerColor player) const diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index 2b88d367d..a8c32fb8b 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -81,7 +81,7 @@ private: int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0) void giveReward(const CGHeroInstance * h) const; - + std::string getMonsterLevelText() const; }; VCMI_LIB_NAMESPACE_END From 60177bffab1f9cf511584359c73883ee44126b92 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Wed, 2 Oct 2024 19:57:26 +0800 Subject: [PATCH 239/726] remove bracket --- lib/mapObjects/CGCreature.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index d74b55a00..26bb03772 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -141,9 +141,7 @@ std::string CGCreature::getPopupText(PlayerColor player) const { std::string hoverName = getHoverText(player); if (settings["general"]["enableUiEnhancements"].Bool()) - { hoverName += getMonsterLevelText(); - } return hoverName; } From af2b150506d6363c0bc289101a31074bc2ec4322 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Wed, 2 Oct 2024 22:28:06 +0800 Subject: [PATCH 240/726] Update german.json --- Mods/vcmi/config/vcmi/german.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index efe786220..481aa37d1 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -12,7 +12,9 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Überwältigend", "vcmi.adventureMap.monsterThreat.levels.10" : "Tödlich", "vcmi.adventureMap.monsterThreat.levels.11" : "Unmöglich", - "vcmi.adventureMap.monsterLevel" : "\n\nStufe %LEVEL %TOWN-Einheit", + "vcmi.adventureMap.monsterLevel" : "\n\nStufe %LEVEL %TOWN-Einheit (%ATTACK_TYPE)", + "vcmi.adventureMap.monsterMeleeType" : "Nahkampf", + "vcmi.adventureMap.monsterRangedType" : "Fernkampf", "vcmi.adventureMap.confirmRestartGame" : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?", "vcmi.adventureMap.noTownWithMarket" : "Kein Marktplatz verfügbar!", From f0802c0b3c8536a722932092dbe4140a4cf926f5 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 2 Oct 2024 20:11:20 +0200 Subject: [PATCH 241/726] Move foreign hero fix Fixed an issue that caused the AI to try and move heroes of other players. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 45824c9ca..b087d0124 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1413,7 +1413,8 @@ void AINodeStorage::calculateChainInfo(std::vector & paths, const int3 & || node.layer != layer || node.action == EPathNodeAction::UNKNOWN || !node.actor - || !node.actor->hero) + || !node.actor->hero + || node.actor->hero->getOwner() != ai->playerID) { continue; } From 0143a5275566737fd0b998e3d971a35f29241eb9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 2 Oct 2024 20:12:47 +0200 Subject: [PATCH 242/726] Update PriorityEvaluator.cpp Scores for all sorts of visitable and pickable objects are now unified in order to prevent AI from ignoring nearby valuables. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 3275ba9aa..9e23377f3 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1431,7 +1431,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.isArmyUpgrade) return 0; - if (evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) + if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1) + return 0; + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; score += evaluationContext.strategicalValue * 1000; score += evaluationContext.goldReward; @@ -1442,11 +1444,10 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage; if (score > 0) { + score = 1000; score *= evaluationContext.closestWayRatio; - score /= (1 + evaluationContext.enemyHeroDangerRatio); if (evaluationContext.movementCost > 0) score /= evaluationContext.movementCost; - score *= (maxWillingToLose - evaluationContext.armyLossPersentage); } break; } From f5c2772f8d7c6dfe52fa076f906b49eb6d1721e5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 2 Oct 2024 19:48:08 +0000 Subject: [PATCH 243/726] Fix potential int32_t overflow when computing total army value --- lib/CCreatureSet.cpp | 2 +- lib/mapObjects/CGCreature.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index fd1bfdc01..69040009d 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -855,7 +855,7 @@ std::string CStackInstance::getName() const ui64 CStackInstance::getPower() const { assert(type); - return type->getAIValue() * count; + return static_cast(type->getAIValue()) * count; } ArtBearer::ArtBearer CStackInstance::bearerType() const diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index d34b5792c..4539c7fe9 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -110,7 +110,9 @@ std::string CGCreature::getPopupText(const CGHeroInstance * hero) const hoverName += VLC->generaltexth->translate("vcmi.adventureMap.monsterThreat.title"); int choice; - double ratio = (static_cast(getArmyStrength()) / hero->getTotalStrength()); + uint64_t armyStrength = getArmyStrength(); + uint64_t heroStrength = hero->getTotalStrength(); + double ratio = static_cast(armyStrength) / heroStrength; if (ratio < 0.1) choice = 0; else if (ratio < 0.25) choice = 1; else if (ratio < 0.6) choice = 2; From 5e6fefdc509dcbad510032f1ea41e3e95336d33f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 2 Oct 2024 23:40:51 +0200 Subject: [PATCH 244/726] Reverted fix for not trying to moving foreign heroes While it fixed the bug it was supposed to fix it caused another severe bug: AI wasn't generating a thread-map anymore. Original bug needs to find another fix. --- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index b087d0124..45824c9ca 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1413,8 +1413,7 @@ void AINodeStorage::calculateChainInfo(std::vector & paths, const int3 & || node.layer != layer || node.action == EPathNodeAction::UNKNOWN || !node.actor - || !node.actor->hero - || node.actor->hero->getOwner() != ai->playerID) + || !node.actor->hero) { continue; } From d9d7d0ea0dc629c60435360ea9f78b9478c24aca Mon Sep 17 00:00:00 2001 From: Evgeniy Meshcheryakov Date: Thu, 3 Oct 2024 11:55:58 +0300 Subject: [PATCH 245/726] Map editor: Transparency fix --- mapeditor/Animation.cpp | 118 ++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 66 deletions(-) diff --git a/mapeditor/Animation.cpp b/mapeditor/Animation.cpp index 0b1c89161..265dbf394 100644 --- a/mapeditor/Animation.cpp +++ b/mapeditor/Animation.cpp @@ -46,7 +46,7 @@ private: std::map > offset; std::unique_ptr data; - std::unique_ptr> palette; + QVector palette; public: DefFile(std::string Name); @@ -138,6 +138,44 @@ enum class DefType : uint32_t static FileCache animationCache; +//First 8 colors in def palette used for transparency +static constexpr std::array TARGET_PALETTE = +{ + qRgba(0, 0, 0, 0 ), // transparency ( used in most images ) + qRgba(0, 0, 0, 64 ), // shadow border ( used in battle, adventure map def's ) + qRgba(0, 0, 0, 64 ), // shadow border ( used in fog-of-war def's ) + qRgba(0, 0, 0, 128), // shadow body ( used in fog-of-war def's ) + qRgba(0, 0, 0, 128), // shadow body ( used in battle, adventure map def's ) + qRgba(0, 0, 0, 0 ), // selection / owner flag ( used in battle, adventure map def's ) + qRgba(0, 0, 0, 128), // shadow body below selection ( used in battle def's ) + qRgba(0, 0, 0, 64 ) // shadow border below selection ( used in battle def's ) +}; + +static constexpr std::array SOURCE_PALETTE = +{ + qRgba(0, 255, 255, 255), + qRgba(255, 150, 255, 255), + qRgba(255, 100, 255, 255), + qRgba(255, 50, 255, 255), + qRgba(255, 0, 255, 255), + qRgba(255, 255, 0, 255), + qRgba(180, 0, 255, 255), + qRgba(0, 255, 0, 255) +}; + +static bool colorsSimilar(const QRgb & lhs, const QRgb & rhs) +{ + // it seems that H3 does not requires exact match to replace colors -> (255, 103, 255) gets interpreted as shadow + // exact logic is not clear and requires extensive testing with image editing + // potential reason is that H3 uses 16-bit color format (565 RGB bits), meaning that 3 least significant bits are lost in red and blue component + static const int threshold = 8; + int diffR = qRed(lhs) - qRed(rhs); + int diffG = qGreen(lhs) - qGreen(rhs); + int diffB = qBlue(lhs) - qBlue(rhs); + int diffA = qAlpha(lhs) - qAlpha(rhs); + return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold; +}; + /************************************************************************* * DefFile, class used for def loading * *************************************************************************/ @@ -160,24 +198,12 @@ DefFile::DefFile(std::string Name): }; #endif // 0 - //First 8 colors in def palette used for transparency - constexpr std::array H3Palette = - { - qRgba(0, 0, 0, 0), // 100% - transparency - qRgba(0, 0, 0, 32), // 75% - shadow border, - qRgba(0, 0, 0, 64), // TODO: find exact value - qRgba(0, 0, 0, 128), // TODO: for transparency - qRgba(0, 0, 0, 128), // 50% - shadow body - qRgba(0, 0, 0, 0), // 100% - selection highlight - qRgba(0, 0, 0, 128), // 50% - shadow body below selection - qRgba(0, 0, 0, 64) // 75% - shadow border below selection - }; data = animationCache.getCachedFile(AnimationPath::builtin("SPRITES/" + Name)); - palette = std::make_unique>(256); + palette = QVector(256); int it = 0; - ui32 type = read_le_u32(data.get() + it); + //ui32 type = read_le_u32(data.get() + it); it += 4; //int width = read_le_u32(data + it); it+=4;//not used //int height = read_le_u32(data + it); it+=4; @@ -191,59 +217,19 @@ DefFile::DefFile(std::string Name): c[0] = data[it++]; c[1] = data[it++]; c[2] = data[it++]; - (*palette)[i] = qRgba(c[0], c[1], c[2], 255); + palette[i] = qRgba(c[0], c[1], c[2], 255); } - switch(static_cast(type)) + // these colors seems to be used unconditionally + palette[0] = TARGET_PALETTE[0]; + palette[1] = TARGET_PALETTE[1]; + palette[4] = TARGET_PALETTE[4]; + + // rest of special colors are used only if their RGB values are close to H3 + for (uint32_t i = 0; i < 8; ++i) { - case DefType::SPELL: - (*palette)[0] = H3Palette[0]; - break; - case DefType::SPRITE: - case DefType::SPRITE_FRAME: - for(ui32 i= 0; i<8; i++) - (*palette)[i] = H3Palette[i]; - break; - case DefType::CREATURE: - (*palette)[0] = H3Palette[0]; - (*palette)[1] = H3Palette[1]; - (*palette)[4] = H3Palette[4]; - (*palette)[5] = H3Palette[5]; - (*palette)[6] = H3Palette[6]; - (*palette)[7] = H3Palette[7]; - break; - case DefType::MAP: - case DefType::MAP_HERO: - (*palette)[0] = H3Palette[0]; - (*palette)[1] = H3Palette[1]; - (*palette)[4] = H3Palette[4]; - //5 = owner flag, handled separately - break; - case DefType::TERRAIN: - (*palette)[0] = H3Palette[0]; - (*palette)[1] = H3Palette[1]; - (*palette)[2] = H3Palette[2]; - (*palette)[3] = H3Palette[3]; - (*palette)[4] = H3Palette[4]; - break; - case DefType::CURSOR: - (*palette)[0] = H3Palette[0]; - break; - case DefType::INTERFACE: - (*palette)[0] = H3Palette[0]; - (*palette)[1] = H3Palette[1]; - (*palette)[4] = H3Palette[4]; - //player colors handled separately - //TODO: disallow colorizing other def types - break; - case DefType::BATTLE_HERO: - (*palette)[0] = H3Palette[0]; - (*palette)[1] = H3Palette[1]; - (*palette)[4] = H3Palette[4]; - break; - default: - logAnim->error("Unknown def type %d in %s", type, Name); - break; + if (colorsSimilar(SOURCE_PALETTE[i], palette[i])) + palette[i] = TARGET_PALETTE[i]; } @@ -421,7 +407,7 @@ std::shared_ptr DefFile::loadFrame(size_t frame, size_t group) const } - img->setColorTable(*palette); + img->setColorTable(palette); return img; } From 5d64da9b6c07bf10ae014dc0b6cc90210da5eb64 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 3 Oct 2024 12:35:15 +0000 Subject: [PATCH 246/726] Fixes randomization of Witch Hut preconfigured in map editor --- lib/mapObjectConstructors/CRewardableConstructor.cpp | 5 +++-- lib/mapObjectConstructors/CRewardableConstructor.h | 2 +- lib/mapObjects/CRewardableObject.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index 3fbcba73d..80d842a47 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -43,9 +43,10 @@ CGObjectInstance * CRewardableConstructor::create(IGameCallback * cb, std::share return ret; } -Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const +Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map & presetVariables) const { Rewardable::Configuration result; + result.variables.preset = presetVariables; objectInfo.configureObject(result, rand, cb); for(auto & rewardInfo : result.info) @@ -67,7 +68,7 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, vstd::RN if (!rewardableObject) throw std::runtime_error("Object " + std::to_string(object->getObjGroupIndex()) + ", " + std::to_string(object->getObjTypeIndex()) + " is not a rewardable object!" ); - rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID); + rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID, rewardableObject->configuration.variables.preset); rewardableObject->initializeGuards(); if (rewardableObject->configuration.info.empty()) diff --git a/lib/mapObjectConstructors/CRewardableConstructor.h b/lib/mapObjectConstructors/CRewardableConstructor.h index eab0e500a..a9f30d500 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.h +++ b/lib/mapObjectConstructors/CRewardableConstructor.h @@ -31,7 +31,7 @@ public: std::unique_ptr getObjectInfo(std::shared_ptr tmpl) const override; - Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID) const; + Rewardable::Configuration generateConfiguration(IGameCallback * cb, vstd::RNG & rand, MapObjectID objectID, const std::map & presetVariables) const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 44ecf20a6..86c43cc8d 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -329,7 +329,7 @@ void CRewardableObject::newTurn(vstd::RNG & rand) const if (configuration.resetParameters.rewards) { auto handler = std::dynamic_pointer_cast(getObjectHandler()); - auto newConfiguration = handler->generateConfiguration(cb, rand, ID); + auto newConfiguration = handler->generateConfiguration(cb, rand, ID, configuration.variables.preset); cb->setRewardableObjectConfiguration(id, newConfiguration); } if (configuration.resetParameters.visitors) From 68e264d990391858f14a8aebddea17377709b7cb Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 3 Oct 2024 15:06:34 +0200 Subject: [PATCH 247/726] Prune paths involving enemy heroes Capture objects and gather army will now skip paths that involve foreign heroes. --- AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp | 3 +++ AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index d9288af38..1e7f39569 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -79,6 +79,9 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals( auto hero = path.targetHero; auto danger = path.getTotalDanger(); + if (hero->getOwner() != nullkiller->playerID) + continue; + if(nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && (path.getTotalDanger() == 0 || path.turn() > 0) && path.exchangeCount > 1) diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 4a17bb429..14ad346f1 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -81,6 +81,9 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength()); #endif + if (path.targetHero->getOwner() != ai->playerID) + continue; + if(path.containsHero(hero)) { #if NKAI_TRACE_LEVEL >= 2 From 259fcedc855bc0177bebb7f73931294944c25967 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 3 Oct 2024 23:03:36 +0200 Subject: [PATCH 248/726] Fix flying units move into damaging obstacles --- AI/BattleAI/BattleEvaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 57014836e..655d07a82 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -394,7 +394,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector { std::set obstacleHexes; - auto insertAffected = [](const CObstacleInstance & spellObst, std::set obstacleHexes) { + auto insertAffected = [](const CObstacleInstance & spellObst, std::set & obstacleHexes) { auto affectedHexes = spellObst.getAffectedTiles(); obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend()); }; From 6c443548439592675ca9fbbdd9d62c4f5e0d6608 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 4 Oct 2024 11:49:20 +0200 Subject: [PATCH 249/726] Update CGameHandler.cpp Improve debugability with more helpful error-texts. --- server/CGameHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 383e3bbe3..23de4565e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -786,7 +786,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme return false; } - logGlobal->trace("Player %d (%s) wants to move hero %d from %s to %s", asker, asker.toString(), hid.getNum(), h->pos.toString(), dst.toString()); + logGlobal->trace("Player %d (%s) wants to move hero %s from %s to %s", asker, asker.toString(), h->getNameTranslated(), h->pos.toString(), dst.toString()); const int3 hmpos = h->convertToVisitablePos(dst); if (!gs->map->isInTheMap(hmpos)) @@ -869,7 +869,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme return complainRet("Cannot disembark hero, tile is blocked!"); if(distance(h->pos, dst) >= 1.5 && movementMode == EMovementMode::STANDARD) - return complainRet("Tiles are not neighboring!"); + return complainRet("Tiles " + h->pos.toString()+ " and "+ dst.toString() +" are not neighboring!"); if(h->inTownGarrison) return complainRet("Can not move garrisoned hero!"); From c472b2ce5e817f9b4847325513128fbc8b4cf383 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:19:57 +0300 Subject: [PATCH 250/726] BulkEraseArtifacts crash fix --- lib/networkPacks/NetPacksLib.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index ae31fdec8..ed5e71039 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1715,7 +1715,7 @@ void BulkEraseArtifacts::applyGs(CGameState *gs) for(auto & slotInfoWorn : artSet->artifactsWorn) { auto art = slotInfoWorn.second.artifact; - if(art->isCombined() && art->isPart(slotInfo->getArt())) + if(art->isCombined() && art->isPart(slotInfo->artifact)) { dis.al.slot = artSet->getArtPos(art); break; From 371eac070f7ad5e890874d7df07cf56bcfdac68c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 12:48:50 +0000 Subject: [PATCH 251/726] Fixes for player disconnection handling - Fixed lack of notification if player disconnects via connection loss, e.g. app crash / network going down - Replaced notification via chat message with notification via info window --- Mods/vcmi/config/vcmi/english.json | 1 + server/CGameHandler.cpp | 24 +++++++++++++++--- server/CVCMIServer.cpp | 40 +++++++++++++++++------------- server/NetPacksLobbyServer.cpp | 1 + 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b62ee25d2..49e940526 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -143,6 +143,7 @@ "vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s", "vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.", "vcmi.server.errors.disconnected" : "{Network Error}\n\nConnection to game server has been lost!", + "vcmi.server.errors.playerLeft" : "{Player Left}\n\n%s player have disconnected from the game!", //%s -> player color "vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.", "vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 383e3bbe3..7c282eadd 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -431,10 +431,28 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr c) continue; auto playerConnection = vstd::find(playerConnections.second, c); - if(playerConnection != playerConnections.second.end()) + if(playerConnection == playerConnections.second.end()) + continue; + + logGlobal->trace("Player %s disconnected. Notifying remaining players", playerId.toString()); + + // this player have left the game - broadcast infowindow to all in-game players + for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++) { - std::string messageText = boost::str(boost::format("%s (cid %d) was disconnected") % playerSettings->name % c->connectionID); - playerMessages->broadcastMessage(playerId, messageText); + if (i->first == playerId) + continue; + + if (getPlayerState(i->first)->status != EPlayerStatus::INGAME) + continue; + + logGlobal->trace("Notifying player %s", i->first); + + InfoWindow out; + out.player = i->first; + out.text.appendTextID("vcmi.server.errors.playerLeft"); + out.text.replaceName(playerId); + out.components.emplace_back(ComponentType::FLAG, playerId); + sendAndApply(&out); } } } diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 0d8cd9280..7f52c801e 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -297,25 +297,19 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & con logNetwork->error("Network error receiving a pack. Connection has been closed"); std::shared_ptr c = findConnection(connection); - if (!c) - return; // player have already disconnected via clientDisconnected call - vstd::erase(activeConnections, c); - - if(activeConnections.empty() || hostClientId == c->connectionID) + // player may have already disconnected via clientDisconnected call + if (c) { - setState(EServerState::SHUTDOWN); - return; - } + //clientDisconnected(c); - if(gh && getState() == EServerState::GAMEPLAY) - { - gh->handleClientDisconnection(c); - - auto lcd = std::make_unique(); - lcd->c = c; - lcd->clientId = c->connectionID; - handleReceivedPack(std::move(lcd)); + if(gh && getState() == EServerState::GAMEPLAY) + { + auto lcd = std::make_unique(); + lcd->c = c; + lcd->clientId = c->connectionID; + handleReceivedPack(std::move(lcd)); + } } } @@ -434,9 +428,21 @@ void CVCMIServer::clientConnected(std::shared_ptr c, std::vector connection) { - connection->getConnection()->close(); + assert(vstd::contains(activeConnections, connection)); + logGlobal->trace("Received disconnection request"); vstd::erase(activeConnections, connection); + if(activeConnections.empty() || hostClientId == connection->connectionID) + { + setState(EServerState::SHUTDOWN); + return; + } + + if(gh && getState() == EServerState::GAMEPLAY) + { + gh->handleClientDisconnection(connection); + } + // PlayerReinitInterface startAiPack; // startAiPack.playerConnectionId = PlayerSettings::PLAYER_AI; // diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 4cf091ac0..17df7c364 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -108,6 +108,7 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { + pack.c->getConnection()->close(); srv.clientDisconnected(pack.c); result = true; } From 4bf02865e41ab8ee73ff14eac8baf20eb3232a03 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 4 Oct 2024 23:14:02 +0200 Subject: [PATCH 252/726] tavern invite show random hero icon as default --- client/windows/GUIClasses.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 46a9775e5..e014d36a8 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -548,11 +548,12 @@ void CTavernWindow::addInvite() if(!inviteableHeroes.empty()) { + int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random if(!heroToInvite) heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; inviteHero = std::make_shared(170, 444, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.tavernWindow.inviteHero")); - inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex, 0, 245, 428); + inviteHeroImage = std::make_shared(AnimationPath::builtin("PortraitsSmall"), imageIndex, 0, 245, 428); inviteHeroImageArea = std::make_shared(Rect(245, 428, 48, 32), [this](){ GH.windows().createAndPushWindow(inviteableHeroes, [this](CGHeroInstance* h){ heroToInvite = h; addInvite(); }); }, [this](){ GH.windows().createAndPushWindow(std::make_shared(heroToInvite)); }); } } From 2439d176a0b79794152a4aed413f800391f26059 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 2 Oct 2024 18:57:38 +0000 Subject: [PATCH 253/726] Analyze filesystem of mods to detect potential mod conflicts --- lib/modding/CModHandler.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 8d20639f2..a028c8da4 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -331,10 +331,32 @@ void CModHandler::loadModFilesystems() coreMod->updateChecksum(calculateModChecksum(ModScope::scopeBuiltin(), CResourceHandler::get(ModScope::scopeBuiltin()))); + std::map modFilesystems; + for(std::string & modName : activeMods) + modFilesystems[modName] = genModFilesystem(modName, allMods[modName].config); + + for(std::string & modName : activeMods) + CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]); + + for(std::string & leftModName : activeMods) { - CModInfo & mod = allMods[modName]; - CResourceHandler::addFilesystem("data", modName, genModFilesystem(modName, mod.config)); + for(std::string & rightModName : activeMods) + { + if (leftModName == rightModName) + continue; + + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; + + std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); + std::unordered_set rightResources = modFilesystems[rightModName]->getFilteredFiles(filter); + + for (auto const & leftFile : leftResources) + { + if (rightResources.count(leftFile)) + logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftModName, rightModName, leftFile.getOriginalName()); + } + } } } From d0aba56a5eadd308f6574e120da9ff9e6e711798 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 2 Oct 2024 18:58:03 +0000 Subject: [PATCH 254/726] Analyze json object modifications to detect mod conflicts --- lib/json/JsonUtils.cpp | 28 ++++++++++++++++++++++++++++ lib/json/JsonUtils.h | 4 ++++ lib/modding/ContentTypeHandler.cpp | 3 +++ 3 files changed, 35 insertions(+) diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index f1680e11f..2e0fdd829 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -275,4 +275,32 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename) return result; } +void JsonUtils::detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName) +{ + if (left == right) + return; + + switch (left.getType()) + { + case JsonNode::JsonType::DATA_NULL: + case JsonNode::JsonType::DATA_BOOL: + case JsonNode::JsonType::DATA_FLOAT: + case JsonNode::JsonType::DATA_INTEGER: + case JsonNode::JsonType::DATA_STRING: + case JsonNode::JsonType::DATA_VECTOR: // NOTE: comparing vectors as whole - since merge will overwrite it in its entirety + { + logMod->warn("Potential confict detected between '%s' and '%s' in object '%s'", left.getModScope(), right.getModScope(), entityName); + logMod->warn("Mod '%s' - value %s set to '%s'", left.getModScope(), keyName, left.toCompactString()); + logMod->warn("Mod '%s' - value %s set to '%s'", right.getModScope(), keyName, right.toCompactString()); + return; + } + case JsonNode::JsonType::DATA_STRUCT: + { + for(auto & node : left.Struct()) + if (!right[node.first].isNull()) + detectConflicts(node.second, right[node.first], entityName, keyName + "/" + node.first); + } + } +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index 954ee0b16..bf14ce2d6 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -72,6 +72,10 @@ namespace JsonUtils /// get schema by json URI: vcmi:# /// example: schema "vcmi:settings" is used to check user settings DLL_LINKAGE const JsonNode & getSchema(const std::string & URI); + + /// detects potential conflicts - json entries present in both nodes + /// any error messages will be printed to error log + DLL_LINKAGE void detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName); } VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 83ae46c1b..43ece3409 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -79,6 +79,9 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std:: logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName); JsonNode & remoteConf = modData[remoteName].patches[objectName]; + if (!remoteConf.isNull()) + JsonUtils::detectConflicts(remoteConf, entry.second, objectName, ""); + JsonUtils::merge(remoteConf, entry.second); } } From d849e53499f35f50654ce3a3cfd34a50dab2bd36 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 6 Oct 2024 15:54:30 +0000 Subject: [PATCH 255/726] Implement detection of mod compatibility patches --- lib/json/JsonUtils.cpp | 14 ++++----- lib/json/JsonUtils.h | 6 ++-- lib/modding/CModHandler.cpp | 10 +++++-- lib/modding/CModHandler.h | 1 + lib/modding/ContentTypeHandler.cpp | 46 ++++++++++++++++++++++++++---- lib/modding/ContentTypeHandler.h | 4 ++- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 2e0fdd829..215a9bc6d 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -275,11 +275,8 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename) return result; } -void JsonUtils::detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName) +void JsonUtils::detectConflicts(JsonNode & result, const JsonNode & left, const JsonNode & right, const std::string & keyName) { - if (left == right) - return; - switch (left.getType()) { case JsonNode::JsonType::DATA_NULL: @@ -289,16 +286,15 @@ void JsonUtils::detectConflicts(const JsonNode & left, const JsonNode & right, c case JsonNode::JsonType::DATA_STRING: case JsonNode::JsonType::DATA_VECTOR: // NOTE: comparing vectors as whole - since merge will overwrite it in its entirety { - logMod->warn("Potential confict detected between '%s' and '%s' in object '%s'", left.getModScope(), right.getModScope(), entityName); - logMod->warn("Mod '%s' - value %s set to '%s'", left.getModScope(), keyName, left.toCompactString()); - logMod->warn("Mod '%s' - value %s set to '%s'", right.getModScope(), keyName, right.toCompactString()); + result[keyName][left.getModScope()] = left; + result[keyName][right.getModScope()] = right; return; } case JsonNode::JsonType::DATA_STRUCT: { - for(auto & node : left.Struct()) + for(const auto & node : left.Struct()) if (!right[node.first].isNull()) - detectConflicts(node.second, right[node.first], entityName, keyName + "/" + node.first); + detectConflicts(result, node.second, right[node.first], keyName + "/" + node.first); } } } diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index bf14ce2d6..5acab9fa0 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -74,8 +74,10 @@ namespace JsonUtils DLL_LINKAGE const JsonNode & getSchema(const std::string & URI); /// detects potential conflicts - json entries present in both nodes - /// any error messages will be printed to error log - DLL_LINKAGE void detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName); + /// returns JsonNode that contains list of conflicting keys + /// For each conflict - list of conflicting mods and list of conflicting json values + /// result[pathToKey][modID] -> node that was conflicting + DLL_LINKAGE void detectConflicts(JsonNode & result, const JsonNode & left, const JsonNode & right, const std::string & keyName); } VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index a028c8da4..5e0e4c8d2 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -392,6 +392,12 @@ std::string CModHandler::getModLanguage(const TModID& modId) const return allMods.at(modId).baseLanguage; } +std::set CModHandler::getModDependencies(const TModID & modId) const +{ + bool isModFound; + return getModDependencies(modId, isModFound); +} + std::set CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const { auto it = allMods.find(modId); @@ -499,8 +505,8 @@ void CModHandler::load() content->loadCustom(); - for(const TModID & modName : activeMods) - loadTranslation(modName); +// for(const TModID & modName : activeMods) +// loadTranslation(modName); #if 0 for(const TModID & modName : activeMods) diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index f88e1fe26..89b1554aa 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -66,6 +66,7 @@ public: std::string getModLanguage(const TModID & modId) const; + std::set getModDependencies(const TModID & modId) const; std::set getModDependencies(const TModID & modId, bool & isModFound) const; /// returns list of all (active) mods diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 43ece3409..60289ca6e 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -39,9 +39,9 @@ VCMI_LIB_NAMESPACE_BEGIN -ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & objectName): +ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & entityName): handler(handler), - objectName(objectName), + entityName(entityName), originalData(handler->loadLegacyData()) { for(auto & node : originalData) @@ -80,7 +80,7 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std:: JsonNode & remoteConf = modData[remoteName].patches[objectName]; if (!remoteConf.isNull()) - JsonUtils::detectConflicts(remoteConf, entry.second, objectName, ""); + JsonUtils::detectConflicts(conflictList, remoteConf, entry.second, objectName); JsonUtils::merge(remoteConf, entry.second); } @@ -96,7 +96,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate) auto performValidate = [&,this](JsonNode & data, const std::string & name){ handler->beforeValidate(data); if (validate) - result &= JsonUtils::validate(data, "vcmi:" + objectName, name); + result &= JsonUtils::validate(data, "vcmi:" + entityName, name); }; // apply patches @@ -116,7 +116,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate) // - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases) // - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data // so emit warning and skip such case - logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, objectName, modName); + logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, entityName, modName); continue; } @@ -189,6 +189,42 @@ void ContentTypeHandler::afterLoadFinalization() } } + for (const auto& [conflictPath, conflictModData] : conflictList.Struct()) + { + std::set conflictingMods; + std::set resolvedConflicts; + + for (auto const & conflictModData : conflictModData.Struct()) + conflictingMods.insert(conflictModData.first); + + for (auto const & modID : conflictingMods) + resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); + + vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);}); + + if (conflictingMods.size() < 2) + continue; // all conflicts were resolved - either via compatibility patch (mod that depends on 2 conflicting mods) or simple mod that depends on another one + + bool allEqual = true; + + for (auto const & modID : conflictingMods) + { + if (conflictModData[modID] != conflictModData[*conflictingMods.begin()]) + { + allEqual = false; + break; + } + } + + if (allEqual) + continue; // conflict still present, but all mods use the same value for conflicting entry - permit it + + logMod->warn("Potential confict in '%s'", conflictPath); + + for (auto const & modID : conflictingMods) + logMod->warn("Mod '%s' - value set to %s", modID, conflictModData[modID].toCompactString()); + } + handler->afterLoadFinalization(); } diff --git a/lib/modding/ContentTypeHandler.h b/lib/modding/ContentTypeHandler.h index 7093c12d5..6c3f553c5 100644 --- a/lib/modding/ContentTypeHandler.h +++ b/lib/modding/ContentTypeHandler.h @@ -19,6 +19,8 @@ class CModInfo; /// internal type to handle loading of one data type (e.g. artifacts, creatures) class DLL_LINKAGE ContentTypeHandler { + JsonNode conflictList; + public: struct ModInfo { @@ -29,7 +31,7 @@ public: }; /// handler to which all data will be loaded IHandlerBase * handler; - std::string objectName; + std::string entityName; /// contains all loaded H3 data std::vector originalData; From e6d34156fcff89ff65ab31f34fd7a7015211874e Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 6 Oct 2024 13:58:54 -0300 Subject: [PATCH 256/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index cbf548f07..7e1a5c174 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -12,7 +12,9 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Avassaladora", "vcmi.adventureMap.monsterThreat.levels.10" : "Mortal", "vcmi.adventureMap.monsterThreat.levels.11" : "Impossível", - "vcmi.adventureMap.monsterLevel" : "\n\nNível %LEVEL, unidade de %TOWN", + "vcmi.adventureMap.monsterLevel" : "\n\nNível %LEVEL, unidade %TOWN de ataque %ATTACK_TYPE", + "vcmi.adventureMap.monsterMeleeType" : "corpo a corpo", + "vcmi.adventureMap.monsterRangedType" : "à distância", "vcmi.adventureMap.confirmRestartGame" : "Tem certeza de que deseja reiniciar o jogo?", "vcmi.adventureMap.noTownWithMarket" : "Não há mercados disponíveis!", @@ -143,6 +145,7 @@ "vcmi.client.errors.invalidMap" : "{Mapa ou campanha inválido}\n\nFalha ao iniciar o jogo! O mapa ou campanha selecionado pode ser inválido ou corrompido. Motivo:\n%s", "vcmi.client.errors.missingCampaigns" : "{Arquivos de dados ausentes}\n\nOs arquivos de dados das campanhas não foram encontrados! Você pode estar usando arquivos de dados incompletos ou corrompidos do Heroes 3. Por favor, reinstale os dados do jogo.", "vcmi.server.errors.disconnected" : "{Erro de Rede}\n\nA conexão com o servidor do jogo foi perdida!", + "vcmi.server.errors.playerLeft" : "{Jogador Saiu}\n\nO jogador %s desconectou-se do jogo!", //%s -> player color "vcmi.server.errors.existingProcess" : "Outro processo do servidor VCMI está em execução. Por favor, termine-o antes de iniciar um novo jogo.", "vcmi.server.errors.modsToEnable" : "{Os seguintes mods são necessários}", "vcmi.server.errors.modsToDisable" : "{Os seguintes mods devem ser desativados}", From 3e3f842fbe80396f352e65d19f8f7cfd9c1d6e37 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 6 Oct 2024 17:20:58 +0000 Subject: [PATCH 257/726] Respect dependencies when checking for filesystem conflicts --- lib/modding/CModHandler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 5e0e4c8d2..15c9d22e4 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -346,6 +346,9 @@ void CModHandler::loadModFilesystems() if (leftModName == rightModName) continue; + if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) + continue; + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); From 66fdad145c00940542868ac5b1ae38430cdeddf9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 6 Oct 2024 19:21:18 +0000 Subject: [PATCH 258/726] Added an option to configure validation level in launcher --- config/schemas/settings.json | 19 +- launcher/settingsView/csettingsview_moc.cpp | 25 + launcher/settingsView/csettingsview_moc.h | 13 +- launcher/settingsView/csettingsview_moc.ui | 2010 +++++++++-------- .../CRewardableConstructor.cpp | 4 +- lib/modding/CModHandler.cpp | 40 +- lib/modding/ContentTypeHandler.cpp | 122 +- lib/modding/ContentTypeHandler.h | 1 + 8 files changed, 1200 insertions(+), 1034 deletions(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index da312e19d..276903873 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -3,7 +3,7 @@ { "type" : "object", "$schema" : "http://json-schema.org/draft-04/schema", - "required" : [ "general", "video", "adventure", "battle", "input", "server", "logging", "launcher", "lobby", "gameTweaks" ], + "required" : [ "general", "video", "adventure", "battle", "input", "server", "logging", "launcher", "lobby", "gameTweaks", "mods" ], "definitions" : { "logLevelEnum" : { "type" : "string", @@ -149,6 +149,23 @@ } } }, + + "mods" : { + "type" : "object", + "additionalProperties" : false, + "default" : {}, + "required" : [ + "validation" + ], + "properties" : { + "validation" : { + "type" : "string", + "enum" : [ "off", "basic", "full" ], + "default" : "basic" + } + } + }, + "video" : { "type" : "object", "additionalProperties" : false, diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 8cb8c32a0..541a909de 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -182,6 +182,13 @@ void CSettingsView::loadSettings() else ui->buttonFontScalable->setChecked(true); + if (settings["mods"]["validation"].String() == "off") + ui->buttonValidationOff->setChecked(true); + else if (settings["mods"]["validation"].String() == "basic") + ui->buttonValidationBasic->setChecked(true); + else + ui->buttonValidationFull->setChecked(true); + loadToggleButtonSettings(); } @@ -791,3 +798,21 @@ void CSettingsView::on_buttonFontOriginal_clicked(bool checked) Settings node = settings.write["video"]["fontsType"]; node->String() = "original"; } + +void CSettingsView::on_buttonValidationOff_clicked(bool checked) +{ + Settings node = settings.write["mods"]["validation"]; + node->String() = "off"; +} + +void CSettingsView::on_buttonValidationBasic_clicked(bool checked) +{ + Settings node = settings.write["mods"]["validation"]; + node->String() = "basic"; +} + +void CSettingsView::on_buttonValidationFull_clicked(bool checked) +{ + Settings node = settings.write["mods"]["validation"]; + node->String() = "full"; +} diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 1e76f6f2e..512762d0c 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -83,19 +83,20 @@ private slots: void on_sliderToleranceDistanceController_valueChanged(int value); void on_lineEditGameLobbyHost_textChanged(const QString &arg1); void on_spinBoxNetworkPortLobby_valueChanged(int arg1); - void on_sliderControllerSticksAcceleration_valueChanged(int value); - void on_sliderControllerSticksSensitivity_valueChanged(int value); - - //void on_buttonTtfFont_toggled(bool value); - void on_sliderScalingFont_valueChanged(int value); - void on_buttonFontAuto_clicked(bool checked); void on_buttonFontScalable_clicked(bool checked); void on_buttonFontOriginal_clicked(bool checked); + + void on_buttonValidationOff_clicked(bool checked); + + void on_buttonValidationBasic_clicked(bool checked); + + void on_buttonValidationFull_clicked(bool checked); + private: Ui::CSettingsView * ui; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 578fd55b4..c0082edd8 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -47,88 +47,19 @@ 0 - 0 + -800 729 - 1449 + 1506 - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - - 0 - 0 - - + + - - true - - - - - Use Relative Pointer Mode - - - - - - - 25 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 5 - - - - - - - Additional repository - - - - - - - Music Volume - - - - - - @@ -157,374 +88,6 @@ - - - - Long Touch Duration - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - - true - - - - Video - - - 5 - - - - - - - - true - - - - General - - - 5 - - - - - - - Heroes III Translation - - - - - - - Show Tutorial again - - - - - - - Online Lobby port - - - - - - - Relative Pointer Speed - - - - - - - Touch Tap Tolerance - - - - - - - Select display mode for game - -Windowed - game will run inside a window that covers part of your screen - -Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen. - -Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution. - - - 0 - - - - Windowed - - - - - Borderless fullscreen - - - - - Exclusive fullscreen - - - - - - - - 500 - - - 2000 - - - 250 - - - 250 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 250 - - - - - - - Neutral AI in battles - - - - - - - - true - - - - Input - Mouse - - - 5 - - - - - - - - 0 - 0 - - - - Automatic - - - true - - - true - - - buttonGroup - - - - - - - Adventure Map Enemies - - - - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - 0 - - - 50 - - - 1 - - - 10 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Autosave - - - - - - - Renderer - - - - - - - Autosave limit (0 = off) - - - - - - - Default repository - - - - - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Sticks Sensitivity - - - - - - - - true - - - - Audio - - - 5 - - - - - - - - - - Downscaling Filter - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - 0 - - - 50 - - - 1 - - - 10 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Ignore SSL errors - - - @@ -532,120 +95,6 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - Refresh now - - - - - - - true - - - - 0 - 0 - - - - - - - true - - - false - - - - - - - VSync - - - - - - - Resolution - - - - - - - VCMI Language - - - - - - - Interface Scaling - - - - - - - - - - - - - - - - - Online Lobby address - - - - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - @@ -674,38 +123,37 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + - Autosave prefix + Additional repository - - + + - Reserved screen area - - - - - - - Sound Volume - - - - - - - Font Scaling (experimental) - - - - - - - Framerate Limit + Ignore SSL errors @@ -728,8 +176,130 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - + + + + VCMI Language + + + + + + + + 0 + 0 + + + + Automatic + + + true + + + true + + + buttonGroupFonts + + + + + + + + true + + + + Video + + + 5 + + + + + + + Show intro + + + + + + + Heroes III Translation + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + + 0 + 0 + + + + Scalable + + + true + + + true + + + buttonGroupFonts + + + + + + + Touch Tap Tolerance + + + + + + + + + + + + 100 @@ -744,56 +314,13 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - Automatic - - - - - None - - - - - xBRZ x2 - - - - - xBRZ x3 - - - - - xBRZ x4 - - - + + - - + + - Autocombat AI in battles - - - - - - - Use scalable fonts - - - - - - - - - - true + Renderer @@ -812,87 +339,24 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - + + - Fullscreen + Neutral AI in battles - - - - 500 - - - 2500 - - - 25 - - - 250 - - - 500 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 250 - - - - - + + - Enemy AI in battles + VSync - - + + - Sticks Acceleration - - - - - - - 100 - - - 300 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 25 - - - - - - - - 0 - 0 - - - - - - - true + Framerate Limit @@ -903,8 +367,61 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - + + + + + true + + + + Input - Touchscreen + + + 5 + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + Relative Pointer Speed + + + + + + + + true @@ -925,29 +442,6 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - @@ -964,33 +458,20 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - + + - Adventure Map Allies + Long Touch Duration - - - - Haptic Feedback - - + + - - - - % - - - 50 - - - 400 - - - 10 + + + + Check on startup @@ -1013,72 +494,6 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - Mouse Click Tolerance - - - - - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - true - - - - 0 - 0 - - - - - - - true - - - false - - - - - - - Reset - - - - - - - - - - Network port - - - - - - @@ -1094,15 +509,8 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - Display index - - - - - + + 0 @@ -1117,128 +525,6 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - true - - - - Network - - - 5 - - - - - - - - 0 - 0 - - - - Original - - - true - - - true - - - buttonGroup - - - - - - - - - - - - - - Show intro - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - - true - - - - Input - Touchscreen - - - 5 - - - - - - - 20 - - - 1000 - - - 10 - - - - - - - Controller Click Tolerance - - - - - - - Check on startup - - - @@ -1270,15 +556,53 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - + + - Software Cursor + Downscaling Filter - - + + + + Select display mode for game + +Windowed - game will run inside a window that covers part of your screen + +Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen. + +Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution. + + + 0 + + + + Windowed + + + + + Borderless fullscreen + + + + + Exclusive fullscreen + + + + + + + + Use scalable fonts + + + + + 0 @@ -1286,7 +610,7 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - Scalable + Original true @@ -1295,13 +619,786 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use true - buttonGroup + buttonGroupFonts + + + + + + + + Automatic + + + + + None + + + + + xBRZ x2 + + + + + xBRZ x3 + + + + + xBRZ x4 + + + + + + + + Online Lobby port + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + + true + + + + Network + + + 5 + + + + + + + Refresh now + + + + + + + 500 + + + 2500 + + + 25 + + + 250 + + + 500 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 250 + + + + + + + Display index + + + + + + + 500 + + + 2000 + + + 250 + + + 250 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 250 + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + Adventure Map Enemies + + + + + + + + + + true + + + + + + + Reset + + + + + + + Show Tutorial again + + + + + + + Fullscreen + + + + + + + Use Relative Pointer Mode + + + + + + + + + + + + + + Sticks Acceleration + + + + + + + Autosave limit (0 = off) + + + + + + + Mouse Click Tolerance + + + + + + + + true + + + + General + + + 5 + + + + + + + Online Lobby address + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Interface Scaling + + + + + + + Default repository + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Haptic Feedback + + + + + + + + + + Autosave prefix + + + + + + + + true + + + + Input - Mouse + + + 5 + + + + + + + 25 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 5 + + + + + + + 20 + + + 1000 + + + 10 + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + 100 + + + 300 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 25 + + + + + + + Enemy AI in battles + + + + + + + Reserved screen area + + + + + + + % + + + 50 + + + 400 + + + 10 + + + + + + + Music Volume + + + + + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + Autosave + + + + + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + Network port + + + + + + + true + + + + 0 + 0 + + + + + + + true + + + false + + + + + + + Resolution + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Sound Volume + + + + + + + Controller Click Tolerance + + + + + + + Adventure Map Allies + + + + + + + + true + + + + Miscellaneous + + + 5 + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + + true + + + + Audio + + + 5 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Software Cursor + + + + + + + Autocombat AI in battles + + + + + + + + + + + + + + true + + + + 0 + 0 + + + + + + + true + + + false + + + + + + + Font Scaling (experimental) + + + + + + + Sticks Sensitivity + + + + + + + Mods Validation + + + + + + + true + + + + 0 + 0 + + + + Off + + + true + + + false + + + buttonGroupValidation + + + + + + + true + + + + 0 + 0 + + + + Basic + + + true + + + false + + + buttonGroupValidation + + + + + + + true + + + + 0 + 0 + + + + Full + + + true + + + false + + + buttonGroupValidation + + + @@ -1311,6 +1408,7 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + + diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index 80d842a47..bbdff2bf2 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -14,6 +14,7 @@ #include "../mapObjects/CRewardableObject.h" #include "../texts/CGeneralTextHandler.h" #include "../IGameCallback.h" +#include "../CConfigHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -25,7 +26,8 @@ void CRewardableConstructor::initTypeData(const JsonNode & config) if (!config["name"].isNull()) VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String()); - JsonUtils::validate(config, "vcmi:rewardable", getJsonKey()); + if (settings["mods"]["validation"].String() != "off") + JsonUtils::validate(config, "vcmi:rewardable", getJsonKey()); } diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 15c9d22e4..ccd71e2a6 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -17,6 +17,7 @@ #include "ModIncompatibility.h" #include "../CCreatureHandler.h" +#include "../CConfigHandler.h" #include "../CStopWatch.h" #include "../GameSettings.h" #include "../ScriptHandler.h" @@ -339,25 +340,28 @@ void CModHandler::loadModFilesystems() for(std::string & modName : activeMods) CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]); - for(std::string & leftModName : activeMods) + if (settings["mods"]["validation"].String() == "full") { - for(std::string & rightModName : activeMods) + for(std::string & leftModName : activeMods) { - if (leftModName == rightModName) - continue; - - if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) - continue; - - const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; - - std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); - std::unordered_set rightResources = modFilesystems[rightModName]->getFilteredFiles(filter); - - for (auto const & leftFile : leftResources) + for(std::string & rightModName : activeMods) { - if (rightResources.count(leftFile)) - logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftModName, rightModName, leftFile.getOriginalName()); + if (leftModName == rightModName) + continue; + + if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) + continue; + + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; + + std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); + std::unordered_set rightResources = modFilesystems[rightModName]->getFilteredFiles(filter); + + for (auto const & leftFile : leftResources) + { + if (rightResources.count(leftFile)) + logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftModName, rightModName, leftFile.getOriginalName()); + } } } } @@ -508,8 +512,8 @@ void CModHandler::load() content->loadCustom(); -// for(const TModID & modName : activeMods) -// loadTranslation(modName); + for(const TModID & modName : activeMods) + loadTranslation(modName); #if 0 for(const TModID & modName : activeMods) diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 60289ca6e..f2887a711 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -17,6 +17,7 @@ #include "../BattleFieldHandler.h" #include "../CArtHandler.h" #include "../CCreatureHandler.h" +#include "../CConfigHandler.h" #include "../entities/faction/CTownHandler.h" #include "../texts/CGeneralTextHandler.h" #include "../CHeroHandler.h" @@ -79,7 +80,7 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std:: logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName); JsonNode & remoteConf = modData[remoteName].patches[objectName]; - if (!remoteConf.isNull()) + if (!remoteConf.isNull() && settings["mods"]["validation"].String() != "off") JsonUtils::detectConflicts(conflictList, remoteConf, entry.second, objectName); JsonUtils::merge(remoteConf, entry.second); @@ -162,67 +163,70 @@ void ContentTypeHandler::loadCustom() void ContentTypeHandler::afterLoadFinalization() { - for (auto const & data : modData) + if (settings["mods"]["validation"].String() != "off") { - if (data.second.modData.isNull()) + for (auto const & data : modData) { - for (auto node : data.second.patches.Struct()) - logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first); - } - - for(auto & otherMod : modData) - { - if (otherMod.first == data.first) - continue; - - if (otherMod.second.modData.isNull()) - continue; - - for(auto & otherObject : otherMod.second.modData.Struct()) + if (data.second.modData.isNull()) { - if (data.second.modData.Struct().count(otherObject.first)) + for (auto node : data.second.patches.Struct()) + logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first); + } + + for(auto & otherMod : modData) + { + if (otherMod.first == data.first) + continue; + + if (otherMod.second.modData.isNull()) + continue; + + for(auto & otherObject : otherMod.second.modData.Struct()) { - logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first); - logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first); + if (data.second.modData.Struct().count(otherObject.first)) + { + logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first); + logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first); + } } } } - } - for (const auto& [conflictPath, conflictModData] : conflictList.Struct()) - { - std::set conflictingMods; - std::set resolvedConflicts; - - for (auto const & conflictModData : conflictModData.Struct()) - conflictingMods.insert(conflictModData.first); - - for (auto const & modID : conflictingMods) - resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); - - vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);}); - - if (conflictingMods.size() < 2) - continue; // all conflicts were resolved - either via compatibility patch (mod that depends on 2 conflicting mods) or simple mod that depends on another one - - bool allEqual = true; - - for (auto const & modID : conflictingMods) + for (const auto& [conflictPath, conflictModData] : conflictList.Struct()) { - if (conflictModData[modID] != conflictModData[*conflictingMods.begin()]) + std::set conflictingMods; + std::set resolvedConflicts; + + for (auto const & conflictModData : conflictModData.Struct()) + conflictingMods.insert(conflictModData.first); + + for (auto const & modID : conflictingMods) + resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); + + vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);}); + + if (conflictingMods.size() < 2) + continue; // all conflicts were resolved - either via compatibility patch (mod that depends on 2 conflicting mods) or simple mod that depends on another one + + bool allEqual = true; + + for (auto const & modID : conflictingMods) { - allEqual = false; - break; + if (conflictModData[modID] != conflictModData[*conflictingMods.begin()]) + { + allEqual = false; + break; + } } + + if (allEqual) + continue; // conflict still present, but all mods use the same value for conflicting entry - permit it + + logMod->warn("Potential confict in '%s'", conflictPath); + + for (auto const & modID : conflictingMods) + logMod->warn("Mod '%s' - value set to %s", modID, conflictModData[modID].toCompactString()); } - - if (allEqual) - continue; // conflict still present, but all mods use the same value for conflicting entry - permit it - - logMod->warn("Potential confict in '%s'", conflictPath); - - for (auto const & modID : conflictingMods) - logMod->warn("Mod '%s' - value set to %s", modID, conflictModData[modID].toCompactString()); } handler->afterLoadFinalization(); @@ -288,7 +292,7 @@ void CContentHandler::afterLoadFinalization() void CContentHandler::preloadData(CModInfo & mod) { - bool validate = (mod.validation != CModInfo::PASSED); + bool validate = validateMod(mod); // print message in format [<8-symbols checksum>] auto & info = mod.getVerificationInfo(); @@ -305,7 +309,7 @@ void CContentHandler::preloadData(CModInfo & mod) void CContentHandler::load(CModInfo & mod) { - bool validate = (mod.validation != CModInfo::PASSED); + bool validate = validateMod(mod); if (!loadMod(mod.identifier, validate)) mod.validation = CModInfo::FAILED; @@ -326,4 +330,18 @@ const ContentTypeHandler & CContentHandler::operator[](const std::string & name) return handlers.at(name); } +bool CContentHandler::validateMod(const CModInfo & mod) const +{ + if (settings["mods"]["validation"].String() == "full") + return true; + + if (mod.validation == CModInfo::PASSED) + return false; + + if (settings["mods"]["validation"].String() == "off") + return false; + + return true; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ContentTypeHandler.h b/lib/modding/ContentTypeHandler.h index 6c3f553c5..5c21bf182 100644 --- a/lib/modding/ContentTypeHandler.h +++ b/lib/modding/ContentTypeHandler.h @@ -58,6 +58,7 @@ class DLL_LINKAGE CContentHandler std::map handlers; + bool validateMod(const CModInfo & mod) const; public: void init(); From c77da2e4765f9ce7b3591d8af2fa0bb07f3916ae Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 21 Jul 2024 14:31:09 +0000 Subject: [PATCH 259/726] Remove no longer used compatibility containers --- lib/texts/CGeneralTextHandler.cpp | 5 ----- lib/texts/CGeneralTextHandler.h | 5 ----- 2 files changed, 10 deletions(-) diff --git a/lib/texts/CGeneralTextHandler.cpp b/lib/texts/CGeneralTextHandler.cpp index 9cc3724c2..24041d8cc 100644 --- a/lib/texts/CGeneralTextHandler.cpp +++ b/lib/texts/CGeneralTextHandler.cpp @@ -120,21 +120,16 @@ void CGeneralTextHandler::readToVector(const std::string & sourceID, const std:: } CGeneralTextHandler::CGeneralTextHandler(): - victoryConditions(*this, "core.vcdesc" ), - lossConditions (*this, "core.lcdesc" ), - colors (*this, "core.plcolors" ), tcommands (*this, "core.tcommand" ), hcommands (*this, "core.hallinfo" ), fcommands (*this, "core.castinfo" ), advobtxt (*this, "core.advevent" ), restypes (*this, "core.restypes" ), - randsign (*this, "core.randsign" ), overview (*this, "core.overview" ), arraytxt (*this, "core.arraytxt" ), primarySkillNames(*this, "core.priskill" ), jktexts (*this, "core.jktext" ), tavernInfo (*this, "core.tvrninfo" ), - tavernRumors (*this, "core.randtvrn" ), turnDurations (*this, "core.turndur" ), heroscrn (*this, "core.heroscrn" ), tentColors (*this, "core.tentcolr" ), diff --git a/lib/texts/CGeneralTextHandler.h b/lib/texts/CGeneralTextHandler.h index ce5263ced..b312a1d0e 100644 --- a/lib/texts/CGeneralTextHandler.h +++ b/lib/texts/CGeneralTextHandler.h @@ -53,7 +53,6 @@ public: LegacyTextContainer jktexts; LegacyTextContainer heroscrn; LegacyTextContainer overview;//text for Kingdom Overview window - LegacyTextContainer colors; //names of player colors ("red",...) LegacyTextContainer capColors; //names of player colors with first letter capitalized ("Red",...) LegacyTextContainer turnDurations; //turn durations for pregame (1 Minute ... Unlimited) @@ -62,18 +61,14 @@ public: LegacyTextContainer hcommands; // town hall screen LegacyTextContainer fcommands; // fort screen LegacyTextContainer tavernInfo; - LegacyTextContainer tavernRumors; LegacyTextContainer qeModCommands; LegacyHelpContainer zelp; - LegacyTextContainer lossConditions; - LegacyTextContainer victoryConditions; //objects LegacyTextContainer advobtxt; LegacyTextContainer restypes; //names of resources - LegacyTextContainer randsign; LegacyTextContainer seerEmpty; LegacyTextContainer seerNames; LegacyTextContainer tentColors; From b85ccccb37f4e1ed76b6444cbdac278e91580151 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 30 Sep 2024 10:21:10 +0000 Subject: [PATCH 260/726] Minor refactoring of translations: - removed unsuccessful and broken validation of translations - pass JsonNode when registering strings to provide information on mod source --- lib/CArtHandler.cpp | 6 +- lib/CBonusTypeHandler.cpp | 4 +- lib/CCreatureHandler.cpp | 6 +- lib/CHeroHandler.cpp | 10 +-- lib/CSkillHandler.cpp | 4 +- lib/RiverHandler.cpp | 2 +- lib/RoadHandler.cpp | 2 +- lib/TerrainHandler.cpp | 2 +- lib/entities/faction/CTownHandler.cpp | 10 +-- .../CBankInstanceConstructor.cpp | 2 +- .../CObjectClassesHandler.cpp | 2 +- .../CRewardableConstructor.cpp | 2 +- .../DwellingInstanceConstructor.cpp | 2 +- lib/modding/CModHandler.cpp | 37 +--------- lib/modding/CModHandler.h | 2 - lib/rewardable/Info.cpp | 2 +- lib/spells/CSpellHandler.cpp | 4 +- lib/texts/TextLocalizationContainer.cpp | 72 ++++--------------- lib/texts/TextLocalizationContainer.h | 18 ++--- 19 files changed, 48 insertions(+), 141 deletions(-) diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 5b3f82a0d..07b83dec3 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -431,9 +431,9 @@ std::shared_ptr CArtHandler::loadFromJson(const std::string & scope, const JsonNode & text = node["text"]; - VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"].String()); - VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"].String()); - VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"].String()); + VLC->generaltexth->registerString(scope, art->getNameTextID(), text["name"]); + VLC->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"]); + VLC->generaltexth->registerString(scope, art->getEventTextID(), text["event"]); const JsonNode & graphics = node["graphics"]; art->image = graphics["image"].String(); diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 799bf2632..666f4e86e 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -240,8 +240,8 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con if (!dest.hidden) { - VLC->generaltexth->registerString( "vcmi", dest.getNameTextID(), source["name"].String()); - VLC->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"].String()); + VLC->generaltexth->registerString( "vcmi", dest.getNameTextID(), source["name"]); + VLC->generaltexth->registerString( "vcmi", dest.getDescriptionTextID(), source["description"]); } const JsonNode & graphics = source["graphics"]; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 546aed9b7..f34608fd2 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -617,9 +617,9 @@ std::shared_ptr CCreatureHandler::loadFromJson(const std::string & sc cre->cost = ResourceSet(node["cost"]); - VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"].String()); - VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"].String()); - VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"].String()); + VLC->generaltexth->registerString(scope, cre->getNameSingularTextID(), node["name"]["singular"]); + VLC->generaltexth->registerString(scope, cre->getNamePluralTextID(), node["name"]["plural"]); + VLC->generaltexth->registerString(scope, cre->getDescriptionTextID(), node["description"]); cre->addBonus(node["hitPoints"].Integer(), BonusType::STACK_HEALTH); cre->addBonus(node["speed"].Integer(), BonusType::STACKS_SPEED); diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 991cbbff2..820f8404d 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -459,11 +459,11 @@ std::shared_ptr CHeroHandler::loadFromJson(const std::string & scope, con hero->onlyOnWaterMap = node["onlyOnWaterMap"].Bool(); hero->onlyOnMapWithoutWater = node["onlyOnMapWithoutWater"].Bool(); - VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"].String()); - VLC->generaltexth->registerString(scope, hero->getBiographyTextID(), node["texts"]["biography"].String()); - VLC->generaltexth->registerString(scope, hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"].String()); - VLC->generaltexth->registerString(scope, hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"].String()); - VLC->generaltexth->registerString(scope, hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"].String()); + VLC->generaltexth->registerString(scope, hero->getNameTextID(), node["texts"]["name"]); + VLC->generaltexth->registerString(scope, hero->getBiographyTextID(), node["texts"]["biography"]); + VLC->generaltexth->registerString(scope, hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"]); + VLC->generaltexth->registerString(scope, hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"]); + VLC->generaltexth->registerString(scope, hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"]); hero->iconSpecSmall = node["images"]["specialtySmall"].String(); hero->iconSpecLarge = node["images"]["specialtyLarge"].String(); diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 12901cd6d..87cad05bc 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -212,7 +212,7 @@ std::shared_ptr CSkillHandler::loadFromJson(const std::string & scope, c skill->onlyOnWaterMap = json["onlyOnWaterMap"].Bool(); - VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"].String()); + VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"]); switch(json["gainChance"].getType()) { case JsonNode::JsonType::DATA_INTEGER: @@ -237,7 +237,7 @@ std::shared_ptr CSkillHandler::loadFromJson(const std::string & scope, c skill->addNewBonus(bonus, level); } CSkill::LevelInfo & skillAtLevel = skill->at(level); - VLC->generaltexth->registerString(scope, skill->getDescriptionTextID(level), levelNode["description"].String()); + VLC->generaltexth->registerString(scope, skill->getDescriptionTextID(level), levelNode["description"]); skillAtLevel.iconSmall = levelNode["images"]["small"].String(); skillAtLevel.iconMedium = levelNode["images"]["medium"].String(); skillAtLevel.iconLarge = levelNode["images"]["large"].String(); diff --git a/lib/RiverHandler.cpp b/lib/RiverHandler.cpp index 0a117e6f8..c903b18e3 100644 --- a/lib/RiverHandler.cpp +++ b/lib/RiverHandler.cpp @@ -50,7 +50,7 @@ std::shared_ptr RiverTypeHandler::loadFromJson( info->paletteAnimation.push_back(element); } - VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"].String()); + VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"]); return info; } diff --git a/lib/RoadHandler.cpp b/lib/RoadHandler.cpp index aed58730f..0d82d9da6 100644 --- a/lib/RoadHandler.cpp +++ b/lib/RoadHandler.cpp @@ -41,7 +41,7 @@ std::shared_ptr RoadTypeHandler::loadFromJson( info->shortIdentifier = json["shortIdentifier"].String(); info->movementCost = json["moveCost"].Integer(); - VLC->generaltexth->registerString(scope,info->getNameTextID(), json["text"].String()); + VLC->generaltexth->registerString(scope,info->getNameTextID(), json["text"]); return info; } diff --git a/lib/TerrainHandler.cpp b/lib/TerrainHandler.cpp index 6b2e81aef..f3431394a 100644 --- a/lib/TerrainHandler.cpp +++ b/lib/TerrainHandler.cpp @@ -45,7 +45,7 @@ std::shared_ptr TerrainTypeHandler::loadFromJson( const std::string info->transitionRequired = json["transitionRequired"].Bool(); info->terrainViewPatterns = json["terrainViewPatterns"].String(); - VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"].String()); + VLC->generaltexth->registerString(scope, info->getNameTextID(), json["text"]); const JsonVector & unblockedVec = json["minimapUnblocked"].Vector(); info->minimapUnblocked = diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 036c2f6ef..3ed640afc 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/lib/entities/faction/CTownHandler.cpp @@ -292,8 +292,8 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons ret->modScope = source.getModScope(); ret->town = town; - VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String()); - VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String()); + VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"]); + VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"]); ret->subId = vstd::find_or(MappedKeys::SPECIAL_BUILDINGS, source["type"].String(), BuildingSubID::NONE); ret->resources = TResources(source["cost"]); @@ -603,7 +603,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source) town->namesCount = 0; for(const auto & name : source["names"].Vector()) { - VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name.String()); + VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name); town->namesCount += 1; } @@ -718,8 +718,8 @@ std::shared_ptr CTownHandler::loadFromJson(const std::string & scope, faction->modScope = scope; faction->identifier = identifier; - VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"].String()); - VLC->generaltexth->registerString(scope, faction->getDescriptionTextID(), source["description"].String()); + VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"]); + VLC->generaltexth->registerString(scope, faction->getDescriptionTextID(), source["description"]); faction->creatureBg120 = ImagePath::fromJson(source["creatureBackground"]["120px"]); faction->creatureBg130 = ImagePath::fromJson(source["creatureBackground"]["130px"]); diff --git a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp index c35a2cf28..71abc90b0 100644 --- a/lib/mapObjectConstructors/CBankInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/CBankInstanceConstructor.cpp @@ -28,7 +28,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input) if (input.Struct().count("name") == 0) logMod->warn("Bank %s missing name!", getJsonKey()); - VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"].String()); + VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"]); levels = input["levels"].Vector(); bankResetDuration = static_cast(input["resetDuration"].Float()); diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index e45332abb..de28a4a0f 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -278,7 +278,7 @@ std::unique_ptr CObjectClassesHandler::loadFromJson(const std::stri newObject->base = json["base"]; newObject->id = index; - VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"].String()); + VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"]); newObject->objectTypeHandlers.resize(json["lastReservedIndex"].Float() + 1); diff --git a/lib/mapObjectConstructors/CRewardableConstructor.cpp b/lib/mapObjectConstructors/CRewardableConstructor.cpp index 80d842a47..20375961a 100644 --- a/lib/mapObjectConstructors/CRewardableConstructor.cpp +++ b/lib/mapObjectConstructors/CRewardableConstructor.cpp @@ -23,7 +23,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config) blockVisit = config["blockedVisitable"].Bool(); if (!config["name"].isNull()) - VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String()); + VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"]); JsonUtils::validate(config, "vcmi:rewardable", getJsonKey()); diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index 609a57efb..24fb98a72 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -29,7 +29,7 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input) if (input.Struct().count("name") == 0) logMod->warn("Dwelling %s missing name!", getJsonKey()); - VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"].String()); + VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"]); const JsonVector & levels = input["creatures"].Vector(); const auto totalLevels = levels.size(); diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 8d20639f2..f5eccc700 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -401,33 +401,6 @@ CModVersion CModHandler::getModVersion(TModID modName) const return {}; } -bool CModHandler::validateTranslations(TModID modName) const -{ - bool result = true; - const auto & mod = allMods.at(modName); - - { - auto fileList = mod.config["translations"].convertTo >(); - JsonNode json = JsonUtils::assembleFromFiles(fileList); - result |= VLC->generaltexth->validateTranslation(mod.baseLanguage, modName, json); - } - - for(const auto & language : Languages::getLanguageList()) - { - if (mod.config[language.identifier].isNull()) - continue; - - if (mod.config[language.identifier]["skipValidation"].Bool()) - continue; - - auto fileList = mod.config[language.identifier]["translations"].convertTo >(); - JsonNode json = JsonUtils::assembleFromFiles(fileList); - result |= VLC->generaltexth->validateTranslation(language.identifier, modName, json); - } - - return result; -} - void CModHandler::loadTranslation(const TModID & modName) { const auto & mod = allMods[modName]; @@ -441,8 +414,8 @@ void CModHandler::loadTranslation(const TModID & modName) JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList); JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList); - VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, modName, baseTranslation); - VLC->generaltexth->loadTranslationOverrides(preferredLanguage, modName, extraTranslation); + VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation); + VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation); } void CModHandler::load() @@ -480,12 +453,6 @@ void CModHandler::load() for(const TModID & modName : activeMods) loadTranslation(modName); -#if 0 - for(const TModID & modName : activeMods) - if (!validateTranslations(modName)) - allMods[modName].validation = CModInfo::FAILED; -#endif - logMod->info("\tLoading mod data: %d ms", timer.getDiff()); VLC->creh->loadCrExpMod(); VLC->identifiersHandler->finalize(); diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index f88e1fe26..358483c22 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -49,8 +49,6 @@ class DLL_LINKAGE CModHandler final : boost::noncopyable void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods); void loadTranslation(const TModID & modName); - bool validateTranslations(TModID modName) const; - CModVersion getModVersion(TModID modName) const; public: diff --git a/lib/rewardable/Info.cpp b/lib/rewardable/Info.cpp index 04512886f..8c8d321de 100644 --- a/lib/rewardable/Info.cpp +++ b/lib/rewardable/Info.cpp @@ -76,7 +76,7 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o auto loadString = [&](const JsonNode & entry, const TextIdentifier & textID){ if (entry.isString() && !entry.String().empty() && entry.String()[0] != '@') - VLC->generaltexth->registerString(entry.getModScope(), textID, entry.String()); + VLC->generaltexth->registerString(entry.getModScope(), textID, entry); }; parameters = objectConfig; diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 454c8080d..a96fed944 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -783,7 +783,7 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c spell->combat = type == "combat"; } - VLC->generaltexth->registerString(scope, spell->getNameTextID(), json["name"].String()); + VLC->generaltexth->registerString(scope, spell->getNameTextID(), json["name"]); logMod->trace("%s: loading spell %s", __FUNCTION__, spell->getNameTranslated()); @@ -1005,7 +1005,7 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c const si32 levelPower = levelObject.power = static_cast(levelNode["power"].Integer()); if (!spell->isCreatureAbility()) - VLC->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"].String()); + VLC->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"]); levelObject.cost = static_cast(levelNode["cost"].Integer()); levelObject.AIValue = static_cast(levelNode["aiValue"].Integer()); diff --git a/lib/texts/TextLocalizationContainer.cpp b/lib/texts/TextLocalizationContainer.cpp index 4a210ede3..17f5d905a 100644 --- a/lib/texts/TextLocalizationContainer.cpp +++ b/lib/texts/TextLocalizationContainer.cpp @@ -22,17 +22,15 @@ VCMI_LIB_NAMESPACE_BEGIN std::recursive_mutex TextLocalizationContainer::globalTextMutex; -void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) +void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) { std::lock_guard globalLock(globalTextMutex); assert(!modContext.empty()); - assert(!language.empty()); // NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment auto & entry = stringsLocalizations[UID.get()]; - entry.overrideLanguage = language; entry.overrideValue = localized; if (entry.modContext.empty()) entry.modContext = modContext; @@ -76,6 +74,15 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier return entry.baseValue; } +void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & inputUID, const JsonNode & localized) +{ + assert(!localized.getModScope().empty()); + assert(!getModLanguage(localized.getModScope()).empty()); + + std::lock_guard globalLock(globalTextMutex); + registerString(modContext, inputUID, localized.String(), getModLanguage(modContext)); +} + void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) { std::lock_guard globalLock(globalTextMutex); @@ -88,13 +95,11 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c if(stringsLocalizations.count(UID.get()) > 0) { auto & value = stringsLocalizations[UID.get()]; - value.baseLanguage = language; value.baseValue = localized; } else { StringState value; - value.baseLanguage = language; value.baseValue = localized; value.modContext = modContext; @@ -108,63 +113,10 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c registerString(modContext, UID, localized, getModLanguage(modContext)); } -bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const -{ - std::lock_guard globalLock(globalTextMutex); - - bool allPresent = true; - - for(const auto & string : stringsLocalizations) - { - if (string.second.modContext != modContext) - continue; // Not our mod - - if (string.second.overrideLanguage == language) - continue; // Already translated - - if (string.second.baseLanguage == language && !string.second.baseValue.empty()) - continue; // Base string already uses our language - - if (string.second.baseLanguage.empty()) - continue; // String added in localization, not present in base language (e.g. maps/campaigns) - - if (config.Struct().count(string.first) > 0) - continue; - - if (allPresent) - logMod->warn("Translation into language '%s' in mod '%s' is incomplete! Missing lines:", language, modContext); - - std::string currentText; - if (string.second.overrideValue.empty()) - currentText = string.second.baseValue; - else - currentText = string.second.overrideValue; - - logMod->warn(R"( "%s" : "%s",)", string.first, TextOperations::escapeString(currentText)); - allPresent = false; - } - - bool allFound = true; - - // for(const auto & string : config.Struct()) - // { - // if (stringsLocalizations.count(string.first) > 0) - // continue; - // - // if (allFound) - // logMod->warn("Translation into language '%s' in mod '%s' has unused lines:", language, modContext); - // - // logMod->warn(R"( "%s" : "%s",)", string.first, TextOperations::escapeString(string.second.String())); - // allFound = false; - // } - - return allPresent && allFound; -} - -void TextLocalizationContainer::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config) +void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config) { for(const auto & node : config.Struct()) - registerStringOverride(modContext, language, node.first, node.second.String()); + registerStringOverride(modContext, node.first, node.second.String()); } bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const diff --git a/lib/texts/TextLocalizationContainer.h b/lib/texts/TextLocalizationContainer.h index 28aabd640..efa907744 100644 --- a/lib/texts/TextLocalizationContainer.h +++ b/lib/texts/TextLocalizationContainer.h @@ -25,15 +25,9 @@ protected: /// Human-readable string that was added on registration std::string baseValue; - /// Language of base string - std::string baseLanguage; - /// Translated human-readable string std::string overrideValue; - /// Language of the override string - std::string overrideLanguage; - /// ID of mod that created this string std::string modContext; @@ -41,7 +35,7 @@ protected: void serialize(Handler & h) { h & baseValue; - h & baseLanguage; + //h & baseLanguage; h & modContext; } }; @@ -52,7 +46,7 @@ protected: std::vector subContainers; /// add selected string to internal storage as high-priority strings - void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized); + void registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); std::string getModLanguage(const std::string & modContext); @@ -60,16 +54,12 @@ protected: bool identifierExists(const TextIdentifier & UID) const; public: - /// validates translation of specified language for specified mod - /// returns true if localization is valid and complete - /// any error messages will be written to log file - bool validateTranslation(const std::string & language, const std::string & modContext, JsonNode const & file) const; - /// Loads translation from provided json /// Any entries loaded by this will have priority over texts registered normally - void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file); + void loadTranslationOverrides(const std::string & modContext, JsonNode const & file); /// add selected string to internal storage + void registerString(const std::string & modContext, const TextIdentifier & UID, const JsonNode & localized); void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language); From 1488629628818ef8f2632fd07784f745aeec990e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 30 Sep 2024 17:07:54 +0000 Subject: [PATCH 261/726] Add simple support for translation of strings that were changed by another mod --- lib/CBonusTypeHandler.cpp | 5 +- lib/mapping/CMapHeader.cpp | 4 +- lib/modding/CModHandler.cpp | 22 +++-- lib/texts/TextLocalizationContainer.cpp | 114 +++++++++++++----------- lib/texts/TextLocalizationContainer.h | 22 ++--- 5 files changed, 97 insertions(+), 70 deletions(-) diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 666f4e86e..52c5f4dbc 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -200,8 +200,9 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr & bonu void CBonusTypeHandler::load() { - const JsonNode gameConf(JsonPath::builtin("config/gameConfig.json")); - const JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo>())); + JsonNode gameConf(JsonPath::builtin("config/gameConfig.json")); + JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo>())); + config.setModScope("vcmi"); load(config); } diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 61a210b8d..ec9c74df8 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -189,7 +189,7 @@ void CMapHeader::registerMapStrings() JsonUtils::mergeCopy(data, translations[language]); for(auto & s : data.Struct()) - texts.registerString("map", TextIdentifier(s.first), s.second.String(), language); + texts.registerString("map", TextIdentifier(s.first), s.second.String()); } std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized) @@ -199,7 +199,7 @@ std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeade std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language) { - mapHeader.texts.registerString(modContext, UID, localized, language); + mapHeader.texts.registerString(modContext, UID, localized); mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized; return UID.get(); } diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index f5eccc700..e2d44cbd3 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -401,6 +401,21 @@ CModVersion CModHandler::getModVersion(TModID modName) const return {}; } +static JsonNode loadReferencesList(const JsonNode & source) +{ + if (source.isVector()) + { + auto configList = source.convertTo >(); + JsonNode result = JsonUtils::assembleFromFiles(configList); + + return result; + } + else + { + return source; + } +} + void CModHandler::loadTranslation(const TModID & modName) { const auto & mod = allMods[modName]; @@ -408,11 +423,8 @@ void CModHandler::loadTranslation(const TModID & modName) std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); std::string modBaseLanguage = allMods[modName].baseLanguage; - auto baseTranslationList = mod.config["translations"].convertTo >(); - auto extraTranslationList = mod.config[preferredLanguage]["translations"].convertTo >(); - - JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList); - JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList); + JsonNode baseTranslation = loadReferencesList(mod.config["translations"]); + JsonNode extraTranslation = loadReferencesList(mod.config[preferredLanguage]["translations"]); VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation); VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation); diff --git a/lib/texts/TextLocalizationContainer.cpp b/lib/texts/TextLocalizationContainer.cpp index 17f5d905a..f45898cf7 100644 --- a/lib/texts/TextLocalizationContainer.cpp +++ b/lib/texts/TextLocalizationContainer.cpp @@ -31,9 +31,22 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo // NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment auto & entry = stringsLocalizations[UID.get()]; - entry.overrideValue = localized; - if (entry.modContext.empty()) - entry.modContext = modContext; + // load string override only in following cases: + // a) string was not modified in another mod (e.g. rebalance mod gave skill new description) + // b) this string override is defined in the same mod as one that provided modified version of this string + if (entry.identifierModContext == entry.baseStringModContext || modContext == entry.baseStringModContext) + { + entry.translatedText = localized; + if (entry.identifierModContext.empty()) + { + entry.identifierModContext = modContext; + entry.baseStringModContext = modContext; + } + } + else + { + logGlobal->debug("Skipping translation override for string %s: changed in a different mod", UID.get()); + } } void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container) @@ -53,7 +66,7 @@ void TextLocalizationContainer::removeSubContainer(const TextLocalizationContain subContainers.erase(std::remove(subContainers.begin(), subContainers.end(), &container), subContainers.end()); } -const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const +const std::string & TextLocalizationContainer::translateString(const TextIdentifier & identifier) const { std::lock_guard globalLock(globalTextMutex); @@ -61,56 +74,57 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier { for(auto containerIter = subContainers.rbegin(); containerIter != subContainers.rend(); ++containerIter) if((*containerIter)->identifierExists(identifier)) - return (*containerIter)->deserialize(identifier); + return (*containerIter)->translateString(identifier); logGlobal->error("Unable to find localization for string '%s'", identifier.get()); return identifier.get(); } const auto & entry = stringsLocalizations.at(identifier.get()); - - if (!entry.overrideValue.empty()) - return entry.overrideValue; - return entry.baseValue; + return entry.translatedText; } void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & inputUID, const JsonNode & localized) { - assert(!localized.getModScope().empty()); - assert(!getModLanguage(localized.getModScope()).empty()); + assert(localized.isNull() || !localized.getModScope().empty()); + assert(localized.isNull() || !getModLanguage(localized.getModScope()).empty()); - std::lock_guard globalLock(globalTextMutex); - registerString(modContext, inputUID, localized.String(), getModLanguage(modContext)); -} - -void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) -{ - std::lock_guard globalLock(globalTextMutex); - - assert(!modContext.empty()); - assert(!Languages::getLanguageOptions(language).identifier.empty()); - assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string - //assert(stringsLocalizations.count(UID.get()) == 0); // registering already registered string? - - if(stringsLocalizations.count(UID.get()) > 0) - { - auto & value = stringsLocalizations[UID.get()]; - value.baseValue = localized; - } + if (localized.isNull()) + registerString(modContext, modContext, inputUID, localized.String()); else - { - StringState value; - value.baseValue = localized; - value.modContext = modContext; - - stringsLocalizations[UID.get()] = value; - } + registerString(modContext, localized.getModScope(), inputUID, localized.String()); } void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) { - assert(!getModLanguage(modContext).empty()); - registerString(modContext, UID, localized, getModLanguage(modContext)); + registerString(modContext, modContext, UID, localized); +} + +void TextLocalizationContainer::registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized) +{ + std::lock_guard globalLock(globalTextMutex); + + assert(!identifierModContext.empty()); + assert(!localizedStringModContext.empty()); + assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string + assert(stringsLocalizations.count(UID.get()) == 0 || identifierModContext == "map"); // registering already registered string? + + if(stringsLocalizations.count(UID.get()) > 0) + { + auto & value = stringsLocalizations[UID.get()]; + value.translatedText = localized; + value.identifierModContext = identifierModContext; + value.baseStringModContext = localizedStringModContext; + } + else + { + StringState value; + value.translatedText = localized; + value.identifierModContext = identifierModContext; + value.baseStringModContext = localizedStringModContext; + + stringsLocalizations[UID.get()] = value; + } } void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config) @@ -136,17 +150,19 @@ void TextLocalizationContainer::exportAllTexts(std::map void serialize(Handler & h) { - h & baseValue; + h & translatedText; //h & baseLanguage; - h & modContext; + h & identifierModContext; + h & baseStringModContext; } }; @@ -61,18 +63,18 @@ public: /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const JsonNode & localized); void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); - void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language); + void registerString(const std::string & identifierModContext, const std::string & localizedStringModContext, const TextIdentifier & UID, const std::string & localized); /// returns translated version of a string that can be displayed to user template std::string translate(std::string arg1, Args ... args) const { TextIdentifier id(arg1, args ...); - return deserialize(id); + return translateString(id); } /// converts identifier into user-readable string - const std::string & deserialize(const TextIdentifier & identifier) const; + const std::string & translateString(const TextIdentifier & identifier) const; /// Debug method, returns all currently stored texts /// Format: [mod ID][string ID] -> human-readable text From 8e4152bc8179a51aa6a9c5a8dec0ceb34002f3ee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 30 Sep 2024 19:26:22 +0000 Subject: [PATCH 262/726] It is now possible to define objects directly in mod.json instead of using path to file with object definition --- config/schemas/mod.json | 80 +++++++++++++-------------- lib/json/JsonUtils.cpp | 21 +++++++ lib/json/JsonUtils.h | 2 + lib/modding/CModHandler.cpp | 21 +------ lib/modding/ContentTypeHandler.cpp | 4 +- lib/modding/ContentTypeHandler.h | 2 +- lib/texts/TextLocalizationContainer.h | 1 - 7 files changed, 69 insertions(+), 62 deletions(-) diff --git a/config/schemas/mod.json b/config/schemas/mod.json index 5c09ef74c..76d174c28 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -5,6 +5,17 @@ "description" : "Format used to define main mod file (mod.json) in VCMI", "required" : [ "name", "description", "modType", "version", "author", "contact" ], "definitions" : { + "fileListOrObject" : { + "oneOf" : [ + { + "type" : "array", + "items" : { "type" : "string", "format" : "textFile" } + }, + { + "type" : "object" + } + ] + }, "localizable" : { "type" : "object", "additionalProperties" : false, @@ -35,9 +46,8 @@ "description" : "If set to true, vcmi will skip validation of current translation json files" }, "translations" : { - "type" : "array", "description" : "List of files with translations for this language", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" } } } @@ -122,9 +132,17 @@ "description" : "If set to true, mod will not be enabled automatically on install" }, "settings" : { - "type" : "object", "description" : "List of changed game settings by mod", - "$ref" : "gameSettings.json" + "oneOf" : [ + { + "type" : "object", + "$ref" : "gameSettings.json" + }, + { + "type" : "array", + "items" : { "type" : "string", "format" : "textFile" } + }, + ] }, "filesystem" : { "type" : "object", @@ -206,94 +224,76 @@ "$ref" : "#/definitions/localizable" }, "translations" : { - "type" : "array", "description" : "List of files with translations for this language", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "factions" : { - "type" : "array", "description" : "List of configuration files for towns/factions", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "heroClasses" : { - "type" : "array", "description" : "List of configuration files for hero classes", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "heroes" : { - "type" : "array", "description" : "List of configuration files for heroes", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "skills" : { - "type" : "array", "description" : "List of configuration files for skills", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "creatures" : { - "type" : "array", "description" : "List of configuration files for creatures", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "artifacts" : { - "type" : "array", "description" : "List of configuration files for artifacts", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "spells" : { - "type" : "array", "description" : "List of configuration files for spells", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "objects" : { - "type" : "array", "description" : "List of configuration files for objects", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "biomes" : { - "type" : "array", "description" : "List of configuration files for biomes", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "bonuses" : { - "type" : "array", "description" : "List of configuration files for bonuses", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "terrains" : { - "type" : "array", "description" : "List of configuration files for terrains", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "roads" : { - "type" : "array", "description" : "List of configuration files for roads", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "rivers" : { - "type" : "array", "description" : "List of configuration files for rivers", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "battlefields" : { - "type" : "array", "description" : "List of configuration files for battlefields", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "obstacles" : { - "type" : "array", "description" : "List of configuration files for obstacles", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "templates" : { - "type" : "array", "description" : "List of configuration files for RMG templates", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" }, "scripts" : { - "type" : "array", "description" : "List of configuration files for scripts", - "items" : { "type" : "string", "format" : "textFile" } + "$ref" : "#/definitions/fileListOrObject" } } } diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index f1680e11f..e1cd47678 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -230,6 +230,27 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base) std::swap(descendant, inheritedNode); } +JsonNode JsonUtils::assembleFromFiles(const JsonNode & files, bool & isValid) +{ + if (files.isVector()) + { + auto configList = files.convertTo >(); + JsonNode result = JsonUtils::assembleFromFiles(configList, isValid); + + return result; + } + else + { + return files; + } +} + +JsonNode JsonUtils::assembleFromFiles(const JsonNode & files) +{ + bool isValid = false; + return assembleFromFiles(files, isValid); +} + JsonNode JsonUtils::assembleFromFiles(const std::vector & files) { bool isValid = false; diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index 954ee0b16..dcf03d30e 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -44,6 +44,8 @@ namespace JsonUtils * @brief generate one Json structure from multiple files * @param files - list of filenames with parts of json structure */ + DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files); + DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files, bool & isValid); DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files); DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files, bool & isValid); diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index e2d44cbd3..8dbf84eda 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -384,7 +384,7 @@ std::set CModHandler::getModDependencies(const TModID & modId, bool & is void CModHandler::initializeConfig() { - VLC->settingsHandler->loadBase(coreMod->config["settings"]); + VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"])); for(const TModID & modName : activeMods) { @@ -401,21 +401,6 @@ CModVersion CModHandler::getModVersion(TModID modName) const return {}; } -static JsonNode loadReferencesList(const JsonNode & source) -{ - if (source.isVector()) - { - auto configList = source.convertTo >(); - JsonNode result = JsonUtils::assembleFromFiles(configList); - - return result; - } - else - { - return source; - } -} - void CModHandler::loadTranslation(const TModID & modName) { const auto & mod = allMods[modName]; @@ -423,8 +408,8 @@ void CModHandler::loadTranslation(const TModID & modName) std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); std::string modBaseLanguage = allMods[modName].baseLanguage; - JsonNode baseTranslation = loadReferencesList(mod.config["translations"]); - JsonNode extraTranslation = loadReferencesList(mod.config[preferredLanguage]["translations"]); + JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]); + JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]); VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation); VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation); diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 83ae46c1b..d5dfb8461 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -50,7 +50,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string } } -bool ContentTypeHandler::preloadModData(const std::string & modName, const std::vector & fileList, bool validate) +bool ContentTypeHandler::preloadModData(const std::string & modName, const JsonNode & fileList, bool validate) { bool result = false; JsonNode data = JsonUtils::assembleFromFiles(fileList, result); @@ -216,7 +216,7 @@ bool CContentHandler::preloadModData(const std::string & modName, JsonNode modCo bool result = true; for(auto & handler : handlers) { - result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo >(), validate); + result &= handler.second.preloadModData(modName, modConfig[handler.first], validate); } return result; } diff --git a/lib/modding/ContentTypeHandler.h b/lib/modding/ContentTypeHandler.h index 7093c12d5..60b29d689 100644 --- a/lib/modding/ContentTypeHandler.h +++ b/lib/modding/ContentTypeHandler.h @@ -39,7 +39,7 @@ public: /// local version of methods in ContentHandler /// returns true if loading was successful - bool preloadModData(const std::string & modName, const std::vector & fileList, bool validate); + bool preloadModData(const std::string & modName, const JsonNode & fileList, bool validate); bool loadMod(const std::string & modName, bool validate); void loadCustom(); void afterLoadFinalization(); diff --git a/lib/texts/TextLocalizationContainer.h b/lib/texts/TextLocalizationContainer.h index e3047c516..cde8e070e 100644 --- a/lib/texts/TextLocalizationContainer.h +++ b/lib/texts/TextLocalizationContainer.h @@ -36,7 +36,6 @@ protected: void serialize(Handler & h) { h & translatedText; - //h & baseLanguage; h & identifierModContext; h & baseStringModContext; } From c04fb60dc025b63b62877d0a5e94d250019a1863 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 6 Oct 2024 19:44:32 +0000 Subject: [PATCH 263/726] Updated docs --- docs/modders/Mod_File_Format.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/modders/Mod_File_Format.md b/docs/modders/Mod_File_Format.md index f0ac58070..fd4354fa9 100644 --- a/docs/modders/Mod_File_Format.md +++ b/docs/modders/Mod_File_Format.md @@ -90,8 +90,9 @@ These are fields that are present only in local mod.json file { // Following section describes configuration files with content added by mod // It can be split into several files in any way you want but recommended organization is - // to keep one file per object (creature/hero/etc) and, if applicable, add separate file - // with translatable strings for each type of content + // to keep one file per object (creature/hero/etc) + // Alternatively, for small changes you can embed changes to content directly in here, e.g. + // "creatures" : { "core:imp" : { "health" : 5 }} // list of factions/towns configuration files "factions" : From 40576e9bb9ee51088e7f74002d8741db04cea59c Mon Sep 17 00:00:00 2001 From: Evgeniy Meshcheryakov Date: Mon, 7 Oct 2024 08:47:00 +0300 Subject: [PATCH 264/726] Separate vcmiqt lib --- CMakeLists.txt | 4 + launcher/CMakeLists.txt | 6 +- launcher/StdInc.h | 22 +--- launcher/modManager/cdownloadmanager_moc.cpp | 2 +- launcher/modManager/cmodlistview_moc.cpp | 4 +- launcher/modManager/cmodmanager.cpp | 4 +- launcher/prepare.cpp | 2 +- launcher/settingsView/csettingsview_moc.cpp | 2 +- mapeditor/CMakeLists.txt | 6 +- mapeditor/StdInc.h | 20 +-- mapeditor/jsonutils.cpp | 124 ------------------- mapeditor/jsonutils.h | 27 ---- mapeditor/launcherdirs.cpp | 36 ------ mapeditor/launcherdirs.h | 22 ---- mapeditor/windownewmap.cpp | 2 +- vcmiqt/CMakeLists.txt | 46 +++++++ vcmiqt/StdInc.cpp | 12 ++ vcmiqt/StdInc.h | 20 +++ vcmiqt/convpathqstring.h | 29 +++++ {launcher => vcmiqt}/jsonutils.cpp | 0 {launcher => vcmiqt}/jsonutils.h | 10 +- {launcher => vcmiqt}/launcherdirs.cpp | 0 {launcher => vcmiqt}/launcherdirs.h | 10 +- vcmiqt/vcmiqt.h | 19 +++ 24 files changed, 157 insertions(+), 272 deletions(-) delete mode 100644 mapeditor/jsonutils.cpp delete mode 100644 mapeditor/jsonutils.h delete mode 100644 mapeditor/launcherdirs.cpp delete mode 100644 mapeditor/launcherdirs.h create mode 100644 vcmiqt/CMakeLists.txt create mode 100644 vcmiqt/StdInc.cpp create mode 100644 vcmiqt/StdInc.h create mode 100644 vcmiqt/convpathqstring.h rename {launcher => vcmiqt}/jsonutils.cpp (100%) rename {launcher => vcmiqt}/jsonutils.h (58%) rename {launcher => vcmiqt}/launcherdirs.cpp (100%) rename {launcher => vcmiqt}/launcherdirs.h (68%) create mode 100644 vcmiqt/vcmiqt.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a9918c0a3..60ef50056 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -666,6 +666,10 @@ if(NOT TARGET minizip::minizip) add_library(minizip::minizip ALIAS minizip) endif() +if(ENABLE_LAUNCHER OR ENABLE_EDITOR) + add_subdirectory(vcmiqt) +endif() + if(ENABLE_LAUNCHER) add_subdirectory(launcher) endif() diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 1432b1b90..e2229ba19 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -20,8 +20,6 @@ set(launcher_SRCS innoextract.cpp mainwindow_moc.cpp languages.cpp - launcherdirs.cpp - jsonutils.cpp updatedialog_moc.cpp prepare.cpp ) @@ -49,8 +47,6 @@ set(launcher_HEADERS firstLaunch/firstlaunch_moc.h mainwindow_moc.h languages.h - launcherdirs.h - jsonutils.h updatedialog_moc.h main.h helper.h @@ -206,7 +202,7 @@ elseif(NOT APPLE_IOS) target_link_libraries(vcmilauncher SDL2::SDL2) endif() -target_link_libraries(vcmilauncher vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) +target_link_libraries(vcmilauncher vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmilauncher PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/launcher/StdInc.h b/launcher/StdInc.h index 506773a9a..52e742b43 100644 --- a/launcher/StdInc.h +++ b/launcher/StdInc.h @@ -18,24 +18,8 @@ #include #include #include -#include +#include + +#include "../vcmiqt/convpathqstring.h" VCMI_LIB_USING_NAMESPACE - -inline QString pathToQString(const boost::filesystem::path & path) -{ -#ifdef VCMI_WINDOWS - return QString::fromStdWString(path.wstring()); -#else - return QString::fromStdString(path.string()); -#endif -} - -inline boost::filesystem::path qstringToPath(const QString & path) -{ -#ifdef VCMI_WINDOWS - return boost::filesystem::path(path.toStdWString()); -#else - return boost::filesystem::path(path.toUtf8().data()); -#endif -} diff --git a/launcher/modManager/cdownloadmanager_moc.cpp b/launcher/modManager/cdownloadmanager_moc.cpp index c1621dd75..c08e6cb4c 100644 --- a/launcher/modManager/cdownloadmanager_moc.cpp +++ b/launcher/modManager/cdownloadmanager_moc.cpp @@ -10,7 +10,7 @@ #include "StdInc.h" #include "cdownloadmanager_moc.h" -#include "../launcherdirs.h" +#include "../vcmiqt/launcherdirs.h" #include "../../lib/CConfigHandler.h" diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index d8797979a..a0b11b6a3 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -22,8 +22,8 @@ #include "cdownloadmanager_moc.h" #include "chroniclesextractor.h" #include "../settingsView/csettingsview_moc.h" -#include "../launcherdirs.h" -#include "../jsonutils.h" +#include "../vcmiqt/launcherdirs.h" +#include "../vcmiqt/jsonutils.h" #include "../helper.h" #include "../../lib/VCMIDirs.h" diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 4012519d1..4288f4a15 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -17,8 +17,8 @@ #include "../../lib/modding/CModInfo.h" #include "../../lib/modding/IdentifierStorage.h" -#include "../jsonutils.h" -#include "../launcherdirs.h" +#include "../vcmiqt/jsonutils.h" +#include "../vcmiqt/launcherdirs.h" #include diff --git a/launcher/prepare.cpp b/launcher/prepare.cpp index c57f703eb..7e158d4ac 100644 --- a/launcher/prepare.cpp +++ b/launcher/prepare.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" #include "prepare.h" -#include "launcherdirs.h" +#include "../vcmiqt/launcherdirs.h" #include #include diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 8cb8c32a0..fd897d1f5 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -15,7 +15,7 @@ #include "../modManager/cmodlistview_moc.h" #include "../helper.h" -#include "../jsonutils.h" +#include "../vcmiqt/jsonutils.h" #include "../languages.h" #include diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index dc69ed286..33f6f6e67 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -1,8 +1,6 @@ set(editor_SRCS StdInc.cpp main.cpp - launcherdirs.cpp - jsonutils.cpp mainwindow.cpp BitmapHandler.cpp maphandler.cpp @@ -45,8 +43,6 @@ set(editor_SRCS set(editor_HEADERS StdInc.h - launcherdirs.h - jsonutils.h mainwindow.h BitmapHandler.h maphandler.h @@ -221,7 +217,7 @@ if(APPLE) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor) endif() -target_link_libraries(vcmieditor vcmi Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) +target_link_libraries(vcmieditor vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmieditor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/mapeditor/StdInc.h b/mapeditor/StdInc.h index 9f51e8a29..34532b7ff 100644 --- a/mapeditor/StdInc.h +++ b/mapeditor/StdInc.h @@ -22,6 +22,8 @@ #include #include +#include "../vcmiqt/convpathqstring.h" + VCMI_LIB_USING_NAMESPACE using NumericPointer = typename std::conditional_t(_numeric); } - -inline QString pathToQString(const boost::filesystem::path & path) -{ -#ifdef VCMI_WINDOWS - return QString::fromStdWString(path.wstring()); -#else - return QString::fromStdString(path.string()); -#endif -} - -inline boost::filesystem::path qstringToPath(const QString & path) -{ -#ifdef VCMI_WINDOWS - return boost::filesystem::path(path.toStdWString()); -#else - return boost::filesystem::path(path.toUtf8().data()); -#endif -} diff --git a/mapeditor/jsonutils.cpp b/mapeditor/jsonutils.cpp deleted file mode 100644 index e4eb884d6..000000000 --- a/mapeditor/jsonutils.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * jsonutils.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "jsonutils.h" - -#include "../lib/json/JsonNode.h" - -static QVariantMap JsonToMap(const JsonMap & json) -{ - QVariantMap map; - for(auto & entry : json) - { - map.insert(QString::fromStdString(entry.first), JsonUtils::toVariant(entry.second)); - } - return map; -} - -static QVariantList JsonToList(const JsonVector & json) -{ - QVariantList list; - for(auto & entry : json) - { - list.push_back(JsonUtils::toVariant(entry)); - } - return list; -} - -static JsonVector VariantToList(QVariantList variant) -{ - JsonVector vector; - for(auto & entry : variant) - { - vector.push_back(JsonUtils::toJson(entry)); - } - return vector; -} - -static JsonMap VariantToMap(QVariantMap variant) -{ - JsonMap map; - for(auto & entry : variant.toStdMap()) - { - map[entry.first.toUtf8().data()] = JsonUtils::toJson(entry.second); - } - return map; -} - -VCMI_LIB_NAMESPACE_BEGIN - -namespace JsonUtils -{ - -QVariant toVariant(const JsonNode & node) -{ - switch(node.getType()) - { - case JsonNode::JsonType::DATA_NULL: - return QVariant(); - case JsonNode::JsonType::DATA_BOOL: - return QVariant(node.Bool()); - case JsonNode::JsonType::DATA_FLOAT: - return QVariant(node.Float()); - case JsonNode::JsonType::DATA_INTEGER: - return QVariant{static_cast(node.Integer())}; - case JsonNode::JsonType::DATA_STRING: - return QVariant(QString::fromStdString(node.String())); - case JsonNode::JsonType::DATA_VECTOR: - return JsonToList(node.Vector()); - case JsonNode::JsonType::DATA_STRUCT: - return JsonToMap(node.Struct()); - } - return QVariant(); -} - -QVariant JsonFromFile(QString filename) -{ - QFile file(filename); - if(!file.open(QFile::ReadOnly)) - { - logGlobal->error("Failed to open file %s. Reason: %s", qUtf8Printable(filename), qUtf8Printable(file.errorString())); - return {}; - } - - const auto data = file.readAll(); - JsonNode node(reinterpret_cast(data.data()), data.size(), filename.toStdString()); - return toVariant(node); -} - -JsonNode toJson(QVariant object) -{ - JsonNode ret; - - if(object.userType() == QMetaType::QString) - ret.String() = object.toString().toUtf8().data(); - else if(object.userType() == QMetaType::Bool) - ret.Bool() = object.toBool(); - else if(object.canConvert()) - ret.Struct() = VariantToMap(object.toMap()); - else if(object.canConvert()) - ret.Vector() = VariantToList(object.toList()); - else if(object.canConvert()) - ret.Integer() = object.toInt(); - else if(object.canConvert()) - ret.Float() = object.toFloat(); - - return ret; -} - -void JsonToFile(QString filename, QVariant object) -{ - std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary); - file << toJson(object).toString(); -} - -} - -VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/jsonutils.h b/mapeditor/jsonutils.h deleted file mode 100644 index 791711eb0..000000000 --- a/mapeditor/jsonutils.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * jsonutils.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -class JsonNode; - -namespace JsonUtils -{ -QVariant toVariant(const JsonNode & node); -QVariant JsonFromFile(QString filename); - -JsonNode toJson(QVariant object); -void JsonToFile(QString filename, QVariant object); -} - -VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/launcherdirs.cpp b/mapeditor/launcherdirs.cpp deleted file mode 100644 index 97d456bb5..000000000 --- a/mapeditor/launcherdirs.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * launcherdirs.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "launcherdirs.h" - -#include "../lib/VCMIDirs.h" - -static CLauncherDirs launcherDirsGlobal; - -CLauncherDirs::CLauncherDirs() -{ - QDir().mkdir(downloadsPath()); - QDir().mkdir(modsPath()); -} - -CLauncherDirs & CLauncherDirs::get() -{ - return launcherDirsGlobal; -} - -QString CLauncherDirs::downloadsPath() -{ - return pathToQString(VCMIDirs::get().userCachePath() / "downloads"); -} - -QString CLauncherDirs::modsPath() -{ - return pathToQString(VCMIDirs::get().userDataPath() / "Mods"); -} diff --git a/mapeditor/launcherdirs.h b/mapeditor/launcherdirs.h deleted file mode 100644 index 9117bd9fb..000000000 --- a/mapeditor/launcherdirs.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * launcherdirs.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -/// similar to lib/VCMIDirs, controls where all launcher-related data will be stored -class CLauncherDirs -{ -public: - CLauncherDirs(); - - static CLauncherDirs & get(); - - QString downloadsPath(); - QString modsPath(); -}; diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index f77b6e2f7..cf67dc8f4 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -21,7 +21,7 @@ #include "../lib/serializer/JsonSerializer.h" #include "../lib/serializer/JsonDeserializer.h" -#include "jsonutils.h" +#include "../vcmiqt/jsonutils.h" #include "windownewmap.h" #include "ui_windownewmap.h" #include "mainwindow.h" diff --git a/vcmiqt/CMakeLists.txt b/vcmiqt/CMakeLists.txt new file mode 100644 index 000000000..e33f6ee48 --- /dev/null +++ b/vcmiqt/CMakeLists.txt @@ -0,0 +1,46 @@ +set(vcmiqt_SRCS + StdInc.cpp + + jsonutils.cpp + launcherdirs.cpp +) + +set(vcmiqt_HEADERS + StdInc.h + + jsonutils.h + launcherdirs.h + convpathqstring.h + vcmiqt.h +) + +assign_source_group(${vcmiqt_SRCS} ${vcmiqt_HEADERS}) + +if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER)) + add_library(vcmiqt STATIC ${vcmiqt_SRCS} ${vcmiqt_HEADERS}) + target_compile_definitions(vcmiqt PRIVATE VCMIQT_STATIC) +else() + add_library(vcmiqt SHARED ${vcmiqt_SRCS} ${vcmiqt_HEADERS}) + target_compile_definitions(vcmiqt PRIVATE VCMIQT_SHARED) +endif() + +if(WIN32) + set_target_properties(vcmiqt + PROPERTIES + OUTPUT_NAME "VCMI_vcmiqt" + PROJECT_LABEL "VCMI_vcmiqt" + ) +endif() + +target_link_libraries(vcmiqt vcmi Qt${QT_VERSION_MAJOR}::Core) + +target_include_directories(vcmiqt PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +if(NOT ENABLE_STATIC_LIBS OR (ENABLE_EDITOR AND ENABLE_LAUNCHER)) + install(TARGETS vcmiqt RUNTIME DESTINATION ${LIB_DIR} LIBRARY DESTINATION ${LIB_DIR}) +endif() + +vcmi_set_output_dir(vcmiqt "") +enable_pch(vcmiqt) diff --git a/vcmiqt/StdInc.cpp b/vcmiqt/StdInc.cpp new file mode 100644 index 000000000..02b98775f --- /dev/null +++ b/vcmiqt/StdInc.cpp @@ -0,0 +1,12 @@ +/* + * StdInc.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +// Creates the precompiled header +#include "StdInc.h" + diff --git a/vcmiqt/StdInc.h b/vcmiqt/StdInc.h new file mode 100644 index 000000000..402615a9e --- /dev/null +++ b/vcmiqt/StdInc.h @@ -0,0 +1,20 @@ +/* + * StdInc.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../Global.h" + +#include +#include +#include + +#include "convpathqstring.h" + +VCMI_LIB_USING_NAMESPACE diff --git a/vcmiqt/convpathqstring.h b/vcmiqt/convpathqstring.h new file mode 100644 index 000000000..a71469edd --- /dev/null +++ b/vcmiqt/convpathqstring.h @@ -0,0 +1,29 @@ +/* + * convpathqstring.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +inline QString pathToQString(const boost::filesystem::path & path) +{ +#ifdef VCMI_WINDOWS + return QString::fromStdWString(path.wstring()); +#else + return QString::fromStdString(path.string()); +#endif +} + +inline boost::filesystem::path qstringToPath(const QString & path) +{ +#ifdef VCMI_WINDOWS + return boost::filesystem::path(path.toStdWString()); +#else + return boost::filesystem::path(path.toUtf8().data()); +#endif +} diff --git a/launcher/jsonutils.cpp b/vcmiqt/jsonutils.cpp similarity index 100% rename from launcher/jsonutils.cpp rename to vcmiqt/jsonutils.cpp diff --git a/launcher/jsonutils.h b/vcmiqt/jsonutils.h similarity index 58% rename from launcher/jsonutils.h rename to vcmiqt/jsonutils.h index 791711eb0..1b78881d6 100644 --- a/launcher/jsonutils.h +++ b/vcmiqt/jsonutils.h @@ -9,6 +9,8 @@ */ #pragma once +#include "vcmiqt.h" + #include VCMI_LIB_NAMESPACE_BEGIN @@ -17,11 +19,11 @@ class JsonNode; namespace JsonUtils { -QVariant toVariant(const JsonNode & node); -QVariant JsonFromFile(QString filename); +VCMIQT_LINKAGE QVariant toVariant(const JsonNode & node); +VCMIQT_LINKAGE QVariant JsonFromFile(QString filename); -JsonNode toJson(QVariant object); -void JsonToFile(QString filename, QVariant object); +VCMIQT_LINKAGE JsonNode toJson(QVariant object); +VCMIQT_LINKAGE void JsonToFile(QString filename, QVariant object); } VCMI_LIB_NAMESPACE_END diff --git a/launcher/launcherdirs.cpp b/vcmiqt/launcherdirs.cpp similarity index 100% rename from launcher/launcherdirs.cpp rename to vcmiqt/launcherdirs.cpp diff --git a/launcher/launcherdirs.h b/vcmiqt/launcherdirs.h similarity index 68% rename from launcher/launcherdirs.h rename to vcmiqt/launcherdirs.h index a8b5ea1c4..91c7c0221 100644 --- a/launcher/launcherdirs.h +++ b/vcmiqt/launcherdirs.h @@ -9,12 +9,14 @@ */ #pragma once +#include "vcmiqt.h" + /// similar to lib/VCMIDirs, controls where all launcher-related data will be stored namespace CLauncherDirs { - void prepare(); + VCMIQT_LINKAGE void prepare(); - QString downloadsPath(); - QString modsPath(); - QString mapsPath(); + VCMIQT_LINKAGE QString downloadsPath(); + VCMIQT_LINKAGE QString modsPath(); + VCMIQT_LINKAGE QString mapsPath(); } diff --git a/vcmiqt/vcmiqt.h b/vcmiqt/vcmiqt.h new file mode 100644 index 000000000..a3cf5f8c0 --- /dev/null +++ b/vcmiqt/vcmiqt.h @@ -0,0 +1,19 @@ +/* + * vcmiqt.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include + +#if VCMIQT_STATIC +# define VCMIQT_LINKAGE +#elif defined(VCMIQT_SHARED) +# define VCMIQT_LINKAGE Q_DECL_EXPORT +#else +# define VCMIQT_LINKAGE Q_DECL_IMPORT +#endif From b885fd9d3bd6ab0dd8ce2a25b5a3119635ff29ab Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:26:43 +0200 Subject: [PATCH 265/726] added custom icons for buttons made by Ivan --- Mods/vcmi/Data/spellResearch/accept.png | Bin 0 -> 931 bytes Mods/vcmi/Data/spellResearch/close.png | Bin 0 -> 675 bytes Mods/vcmi/Data/spellResearch/reroll.png | Bin 0 -> 1047 bytes client/windows/CCastleInterface.cpp | 8 +++++--- 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Mods/vcmi/Data/spellResearch/accept.png create mode 100644 Mods/vcmi/Data/spellResearch/close.png create mode 100644 Mods/vcmi/Data/spellResearch/reroll.png diff --git a/Mods/vcmi/Data/spellResearch/accept.png b/Mods/vcmi/Data/spellResearch/accept.png new file mode 100644 index 0000000000000000000000000000000000000000..d3958902c9161ea81941a2dacb02be1c9c384598 GIT binary patch literal 931 zcmV;U16=%xP)X1^@s6S>%lC000AONkl_!k0a(5xI|!`_DAS1=&IMfG^!9rhZZ9XvjZDXv?%+4R)W)}UIbR$-s1e25Usgf`K_ta?`?*|} zISp!+wve2}4iq=qb^|wiLQ9JAN|q^Aq0C9F@Yu?o+S!lxJAj5XUkXC`Cvmq9H6k%J zkmgH72xFar2^a$^6*7q{m!yz+`p>aV{rc*Zl^CkTp2Wo?bX-1#Z}(>=uV2`yhxAh! z(pa=zK1p0iAp^m0r!b&L-Rwfu03T?&Cc98QRBjLY@>I92;)_d@FQek0l_ejYoj~gy zDT!HR=Fus{Ic!Wumj~4{&qLIazU1+_i7uJP&0fv#5bwAtQF;zEx#Z2-a*Ty5h4G5VyME$aOGk2 z=VxLOSd1Jxij$(pS9oxG{ItwhT;;VbhGaUSB&3^!@&<#QUem3-mcJ zV(mAYchv^Ii|B5E8J?`KvZJYnUllBuI+22V5nbFWy*th6x{jy(wNf- zB>3&%@)449W1elW*&1`{^-q{Pu~qF%VY^E|_v9kg8)|YDjj&!HeRy+k5&5B_enH?C ziJ$MxxP}u7rM}bGr9KE(9xykO*^+)$x5RB<*-cD#*^);6!jk5`Rs48s`nE*p&8gHk zelhQ-HZo&?n)BJ7yk}hxG1Ovh;bk%rWwV~se>!IYsABhu*kyw|Hg}xM>c(m z6H3Mnq^oYoBJ>~qbxO}|0%}SSP000>X1^@s6#OZ}&0007NNklha8+qT{Q?YpS9Z8NBC+qP}n=61ex=d#C#Nxo!nF~8fLc{3Er)IDz@ zR$tZ*)??N$NRM~uwL;{=;Jg%T0ZZA@m#fJeaK;J1%X(rLAo_Ma^~;mvF=Oli2C)8` zhp+AKpcYN6q0&0#S9!B%hA_q))`DO+_H^82bps!O|CO3LsG=ewXbmfhm7A3g(&MPb zlWHEme>lnybZSFufP!NX_(X5cC8)Lyvz;cQ24rFL$5oHJxwqppIs55KvSPUZ)0shj zdp?2RH$`sqW#7`KdB}zxSkbK7%hPc_(B&QNzwmS^GfkCf`jAR$?#QZ-KV44xgWu0i z#=f%)dT=-@&h#~^N0|)*>1zfVQ}wE3X?|jTt3iD2-%ddvE+rinf7|Cbg1TRIOg@%Lq^8ayasd)ZZt=%Y4u3l^`Aa784d~P)7a)P8 z6Muj1c;N4GC`dYj*#%iceg!|5K8oo(VL} zi|m5FT>TF+3uJ$9aR3&OX=pg+Mie{LA}i3(TWQU|-$=b{o?SDmE&xwh!QB!KM>dLd zEsD~qnNADiC<(Hu1IwEM0C_2#q2b`VPTMce=o>(z$;!}h&<|_^5M+tL6S;w1D}{c5 zT*pU+uNP$k!WeT{(`hO}E=4Y_kk0iTtjDZhT95TyU%e-+{R3n9=V)3aNfiJ9002ov JPDHLkV1g1@I(Gm7 literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Data/spellResearch/reroll.png b/Mods/vcmi/Data/spellResearch/reroll.png new file mode 100644 index 0000000000000000000000000000000000000000..f5603dde099dc28a4366a9f0ab60169a666acf72 GIT binary patch literal 1047 zcmV+y1nB#TP)X1^@s6S>%lC000BtNklp5r9$(K~s>8|rntNOa; z8q4y(A4Gtxb`S?1GmQ1p>`ov^%f)L%Xt7S$wYQ=3eM*L3l$`FTz-ILcUQ(S7@+pqr z)$&L;!O8Z;Bn*2@8{1#W^h?M&zwij`+S+)})ti19w>i+MbDYv{2Z>Sp97`}( zsEzYnfj+mkSJ!>{7oBV9kNbDOkHOCaxx}De59Coi^UMN@b09`1$ObS75w`s?VH-lV zAPkHF&w=N`*mu3UFV8*iB;$i}j<277PkU$Ts3u5-d%G@vJ5V)2M-5|a)J#7fv&gDq zJVY1!7pJ$B|LeEkBg3 zxMc4|=eOAkFgyJ(1R>X$nmErAm_b$*gjfK$(S)EE)l7d8W`wl`@nN3~`T}N$MA1i- zN4e8D-r9m9f%+&!>*G`oa05%BpA34wmE5<3Bv1qz zfK6Ls`ht4YB<@4so-{jA&GhLlrTHqj)YzV-lit?FZ>4fTH!v=}ms+<#;AA6iJEv);PD-CL-^G(?zZNSI->#)ZK>f>;!^91mFqH4$_qhFL1w6 zPzYefS6$2A_3X|t-2(Y-fp-(zzhcTF%uoUOu?J_TBi;3uo+vNC8WO!Kedl*#b@5(} z6SjBRWFIU|1NmpS*c`P)%m^S#>ArQZxpZL#mz*^*9i#M`t8{YX3_C8_N5-tt z$qOEt%yH7+ZlV0;Vz=VfQE@bLA+FpK?iN(95ERPZ>LECKIm>EaGMuFFez(hLOyQ|%TI0#ZPh6-WW0THX$|zW}5bu(L|z R(BA+6002ovPDHLkV1k~N{)Yen literal 0 HcmV?d00001 diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index d8f069e56..90f2b1dca 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2063,20 +2063,22 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto showSpellResearchDialog = [this, resComps, town, cost, newSpell](){ std::vector>> pom; - pom.emplace_back(AnimationPath::builtin("ibuy30.DEF"), nullptr); - pom.emplace_back(AnimationPath::builtin("hsbtns4.DEF"), nullptr); - pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr); + for(int i = 0; i < 3; i++) + pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr); auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated()); boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated()); auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); + temp->buttons[0]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/accept"))); temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); + temp->buttons[1]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/reroll"))); temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); + temp->buttons[2]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/close"))); temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); GH.windows().pushWindow(temp); From 613da80f550ce917879a89a6b0d5b4216fec9477 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:25:18 +0200 Subject: [PATCH 266/726] fix 8th creature and portal of summoning --- client/windows/CCastleInterface.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 90f2b1dca..9729bd73c 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -1782,6 +1782,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): ui32 fortSize = static_cast(town->creatures.size()); if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty()) fortSize--; + fortSize = std::min(fortSize, static_cast(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6)); title = std::make_shared(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated()); @@ -1840,6 +1841,7 @@ ImagePath CFortScreen::getBgName(const CGTownInstance * town) ui32 fortSize = static_cast(town->creatures.size()); if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty()) fortSize--; + fortSize = std::min(fortSize, static_cast(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning if(fortSize == GameConstants::CREATURES_PER_TOWN) return ImagePath::builtin("TPCASTL8"); From cc6d6b0d63cc5f6d6093250142803dec80ead650 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 13:10:34 +0000 Subject: [PATCH 267/726] Replaced unique_ptr with simple references to packs --- server/CVCMIServer.cpp | 50 ++++++++++---------- server/CVCMIServer.h | 4 +- server/NetPacksLobbyServer.cpp | 6 +-- server/processors/PlayerMessageProcessor.cpp | 2 +- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 7f52c801e..bc4d24481 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -49,7 +49,7 @@ public: void visitForLobby(CPackForLobby & packForLobby) override { - handler.handleReceivedPack(std::unique_ptr(&packForLobby)); + handler.handleReceivedPack(packForLobby); } void visitForServer(CPackForServer & serverPack) override @@ -231,9 +231,9 @@ bool CVCMIServer::prepareToStartGame() { //FIXME: UNGUARDED MULTITHREADED ACCESS!!! currentProgress = progressTracking.get(); - std::unique_ptr loadProgress(new LobbyLoadProgress); - loadProgress->progress = currentProgress; - announcePack(std::move(loadProgress)); + LobbyLoadProgress loadProgress; + loadProgress.progress = currentProgress; + announcePack(loadProgress); } boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } @@ -305,29 +305,29 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & con if(gh && getState() == EServerState::GAMEPLAY) { - auto lcd = std::make_unique(); - lcd->c = c; - lcd->clientId = c->connectionID; - handleReceivedPack(std::move(lcd)); + LobbyClientDisconnected lcd; + lcd.c = c; + lcd.clientId = c->connectionID; + handleReceivedPack(lcd); } } } -void CVCMIServer::handleReceivedPack(std::unique_ptr pack) +void CVCMIServer::handleReceivedPack(CPackForLobby & pack) { ClientPermissionsCheckerNetPackVisitor checker(*this); - pack->visit(checker); + pack.visit(checker); if(checker.getResult()) { ApplyOnServerNetPackVisitor applier(*this); - pack->visit(applier); + pack.visit(applier); if (applier.getResult()) - announcePack(std::move(pack)); + announcePack(pack); } } -void CVCMIServer::announcePack(std::unique_ptr pack) +void CVCMIServer::announcePack(CPackForLobby & pack) { for(auto activeConnection : activeConnections) { @@ -335,19 +335,19 @@ void CVCMIServer::announcePack(std::unique_ptr pack) // Until UUID set we only pass LobbyClientConnected to this client //if(c->uuid == uuid && !dynamic_cast(pack.get())) // continue; - activeConnection->sendPack(pack.get()); + activeConnection->sendPack(pack); } ApplyOnServerAfterAnnounceNetPackVisitor applier(*this); - pack->visit(applier); + pack.visit(applier); } void CVCMIServer::announceMessage(const MetaString & txt) { logNetwork->info("Show message: %s", txt.toString()); - auto cm = std::make_unique(); - cm->message = txt; - announcePack(std::move(cm)); + LobbyShowMessage cm; + cm.message = txt; + announcePack(cm); } void CVCMIServer::announceMessage(const std::string & txt) @@ -360,10 +360,10 @@ void CVCMIServer::announceMessage(const std::string & txt) void CVCMIServer::announceTxt(const MetaString & txt, const std::string & playerName) { logNetwork->info("%s says: %s", playerName, txt.toString()); - auto cm = std::make_unique(); - cm->playerName = playerName; - cm->message = txt; - announcePack(std::move(cm)); + LobbyChatMessage cm; + cm.playerName = playerName; + cm.message = txt; + announcePack(cm); } void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName) @@ -633,9 +633,9 @@ void CVCMIServer::updateAndPropagateLobbyState() } } - auto lus = std::make_unique(); - lus->state = *this; - announcePack(std::move(lus)); + LobbyUpdateState lus; + lus.state = *this; + announcePack(lus); } void CVCMIServer::setPlayer(PlayerColor clickedColor) diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 46bddeb01..813352ae3 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -86,7 +86,7 @@ public: void threadHandleClient(std::shared_ptr c); - void announcePack(std::unique_ptr pack); + void announcePack(CPackForLobby & pack); bool passHost(int toConnectionId); void announceTxt(const MetaString & txt, const std::string & playerName = "system"); @@ -102,7 +102,7 @@ public: void announceMessage(const MetaString & txt); void announceMessage(const std::string & txt); - void handleReceivedPack(std::unique_ptr pack); + void handleReceivedPack(CPackForLobby & pack); void updateAndPropagateLobbyState(); diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 17df7c364..d46799b12 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -128,10 +128,10 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(Lobb } else if(pack.c->connectionID == srv.hostClientId) { - auto ph = std::make_unique(); + LobbyChangeHost ph; auto newHost = srv.activeConnections.front(); - ph->newHostConnectionId = newHost->connectionID; - srv.announcePack(std::move(ph)); + ph.newHostConnectionId = newHost->connectionID; + srv.announcePack(ph); } srv.updateAndPropagateLobbyState(); diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 5929bbd87..0804a4940 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -847,7 +847,7 @@ void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr conn { SystemMessage sm; sm.text = message; - connection->sendPack(&sm); + connection->sendPack(sm); } void PlayerMessageProcessor::sendSystemMessage(std::shared_ptr connection, const std::string & message) From c1c552d39477f8453b2e1938c0085574aed61f22 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 13:35:31 +0000 Subject: [PATCH 268/726] Replaced some of pointers to CPack's with references --- client/CServerHandler.cpp | 4 ++-- client/Client.cpp | 4 +--- lib/serializer/Connection.cpp | 12 ++++++------ lib/serializer/Connection.h | 4 ++-- server/CGameHandler.cpp | 4 ++-- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 373bbec3e..6214e6380 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -853,7 +853,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr if(getState() == EClientState::DISCONNECTING) return; - CPack * pack = logicConnection->retrievePack(message); + auto pack = logicConnection->retrievePack(message); ServerHandlerCPackVisitor visitor(*this); pack->visit(visitor); } @@ -945,7 +945,7 @@ void CServerHandler::visitForClient(CPackForClient & clientPack) void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const { if(getState() != EClientState::STARTING) - logicConnection->sendPack(&pack); + logicConnection->sendPack(pack); } bool CServerHandler::inLobbyRoom() const diff --git a/client/Client.cpp b/client/Client.cpp index 2d57375ca..c6b024198 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -362,8 +362,6 @@ void CClient::handlePack(CPackForClient * 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; } int CClient::sendRequest(const CPackForServer * request, PlayerColor player) @@ -376,7 +374,7 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player) waitingRequest.pushBack(requestID); request->requestID = requestID; request->player = player; - CSH->logicConnection->sendPack(request); + CSH->logicConnection->sendPack(*request); if(vstd::contains(playerint, player)) playerint[player]->requestSent(request, requestID); diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 3560dff95..823fe721c 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -68,7 +68,7 @@ CConnection::CConnection(std::weak_ptr networkConnection) CConnection::~CConnection() = default; -void CConnection::sendPack(const CPack * pack) +void CConnection::sendPack(const CPack & pack) { boost::mutex::scoped_lock lock(writeMutex); @@ -78,18 +78,18 @@ void CConnection::sendPack(const CPack * pack) throw std::runtime_error("Attempt to send packet on a closed connection!"); packWriter->buffer.clear(); - *serializer & pack; + (*serializer) & (&pack); - logNetwork->trace("Sending a pack of type %s", typeid(*pack).name()); + logNetwork->trace("Sending a pack of type %s", typeid(pack).name()); connectionPtr->sendPacket(packWriter->buffer); packWriter->buffer.clear(); serializer->savedPointers.clear(); } -CPack * CConnection::retrievePack(const std::vector & data) +std::unique_ptr CConnection::retrievePack(const std::vector & data) { - CPack * result; + std::unique_ptr result; packReader->buffer = &data; packReader->position = 0; @@ -102,7 +102,7 @@ CPack * CConnection::retrievePack(const std::vector & data) if (packReader->position != data.size()) throw std::runtime_error("Failed to retrieve pack! Not all data has been read!"); - logNetwork->trace("Received CPack of type %s", typeid(*result).name()); + logNetwork->trace("Received CPack of type %s", typeid(result.get()).name()); deserializer->loadedPointers.clear(); deserializer->loadedSharedPointers.clear(); return result; diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 838bcba53..b62849894 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -51,8 +51,8 @@ public: explicit CConnection(std::weak_ptr networkConnection); ~CConnection(); - void sendPack(const CPack * pack); - CPack * retrievePack(const std::vector & data); + void sendPack(const CPack & pack); + std::unique_ptr retrievePack(const std::vector & data); void enterLobbyConnectionMode(); void setCallback(IGameCallback * cb); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 25b7c1c81..c859d81b3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -467,7 +467,7 @@ void CGameHandler::handleReceivedPack(CPackForServer * pack) applied.result = successfullyApplied; applied.packType = CTypeList::getInstance().getTypeID(pack); applied.requestID = pack->requestID; - pack->c->sendPack(&applied); + pack->c->sendPack(applied); }; if(isBlockedByQueries(pack, pack->player)) @@ -1438,7 +1438,7 @@ void CGameHandler::sendToAllClients(CPackForClient * pack) { logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name()); for (auto c : lobby->activeConnections) - c->sendPack(pack); + c->sendPack(*pack); } void CGameHandler::sendAndApply(CPackForClient * pack) From ee831c06e78858d5f564fc3a4ca7c751b113c752 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 13:41:53 +0000 Subject: [PATCH 269/726] Reduced usage of CPack, replaced with CPackForServer where applicable --- client/CServerHandler.h | 1 - client/Client.h | 1 - server/CGameHandler.cpp | 2 +- server/CGameHandler.h | 3 +-- server/queries/BattleQueries.cpp | 2 +- server/queries/BattleQueries.h | 2 +- server/queries/CQuery.cpp | 8 ++++---- server/queries/CQuery.h | 10 +++++----- server/queries/MapQueries.cpp | 6 +++--- server/queries/MapQueries.h | 6 +++--- server/queries/VisitQueries.cpp | 2 +- server/queries/VisitQueries.h | 2 +- 12 files changed, 21 insertions(+), 24 deletions(-) diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 1ef583e0c..a62aa8a45 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -25,7 +25,6 @@ struct TurnTimerInfo; class CMapInfo; class CGameState; struct ClientPlayer; -struct CPack; struct CPackForLobby; struct CPackForClient; diff --git a/client/Client.h b/client/Client.h index 7f14f2b82..b9e855ca5 100644 --- a/client/Client.h +++ b/client/Client.h @@ -16,7 +16,6 @@ VCMI_LIB_NAMESPACE_BEGIN -struct CPack; struct CPackForServer; class IBattleEventsReceiver; class CBattleGameInterface; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c859d81b3..aa29b9afc 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4008,7 +4008,7 @@ bool CGameHandler::isValidObject(const CGObjectInstance *obj) const return vstd::contains(gs->map->objects, obj); } -bool CGameHandler::isBlockedByQueries(const CPack *pack, PlayerColor player) +bool CGameHandler::isBlockedByQueries(const CPackForServer *pack, PlayerColor player) { if (dynamic_cast(pack) != nullptr) return false; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index e4df739a2..9e97fb716 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -27,7 +27,6 @@ class CCommanderInstance; class EVictoryLossCheckResult; class CRandomGenerator; -struct CPack; struct CPackForServer; struct NewTurn; struct CGarrisonOperationPack; @@ -89,7 +88,7 @@ public: CVCMIServer * gameLobby() const; bool isValidObject(const CGObjectInstance *obj) const; - bool isBlockedByQueries(const CPack *pack, PlayerColor player); + bool isBlockedByQueries(const CPackForServer *pack, PlayerColor player); bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2); void giveSpells(const CGTownInstance *t, const CGHeroInstance *h); diff --git a/server/queries/BattleQueries.cpp b/server/queries/BattleQueries.cpp index 30b06db91..52fcbd63b 100644 --- a/server/queries/BattleQueries.cpp +++ b/server/queries/BattleQueries.cpp @@ -49,7 +49,7 @@ CBattleQuery::CBattleQuery(CGameHandler * owner): belligerents[BattleSide::DEFENDER] = nullptr; } -bool CBattleQuery::blocksPack(const CPack * pack) const +bool CBattleQuery::blocksPack(const CPackForServer * pack) const { if(dynamic_cast(pack) != nullptr) return false; diff --git a/server/queries/BattleQueries.h b/server/queries/BattleQueries.h index ae32b08b3..3cb525a28 100644 --- a/server/queries/BattleQueries.h +++ b/server/queries/BattleQueries.h @@ -30,7 +30,7 @@ public: CBattleQuery(CGameHandler * owner); CBattleQuery(CGameHandler * owner, const IBattleInfo * Bi); //TODO void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; - bool blocksPack(const CPack *pack) const override; + bool blocksPack(const CPackForServer *pack) const override; void onRemoval(PlayerColor color) override; void onExposure(QueryPtr topQuery) override; }; diff --git a/server/queries/CQuery.cpp b/server/queries/CQuery.cpp index 3b9d8dd84..26a3b571c 100644 --- a/server/queries/CQuery.cpp +++ b/server/queries/CQuery.cpp @@ -81,7 +81,7 @@ void CQuery::onRemoval(PlayerColor color) } -bool CQuery::blocksPack(const CPack * pack) const +bool CQuery::blocksPack(const CPackForServer * pack) const { return false; } @@ -112,7 +112,7 @@ void CQuery::setReply(std::optional reply) } -bool CQuery::blockAllButReply(const CPack * pack) const +bool CQuery::blockAllButReply(const CPackForServer * pack) const { //We accept only query replies from correct player if(auto reply = dynamic_cast(pack)) @@ -132,7 +132,7 @@ bool CDialogQuery::endsByPlayerAnswer() const return true; } -bool CDialogQuery::blocksPack(const CPack * pack) const +bool CDialogQuery::blocksPack(const CPackForServer * pack) const { return blockAllButReply(pack); } @@ -149,7 +149,7 @@ CGenericQuery::CGenericQuery(CGameHandler * gh, PlayerColor color, std::function addPlayer(color); } -bool CGenericQuery::blocksPack(const CPack * pack) const +bool CGenericQuery::blocksPack(const CPackForServer * pack) const { return blockAllButReply(pack); } diff --git a/server/queries/CQuery.h b/server/queries/CQuery.h index 2fde16295..5cdae43d4 100644 --- a/server/queries/CQuery.h +++ b/server/queries/CQuery.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN -struct CPack; +struct CPackForServer; class CGObjectInstance; class CGHeroInstance; @@ -43,7 +43,7 @@ public: CQuery(CGameHandler * gh); /// query can block attempting actions by player. Eg. he can't move hero during the battle. - virtual bool blocksPack(const CPack *pack) const; + virtual bool blocksPack(const CPackForServer *pack) const; /// query is removed after player gives answer (like dialogs) virtual bool endsByPlayerAnswer() const; @@ -71,7 +71,7 @@ protected: QueriesProcessor * owner; CGameHandler * gh; void addPlayer(PlayerColor color); - bool blockAllButReply(const CPack * pack) const; + bool blockAllButReply(const CPackForServer * pack) const; }; std::ostream &operator<<(std::ostream &out, const CQuery &query); @@ -82,7 +82,7 @@ class CDialogQuery : public CQuery public: CDialogQuery(CGameHandler * owner); bool endsByPlayerAnswer() const override; - bool blocksPack(const CPack *pack) const override; + bool blocksPack(const CPackForServer *pack) const override; void setReply(std::optional reply) override; protected: std::optional answer; @@ -93,7 +93,7 @@ class CGenericQuery : public CQuery public: CGenericQuery(CGameHandler * gh, PlayerColor color, std::function)> Callback); - bool blocksPack(const CPack * pack) const override; + bool blocksPack(const CPackForServer * pack) const override; bool endsByPlayerAnswer() const override; void onExposure(QueryPtr topQuery) override; void setReply(std::optional reply) override; diff --git a/server/queries/MapQueries.cpp b/server/queries/MapQueries.cpp index 2881368d4..ff83948ad 100644 --- a/server/queries/MapQueries.cpp +++ b/server/queries/MapQueries.cpp @@ -23,7 +23,7 @@ TimerPauseQuery::TimerPauseQuery(CGameHandler * owner, PlayerColor player): addPlayer(player); } -bool TimerPauseQuery::blocksPack(const CPack *pack) const +bool TimerPauseQuery::blocksPack(const CPackForServer *pack) const { return blockAllButReply(pack); } @@ -58,7 +58,7 @@ CGarrisonDialogQuery::CGarrisonDialogQuery(CGameHandler * owner, const CArmedIns addPlayer(down->tempOwner); } -bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const +bool CGarrisonDialogQuery::blocksPack(const CPackForServer * pack) const { std::set ourIds; ourIds.insert(this->exchangingArmies[0]->id); @@ -143,7 +143,7 @@ void OpenWindowQuery::onExposure(QueryPtr topQuery) //do nothing - wait for reply } -bool OpenWindowQuery::blocksPack(const CPack *pack) const +bool OpenWindowQuery::blocksPack(const CPackForServer *pack) const { if (mode == EOpenWindowMode::RECRUITMENT_FIRST || mode == EOpenWindowMode::RECRUITMENT_ALL) { diff --git a/server/queries/MapQueries.h b/server/queries/MapQueries.h index 41b06d3b7..f202e11b7 100644 --- a/server/queries/MapQueries.h +++ b/server/queries/MapQueries.h @@ -25,7 +25,7 @@ class TimerPauseQuery : public CQuery public: TimerPauseQuery(CGameHandler * owner, PlayerColor player); - bool blocksPack(const CPack *pack) const override; + bool blocksPack(const CPackForServer *pack) const override; void onAdding(PlayerColor color) override; void onRemoval(PlayerColor color) override; bool endsByPlayerAnswer() const override; @@ -54,7 +54,7 @@ public: CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance *up, const CArmedInstance *down); void notifyObjectAboutRemoval(const CGObjectInstance * visitedObject, const CGHeroInstance * visitingHero) const override; - bool blocksPack(const CPack *pack) const override; + bool blocksPack(const CPackForServer *pack) const override; }; //yes/no and component selection dialogs @@ -75,7 +75,7 @@ class OpenWindowQuery : public CDialogQuery public: OpenWindowQuery(CGameHandler * owner, const CGHeroInstance *hero, EOpenWindowMode mode); - bool blocksPack(const CPack *pack) const override; + bool blocksPack(const CPackForServer *pack) const override; void onExposure(QueryPtr topQuery) override; }; diff --git a/server/queries/VisitQueries.cpp b/server/queries/VisitQueries.cpp index ea53b3648..36ff95291 100644 --- a/server/queries/VisitQueries.cpp +++ b/server/queries/VisitQueries.cpp @@ -24,7 +24,7 @@ VisitQuery::VisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const addPlayer(Hero->tempOwner); } -bool VisitQuery::blocksPack(const CPack * pack) const +bool VisitQuery::blocksPack(const CPackForServer * pack) const { //During the visit itself ALL actions are blocked. //(However, the visit may trigger a query above that'll pass some.) diff --git a/server/queries/VisitQueries.h b/server/queries/VisitQueries.h index d2f554a49..fe41df45f 100644 --- a/server/queries/VisitQueries.h +++ b/server/queries/VisitQueries.h @@ -26,7 +26,7 @@ public: const CGObjectInstance * visitedObject; const CGHeroInstance * visitingHero; - bool blocksPack(const CPack * pack) const final; + bool blocksPack(const CPackForServer * pack) const final; }; class MapObjectVisitQuery final : public VisitQuery From 786f80871ebb5b98554f94a9544957dbcb4ecc73 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 14:01:10 +0000 Subject: [PATCH 270/726] Replace more usages of pointers to packs with references --- CCallback.cpp | 76 ++++++++++++++++++------------------ CCallback.h | 2 +- client/CServerHandler.cpp | 2 +- client/Client.cpp | 26 ++++++------ client/Client.h | 4 +- lib/gameState/CGameState.cpp | 4 +- lib/gameState/CGameState.h | 2 +- server/CGameHandler.cpp | 4 +- test/game/CGameStateTest.cpp | 16 ++++---- 9 files changed, 68 insertions(+), 68 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 57462db27..77bf4d72c 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -29,20 +29,20 @@ bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where) { CastleTeleportHero pack(who->id, where->id, 1); - sendRequest(&pack); + sendRequest(pack); return true; } void CCallback::moveHero(const CGHeroInstance *h, const int3 & destination, bool transit) { MoveHero pack({destination}, h->id, transit); - sendRequest(&pack); + sendRequest(pack); } void CCallback::moveHero(const CGHeroInstance *h, const std::vector & path, bool transit) { MoveHero pack(path, h->id, transit); - sendRequest(&pack); + sendRequest(pack); } int CCallback::selectionMade(int selection, QueryID queryID) @@ -61,7 +61,7 @@ int CCallback::sendQueryReply(std::optional reply, QueryID queryID) QueryReply pack(queryID, reply); pack.player = *player; - return sendRequest(&pack); + return sendRequest(pack); } void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level) @@ -71,7 +71,7 @@ void CCallback::recruitCreatures(const CGDwelling * obj, const CArmedInstance * return; RecruitCreatures pack(obj->id, dst->id, ID, amount, level); - sendRequest(&pack); + sendRequest(pack); } bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos) @@ -80,14 +80,14 @@ bool CCallback::dismissCreature(const CArmedInstance *obj, SlotID stackPos) return false; DisbandCreature pack(stackPos,obj->id); - sendRequest(&pack); + sendRequest(pack); return true; } bool CCallback::upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID) { UpgradeCreature pack(stackPos,obj->id,newID); - sendRequest(&pack); + sendRequest(pack); return false; } @@ -95,54 +95,54 @@ void CCallback::endTurn() { logGlobal->trace("Player %d ended his turn.", player->getNum()); EndTurn pack; - sendRequest(&pack); + sendRequest(pack); } int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) { ArrangeStacks pack(1,p1,p2,s1->id,s2->id,0); - sendRequest(&pack); + sendRequest(pack); return 0; } int CCallback::mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) { ArrangeStacks pack(2,p1,p2,s1->id,s2->id,0); - sendRequest(&pack); + sendRequest(pack); return 0; } int CCallback::splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val) { ArrangeStacks pack(3,p1,p2,s1->id,s2->id,val); - sendRequest(&pack); + sendRequest(pack); return 0; } int CCallback::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destArmy, SlotID srcSlot) { BulkMoveArmy pack(srcArmy, destArmy, srcSlot); - sendRequest(&pack); + sendRequest(pack); return 0; } int CCallback::bulkSplitStack(ObjectInstanceID armyId, SlotID srcSlot, int howMany) { BulkSplitStack pack(armyId, srcSlot, howMany); - sendRequest(&pack); + sendRequest(pack); return 0; } int CCallback::bulkSmartSplitStack(ObjectInstanceID armyId, SlotID srcSlot) { BulkSmartSplitStack pack(armyId, srcSlot); - sendRequest(&pack); + sendRequest(pack); return 0; } int CCallback::bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) { BulkMergeStacks pack(armyId, srcSlot); - sendRequest(&pack); + sendRequest(pack); return 0; } @@ -151,7 +151,7 @@ bool CCallback::dismissHero(const CGHeroInstance *hero) if(player!=hero->tempOwner) return false; DismissHero pack(hero->id); - sendRequest(&pack); + sendRequest(pack); return true; } @@ -160,7 +160,7 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation ExchangeArtifacts ea; ea.src = l1; ea.dst = l2; - sendRequest(&ea); + sendRequest(ea); return true; } @@ -175,13 +175,13 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation void CCallback::assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) { AssembleArtifacts aa(heroID, artifactSlot, assemble, assembleTo); - sendRequest(&aa); + sendRequest(aa); } void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap, bool equipped, bool backpack) { BulkExchangeArtifacts bma(srcHero, dstHero, swap, equipped, backpack); - sendRequest(&bma); + sendRequest(bma); } void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left) @@ -189,7 +189,7 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left) ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SCROLL_RIGHT); if(left) mba.cmd = ManageBackpackArtifacts::ManageCmd::SCROLL_LEFT; - sendRequest(&mba); + sendRequest(mba); } void CCallback::sortBackpackArtifactsBySlot(const ObjectInstanceID hero) @@ -213,13 +213,13 @@ void CCallback::sortBackpackArtifactsByClass(const ObjectInstanceID hero) void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) { ManageEquippedArtifacts mea(hero, costumeIndex, saveCostume); - sendRequest(&mea); + sendRequest(mea); } void CCallback::eraseArtifactByClient(const ArtifactLocation & al) { EraseArtifactByClient ea(al); - sendRequest(&ea); + sendRequest(ea); } bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) @@ -231,7 +231,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) return false; BuildStructure pack(town->id,buildingID); - sendRequest(&pack); + sendRequest(pack); return true; } @@ -241,7 +241,7 @@ bool CCallback::visitTownBuilding(const CGTownInstance *town, BuildingID buildin return false; VisitTownBuilding pack(town->id, buildingID); - sendRequest(&pack); + sendRequest(pack); return true; } @@ -250,10 +250,10 @@ void CBattleCallback::battleMakeSpellAction(const BattleID & battleID, const Bat assert(action.actionType == EActionType::HERO_SPELL); MakeAction mca(action); mca.battleID = battleID; - sendRequest(&mca); + sendRequest(mca); } -int CBattleCallback::sendRequest(const CPackForServer * request) +int CBattleCallback::sendRequest(const CPackForServer & request) { int requestID = cl->sendRequest(request, *getPlayerID()); if(waitTillRealize) @@ -278,7 +278,7 @@ void CCallback::swapGarrisonHero( const CGTownInstance *town ) if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player )) { GarrisonHeroSwap pack(town->id); - sendRequest(&pack); + sendRequest(pack); } } @@ -287,7 +287,7 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid) if(hero->tempOwner != *player) return; BuyArtifact pack(hero->id,aid); - sendRequest(&pack); + sendRequest(pack); } void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero) @@ -304,13 +304,13 @@ void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const s pack.r1 = id1; pack.r2 = id2; pack.val = val1; - sendRequest(&pack); + sendRequest(pack); } void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode) { SetFormation pack(hero->id, mode); - sendRequest(&pack); + sendRequest(pack); } void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero) @@ -320,7 +320,7 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero); pack.player = *player; - sendRequest(&pack); + sendRequest(pack); } void CCallback::save( const std::string &fname ) @@ -334,7 +334,7 @@ void CCallback::gamePause(bool pause) { GamePause pack; pack.player = *player; - sendRequest(&pack); + sendRequest(pack); } else { @@ -348,14 +348,14 @@ void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * cu PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1)); if(player) pm.player = *player; - sendRequest(&pm); + sendRequest(pm); } void CCallback::buildBoat( const IShipyard *obj ) { BuildBoat bb; bb.objid = dynamic_cast(obj)->id; - sendRequest(&bb); + sendRequest(bb); } CCallback::CCallback(CGameState * GS, std::optional Player, CClient * C) @@ -397,7 +397,7 @@ void CCallback::dig( const CGObjectInstance *hero ) { DigWithHero dwh; dwh.id = hero->id; - sendRequest(&dwh); + sendRequest(dwh); } void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int3 &pos) @@ -406,7 +406,7 @@ void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int cas.hid = hero->id; cas.sid = spellID; cas.pos = pos; - sendRequest(&cas); + sendRequest(cas); } int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) @@ -439,7 +439,7 @@ void CBattleCallback::battleMakeUnitAction(const BattleID & battleID, const Batt MakeAction ma; ma.ba = action; ma.battleID = battleID; - sendRequest(&ma); + sendRequest(ma); } void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const BattleAction & action ) @@ -448,7 +448,7 @@ void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const Ba MakeAction ma; ma.ba = action; ma.battleID = battleID; - sendRequest(&ma); + sendRequest(ma); } std::optional CBattleCallback::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) diff --git a/CCallback.h b/CCallback.h index 1557b9254..39b9fc8ce 100644 --- a/CCallback.h +++ b/CCallback.h @@ -127,7 +127,7 @@ class CBattleCallback : public IBattleCallback std::optional player; protected: - int sendRequest(const CPackForServer * request); //returns requestID (that'll be matched to requestID in PackageApplied) + int sendRequest(const CPackForServer & request); //returns requestID (that'll be matched to requestID in PackageApplied) CClient *cl; public: diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 6214e6380..dcba9897f 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -938,7 +938,7 @@ void CServerHandler::visitForLobby(CPackForLobby & lobbyPack) void CServerHandler::visitForClient(CPackForClient & clientPack) { - client->handlePack(&clientPack); + client->handlePack(clientPack); } diff --git a/client/Client.cpp b/client/Client.cpp index c6b024198..0f55cbd52 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -163,7 +163,7 @@ void CClient::save(const std::string & fname) } SaveGame save_game(fname); - sendRequest(&save_game, PlayerColor::NEUTRAL); + sendRequest(save_game, PlayerColor::NEUTRAL); } void CClient::endNetwork() @@ -348,35 +348,35 @@ void CClient::installNewBattleInterface(std::shared_ptr ba } } -void CClient::handlePack(CPackForClient * pack) +void CClient::handlePack(CPackForClient & pack) { ApplyClientNetPackVisitor afterVisitor(*this, *gameState()); ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState()); - pack->visit(beforeVisitor); - logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name()); + pack.visit(beforeVisitor); + logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name()); { boost::unique_lock lock(CGameState::mutex); gs->apply(pack); } - logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name()); - pack->visit(afterVisitor); - logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name()); + logNetwork->trace("\tApplied on gs: %s", typeid(pack).name()); + pack.visit(afterVisitor); + logNetwork->trace("\tMade second apply on cl: %s", typeid(pack).name()); } -int CClient::sendRequest(const CPackForServer * request, PlayerColor player) +int CClient::sendRequest(const CPackForServer & request, PlayerColor player) { static ui32 requestCounter = 1; ui32 requestID = requestCounter++; - logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(*request).name(), requestID); + logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(request).name(), requestID); waitingRequest.pushBack(requestID); - request->requestID = requestID; - request->player = player; - CSH->logicConnection->sendPack(*request); + request.requestID = requestID; + request.player = player; + CSH->logicConnection->sendPack(request); if(vstd::contains(playerint, player)) - playerint[player]->requestSent(request, requestID); + playerint[player]->requestSent(&request, requestID); return requestID; } diff --git a/client/Client.h b/client/Client.h index b9e855ca5..a01d893aa 100644 --- a/client/Client.h +++ b/client/Client.h @@ -142,8 +142,8 @@ public: static ThreadSafeVector waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction) - 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 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); void battleFinished(const BattleID & battleID); diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index a378a796d..e35432cef 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1143,9 +1143,9 @@ PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor return PlayerRelations::ENEMIES; } -void CGameState::apply(CPackForClient *pack) +void CGameState::apply(CPackForClient & pack) { - pack->applyGs(this); + pack.applyGs(this); } void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out) diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index d247eb47b..a149575b4 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -98,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(CPackForClient *pack); + void apply(CPackForClient & pack); BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand); void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index aa29b9afc..765cf2d12 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1444,8 +1444,8 @@ void CGameHandler::sendToAllClients(CPackForClient * pack) void CGameHandler::sendAndApply(CPackForClient * pack) { sendToAllClients(pack); - gs->apply(pack); - logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name()); + gs->apply(*pack); + logNetwork->trace("\tApplied on gs: %s", typeid(pack).name()); } void CGameHandler::sendAndApply(CGarrisonOperationPack * pack) diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index be71314fc..b720fdfd6 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -65,42 +65,42 @@ public: void apply(CPackForClient * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(BattleLogMessage * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(BattleStackMoved * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(BattleUnitsChanged * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(SetStackEffect * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(StacksInjured * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(BattleObstaclesChanged * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void apply(CatapultAttack * pack) override { - gameState->apply(pack); + gameState->apply(*pack); } void complain(const std::string & problem) override From 48fb58e7a0bbffb5f1f96cf503c6f5b7928605fe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 18:32:38 +0000 Subject: [PATCH 271/726] Replace few more pointers with references, remove manual delete call --- server/CGameHandler.cpp | 19 +++++++++---------- server/CGameHandler.h | 2 +- server/CVCMIServer.cpp | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 765cf2d12..f091d5e21 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -457,20 +457,20 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr c) } } -void CGameHandler::handleReceivedPack(CPackForServer * pack) +void CGameHandler::handleReceivedPack(CPackForServer & pack) { //prepare struct informing that action was applied auto sendPackageResponse = [&](bool successfullyApplied) { PackageApplied applied; - applied.player = pack->player; + applied.player = pack.player; applied.result = successfullyApplied; - applied.packType = CTypeList::getInstance().getTypeID(pack); - applied.requestID = pack->requestID; - pack->c->sendPack(applied); + applied.packType = CTypeList::getInstance().getTypeID(&pack); + applied.requestID = pack.requestID; + pack.c->sendPack(applied); }; - if(isBlockedByQueries(pack, pack->player)) + if(isBlockedByQueries(&pack, pack.player)) { sendPackageResponse(false); } @@ -480,7 +480,7 @@ void CGameHandler::handleReceivedPack(CPackForServer * pack) try { ApplyGhNetPackVisitor applier(*this); - pack->visit(applier); + pack.visit(applier); result = applier.getResult(); } catch(ExceptionNotAllowedAction &) @@ -489,14 +489,13 @@ void CGameHandler::handleReceivedPack(CPackForServer * pack) } if(result) - logGlobal->trace("Message %s successfully applied!", typeid(*pack).name()); + logGlobal->trace("Message %s successfully applied!", typeid(pack).name()); else complain((boost::format("Got false in applying %s... that request must have been fishy!") - % typeid(*pack).name()).str()); + % typeid(pack).name()).str()); sendPackageResponse(true); } - vstd::clear_pointer(pack); } CGameHandler::CGameHandler(CVCMIServer * lobby) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 9e97fb716..fae1b5358 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -194,7 +194,7 @@ public: void init(StartInfo *si, Load::ProgressAccumulator & progressTracking); void handleClientDisconnection(std::shared_ptr c); - void handleReceivedPack(CPackForServer * pack); + void handleReceivedPack(CPackForServer & pack); bool hasPlayerAt(PlayerColor player, std::shared_ptr c) const; bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index bc4d24481..cda1ec10d 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -55,7 +55,7 @@ public: void visitForServer(CPackForServer & serverPack) override { if (gh) - gh->handleReceivedPack(&serverPack); + gh->handleReceivedPack(serverPack); else logNetwork->error("Received pack for game server while in lobby!"); } From c0f5c7c0ea35026208c6ce91428e674158c44faa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 4 Oct 2024 18:59:51 +0000 Subject: [PATCH 272/726] Replace pointer with reference in pack apply functions --- AI/BattleAI/StackWithBonuses.cpp | 30 +-- AI/BattleAI/StackWithBonuses.h | 16 +- client/Client.h | 2 +- include/vcmi/ServerCallback.h | 16 +- lib/CStack.cpp | 2 +- lib/IGameCallback.h | 2 +- lib/battle/CBattleInfoCallback.cpp | 2 +- lib/mapObjects/CBank.cpp | 2 +- lib/mapObjects/CGDwelling.cpp | 14 +- lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/mapObjects/CGMarket.cpp | 2 +- lib/mapObjects/CGTownInstance.cpp | 4 +- lib/mapObjects/CQuest.cpp | 8 +- lib/mapObjects/CRewardableObject.cpp | 8 +- lib/mapObjects/IObjectInterface.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 14 +- lib/spells/AdventureSpellMechanics.cpp | 36 ++-- lib/spells/BattleSpellMechanics.cpp | 8 +- lib/spells/effects/Catapult.cpp | 6 +- lib/spells/effects/Clone.cpp | 6 +- lib/spells/effects/Damage.cpp | 4 +- lib/spells/effects/DemonSummon.cpp | 2 +- lib/spells/effects/Dispel.cpp | 4 +- lib/spells/effects/Heal.cpp | 4 +- lib/spells/effects/Moat.cpp | 4 +- lib/spells/effects/Obstacle.cpp | 2 +- lib/spells/effects/RemoveObstacle.cpp | 2 +- lib/spells/effects/Sacrifice.cpp | 2 +- lib/spells/effects/Summon.cpp | 2 +- lib/spells/effects/Teleport.cpp | 2 +- lib/spells/effects/Timed.cpp | 4 +- server/CGameHandler.cpp | 212 +++++++++---------- server/CGameHandler.h | 10 +- server/CVCMIServer.cpp | 4 +- server/ServerSpellCastEnvironment.cpp | 18 +- server/ServerSpellCastEnvironment.h | 16 +- server/TurnTimerHandler.cpp | 2 +- server/battles/BattleActionProcessor.cpp | 32 +-- server/battles/BattleFlowProcessor.cpp | 28 +-- server/battles/BattleProcessor.cpp | 12 +- server/battles/BattleResultProcessor.cpp | 24 +-- server/processors/HeroPoolProcessor.cpp | 10 +- server/processors/NewTurnProcessor.cpp | 12 +- server/processors/PlayerMessageProcessor.cpp | 24 +-- server/processors/TurnOrderProcessor.cpp | 4 +- server/queries/MapQueries.cpp | 4 +- test/game/CGameStateTest.cpp | 44 ++-- test/mock/BattleFake.h | 4 +- test/mock/mock_IGameCallback.cpp | 2 +- test/mock/mock_IGameCallback.h | 2 +- test/mock/mock_ServerCallback.h | 16 +- test/spells/effects/CatapultTest.cpp | 2 +- test/spells/effects/CloneTest.cpp | 4 +- test/spells/effects/DamageTest.cpp | 6 +- test/spells/effects/DispelTest.cpp | 2 +- test/spells/effects/EffectFixture.cpp | 14 +- test/spells/effects/HealTest.cpp | 4 +- test/spells/effects/SacrificeTest.cpp | 4 +- test/spells/effects/SummonTest.cpp | 4 +- test/spells/effects/TeleportTest.cpp | 2 +- test/spells/effects/TimedTest.cpp | 2 +- 61 files changed, 369 insertions(+), 369 deletions(-) diff --git a/AI/BattleAI/StackWithBonuses.cpp b/AI/BattleAI/StackWithBonuses.cpp index fbecaf541..149eb33f5 100644 --- a/AI/BattleAI/StackWithBonuses.cpp +++ b/AI/BattleAI/StackWithBonuses.cpp @@ -531,44 +531,44 @@ vstd::RNG * HypotheticBattle::HypotheticServerCallback::getRNG() return &rngStub; } -void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient * pack) +void HypotheticBattle::HypotheticServerCallback::apply(CPackForClient & pack) { logAi->error("Package of type %s is not allowed in battle evaluation", typeid(pack).name()); } -void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage * pack) +void HypotheticBattle::HypotheticServerCallback::apply(BattleLogMessage & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } -void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved * pack) +void HypotheticBattle::HypotheticServerCallback::apply(BattleStackMoved & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } -void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged * pack) +void HypotheticBattle::HypotheticServerCallback::apply(BattleUnitsChanged & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } -void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect * pack) +void HypotheticBattle::HypotheticServerCallback::apply(SetStackEffect & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } -void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured * pack) +void HypotheticBattle::HypotheticServerCallback::apply(StacksInjured & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } -void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged * pack) +void HypotheticBattle::HypotheticServerCallback::apply(BattleObstaclesChanged & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } -void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack * pack) +void HypotheticBattle::HypotheticServerCallback::apply(CatapultAttack & pack) { - pack->applyBattle(owner); + pack.applyBattle(owner); } HypotheticBattle::HypotheticEnvironment::HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment) diff --git a/AI/BattleAI/StackWithBonuses.h b/AI/BattleAI/StackWithBonuses.h index f5b4fac23..3a34cc761 100644 --- a/AI/BattleAI/StackWithBonuses.h +++ b/AI/BattleAI/StackWithBonuses.h @@ -189,15 +189,15 @@ private: vstd::RNG * getRNG() override; - void apply(CPackForClient * pack) override; + void apply(CPackForClient & pack) override; - void apply(BattleLogMessage * pack) override; - void apply(BattleStackMoved * pack) override; - void apply(BattleUnitsChanged * pack) override; - void apply(SetStackEffect * pack) override; - void apply(StacksInjured * pack) override; - void apply(BattleObstaclesChanged * pack) override; - void apply(CatapultAttack * pack) override; + void apply(BattleLogMessage & pack) override; + void apply(BattleStackMoved & pack) override; + void apply(BattleUnitsChanged & pack) override; + void apply(SetStackEffect & pack) override; + void apply(StacksInjured & pack) override; + void apply(BattleObstaclesChanged & pack) override; + void apply(CatapultAttack & pack) override; private: HypotheticBattle * owner; RNGStub rngStub; diff --git a/client/Client.h b/client/Client.h index a01d893aa..f8c7eaf12 100644 --- a/client/Client.h +++ b/client/Client.h @@ -204,7 +204,7 @@ public: void setManaPoints(ObjectInstanceID hid, int val) override {}; void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {}; void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {}; - void sendAndApply(CPackForClient * pack) override {}; + void sendAndApply(CPackForClient & pack) override {}; void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {}; void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {}; diff --git a/include/vcmi/ServerCallback.h b/include/vcmi/ServerCallback.h index d69257009..1267e885e 100644 --- a/include/vcmi/ServerCallback.h +++ b/include/vcmi/ServerCallback.h @@ -36,15 +36,15 @@ public: virtual vstd::RNG * getRNG() = 0; - virtual void apply(CPackForClient * pack) = 0; + virtual void apply(CPackForClient & pack) = 0; - virtual void apply(BattleLogMessage * pack) = 0; - virtual void apply(BattleStackMoved * pack) = 0; - virtual void apply(BattleUnitsChanged * pack) = 0; - virtual void apply(SetStackEffect * pack) = 0; - virtual void apply(StacksInjured * pack) = 0; - virtual void apply(BattleObstaclesChanged * pack) = 0; - virtual void apply(CatapultAttack * pack) = 0; + virtual void apply(BattleLogMessage & pack) = 0; + virtual void apply(BattleStackMoved & pack) = 0; + virtual void apply(BattleUnitsChanged & pack) = 0; + virtual void apply(SetStackEffect & pack) = 0; + virtual void apply(StacksInjured & pack) = 0; + virtual void apply(BattleObstaclesChanged & pack) = 0; + virtual void apply(CatapultAttack & pack) = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 68e80ddd9..61e177047 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -401,7 +401,7 @@ void CStack::spendMana(ServerCallback * server, const int spellCost) const ssp.which = BattleSetStackProperty::CASTS; ssp.val = -spellCost; ssp.absolute = false; - server->apply(&ssp); + server->apply(ssp); } VCMI_LIB_NAMESPACE_END diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 9c9b2b8a6..fc1f104c3 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -140,7 +140,7 @@ public: virtual void setManaPoints(ObjectInstanceID hid, int val)=0; virtual void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) = 0; virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)=0; - virtual void sendAndApply(CPackForClient * pack) = 0; + virtual void sendAndApply(CPackForClient & pack) = 0; virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) = 0; virtual void changeFogOfWar(const std::unordered_set &tiles, PlayerColor player, ETileVisibility mode) = 0; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index b8d7e881f..05afc364c 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -927,7 +927,7 @@ bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & s bocp.battleID = getBattle()->getBattleID(); bocp.changes.emplace_back(spellObstacle.uniqueID, operation); changedObstacle.toInfo(bocp.changes.back(), operation); - spellEnv.apply(&bocp); + spellEnv.apply(bocp); }; const auto side = unit.unitSide(); auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, side); diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index ac65f7fa8..86ab8c876 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -138,7 +138,7 @@ bool CBank::wasVisited (PlayerColor player) const void CBank::onHeroVisit(const CGHeroInstance * h) const { ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id); - cb->sendAndApply(&cov); + cb->sendAndApply(cov); BlockingDialog bd(true, false); bd.player = h->getOwner(); diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 1ec3e492c..51dd897e3 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -224,7 +224,7 @@ void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const iw.player = h->tempOwner; iw.text.appendLocalString(EMetaText::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. iw.text.replaceName(ID); - cb->sendAndApply(&iw); + cb->sendAndApply(iw); return; } @@ -324,7 +324,7 @@ void CGDwelling::newTurn(vstd::RNG & rand) const } if(change) - cb->sendAndApply(&sac); + cb->sendAndApply(sac); updateGuards(); } @@ -392,7 +392,7 @@ void CGDwelling::updateGuards() const csc.slot = slot; csc.count = crea->getGrowth() * 3; csc.absoluteValue = true; - cb->sendAndApply(&csc); + cb->sendAndApply(csc); } else //slot is empty, create whole new stack { @@ -401,7 +401,7 @@ void CGDwelling::updateGuards() const ns.slot = slot; ns.type = crea->getId(); ns.count = crea->getGrowth() * 3; - cb->sendAndApply(&ns); + cb->sendAndApply(ns); } } } @@ -458,7 +458,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const iw.text.replaceNamePlural(crid); cb->showInfoDialog(&iw); - cb->sendAndApply(&sac); + cb->sendAndApply(sac); cb->addToSlot(StackLocation(h, slot), crs, count); } } @@ -469,7 +469,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const iw.text.appendLocalString(EMetaText::GENERAL_TXT, 422); //There are no %s here to recruit. iw.text.replaceNamePlural(crid); iw.player = h->tempOwner; - cb->sendAndApply(&iw); + cb->sendAndApply(iw); } } else @@ -483,7 +483,7 @@ void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart - cb->sendAndApply(&sac); + cb->sendAndApply(sac); } auto windowMode = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) ? EOpenWindowMode::RECRUITMENT_FIRST : EOpenWindowMode::RECRUITMENT_ALL; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index ce5653743..53426d09b 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -804,7 +804,7 @@ void CGHeroInstance::spendMana(ServerCallback * server, const int spellCost) con sm.hid = id; sm.val = -spellCost; - server->apply(&sm); + server->apply(sm); } } diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 448eedf20..b2a44455c 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -93,7 +93,7 @@ void CGBlackMarket::newTurn(vstd::RNG & rand) const SetAvailableArtifacts saa; saa.id = id; cb->pickAllowedArtsSet(saa.arts, rand); - cb->sendAndApply(&saa); + cb->sendAndApply(saa); } std::vector CGUniversity::availableItemsIds(EMarketMode mode) const diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index fc968ffb5..9d28aee82 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -350,7 +350,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const scp.heroid = h->id; scp.which = SetCommanderProperty::ALIVE; scp.amount = 1; - cb->sendAndApply(&scp); + cb->sendAndApply(scp); } cb->heroVisitCastle(this, h); // TODO(vmarkovtsev): implement payment for rising the commander @@ -631,7 +631,7 @@ void CGTownInstance::removeCapitols(const PlayerColor & owner) const rs.tid = id; rs.bid.insert(BuildingID::CAPITOL); rs.destroyed = destroyed; - cb->sendAndApply(&rs); + cb->sendAndApply(rs); return; } } diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 1cdfa40e3..7e1e00298 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -588,7 +588,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const AddQuest aq; aq.quest = QuestInfo (quest, this, visitablePos()); aq.player = h->tempOwner; - cb->sendAndApply(&aq); //TODO: merge with setObjProperty? + cb->sendAndApply(aq); //TODO: merge with setObjProperty? } if(firstVisit || failRequirements) @@ -811,7 +811,7 @@ void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const cow.mode = ChangeObjectVisitors::VISITOR_GLOBAL; cow.hero = h->id; cow.object = id; - cb->sendAndApply(&cow); + cb->sendAndApply(cow); txt_id=19; } else @@ -860,7 +860,7 @@ void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const AddQuest aq; aq.quest = QuestInfo (quest, this, visitablePos()); aq.player = h->tempOwner; - cb->sendAndApply (&aq); + cb->sendAndApply(aq); //TODO: add this quest only once OR check for multiple instances later } } @@ -885,7 +885,7 @@ void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passabili AddQuest aq; aq.quest = QuestInfo (quest, this, visitablePos()); aq.player = h->tempOwner; - cb->sendAndApply (&aq); + cb->sendAndApply(aq); } } diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 86c43cc8d..80a9a5603 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -35,7 +35,7 @@ const IObjectInterface * CRewardableObject::getObject() const void CRewardableObject::markAsScouted(const CGHeroInstance * hero) const { ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, hero->id); - cb->sendAndApply(&cov); + cb->sendAndApply(cov); } bool CRewardableObject::isGuarded() const @@ -48,7 +48,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const if(!wasScouted(hero->getOwner())) { ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_SCOUTED, id, hero->id); - cb->sendAndApply(&cov); + cb->sendAndApply(cov); } if (isGuarded()) @@ -116,7 +116,7 @@ void CRewardableObject::markAsVisited(const CGHeroInstance * hero) const cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, true); ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_HERO, id, hero->id); - cb->sendAndApply(&cov); + cb->sendAndApply(cov); } void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const @@ -336,7 +336,7 @@ void CRewardableObject::newTurn(vstd::RNG & rand) const { cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, false); ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_CLEAR, id); - cb->sendAndApply(&cov); + cb->sendAndApply(cov); } } } diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index 11d9e294c..432b9e35d 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -28,7 +28,7 @@ void IObjectInterface::showInfoDialog(const ui32 txtID, const ui16 soundID, EInf iw.player = getOwner(); iw.type = mode; iw.text.appendLocalString(EMetaText::ADVOB_TXT,txtID); - cb->sendAndApply(&iw); + cb->sendAndApply(iw); } ///IObjectInterface diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 9c01e64aa..9956396f3 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1087,14 +1087,14 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const for(const auto & eye : eyes) { cb->getTilesInRange (fw.tiles, eye->pos, 10, ETileVisibility::HIDDEN, h->tempOwner); - cb->sendAndApply(&fw); + cb->sendAndApply(fw); cv.pos = eye->pos; - cb->sendAndApply(&cv); + cb->sendAndApply(cv); } cv.pos = h->visitablePos(); cv.focusTime = 0; - cb->sendAndApply(&cv); + cb->sendAndApply(cv); } } else if (ID == Obj::EYE_OF_MAGI) @@ -1258,7 +1258,7 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const if(!wasVisited(team)) { iw.text.appendLocalString(EMetaText::ADVOB_TXT, 96); - cb->sendAndApply(&iw); + cb->sendAndApply(iw); // increment general visited obelisks counter cb->setObjPropertyID(id, ObjProperty::OBELISK_VISITED, team); @@ -1273,7 +1273,7 @@ void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const else { iw.text.appendLocalString(EMetaText::ADVOB_TXT, 97); - cb->sendAndApply(&iw); + cb->sendAndApply(iw); } } @@ -1341,7 +1341,7 @@ void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const rb.whoID = oldOwner; rb.source = BonusSource::OBJECT_INSTANCE; rb.id = BonusSourceID(id); - cb->sendAndApply(&rb); + cb->sendAndApply(rb); } } } @@ -1372,7 +1372,7 @@ void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const if(onInit) gb.applyGs(cb->gameState()); else - cb->sendAndApply(&gb); + cb->sendAndApply(gb); } void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler) diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index c7785d522..decfa7140 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -105,7 +105,7 @@ ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(SpellCastEnviron GiveBonus gb; gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId()); gb.bonus = b; - env->apply(&gb); + env->apply(gb); } return ESpellCastResult::OK; @@ -136,7 +136,7 @@ void AdventureSpellMechanics::performCast(SpellCastEnvironment * env, const Adve AdvmapSpellCast asc; asc.casterID = ObjectInstanceID(parameters.caster->getCasterUnitId()); asc.spellID = owner->id; - env->apply(&asc); + env->apply(asc); ESpellCastResult result = applyAdventureEffects(env, parameters); @@ -194,7 +194,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 336); //%s tried to summon a boat, but failed. parameters.caster->getCasterName(iw.text); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::OK; } @@ -226,14 +226,14 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment cop.objid = nearest->id; cop.nPos = summonPos; cop.initiator = parameters.caster->getCasterOwner(); - env->apply(&cop); + env->apply(cop); } else if(schoolLevel < 2) //none or basic level -> cannot create boat :( { InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 335); //There are no boats to summon. - env->apply(&iw); + env->apply(iw); return ESpellCastResult::ERROR; } else //create boat @@ -282,7 +282,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed parameters.caster->getCasterName(iw.text); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::OK; } @@ -291,7 +291,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen RemoveObject ro; ro.initiator = parameters.caster->getCasterOwner(); ro.objectID = t.visitableObjects.back()->id; - env->apply(&ro); + env->apply(ro); return ESpellCastResult::OK; } @@ -400,14 +400,14 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm { // SOD: DD to such "wrong" terrain results in mana and move points spending, but fails to move hero iw.text = MetaString::createFromTextID("core.genrltxt.70"); // Dimension Door failed! - env->apply(&iw); + env->apply(iw); // no return - resources will be spent } else { // HotA: game will show error message without taking mana or move points, even when DD into terra incognita iw.text = MetaString::createFromTextID("vcmi.dimensionDoor.seaToLandError"); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::CANCEL; } } @@ -415,7 +415,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm GiveBonus gb; gb.id = ObjectInstanceID(parameters.caster->getCasterUnitId()); gb.bonus = Bonus(BonusDuration::ONE_DAY, BonusType::NONE, BonusSource::SPELL_EFFECT, 0, BonusSourceID(owner->id)); - env->apply(&gb); + env->apply(gb); SetMovePoints smp; smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId()); @@ -423,7 +423,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm smp.val = parameters.caster->getHeroCaster()->movementPointsRemaining() - movementCost; else smp.val = 0; - env->apply(&smp); + env->apply(smp); return ESpellCastResult::OK; } @@ -471,7 +471,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 123); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::CANCEL; } } @@ -539,7 +539,7 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(SpellCastEnvironment InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 135); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::ERROR; } @@ -568,7 +568,7 @@ void TownPortalMechanics::endCast(SpellCastEnvironment * env, const AdventureSpe SetMovePoints smp; smp.hid = ObjectInstanceID(parameters.caster->getCasterUnitId()); smp.val = std::max(0, parameters.caster->getHeroCaster()->movementPointsRemaining() - moveCost); - env->apply(&smp); + env->apply(smp); } } @@ -587,7 +587,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::CANCEL; } @@ -598,7 +598,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 125); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::CANCEL; } @@ -643,7 +643,7 @@ ESpellCastResult TownPortalMechanics::beginCast(SpellCastEnvironment * env, cons InfoWindow iw; iw.player = parameters.caster->getCasterOwner(); iw.text.appendLocalString(EMetaText::GENERAL_TXT, 124); - env->apply(&iw); + env->apply(iw); return ESpellCastResult::CANCEL; } @@ -737,7 +737,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(SpellCastEnvironment * env } pack.showTerrain = showTerrain(spellLevel); - env->apply(&pack); + env->apply(pack); return ESpellCastResult::OK; } diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index fa486002c..f6585d8bb 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -353,9 +353,9 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target) sc.affectedCres.insert(unit->unitId()); if(!castDescription.lines.empty()) - server->apply(&castDescription); + server->apply(castDescription); - server->apply(&sc); + server->apply(sc); for(auto & p : effectsToApply) p.first->apply(server, this, p.second); @@ -375,7 +375,7 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target) // temporary(?) workaround to force animations to trigger StacksInjured fakeEvent; fakeEvent.battleID = battle()->getBattle()->getBattleID(); - server->apply(&fakeEvent); + server->apply(fakeEvent); } void BattleSpellMechanics::beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target) @@ -491,7 +491,7 @@ void BattleSpellMechanics::doRemoveEffects(ServerCallback * server, const std::v } if(!sse.toRemove.empty()) - server->apply(&sse); + server->apply(sse); } bool BattleSpellMechanics::counteringSelector(const Bonus * bonus) const diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 49737dc78..28ec7998e 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -104,7 +104,7 @@ void Catapult::applyMassive(ServerCallback * server, const Mechanics * m) const attackInfo->damageDealt += getRandomDamage(server); } } - server->apply(&ca); + server->apply(ca); removeTowerShooters(server, m); } @@ -144,7 +144,7 @@ void Catapult::applyTargeted(ServerCallback * server, const Mechanics * m, const ca.battleID = m->battle()->getBattle()->getBattleID(); ca.attacker = m->caster->getHeroCaster() ? -1 : m->caster->getCasterUnitId(); ca.attackedParts.push_back(attack); - server->apply(&ca); + server->apply(ca); removeTowerShooters(server, m); } } @@ -228,7 +228,7 @@ void Catapult::removeTowerShooters(ServerCallback * server, const Mechanics * m) } if(!removeUnits.changedStacks.empty()) - server->apply(&removeUnits); + server->apply(removeUnits); } std::vector Catapult::getPotentialTargets(const Mechanics * m, bool bypassGateCheck, bool bypassTowerCheck) const diff --git a/lib/spells/effects/Clone.cpp b/lib/spells/effects/Clone.cpp index 86e3085d0..03f35ff9d 100644 --- a/lib/spells/effects/Clone.cpp +++ b/lib/spells/effects/Clone.cpp @@ -65,7 +65,7 @@ void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarg pack.battleID = m->battle()->getBattle()->getBattleID(); pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD); info.save(pack.changedStacks.back().data); - server->apply(&pack); + server->apply(pack); //TODO: use BattleUnitsChanged with UPDATE operation @@ -90,7 +90,7 @@ void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarg cloneFlags.changedStacks.emplace_back(originalState->unitId(), UnitChanges::EOperation::RESET_STATE); originalState->save(cloneFlags.changedStacks.back().data); - server->apply(&cloneFlags); + server->apply(cloneFlags); SetStackEffect sse; sse.battleID = m->battle()->getBattle()->getBattleID(); @@ -100,7 +100,7 @@ void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarg std::vector buffer; buffer.push_back(lifeTimeMarker); sse.toAdd.emplace_back(unitId, buffer); - server->apply(&sse); + server->apply(sse); } } diff --git a/lib/spells/effects/Damage.cpp b/lib/spells/effects/Damage.cpp index 59d19658f..0e9297329 100644 --- a/lib/spells/effects/Damage.cpp +++ b/lib/spells/effects/Damage.cpp @@ -80,10 +80,10 @@ void Damage::apply(ServerCallback * server, const Mechanics * m, const EffectTar } if(!stacksInjured.stacks.empty()) - server->apply(&stacksInjured); + server->apply(stacksInjured); if(!blm.lines.empty()) - server->apply(&blm); + server->apply(blm); } bool Damage::isReceptive(const Mechanics * m, const battle::Unit * unit) const diff --git a/lib/spells/effects/DemonSummon.cpp b/lib/spells/effects/DemonSummon.cpp index d55f24a57..b86a87d1a 100644 --- a/lib/spells/effects/DemonSummon.cpp +++ b/lib/spells/effects/DemonSummon.cpp @@ -98,7 +98,7 @@ void DemonSummon::apply(ServerCallback * server, const Mechanics * m, const Effe } if(!pack.changedStacks.empty()) - server->apply(&pack); + server->apply(pack); } bool DemonSummon::isValidTarget(const Mechanics * m, const battle::Unit * unit) const diff --git a/lib/spells/effects/Dispel.cpp b/lib/spells/effects/Dispel.cpp index 8ee66b75f..41fedab42 100644 --- a/lib/spells/effects/Dispel.cpp +++ b/lib/spells/effects/Dispel.cpp @@ -65,10 +65,10 @@ void Dispel::apply(ServerCallback * server, const Mechanics * m, const EffectTar } if(!sse.toRemove.empty()) - server->apply(&sse); + server->apply(sse); if(describe && !blm.lines.empty()) - server->apply(&blm); + server->apply(blm); } bool Dispel::isValidTarget(const Mechanics * m, const battle::Unit * unit) const diff --git a/lib/spells/effects/Heal.cpp b/lib/spells/effects/Heal.cpp index c0aedb3a1..ce5f13cf5 100644 --- a/lib/spells/effects/Heal.cpp +++ b/lib/spells/effects/Heal.cpp @@ -42,9 +42,9 @@ void Heal::apply(int64_t value, ServerCallback * server, const Mechanics * m, co prepareHealEffect(value, pack, logMessage, *server->getRNG(), m, target); if(!pack.changedStacks.empty()) - server->apply(&pack); + server->apply(pack); if(!logMessage.lines.empty()) - server->apply(&logMessage); + server->apply(logMessage); } bool Heal::isValidTarget(const Mechanics * m, const battle::Unit * unit) const diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index 6cb29016e..5be7c92fa 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -122,7 +122,7 @@ void Moat::apply(ServerCallback * server, const Mechanics * m, const EffectTarge GiveBonus gb(GiveBonus::ETarget::BATTLE); gb.id = m->battle()->getBattle()->getBattleID(); gb.bonus = b; - server->apply(&gb); + server->apply(gb); } } } @@ -171,7 +171,7 @@ void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const Ef } if(!pack.changes.empty()) - server->apply(&pack); + server->apply(pack); } } diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 918c89c3e..40c22a088 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -326,7 +326,7 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons } if(!pack.changes.empty()) - server->apply(&pack); + server->apply(pack); } } diff --git a/lib/spells/effects/RemoveObstacle.cpp b/lib/spells/effects/RemoveObstacle.cpp index 58e462c69..c8d9840cb 100644 --- a/lib/spells/effects/RemoveObstacle.cpp +++ b/lib/spells/effects/RemoveObstacle.cpp @@ -54,7 +54,7 @@ void RemoveObstacle::apply(ServerCallback * server, const Mechanics * m, const E } if(!pack.changes.empty()) - server->apply(&pack); + server->apply(pack); } void RemoveObstacle::serializeJsonEffect(JsonSerializeFormat & handler) diff --git a/lib/spells/effects/Sacrifice.cpp b/lib/spells/effects/Sacrifice.cpp index 978d0f4d4..e51715e87 100644 --- a/lib/spells/effects/Sacrifice.cpp +++ b/lib/spells/effects/Sacrifice.cpp @@ -125,7 +125,7 @@ void Sacrifice::apply(ServerCallback * server, const Mechanics * m, const Effect BattleUnitsChanged removeUnits; removeUnits.battleID = m->battle()->getBattle()->getBattleID(); removeUnits.changedStacks.emplace_back(victim->unitId(), UnitChanges::EOperation::REMOVE); - server->apply(&removeUnits); + server->apply(removeUnits); } bool Sacrifice::isValidTarget(const Mechanics * m, const battle::Unit * unit) const diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index 65eb5dafb..5f9c4d34b 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -159,7 +159,7 @@ void Summon::apply(ServerCallback * server, const Mechanics * m, const EffectTar } if(!pack.changedStacks.empty()) - server->apply(&pack); + server->apply(pack); } EffectTarget Summon::filterTarget(const Mechanics * m, const EffectTarget & target) const diff --git a/lib/spells/effects/Teleport.cpp b/lib/spells/effects/Teleport.cpp index c0dce4d80..cffe3c2f1 100644 --- a/lib/spells/effects/Teleport.cpp +++ b/lib/spells/effects/Teleport.cpp @@ -85,7 +85,7 @@ void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectT tiles.push_back(destination); pack.tilesToMove = tiles; pack.teleporting = true; - server->apply(&pack); + server->apply(pack); if(triggerObstacles) { diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index 545505f20..694f5628d 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -205,10 +205,10 @@ void Timed::apply(ServerCallback * server, const Mechanics * m, const EffectTarg } if(!(sse.toAdd.empty() && sse.toUpdate.empty())) - server->apply(&sse); + server->apply(sse); if(describe && !blm.lines.empty()) - server->apply(&blm); + server->apply(blm); } void Timed::convertBonus(const Mechanics * m, int32_t & duration, std::vector & converted) const diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index f091d5e21..3afbcb779 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -156,7 +156,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) sps.which = primarySkill; sps.abs = false; sps.val = 1; - sendAndApply(&sps); + sendAndApply(sps); HeroLevelUp hlu; hlu.player = hero->tempOwner; @@ -166,12 +166,12 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) if (hlu.skills.size() == 0) { - sendAndApply(&hlu); + sendAndApply(hlu); levelUpHero(hero); } else if (hlu.skills.size() == 1 || !hero->getOwner().isValidPlayer()) { - sendAndApply(&hlu); + sendAndApply(hlu); levelUpHero(hero, hlu.skills.front()); } else if (hlu.skills.size() > 1) @@ -179,7 +179,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) auto levelUpQuery = std::make_shared(this, hlu, hero); hlu.queryID = levelUpQuery->queryID; queries->addQuery(levelUpQuery); - sendAndApply(&hlu); + sendAndApply(hlu); //level up will be called on query reply } } @@ -237,31 +237,31 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill) case ECommander::SPELL_POWER: scp.accumulatedBonus.type = BonusType::MAGIC_RESISTANCE; scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::RESISTANCE); - sendAndApply (&scp); //additional pack + sendAndApply(scp); //additional pack scp.accumulatedBonus.type = BonusType::CREATURE_SPELL_POWER; scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::SPELL_POWER) * 100; //like hero with spellpower = ability level - sendAndApply (&scp); //additional pack + sendAndApply(scp); //additional pack scp.accumulatedBonus.type = BonusType::CASTS; scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::CASTS); - sendAndApply (&scp); //additional pack + sendAndApply(scp); //additional pack scp.accumulatedBonus.type = BonusType::CREATURE_ENCHANT_POWER; //send normally break; } scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, skill); - sendAndApply (&scp); + sendAndApply(scp); scp.which = SetCommanderProperty::SECONDARY_SKILL; scp.additionalInfo = skill; scp.amount = c->secondarySkills.at(skill) + 1; - sendAndApply (&scp); + sendAndApply(scp); } else if (skill >= 100) { scp.which = SetCommanderProperty::SPECIAL_SKILL; scp.accumulatedBonus = *VLC->creh->skillRequirements.at(skill-100).first; scp.additionalInfo = skill; //unnormalized - sendAndApply (&scp); + sendAndApply(scp); } expGiven(hero); } @@ -306,12 +306,12 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c) if (!skillAmount) { - sendAndApply(&clu); + sendAndApply(clu); levelUpCommander(c); } else if (skillAmount == 1 || hero->tempOwner == PlayerColor::NEUTRAL) //choose skill automatically { - sendAndApply(&clu); + sendAndApply(clu); levelUpCommander(c, *RandomGeneratorUtil::nextItem(clu.skills, getRandomGenerator())); } else if (skillAmount > 1) //apply and ask for secondary skill @@ -319,7 +319,7 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c) auto commanderLevelUp = std::make_shared(this, clu, hero); clu.queryID = commanderLevelUp->queryID; queries->addQuery(commanderLevelUp); - sendAndApply(&clu); + sendAndApply(clu); } } @@ -357,7 +357,7 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo iw.player = hero->tempOwner; iw.text.appendLocalString(EMetaText::GENERAL_TXT, 1); //can gain no more XP iw.text.replaceTextID(hero->getNameTextID()); - sendAndApply(&iw); + sendAndApply(iw); } SetPrimSkill sps; @@ -365,7 +365,7 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo sps.which = PrimarySkill::EXPERIENCE; sps.abs = false; sps.val = amountToGain; - sendAndApply(&sps); + sendAndApply(sps); //hero may level up if (hero->commander && hero->commander->alive) @@ -375,7 +375,7 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo scp.heroid = hero->id; scp.which = SetCommanderProperty::EXPERIENCE; scp.amount = amountToGain; - sendAndApply (&scp); + sendAndApply(scp); CBonusSystemNode::treeHasChanged(); } @@ -389,7 +389,7 @@ void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill whi sps.which = which; sps.abs = abs; sps.val = val; - sendAndApply(&sps); + sendAndApply(sps); } void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs) @@ -404,7 +404,7 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh sss.which = which; sss.val = val; sss.abs = abs; - sendAndApply(&sss); + sendAndApply(sss); if (hero->visitedTown) giveSpells(hero->visitedTown, hero); @@ -596,7 +596,7 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa ssi.creatures[town->town->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth(); } ssi.creatures[town->town->creatures.size()].second.push_back(creatureId); - sendAndApply(&ssi); + sendAndApply(ssi); } } @@ -683,7 +683,7 @@ void CGameHandler::onNewTurn() SetAvailableArtifacts saa; saa.id = ObjectInstanceID::NONE; pickAllowedArtsSet(saa.arts, getRandomGenerator()); - sendAndApply(&saa); + sendAndApply(saa); } newTurnProcessor->onNewTurn(); @@ -770,7 +770,7 @@ void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h) } } if (!cs.spells.empty()) - sendAndApply(&cs); + sendAndApply(cs); } bool CGameHandler::removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) @@ -784,7 +784,7 @@ bool CGameHandler::removeObject(const CGObjectInstance * obj, const PlayerColor RemoveObject ro; ro.objectID = obj->id; ro.initiator = initiator; - sendAndApply(&ro); + sendAndApply(ro); checkVictoryLossConditionsForAll(); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function) return true; @@ -857,7 +857,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme { //send info about movement failure complain(message); - sendAndApply(&tmh); + sendAndApply(tmh); return false; }; @@ -924,7 +924,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme tmh.attackedFrom = std::make_optional(guardPos); tmh.result = result; - sendAndApply(&tmh); + sendAndApply(tmh); if (visitDest == VISIT_DEST && objectToVisit && objectToVisit->id == h->id) { // Hero should be always able to visit any object he is staying on even if there are guards around @@ -1101,7 +1101,7 @@ void CGameHandler::showBlockingDialog(const IObjectInterface * caller, BlockingD auto dialogQuery = std::make_shared(this, caller, *iw); queries->addQuery(dialogQuery); iw->queryID = dialogQuery->queryID; - sendToAllClients(iw); + sendToAllClients(*iw); } void CGameHandler::showTeleportDialog(TeleportDialog *iw) @@ -1109,7 +1109,7 @@ void CGameHandler::showTeleportDialog(TeleportDialog *iw) auto dialogQuery = std::make_shared(this, *iw); queries->addQuery(dialogQuery); iw->queryID = dialogQuery->queryID; - sendToAllClients(iw); + sendToAllClients(*iw); } void CGameHandler::giveResource(PlayerColor player, GameResID which, int val) //TODO: cap according to Bersy's suggestion @@ -1127,7 +1127,7 @@ void CGameHandler::giveResources(PlayerColor player, TResources resources) sr.abs = false; sr.player = player; sr.res = resources; - sendAndApply(&sr); + sendAndApply(sr); } void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) @@ -1187,7 +1187,7 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta vc.hid = hero->id; vc.tid = obj->id; vc.flags |= 1; - sendAndApply(&vc); + sendAndApply(vc); } visitCastleObjects(obj, hero); @@ -1227,7 +1227,7 @@ void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroI HeroVisitCastle vc; vc.hid = hero->id; vc.tid = obj->id; - sendAndApply(&vc); + sendAndApply(vc); } void CGameHandler::removeArtifact(const ArtifactLocation & al) @@ -1240,7 +1240,7 @@ void CGameHandler::removeArtifact(const ObjectInstanceID & srcId, const std::vec BulkEraseArtifacts ea; ea.artHolder = srcId; ea.posPack.insert(ea.posPack.end(), slotsPack.begin(), slotsPack.end()); - sendAndApply(&ea); + sendAndApply(ea); } void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) @@ -1249,7 +1249,7 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st cs.hid = hero->id; cs.spells = spells; cs.learn = give; - sendAndApply(&cs); + sendAndApply(cs); } void CGameHandler::setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) @@ -1264,12 +1264,12 @@ void CGameHandler::setResearchedSpells(const CGTownInstance * town, int level, c void CGameHandler::giveHeroBonus(GiveBonus * bonus) { - sendAndApply(bonus); + sendAndApply(*bonus); } void CGameHandler::setMovePoints(SetMovePoints * smp) { - sendAndApply(smp); + sendAndApply(*smp); } void CGameHandler::setMovePoints(ObjectInstanceID hid, int val, bool absolute) @@ -1278,7 +1278,7 @@ void CGameHandler::setMovePoints(ObjectInstanceID hid, int val, bool absolute) smp.hid = hid; smp.val = val; smp.absolute = absolute; - sendAndApply(&smp); + sendAndApply(smp); } void CGameHandler::setManaPoints(ObjectInstanceID hid, int val) @@ -1287,7 +1287,7 @@ void CGameHandler::setManaPoints(ObjectInstanceID hid, int val) sm.hid = hid; sm.val = val; sm.absolute = true; - sendAndApply(&sm); + sendAndApply(sm); } void CGameHandler::giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId) @@ -1296,7 +1296,7 @@ void CGameHandler::giveHero(ObjectInstanceID id, PlayerColor player, ObjectInsta gh.id = id; gh.player = player; gh.boatId = boatId; - sendAndApply(&gh); + sendAndApply(gh); //Reveal fow around new hero, especially released from Prison auto h = getHero(id); @@ -1309,7 +1309,7 @@ void CGameHandler::changeObjPos(ObjectInstanceID objid, int3 newPos, const Playe cop.objid = objid; cop.nPos = newPos; cop.initiator = initiator; - sendAndApply(&cop); + sendAndApply(cop); } void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID toHero) @@ -1379,7 +1379,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t } iw.text.appendLocalString(EMetaText::GENERAL_TXT, 142);//from %s iw.text.replaceTextID(h2->getNameTextID()); - sendAndApply(&cs2); + sendAndApply(cs2); } if (!cs1.spells.empty() && !cs2.spells.empty()) @@ -1407,9 +1407,9 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t } iw.text.appendLocalString(EMetaText::GENERAL_TXT, 148);//from %s iw.text.replaceTextID(h2->getNameTextID()); - sendAndApply(&cs1); + sendAndApply(cs1); } - sendAndApply(&iw); + sendAndApply(iw); } } @@ -1426,43 +1426,43 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) hex.player = h1->getOwner(); hex.hero1 = hero1; hex.hero2 = hero2; - sendAndApply(&hex); + sendAndApply(hex); useScholarSkill(hero1,hero2); queries->addQuery(exchange); } } -void CGameHandler::sendToAllClients(CPackForClient * pack) +void CGameHandler::sendToAllClients(CPackForClient & pack) { - logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name()); + logNetwork->trace("\tSending to all clients: %s", typeid(pack).name()); for (auto c : lobby->activeConnections) - c->sendPack(*pack); + c->sendPack(pack); } -void CGameHandler::sendAndApply(CPackForClient * pack) +void CGameHandler::sendAndApply(CPackForClient & pack) { sendToAllClients(pack); - gs->apply(*pack); + gs->apply(pack); logNetwork->trace("\tApplied on gs: %s", typeid(pack).name()); } -void CGameHandler::sendAndApply(CGarrisonOperationPack * pack) +void CGameHandler::sendAndApply(CGarrisonOperationPack & pack) { - sendAndApply(static_cast(pack)); + sendAndApply(static_cast(pack)); checkVictoryLossConditionsForAll(); } -void CGameHandler::sendAndApply(SetResources * pack) +void CGameHandler::sendAndApply(SetResources & pack) { - sendAndApply(static_cast(pack)); - checkVictoryLossConditionsForPlayer(pack->player); + sendAndApply(static_cast(pack)); + checkVictoryLossConditionsForPlayer(pack.player); } -void CGameHandler::sendAndApply(NewStructures * pack) +void CGameHandler::sendAndApply(NewStructures & pack) { - sendAndApply(static_cast(pack)); - checkVictoryLossConditionsForPlayer(getTown(pack->tid)->tempOwner); + sendAndApply(static_cast(pack)); + checkVictoryLossConditionsForPlayer(getTown(pack.tid)->tempOwner); } bool CGameHandler::isPlayerOwns(CPackForServer * pack, ObjectInstanceID id) @@ -1644,7 +1644,7 @@ bool CGameHandler::bulkSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner, si3 if(actualAmount <= howMany) break; } - sendAndApply(&bulkRS); + sendAndApply(bulkRS); return true; } @@ -1686,7 +1686,7 @@ bool CGameHandler::bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner) rs.count = creatureSet.getStackCount(slot); bulkRS.moves.push_back(rs); } - sendAndApply(&bulkRS); + sendAndApply(bulkRS); return true; } @@ -1773,7 +1773,7 @@ bool CGameHandler::bulkMoveArmy(ObjectInstanceID srcArmy, ObjectInstanceID destA rs.count = move.second.second; bulkRS.moves.push_back(rs); } - sendAndApply(&bulkRS); + sendAndApply(bulkRS); return true; } @@ -1855,7 +1855,7 @@ bool CGameHandler::bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner complain((boost::format("Failure: totalCreatures=%d but check=%d") % totalCreatures % check).str()); return false; } - sendAndApply(&bulkSRS); + sendAndApply(bulkSRS); return true; } @@ -2096,7 +2096,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, if (ssi.creatures[level].second.empty()) // first creature in a dwelling ssi.creatures[level].first = crea->getGrowth(); ssi.creatures[level].second.push_back(crea->getId()); - sendAndApply(&ssi); + sendAndApply(ssi); } if(t->town->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING) { @@ -2178,7 +2178,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, } //We know what has been built, apply changes. Do this as final step to properly update town window - sendAndApply(&ns); + sendAndApply(ns); //Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place! for(auto builtID : ns.bid) @@ -2247,7 +2247,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) rs.tid = tid; rs.bid.insert(bid); rs.destroyed = t->destroyed + 1; - sendAndApply(&rs); + sendAndApply(rs); //TODO: Remove dwellers // if (t->subID == 4 && bid == 17) //Veil of Darkness // { @@ -2255,7 +2255,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid) // rb.whoID = t->id; // rb.source = BonusSource::TOWN_STRUCTURE; // rb.id = 17; -// sendAndApply(&rb); +// sendAndApply(rb); // } return true; } @@ -2380,7 +2380,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst sac.tid = objid; sac.creatures = dwelling->creatures; sac.creatures[level].first -= cram; - sendAndApply(&sac); + sendAndApply(sac); if (warMachine) { @@ -2443,7 +2443,7 @@ bool CGameHandler::changeStackType(const StackLocation &sl, const CCreature *c) sst.army = sl.army->id; sst.slot = sl.slot; sst.type = c->getId(); - sendAndApply(&sst); + sendAndApply(sst); return true; } @@ -2502,7 +2502,7 @@ bool CGameHandler::swapGarrisonOnSiege(ObjectInstanceID tid) intown.visiting = ObjectInstanceID(); intown.garrison = town->visitingHero->id; } - sendAndApply(&intown); + sendAndApply(intown); return true; } @@ -2524,7 +2524,7 @@ bool CGameHandler::garrisonSwap(ObjectInstanceID tid) intown.tid = tid; intown.visiting = ObjectInstanceID(); intown.garrison = town->visitingHero->id; - sendAndApply(&intown); + sendAndApply(intown); return true; } else if (town->garrisonHero && !town->visitingHero) //move hero out of the garrison @@ -2541,7 +2541,7 @@ bool CGameHandler::garrisonSwap(ObjectInstanceID tid) intown.tid = tid; intown.garrison = ObjectInstanceID(); intown.visiting = town->garrisonHero->id; - sendAndApply(&intown); + sendAndApply(intown); return true; } else if (!!town->garrisonHero && town->visitingHero) //swap visiting and garrison hero @@ -2550,7 +2550,7 @@ bool CGameHandler::garrisonSwap(ObjectInstanceID tid) intown.tid = tid; intown.garrison = town->visitingHero->id; intown.visiting = town->garrisonHero->id; - sendAndApply(&intown); + sendAndApply(intown); return true; } else @@ -2631,7 +2631,7 @@ bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocati ma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(src.slot, dstSlot)); if(src.artHolder != dst.artHolder) ma.artsPack0.back().askAssemble = true; - sendAndApply(&ma); + sendAndApply(ma); return true; } @@ -2732,7 +2732,7 @@ bool CGameHandler::bulkMoveArtifacts(const PlayerColor & player, ObjectInstanceI } } } - sendAndApply(&ma); + sendAndApply(ma); return true; } @@ -2799,7 +2799,7 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj bma.artsPack0.emplace_back(ArtifactPosition::BACKPACK_START, backpackEnd); } } - sendAndApply(&bma); + sendAndApply(bma); return true; } @@ -2815,7 +2815,7 @@ bool CGameHandler::saveArtifactsCostume(const PlayerColor & player, const Object costume.costumeSet.emplace(slot, slotInfo->getArt()->getTypeId()); } - sendAndApply(&costume); + sendAndApply(costume); return true; } @@ -2870,7 +2870,7 @@ bool CGameHandler::switchArtifactsCostume(const PlayerColor & player, const Obje const auto backpackCap = getSettings().getInteger(EGameSettings::HEROES_BACKPACK_CAP); if((backpackCap < 0 || estimateBackpackSize <= backpackCap) && !bma.artsPack0.empty()) - sendAndApply(&bma); + sendAndApply(bma); } return true; } @@ -2913,7 +2913,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a AssembledArtifact aa; aa.al = dstLoc; aa.builtArt = combinedArt; - sendAndApply(&aa); + sendAndApply(aa); } else { @@ -2926,7 +2926,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a DisassembledArtifact da; da.al = dstLoc; - sendAndApply(&da); + sendAndApply(da); } return true; @@ -3045,7 +3045,7 @@ bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, GameRe if (!found) COMPLAIN_RET("Cannot find selected artifact on the list"); - sendAndApply(&saa); + sendAndApply(saa); giveHeroNewArtifact(h, aid, ArtifactPosition::FIRST_AVAILABLE); return true; } @@ -3209,7 +3209,7 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, EArmyFormation formation) ChangeFormation cf; cf.hid = hid; cf.formation = formation; - sendAndApply(&cf); + sendAndApply(cf); return true; } @@ -3266,7 +3266,7 @@ void CGameHandler::showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID h gd.objid = upobj; gd.removableUnits = removableUnits; gd.queryID = garrisonQuery->queryID; - sendAndApply(&gd); + sendAndApply(gd); } void CGameHandler::showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) @@ -3282,7 +3282,7 @@ void CGameHandler::showObjectWindow(const CGObjectInstance * object, EOpenWindow pack.queryID = windowQuery->queryID; queries->addQuery(windowQuery); } - sendAndApply(&pack); + sendAndApply(pack); } bool CGameHandler::isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2) @@ -3384,7 +3384,7 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta hv.heroId = h->id; hv.player = h->tempOwner; hv.starting = true; - sendAndApply(&hv); + sendAndApply(hv); obj->onHeroVisit(h); }; @@ -3407,7 +3407,7 @@ void CGameHandler::objectVisitEnded(const CGHeroInstance *h, PlayerColor player) hv.player = event.getPlayer(); hv.heroId = event.getHero(); hv.starting = false; - sendAndApply(&hv); + sendAndApply(hv); }; //TODO: ObjectVisitEnded should also have id of visited object, @@ -3478,14 +3478,14 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) { InfoWindow iw; getVictoryLossMessage(player, victoryLossCheckResult, iw); - sendAndApply(&iw); + sendAndApply(iw); PlayerEndsGame peg; peg.player = player; peg.victoryLossCheckResult = victoryLossCheckResult; peg.statistic = StatisticDataSet(gameState()->statistic); addStatistics(peg.statistic); // add last turn befor win / loss - sendAndApply(&peg); + sendAndApply(peg); turnOrder->onPlayerEndsGame(player); @@ -3504,8 +3504,8 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) getVictoryLossMessage(player, peg.victoryLossCheckResult, iw); iw.player = i->first; - sendAndApply(&iw); - sendAndApply(&peg); + sendAndApply(iw); + sendAndApply(peg); } } @@ -3549,7 +3549,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) InfoWindow iw; getVictoryLossMessage(player, victoryLossCheckResult.invert(), iw); iw.player = pc; - sendAndApply(&iw); + sendAndApply(iw); } } checkVictoryLossConditions(playerColors); @@ -3576,7 +3576,7 @@ bool CGameHandler::dig(const CGHeroInstance *h) SetMovePoints smp; smp.hid = h->id; smp.val = 0; - sendAndApply(&smp); + sendAndApply(smp); InfoWindow iw; iw.type = EInfoWindowMode::AUTO; @@ -3589,19 +3589,19 @@ bool CGameHandler::dig(const CGHeroInstance *h) iw.text.appendName(grail); // ... " The Grail" iw.soundID = soundBase::ULTIMATEARTIFACT; giveHeroNewArtifact(h, grail, ArtifactPosition::FIRST_AVAILABLE); //give grail - sendAndApply(&iw); + sendAndApply(iw); iw.soundID = soundBase::invalid; iw.components.emplace_back(ComponentType::ARTIFACT, grail); iw.text.clear(); iw.text.appendTextID(grail.toArtifact()->getDescriptionTextID()); - sendAndApply(&iw); + sendAndApply(iw); } else { iw.text.appendLocalString(EMetaText::GENERAL_TXT, 59); //"Nothing here. \n Where could it be?" iw.soundID = soundBase::Dig; - sendAndApply(&iw); + sendAndApply(iw); } return true; @@ -3721,7 +3721,7 @@ bool CGameHandler::insertNewStack(const StackLocation &sl, const CCreature *c, T ins.slot = sl.slot; ins.type = c->getId(); ins.count = count; - sendAndApply(&ins); + sendAndApply(ins); return true; } @@ -3740,7 +3740,7 @@ bool CGameHandler::eraseStack(const StackLocation &sl, bool forceRemoval) EraseStack es; es.army = sl.army->id; es.slot = sl.slot; - sendAndApply(&es); + sendAndApply(es); return true; } @@ -3765,7 +3765,7 @@ bool CGameHandler::changeStackCount(const StackLocation &sl, TQuantity count, bo csc.slot = sl.slot; csc.count = count; csc.absoluteValue = absoluteValue; - sendAndApply(&csc); + sendAndApply(csc); } return true; } @@ -3847,7 +3847,7 @@ bool CGameHandler::moveStack(const StackLocation &src, const StackLocation &dst, rs.srcSlot = src.slot; rs.dstSlot = dst.slot; rs.count = count; - sendAndApply(&rs); + sendAndApply(rs); return true; } @@ -3881,7 +3881,7 @@ bool CGameHandler::swapStacks(const StackLocation & sl1, const StackLocation & s ss.dstArmy = sl2.army->id; ss.srcSlot = sl1.slot; ss.dstSlot = sl2.slot; - sendAndApply(&ss); + sendAndApply(ss); return true; } } @@ -3919,7 +3919,7 @@ bool CGameHandler::putArtifact(const ArtifactLocation & al, const ArtifactInstan if(artInst->canBePutAt(putTo, dst.slot)) { PutArtifact pa(id, dst, askAssemble.value()); - sendAndApply(&pa); + sendAndApply(pa); return true; } else @@ -3954,7 +3954,7 @@ bool CGameHandler::giveHeroNewArtifact( { COMPLAIN_RET_FALSE_IF(!artType->canBePutAt(h, pos, false), "Cannot put artifact in that slot!"); } - sendAndApply(&na); + sendAndApply(na); return true; } @@ -3999,7 +3999,7 @@ void CGameHandler::synchronizeArtifactHandlerLists() { UpdateArtHandlerLists uahl; uahl.allocatedArtifacts = gs->allocatedArtifacts; - sendAndApply(&uahl); + sendAndApply(uahl); } bool CGameHandler::isValidObject(const CGObjectInstance *obj) const @@ -4086,7 +4086,7 @@ void CGameHandler::changeFogOfWar(const std::unordered_set &tiles, PlayerC return; } - sendAndApply(&fow); + sendAndApply(fow); } const CGHeroInstance * CGameHandler::getVisitingHero(const CGObjectInstance *obj) @@ -4137,7 +4137,7 @@ void CGameHandler::setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, sob.id = objid; sob.what = prop; sob.identifier = NumericID(value); - sendAndApply(&sob); + sendAndApply(sob); } void CGameHandler::setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) @@ -4146,7 +4146,7 @@ void CGameHandler::setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, Ob sob.id = objid; sob.what = prop; sob.identifier = identifier; - sendAndApply(&sob); + sendAndApply(sob); } void CGameHandler::setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) @@ -4154,7 +4154,7 @@ void CGameHandler::setBankObjectConfiguration(ObjectInstanceID objid, const Bank SetBankConfiguration srb; srb.objectID = objid; srb.configuration = configuration; - sendAndApply(&srb); + sendAndApply(srb); } void CGameHandler::setRewardableObjectConfiguration(ObjectInstanceID objid, const Rewardable::Configuration & configuration) @@ -4162,7 +4162,7 @@ void CGameHandler::setRewardableObjectConfiguration(ObjectInstanceID objid, cons SetRewardableConfiguration srb; srb.objectID = objid; srb.configuration = configuration; - sendAndApply(&srb); + sendAndApply(srb); } void CGameHandler::setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) @@ -4171,12 +4171,12 @@ void CGameHandler::setRewardableObjectConfiguration(ObjectInstanceID townInstanc srb.objectID = townInstanceID; srb.buildingID = buildingID; srb.configuration = configuration; - sendAndApply(&srb); + sendAndApply(srb); } void CGameHandler::showInfoDialog(InfoWindow * iw) { - sendAndApply(iw); + sendAndApply(*iw); } vstd::RNG & CGameHandler::getRandomGenerator() @@ -4261,7 +4261,7 @@ void CGameHandler::newObject(CGObjectInstance * object, PlayerColor initiator) NewObject no; no.newObject = object; no.initiator = initiator; - sendAndApply(&no); + sendAndApply(no); } void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index fae1b5358..4448cbd22 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -260,11 +260,11 @@ public: #endif } - void sendToAllClients(CPackForClient * pack); - void sendAndApply(CPackForClient * pack) override; - void sendAndApply(CGarrisonOperationPack * pack); - void sendAndApply(SetResources * pack); - void sendAndApply(NewStructures * pack); + void sendToAllClients(CPackForClient & pack); + void sendAndApply(CPackForClient & pack) override; + void sendAndApply(CGarrisonOperationPack & pack); + void sendAndApply(SetResources & pack); + void sendAndApply(NewStructures & pack); void wrongPlayerMessage(CPackForServer * pack, PlayerColor expectedplayer); /// Unconditionally throws with "Action not allowed" message diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index cda1ec10d..c0753d2a1 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -476,7 +476,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr connection) // } // // if(!startAiPack.players.empty()) -// gh->sendAndApply(&startAiPack); +// gh->sendAndApply(startAiPack); } void CVCMIServer::reconnectPlayer(int connId) @@ -503,7 +503,7 @@ void CVCMIServer::reconnectPlayer(int connId) } if(!startAiPack.players.empty()) - gh->sendAndApply(&startAiPack); + gh->sendAndApply(startAiPack); } } diff --git a/server/ServerSpellCastEnvironment.cpp b/server/ServerSpellCastEnvironment.cpp index 9213aef7b..81a9b114e 100644 --- a/server/ServerSpellCastEnvironment.cpp +++ b/server/ServerSpellCastEnvironment.cpp @@ -39,42 +39,42 @@ vstd::RNG * ServerSpellCastEnvironment::getRNG() return &gh->getRandomGenerator(); } -void ServerSpellCastEnvironment::apply(CPackForClient * pack) +void ServerSpellCastEnvironment::apply(CPackForClient & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(BattleLogMessage * pack) +void ServerSpellCastEnvironment::apply(BattleLogMessage & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(BattleStackMoved * pack) +void ServerSpellCastEnvironment::apply(BattleStackMoved & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(BattleUnitsChanged * pack) +void ServerSpellCastEnvironment::apply(BattleUnitsChanged & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(SetStackEffect * pack) +void ServerSpellCastEnvironment::apply(SetStackEffect & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(StacksInjured * pack) +void ServerSpellCastEnvironment::apply(StacksInjured & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(BattleObstaclesChanged * pack) +void ServerSpellCastEnvironment::apply(BattleObstaclesChanged & pack) { gh->sendAndApply(pack); } -void ServerSpellCastEnvironment::apply(CatapultAttack * pack) +void ServerSpellCastEnvironment::apply(CatapultAttack & pack) { gh->sendAndApply(pack); } @@ -104,5 +104,5 @@ void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color auto query = std::make_shared(gh, color, callback); request->queryID = query->queryID; gh->queries->addQuery(query); - gh->sendAndApply(request); + gh->sendAndApply(*request); } diff --git a/server/ServerSpellCastEnvironment.h b/server/ServerSpellCastEnvironment.h index e2aa9b796..7af8a0a67 100644 --- a/server/ServerSpellCastEnvironment.h +++ b/server/ServerSpellCastEnvironment.h @@ -24,15 +24,15 @@ public: vstd::RNG * getRNG() override; - void apply(CPackForClient * pack) override; + void apply(CPackForClient & pack) override; - void apply(BattleLogMessage * pack) override; - void apply(BattleStackMoved * pack) override; - void apply(BattleUnitsChanged * pack) override; - void apply(SetStackEffect * pack) override; - void apply(StacksInjured * pack) override; - void apply(BattleObstaclesChanged * pack) override; - void apply(CatapultAttack * pack) override; + void apply(BattleLogMessage & pack) override; + void apply(BattleStackMoved & pack) override; + void apply(BattleUnitsChanged & pack) override; + void apply(SetStackEffect & pack) override; + void apply(StacksInjured & pack) override; + void apply(BattleObstaclesChanged & pack) override; + void apply(CatapultAttack & pack) override; const CMap * getMap() const override; const CGameInfoCallback * getCb() const override; diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index a694e1df6..b5e6cdb75 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -60,7 +60,7 @@ void TurnTimerHandler::sendTimerUpdate(PlayerColor player) TurnTimeUpdate ttu; ttu.player = player; ttu.turnTimer = timers[player]; - gameHandler.sendAndApply(&ttu); + gameHandler.sendAndApply(ttu); lastUpdate[player] = 0; } diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 2ab7665e1..6c0799cbd 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -187,7 +187,7 @@ bool BattleActionProcessor::doDefendAction(const CBattleInfoCallback & battle, c buffer.push_back(bonus2); sse.toUpdate.push_back(std::make_pair(ba.stackNumber, buffer)); - gameHandler->sendAndApply(&sse); + gameHandler->sendAndApply(sse); BattleLogMessage message; message.battleID = battle.getBattle()->getBattleID(); @@ -199,7 +199,7 @@ bool BattleActionProcessor::doDefendAction(const CBattleInfoCallback & battle, c message.lines.push_back(text); - gameHandler->sendAndApply(&message); + gameHandler->sendAndApply(message); return true; } @@ -596,7 +596,7 @@ bool BattleActionProcessor::makeBattleActionImpl(const CBattleInfoCallback & bat { StartAction startAction(ba); startAction.battleID = battle.getBattle()->getBattleID(); - gameHandler->sendAndApply(&startAction); + gameHandler->sendAndApply(startAction); } bool result = dispatchBattleAction(battle, ba); @@ -605,7 +605,7 @@ bool BattleActionProcessor::makeBattleActionImpl(const CBattleInfoCallback & bat { EndAction endAction; endAction.battleID = battle.getBattle()->getBattleID(); - gameHandler->sendAndApply(&endAction); + gameHandler->sendAndApply(endAction); } if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND || ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL) @@ -716,7 +716,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta BattleUpdateGateState db; db.battleID = battle.getBattle()->getBattleID(); db.state = EGateState::OPENED; - gameHandler->sendAndApply(&db); + gameHandler->sendAndApply(db); } //inform clients about move @@ -728,7 +728,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta sm.tilesToMove = tiles; sm.distance = path.second; sm.teleporting = false; - gameHandler->sendAndApply(&sm); + gameHandler->sendAndApply(sm); } } else //for non-flying creatures @@ -856,7 +856,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta sm.distance = path.second; sm.teleporting = false; sm.tilesToMove = tiles; - gameHandler->sendAndApply(&sm); + gameHandler->sendAndApply(sm); tiles.clear(); } @@ -881,7 +881,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta BattleUpdateGateState db; db.battleID = battle.getBattle()->getBattleID(); db.state = EGateState::OPENED; - gameHandler->sendAndApply(&db); + gameHandler->sendAndApply(db); } } else if (curStack->getPosition() == gateMayCloseAtHex) @@ -1034,7 +1034,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const for (BattleStackAttacked & bsa : bat.bsa) bsa.battleID = battle.getBattle()->getBattleID(); - gameHandler->sendAndApply(&bat); + gameHandler->sendAndApply(bat); { const bool multipleTargets = bat.bsa.size() > 1; @@ -1101,7 +1101,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const StacksInjured pack; pack.battleID = battle.getBattle()->getBattleID(); pack.stacks.push_back(bsa); - gameHandler->sendAndApply(&pack); + gameHandler->sendAndApply(pack); // TODO: this is already implemented in Damage::describeEffect() { @@ -1115,7 +1115,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const } } - gameHandler->sendAndApply(&blm); + gameHandler->sendAndApply(blm); if(defender) handleAfterAttackCasting(battle, ranged, attacker, defender); @@ -1386,14 +1386,14 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback & BattleUnitsChanged removeUnits; removeUnits.battleID = battle.getBattle()->getBattleID(); removeUnits.changedStacks.emplace_back(defender->unitId(), UnitChanges::EOperation::REMOVE); - gameHandler->sendAndApply(&removeUnits); - gameHandler->sendAndApply(&addUnits); + gameHandler->sendAndApply(removeUnits); + gameHandler->sendAndApply(addUnits); // send empty event to client // temporary(?) workaround to force animations to trigger StacksInjured fakeEvent; fakeEvent.battleID = battle.getBattle()->getBattleID(); - gameHandler->sendAndApply(&fakeEvent); + gameHandler->sendAndApply(fakeEvent); } if(attacker->hasBonusOfType(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillPercentage) || attacker->hasBonusOfType(BonusType::DESTRUCTION, BonusCustomSubtype::destructionKillAmount)) @@ -1430,7 +1430,7 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback & si.battleID = battle.getBattle()->getBattleID(); si.stacks.push_back(bsa); - gameHandler->sendAndApply(&si); + gameHandler->sendAndApply(si); sendGenericKilledLog(battle, defender, bsa.killedAmount, false); } } @@ -1504,7 +1504,7 @@ void BattleActionProcessor::sendGenericKilledLog(const CBattleInfoCallback & bat BattleLogMessage blm; blm.battleID = battle.getBattle()->getBattleID(); addGenericKilledLog(blm, defender, killed, multiple); - gameHandler->sendAndApply(&blm); + gameHandler->sendAndApply(blm); } } diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index 7510b519a..e98053e40 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -179,7 +179,7 @@ void BattleFlowProcessor::trySummonGuardians(const CBattleInfoCallback & battle, pack.battleID = battle.getBattle()->getBattleID(); pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD); info.save(pack.changedStacks.back().data); - gameHandler->sendAndApply(&pack); + gameHandler->sendAndApply(pack); } } @@ -187,7 +187,7 @@ void BattleFlowProcessor::trySummonGuardians(const CBattleInfoCallback & battle, // temporary(?) workaround to force animations to trigger StacksInjured fakeEvent; fakeEvent.battleID = battle.getBattle()->getBattleID(); - gameHandler->sendAndApply(&fakeEvent); + gameHandler->sendAndApply(fakeEvent); } void BattleFlowProcessor::castOpeningSpells(const CBattleInfoCallback & battle) @@ -241,7 +241,7 @@ void BattleFlowProcessor::startNextRound(const CBattleInfoCallback & battle, boo BattleNextRound bnr; bnr.battleID = battle.getBattle()->getBattleID(); logGlobal->debug("Next round starts"); - gameHandler->sendAndApply(&bnr); + gameHandler->sendAndApply(bnr); // operate on copy - removing obstacles will invalidate iterator on 'battle' container auto obstacles = battle.battleGetAllObstacles(); @@ -287,7 +287,7 @@ const CStack * BattleFlowProcessor::getNextStack(const CBattleInfoCallback & bat bte.val = std::min(lostHealth, stack->valOfBonuses(BonusType::HP_REGENERATION)); if(bte.val) // anything to heal - gameHandler->sendAndApply(&bte); + gameHandler->sendAndApply(bte); } if(!next || !next->willMove()) @@ -327,7 +327,7 @@ void BattleFlowProcessor::activateNextStack(const CBattleInfoCallback & battle) removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE); if(!removeGhosts.changedStacks.empty()) - gameHandler->sendAndApply(&removeGhosts); + gameHandler->sendAndApply(removeGhosts); gameHandler->turnTimerHandler->onBattleNextStack(battle.getBattle()->getBattleID(), *next); @@ -537,7 +537,7 @@ bool BattleFlowProcessor::rollGoodMorale(const CBattleInfoCallback & battle, con bte.effect = vstd::to_underlying(BonusType::MORALE); bte.val = 1; bte.additionalInfo = 0; - gameHandler->sendAndApply(&bte); //play animation + gameHandler->sendAndApply(bte); //play animation return true; } } @@ -621,7 +621,7 @@ bool BattleFlowProcessor::makeAutomaticAction(const CBattleInfoCallback & battle bsa.battleID = battle.getBattle()->getBattleID(); bsa.stack = stack->unitId(); bsa.askPlayerInterface = false; - gameHandler->sendAndApply(&bsa); + gameHandler->sendAndApply(bsa); bool ret = owner->makeAutomaticBattleAction(battle, ba); return ret; @@ -664,7 +664,7 @@ void BattleFlowProcessor::removeObstacle(const CBattleInfoCallback & battle, con BattleObstaclesChanged obsRem; obsRem.battleID = battle.getBattle()->getBattleID(); obsRem.changes.emplace_back(obstacle.uniqueID, ObstacleChanges::EOperation::REMOVE); - gameHandler->sendAndApply(&obsRem); + gameHandler->sendAndApply(obsRem); } void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, const CStack *st) @@ -706,7 +706,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c ssp.battleID = battle.getBattle()->getBattleID(); ssp.which = BattleSetStackProperty::UNBIND; ssp.stackID = st->unitId(); - gameHandler->sendAndApply(&ssp); + gameHandler->sendAndApply(ssp); } } @@ -719,7 +719,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c if (bte.val < b->val) //(negative) poison effect increases - update it { bte.effect = vstd::to_underlying(BonusType::POISON); - gameHandler->sendAndApply(&bte); + gameHandler->sendAndApply(bte); } } } @@ -735,7 +735,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c bte.effect = vstd::to_underlying(BonusType::MANA_DRAIN); bte.val = manaDrained; bte.additionalInfo = opponentHero->id.getNum(); //for sanity - gameHandler->sendAndApply(&bte); + gameHandler->sendAndApply(bte); } } } @@ -755,7 +755,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c if (gameHandler->getRandomGenerator().nextInt(99) < 10) //fixed 10% { bte.effect = vstd::to_underlying(BonusType::FEAR); - gameHandler->sendAndApply(&bte); + gameHandler->sendAndApply(bte); } } } @@ -800,7 +800,7 @@ void BattleFlowProcessor::stackTurnTrigger(const CBattleInfoCallback & battle, c ssp.absolute = false; ssp.val = cooldown; ssp.stackID = st->unitId(); - gameHandler->sendAndApply(&ssp); + gameHandler->sendAndApply(ssp); } } } @@ -814,5 +814,5 @@ void BattleFlowProcessor::setActiveStack(const CBattleInfoCallback & battle, con BattleSetActiveStack sas; sas.battleID = battle.getBattle()->getBattleID(); sas.stack = stack->unitId(); - gameHandler->sendAndApply(&sas); + gameHandler->sendAndApply(sas); } diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index 57d0e32d1..87b9deb74 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -50,7 +50,7 @@ void BattleProcessor::engageIntoBattle(PlayerColor player) pb.player = player; pb.reason = PlayerBlocked::UPCOMING_BATTLE; pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED; - gameHandler->sendAndApply(&pb); + gameHandler->sendAndApply(pb); } void BattleProcessor::restartBattle(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, @@ -76,7 +76,7 @@ void BattleProcessor::restartBattle(const BattleID & battleID, const CArmedInsta SetMana restoreInitialMana; restoreInitialMana.val = lastBattleQuery->initialHeroMana[i]; restoreInitialMana.hid = heroes[i]->id; - gameHandler->sendAndApply(&restoreInitialMana); + gameHandler->sendAndApply(restoreInitialMana); } } @@ -88,7 +88,7 @@ void BattleProcessor::restartBattle(const BattleID & battleID, const CArmedInsta BattleCancelled bc; bc.battleID = battleID; - gameHandler->sendAndApply(&bc); + gameHandler->sendAndApply(bc); startBattle(army1, army2, tile, hero1, hero2, layout, town); } @@ -116,7 +116,7 @@ void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInsta GiveBonus giveBonus(GiveBonus::ETarget::OBJECT); giveBonus.id = hero1->id; giveBonus.bonus = bonus; - gameHandler->sendAndApply(&giveBonus); + gameHandler->sendAndApply(giveBonus); } } @@ -180,7 +180,7 @@ BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArrayreplayAllowed = lastBattleQuery == nullptr && onlyOnePlayerHuman; - gameHandler->sendAndApply(&bs); + gameHandler->sendAndApply(bs); return bs.battleID; } @@ -258,7 +258,7 @@ void BattleProcessor::updateGateState(const CBattleInfoCallback & battle) } if (db.state != battle.battleGetGateState()) - gameHandler->sendAndApply(&db); + gameHandler->sendAndApply(db); } bool BattleProcessor::makePlayerBattleAction(const BattleID & battleID, PlayerColor player, const BattleAction &ba) diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index a0a7a0e8f..ab4d07dbc 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -178,7 +178,7 @@ void CasualtiesAfterBattle::updateArmy(CGameHandler *gh) scp.heroid = heroWithDeadCommander; scp.which = SetCommanderProperty::ALIVE; scp.amount = 0; - gh->sendAndApply(&scp); + gh->sendAndApply(scp); } } @@ -291,7 +291,7 @@ void BattleResultProcessor::endBattle(const CBattleInfoCallback & battle) } gameHandler->turnTimerHandler->onBattleEnd(battle.getBattle()->getBattleID()); - gameHandler->sendAndApply(battleResult); + gameHandler->sendAndApply(*battleResult); if (battleResult->queryID == QueryID::NONE) endBattleConfirm(battle); @@ -384,8 +384,8 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) iw.text.replaceLocalString(EMetaText::GENERAL_TXT, 141); // " and " iw.components.emplace_back(ComponentType::SPELL, *it); } - gameHandler->sendAndApply(&iw); - gameHandler->sendAndApply(&spells); + gameHandler->sendAndApply(iw); + gameHandler->sendAndApply(spells); } } // Artifacts handling @@ -410,7 +410,7 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) const auto sendArtifacts = [this](BulkMoveArtifacts & bma) { if(!bma.artsPack0.empty()) - gameHandler->sendAndApply(&bma); + gameHandler->sendAndApply(bma); }; BulkMoveArtifacts packHero(finishingBattle->winnerHero->getOwner(), ObjectInstanceID::NONE, finishingBattle->winnerHero->id, false); @@ -466,11 +466,11 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) if(iw.components.size() >= GameConstants::INFO_WINDOW_ARTIFACTS_MAX_ITEMS) { - gameHandler->sendAndApply(&iw); + gameHandler->sendAndApply(iw); iw.components.clear(); } } - gameHandler->sendAndApply(&iw); + gameHandler->sendAndApply(iw); } if(!packHero.artsPack0.empty()) sendArtifacts(packHero); @@ -491,13 +491,13 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) } RemoveObject ro(finishingBattle->loserHero->id, finishingBattle->victor); - gameHandler->sendAndApply(&ro); + gameHandler->sendAndApply(ro); } // For draw case both heroes should be removed if(finishingBattle->isDraw() && finishingBattle->winnerHero) { RemoveObject ro(finishingBattle->winnerHero->id, finishingBattle->loser); - gameHandler->sendAndApply(&ro); + gameHandler->sendAndApply(ro); } // add statistic @@ -525,7 +525,7 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle) raccepted.heroResult[BattleSide::ATTACKER].exp = battleResult->exp[BattleSide::ATTACKER]; raccepted.heroResult[BattleSide::DEFENDER].exp = battleResult->exp[BattleSide::DEFENDER]; raccepted.winnerSide = finishingBattle->winnerSide; - gameHandler->sendAndApply(&raccepted); + gameHandler->sendAndApply(raccepted); gameHandler->queries->popIfTop(battleQuery); //--> continuation (battleAfterLevelUp) occurs after level-up gameHandler->queries are handled or on removing query @@ -568,7 +568,7 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleID & battleID, const resultsApplied.battleID = battleID; resultsApplied.player1 = finishingBattle->victor; resultsApplied.player2 = finishingBattle->loser; - gameHandler->sendAndApply(&resultsApplied); + gameHandler->sendAndApply(resultsApplied); //handle victory/loss of engaged players std::set playerColors = {finishingBattle->loser, finishingBattle->victor}; @@ -590,7 +590,7 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleID & battleID, const && (!finishingBattle->winnerHero->commander || !finishingBattle->winnerHero->commander->alive)) { RemoveObject ro(finishingBattle->winnerHero->id, finishingBattle->winnerHero->getOwner()); - gameHandler->sendAndApply(&ro); + gameHandler->sendAndApply(ro); if (gameHandler->getSettings().getBoolean(EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS)) gameHandler->heroPool->onHeroEscaped(finishingBattle->victor, finishingBattle->winnerHero); diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 9b436ceae..fd26db5f5 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -72,7 +72,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer sah.player = color; sah.hid = hero->getHeroType(); sah.replenishPoints = false; - gameHandler->sendAndApply(&sah); + gameHandler->sendAndApply(sah); } void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroInstance * hero) @@ -87,7 +87,7 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1); sah.replenishPoints = false; - gameHandler->sendAndApply(&sah); + gameHandler->sendAndApply(sah); } void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroSlot slot) @@ -98,7 +98,7 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS sah.slotID = slot; sah.hid = HeroTypeID::NONE; sah.replenishPoints = false; - gameHandler->sendAndApply(&sah); + gameHandler->sendAndApply(sah); } void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHeroSlot slot, bool needNativeHero, bool giveArmy, const HeroTypeID & nextHero) @@ -131,7 +131,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe sah.hid = HeroTypeID::NONE; } - gameHandler->sendAndApply(&sah); + gameHandler->sendAndApply(sah); } void HeroPoolProcessor::onNewWeek(const PlayerColor & color) @@ -232,7 +232,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy } // apply netpack -> this will remove hired hero from pool - gameHandler->sendAndApply(&hr); + gameHandler->sendAndApply(hr); if(recruitableHeroes[0] == recruitedHero) selectNewHeroForSlot(player, TavernHeroSlot::NATIVE, false, false, nextHero); diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index 4107720d5..a1c9c61b8 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -60,7 +60,7 @@ void NewTurnProcessor::handleTimeEvents(PlayerColor color) if (event.resources[i]) iw.components.emplace_back(ComponentType::RESOURCE, i, event.resources[i]); } - gameHandler->sendAndApply(&iw); //show dialog + gameHandler->sendAndApply(iw); //show dialog } } @@ -117,7 +117,7 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town) } } } - gameHandler->sendAndApply(&iw); //show dialog + gameHandler->sendAndApply(iw); //show dialog } } @@ -150,7 +150,7 @@ void NewTurnProcessor::onPlayerTurnEnded(PlayerColor which) DaysWithoutTown pack; pack.player = which; pack.daysWithoutCastle = playerState->daysWithoutCastle.value_or(0) + 1; - gameHandler->sendAndApply(&pack); + gameHandler->sendAndApply(pack); } else { @@ -159,7 +159,7 @@ void NewTurnProcessor::onPlayerTurnEnded(PlayerColor which) DaysWithoutTown pack; pack.player = which; pack.daysWithoutCastle = std::nullopt; - gameHandler->sendAndApply(&pack); + gameHandler->sendAndApply(pack); } } @@ -321,7 +321,7 @@ void NewTurnProcessor::updateNeutralTownGarrison(const CGTownInstance * t, int c sac.tid = t->id; sac.creatures = t->creatures; sac.creatures[tierToSubstract].first = creaturesLeft; - gameHandler->sendAndApply(&sac); + gameHandler->sendAndApply(sac); } }; @@ -657,7 +657,7 @@ void NewTurnProcessor::onNewTurn() bool newWeek = gameHandler->getDate(Date::DAY_OF_WEEK) == 7; //day numbers are confusing, as day was not yet switched bool newMonth = gameHandler->getDate(Date::DAY_OF_MONTH) == 28; - gameHandler->sendAndApply(&n); + gameHandler->sendAndApply(n); if (newWeek) { diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 0804a4940..83183d478 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -100,7 +100,7 @@ void PlayerMessageProcessor::commandKick(PlayerColor player, const std::vectorsendAndApply(&pc); + gameHandler->sendAndApply(pc); gameHandler->checkVictoryLossConditionsForPlayer(playerToKick); } } @@ -354,7 +354,7 @@ void PlayerMessageProcessor::cheatGiveSpells(PlayerColor player, const CGHeroIns for (int level = 1; level <= GameConstants::SPELL_LEVELS; level++) { giveBonus.bonus.subtype = BonusCustomSubtype::spellLevel(level); - gameHandler->sendAndApply(&giveBonus); + gameHandler->sendAndApply(giveBonus); } ///Give mana @@ -362,7 +362,7 @@ void PlayerMessageProcessor::cheatGiveSpells(PlayerColor player, const CGHeroIns sm.hid = hero->id; sm.val = 999; sm.absolute = true; - gameHandler->sendAndApply(&sm); + gameHandler->sendAndApply(sm); } void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInstance * town) @@ -520,7 +520,7 @@ void PlayerMessageProcessor::cheatMovement(PlayerColor player, const CGHeroInsta unlimited = true; } - gameHandler->sendAndApply(&smp); + gameHandler->sendAndApply(smp); GiveBonus gb(GiveBonus::ETarget::OBJECT); gb.bonus.type = BonusType::FREE_SHIP_BOARDING; @@ -565,7 +565,7 @@ void PlayerMessageProcessor::cheatVictory(PlayerColor player) PlayerCheated pc; pc.player = player; pc.winningCheatCode = true; - gameHandler->sendAndApply(&pc); + gameHandler->sendAndApply(pc); } void PlayerMessageProcessor::cheatDefeat(PlayerColor player) @@ -573,7 +573,7 @@ void PlayerMessageProcessor::cheatDefeat(PlayerColor player) PlayerCheated pc; pc.player = player; pc.losingCheatCode = true; - gameHandler->sendAndApply(&pc); + gameHandler->sendAndApply(pc); } void PlayerMessageProcessor::cheatMapReveal(PlayerColor player, bool reveal) @@ -594,7 +594,7 @@ void PlayerMessageProcessor::cheatMapReveal(PlayerColor player, bool reveal) fc.tiles.insert(hlp_tab, hlp_tab + lastUnc); delete [] hlp_tab; - gameHandler->sendAndApply(&fc); + gameHandler->sendAndApply(fc); } void PlayerMessageProcessor::cheatPuzzleReveal(PlayerColor player) @@ -612,7 +612,7 @@ void PlayerMessageProcessor::cheatPuzzleReveal(PlayerColor player) PlayerCheated pc; pc.player = color; - gameHandler->sendAndApply(&pc); + gameHandler->sendAndApply(pc); } } } @@ -715,7 +715,7 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo PlayerCheated pc; pc.player = i.first; - gameHandler->sendAndApply(&pc); + gameHandler->sendAndApply(pc); playerTargetedCheat = true; parameters.erase(parameters.begin()); @@ -734,7 +734,7 @@ bool PlayerMessageProcessor::handleCheatCode(const std::string & cheat, PlayerCo PlayerCheated pc; pc.player = player; - gameHandler->sendAndApply(&pc); + gameHandler->sendAndApply(pc); if (!playerTargetedCheat) executeCheatCode(cheatName, player, currObj, words); @@ -861,7 +861,7 @@ void PlayerMessageProcessor::broadcastSystemMessage(MetaString message) { SystemMessage sm; sm.text = message; - gameHandler->sendToAllClients(&sm); + gameHandler->sendToAllClients(sm); } void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message) @@ -874,5 +874,5 @@ void PlayerMessageProcessor::broadcastSystemMessage(const std::string & message) void PlayerMessageProcessor::broadcastMessage(PlayerColor playerSender, const std::string & message) { PlayerMessageClient temp_message(playerSender, message); - gameHandler->sendAndApply(&temp_message); + gameHandler->sendAndApply(temp_message); } diff --git a/server/processors/TurnOrderProcessor.cpp b/server/processors/TurnOrderProcessor.cpp index b50f56324..0f02ccebf 100644 --- a/server/processors/TurnOrderProcessor.cpp +++ b/server/processors/TurnOrderProcessor.cpp @@ -287,7 +287,7 @@ void TurnOrderProcessor::doStartPlayerTurn(PlayerColor which) PlayerStartsTurn pst; pst.player = which; pst.queryID = turnQuery->queryID; - gameHandler->sendAndApply(&pst); + gameHandler->sendAndApply(pst); assert(!actingPlayers.empty()); } @@ -302,7 +302,7 @@ void TurnOrderProcessor::doEndPlayerTurn(PlayerColor which) PlayerEndsTurn pet; pet.player = which; - gameHandler->sendAndApply(&pet); + gameHandler->sendAndApply(pet); if (!awaitingPlayers.empty()) tryStartTurnsForPlayers(); diff --git a/server/queries/MapQueries.cpp b/server/queries/MapQueries.cpp index ff83948ad..a2ae04911 100644 --- a/server/queries/MapQueries.cpp +++ b/server/queries/MapQueries.cpp @@ -273,7 +273,7 @@ void CHeroMovementQuery::onRemoval(PlayerColor color) pb.player = color; pb.reason = PlayerBlocked::ONGOING_MOVEMENT; pb.startOrEnd = PlayerBlocked::BLOCKADE_ENDED; - gh->sendAndApply(&pb); + gh->sendAndApply(pb); } void CHeroMovementQuery::onAdding(PlayerColor color) @@ -282,5 +282,5 @@ void CHeroMovementQuery::onAdding(PlayerColor color) pb.player = color; pb.reason = PlayerBlocked::ONGOING_MOVEMENT; pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED; - gh->sendAndApply(&pb); + gh->sendAndApply(pb); } diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index b720fdfd6..1047da97c 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -63,44 +63,44 @@ public: return true; } - void apply(CPackForClient * pack) override + void apply(CPackForClient & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(BattleLogMessage * pack) override + void apply(BattleLogMessage & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(BattleStackMoved * pack) override + void apply(BattleStackMoved & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(BattleUnitsChanged * pack) override + void apply(BattleUnitsChanged & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(SetStackEffect * pack) override + void apply(SetStackEffect & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(StacksInjured * pack) override + void apply(StacksInjured & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(BattleObstaclesChanged * pack) override + void apply(BattleObstaclesChanged & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } - void apply(CatapultAttack * pack) override + void apply(CatapultAttack & pack) override { - gameState->apply(*pack); + gameState->apply(pack); } void complain(const std::string & problem) override @@ -207,7 +207,7 @@ public: BattleStart bs; bs.info = battle; ASSERT_EQ(gameState->currentBattles.size(), 0); - gameCallback->sendAndApply(&bs); + gameCallback->sendAndApply(bs); ASSERT_EQ(gameState->currentBattles.size(), 1); } @@ -236,7 +236,7 @@ TEST_F(CGameStateTest, DISABLED_issue2765) na.artHolder = defender->id; na.artId = ArtifactID::BALLISTA; na.pos = ArtifactPosition::MACH1; - gameCallback->sendAndApply(&na); + gameCallback->sendAndApply(na); } startTestBattle(attacker, defender); @@ -253,7 +253,7 @@ TEST_F(CGameStateTest, DISABLED_issue2765) BattleUnitsChanged pack; pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD); info.save(pack.changedStacks.back().data); - gameCallback->sendAndApply(&pack); + gameCallback->sendAndApply(pack); } const CStack * att = nullptr; @@ -324,7 +324,7 @@ TEST_F(CGameStateTest, DISABLED_battleResurrection) na.artHolder = attacker->id; na.artId = ArtifactID::SPELLBOOK; na.pos = ArtifactPosition::SPELLBOOK; - gameCallback->sendAndApply(&na); + gameCallback->sendAndApply(na); } startTestBattle(attacker, defender); @@ -343,7 +343,7 @@ TEST_F(CGameStateTest, DISABLED_battleResurrection) BattleUnitsChanged pack; pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD); info.save(pack.changedStacks.back().data); - gameCallback->sendAndApply(&pack); + gameCallback->sendAndApply(pack); } { @@ -358,7 +358,7 @@ TEST_F(CGameStateTest, DISABLED_battleResurrection) BattleUnitsChanged pack; pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD); info.save(pack.changedStacks.back().data); - gameCallback->sendAndApply(&pack); + gameCallback->sendAndApply(pack); } CStack * unit = gameState->currentBattles.front()->getStack(unitId); diff --git a/test/mock/BattleFake.h b/test/mock/BattleFake.h index 42ff10e07..59f49a80d 100644 --- a/test/mock/BattleFake.h +++ b/test/mock/BattleFake.h @@ -81,9 +81,9 @@ public: #endif template - void accept(T * pack) + void accept(T & pack) { - pack->applyBattle(this); + pack.applyBattle(this); } const IBattleInfo * getBattle() const override diff --git a/test/mock/mock_IGameCallback.cpp b/test/mock/mock_IGameCallback.cpp index 43428b698..378d43b95 100644 --- a/test/mock/mock_IGameCallback.cpp +++ b/test/mock/mock_IGameCallback.cpp @@ -27,7 +27,7 @@ void GameCallbackMock::setGameState(CGameState * gameState) gs = gameState; } -void GameCallbackMock::sendAndApply(CPackForClient * pack) +void GameCallbackMock::sendAndApply(CPackForClient & pack) { upperCallback->apply(pack); } diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index 13b8f6965..af5dd1171 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -96,7 +96,7 @@ public: void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {} ///useful callback methods - void sendAndApply(CPackForClient * pack) override; + void sendAndApply(CPackForClient & pack) override; vstd::RNG & getRandomGenerator() override; diff --git a/test/mock/mock_ServerCallback.h b/test/mock/mock_ServerCallback.h index 967f55e74..faefcfa04 100644 --- a/test/mock/mock_ServerCallback.h +++ b/test/mock/mock_ServerCallback.h @@ -20,13 +20,13 @@ public: MOCK_METHOD1(complain, void(const std::string &)); MOCK_METHOD0(getRNG, vstd::RNG *()); - MOCK_METHOD1(apply, void(CPackForClient *)); + MOCK_METHOD1(apply, void(CPackForClient &)); - MOCK_METHOD1(apply, void(BattleLogMessage *)); - MOCK_METHOD1(apply, void(BattleStackMoved *)); - MOCK_METHOD1(apply, void(BattleUnitsChanged *)); - MOCK_METHOD1(apply, void(SetStackEffect *)); - MOCK_METHOD1(apply, void(StacksInjured *)); - MOCK_METHOD1(apply, void(BattleObstaclesChanged *)); - MOCK_METHOD1(apply, void(CatapultAttack *)); + MOCK_METHOD1(apply, void(BattleLogMessage &)); + MOCK_METHOD1(apply, void(BattleStackMoved &)); + MOCK_METHOD1(apply, void(BattleUnitsChanged &)); + MOCK_METHOD1(apply, void(SetStackEffect &)); + MOCK_METHOD1(apply, void(StacksInjured &)); + MOCK_METHOD1(apply, void(BattleObstaclesChanged &)); + MOCK_METHOD1(apply, void(CatapultAttack &)); }; diff --git a/test/spells/effects/CatapultTest.cpp b/test/spells/effects/CatapultTest.cpp index 9743f9378..e6e2d2f18 100644 --- a/test/spells/effects/CatapultTest.cpp +++ b/test/spells/effects/CatapultTest.cpp @@ -134,7 +134,7 @@ TEST_F(CatapultApplyTest, DISABLED_DamageToIntactPart) EXPECT_CALL(*battleFake, getWallState(_)).WillRepeatedly(Return(EWallState::DESTROYED)); EXPECT_CALL(*battleFake, getWallState(Eq(targetPart))).WillRepeatedly(Return(EWallState::INTACT)); EXPECT_CALL(*battleFake, setWallState(Eq(targetPart), Eq(EWallState::DAMAGED))).Times(1); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EffectTarget target; target.emplace_back(); diff --git a/test/spells/effects/CloneTest.cpp b/test/spells/effects/CloneTest.cpp index b68124242..d64896c5c 100644 --- a/test/spells/effects/CloneTest.cpp +++ b/test/spells/effects/CloneTest.cpp @@ -148,8 +148,8 @@ public: battleFake->setupEmptyBattlefield(); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(2); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(2); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EXPECT_CALL(mechanicsMock, getEffectDuration()).WillOnce(Return(effectDuration)); EXPECT_CALL(*battleFake, getUnitsIf(_)).Times(AtLeast(1)); diff --git a/test/spells/effects/DamageTest.cpp b/test/spells/effects/DamageTest.cpp index 227764405..f4a3583a7 100644 --- a/test/spells/effects/DamageTest.cpp +++ b/test/spells/effects/DamageTest.cpp @@ -109,7 +109,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageToAliveUnit) targetUnitState->localInit(&unitEnvironmentMock); EXPECT_CALL(targetUnit, acquireState()).WillOnce(Return(targetUnitState)); EXPECT_CALL(*battleFake, setUnitState(Eq(unitId),_, Lt(0))).Times(1); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EXPECT_CALL(serverMock, describeChanges()).WillRepeatedly(Return(false)); setupDefaultRNG(); @@ -174,7 +174,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageByPercent) EXPECT_CALL(targetUnit, acquireState()).WillOnce(Return(targetUnitState)); EXPECT_CALL(*battleFake, setUnitState(Eq(unitId),_, Lt(0))).Times(1); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EXPECT_CALL(serverMock, describeChanges()).WillRepeatedly(Return(false)); setupDefaultRNG(); @@ -218,7 +218,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageByCount) EXPECT_CALL(targetUnit, acquireState()).WillOnce(Return(targetUnitState)); EXPECT_CALL(*battleFake, setUnitState(Eq(unitId), _, Lt(0))).Times(1); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EXPECT_CALL(serverMock, describeChanges()).WillRepeatedly(Return(false)); setupDefaultRNG(); diff --git a/test/spells/effects/DispelTest.cpp b/test/spells/effects/DispelTest.cpp index 5817293d1..4087498cc 100644 --- a/test/spells/effects/DispelTest.cpp +++ b/test/spells/effects/DispelTest.cpp @@ -209,7 +209,7 @@ TEST_F(DispelApplyTest, DISABLED_RemovesEffects) EXPECT_CALL(mechanicsMock, getSpellIndex()).Times(AtLeast(1)).WillRepeatedly(Return(neutralID.toEnum())); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EXPECT_CALL(serverMock, describeChanges()).WillRepeatedly(Return(false)); setDefaultExpectations(); diff --git a/test/spells/effects/EffectFixture.cpp b/test/spells/effects/EffectFixture.cpp index cf4478786..0f31118c7 100644 --- a/test/spells/effects/EffectFixture.cpp +++ b/test/spells/effects/EffectFixture.cpp @@ -92,13 +92,13 @@ void EffectFixture::setUp() ON_CALL(serverMock, getRNG()).WillByDefault(Return(&rngMock)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); - ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); + ON_CALL(serverMock, apply(Matcher(_))).WillByDefault(Invoke(battleFake.get(), &battle::BattleFake::accept)); } static int64_t getInt64Range(int64_t lower, int64_t upper) diff --git a/test/spells/effects/HealTest.cpp b/test/spells/effects/HealTest.cpp index 8cfdb396b..a3b5e13b0 100644 --- a/test/spells/effects/HealTest.cpp +++ b/test/spells/effects/HealTest.cpp @@ -375,8 +375,8 @@ TEST_P(HealApplyTest, DISABLED_Heals) EXPECT_CALL(actualCaster, getCasterUnitId()).WillRepeatedly(Return(-1)); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(AtLeast(1)); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(AtLeast(1)); setupDefaultRNG(); diff --git a/test/spells/effects/SacrificeTest.cpp b/test/spells/effects/SacrificeTest.cpp index 40c04bc4e..6b6e07740 100644 --- a/test/spells/effects/SacrificeTest.cpp +++ b/test/spells/effects/SacrificeTest.cpp @@ -203,8 +203,8 @@ TEST_F(SacrificeApplyTest, DISABLED_ResurrectsTarget) EXPECT_CALL(targetUnit, acquire()).WillOnce(Return(targetUnitState)); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(AtLeast(1)); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(AtLeast(1)); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(AtLeast(1)); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(AtLeast(1)); setupDefaultRNG(); diff --git a/test/spells/effects/SummonTest.cpp b/test/spells/effects/SummonTest.cpp index ebbd5563c..00815b3cd 100644 --- a/test/spells/effects/SummonTest.cpp +++ b/test/spells/effects/SummonTest.cpp @@ -225,7 +225,7 @@ TEST_P(SummonApplyTest, DISABLED_SpawnsNewUnit) EXPECT_CALL(*battleFake, nextUnitId()).WillOnce(Return(unitId)); EXPECT_CALL(*battleFake, addUnit(Eq(unitId), _)).WillOnce(Invoke(this, &SummonApplyTest::onUnitAdded)); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); EffectTarget target; target.emplace_back(unitPosition); @@ -261,7 +261,7 @@ TEST_P(SummonApplyTest, DISABLED_UpdatesOldUnit) EXPECT_CALL(unit, unitId()).WillOnce(Return(unitId)); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); unitsFake.setDefaultBonusExpectations(); diff --git a/test/spells/effects/TeleportTest.cpp b/test/spells/effects/TeleportTest.cpp index 00f46bfd1..327dc6ab1 100644 --- a/test/spells/effects/TeleportTest.cpp +++ b/test/spells/effects/TeleportTest.cpp @@ -71,7 +71,7 @@ TEST_F(TeleportApplyTest, DISABLED_MovesUnit) EXPECT_CALL(*battleFake, moveUnit(Eq(unitId), Eq(destination))); EXPECT_CALL(mechanicsMock, getEffectLevel()).WillRepeatedly(Return(0)); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); Target target; target.emplace_back(&unit, BattleHex()); diff --git a/test/spells/effects/TimedTest.cpp b/test/spells/effects/TimedTest.cpp index bfd4a61e9..3f3f56ff1 100644 --- a/test/spells/effects/TimedTest.cpp +++ b/test/spells/effects/TimedTest.cpp @@ -118,7 +118,7 @@ TEST_P(TimedApplyTest, DISABLED_ChangesBonuses) setDefaultExpectations(); - EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); + EXPECT_CALL(serverMock, apply(Matcher(_))).Times(1); subject->apply(&serverMock, &mechanicsMock, target); From d0606d2dc9e34687597dcb829e471a8ca140a2c9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Oct 2024 13:25:39 +0000 Subject: [PATCH 273/726] Fix scripting build --- CCallback.cpp | 8 ++++---- scripting/lua/api/ServerCb.cpp | 4 ++-- server/CGameHandler.cpp | 4 ++-- test/scripting/LuaSpellEffectAPITest.cpp | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CCallback.cpp b/CCallback.cpp index 77bf4d72c..02f6d7a2d 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -195,19 +195,19 @@ void CCallback::scrollBackpackArtifacts(ObjectInstanceID hero, bool left) void CCallback::sortBackpackArtifactsBySlot(const ObjectInstanceID hero) { ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_SLOT); - sendRequest(&mba); + sendRequest(mba); } void CCallback::sortBackpackArtifactsByCost(const ObjectInstanceID hero) { ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_COST); - sendRequest(&mba); + sendRequest(mba); } void CCallback::sortBackpackArtifactsByClass(const ObjectInstanceID hero) { ManageBackpackArtifacts mba(hero, ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS); - sendRequest(&mba); + sendRequest(mba); } void CCallback::manageHeroCostume(ObjectInstanceID hero, size_t costumeIndex, bool saveCostume) @@ -270,7 +270,7 @@ int CBattleCallback::sendRequest(const CPackForServer & request) void CCallback::spellResearch( const CGTownInstance *town, SpellID spellAtSlot, bool accepted ) { SpellResearch pack(town->id, spellAtSlot, accepted); - sendRequest(&pack); + sendRequest(pack); } void CCallback::swapGarrisonHero( const CGTownInstance *town ) diff --git a/scripting/lua/api/ServerCb.cpp b/scripting/lua/api/ServerCb.cpp index aa257c6e9..445f19a0b 100644 --- a/scripting/lua/api/ServerCb.cpp +++ b/scripting/lua/api/ServerCb.cpp @@ -74,7 +74,7 @@ int ServerCbProxy::commitPackage(lua_State * L) auto * pack = static_cast(lua_touserdata(L, 1)); - object->apply(pack); + object->apply(*pack); return S.retVoid(); } @@ -96,7 +96,7 @@ int ServerCbProxy::apply(lua_State * L) if(!S.tryGet(1, pack)) return S.retVoid(); - object->apply(pack.get()); + object->apply(*pack); return S.retVoid(); } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3afbcb779..c2505b29e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -452,7 +452,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr c) out.text.appendTextID("vcmi.server.errors.playerLeft"); out.text.replaceName(playerId); out.components.emplace_back(ComponentType::FLAG, playerId); - sendAndApply(&out); + sendAndApply(out); } } } @@ -1259,7 +1259,7 @@ void CGameHandler::setResearchedSpells(const CGTownInstance * town, int level, c cs.spells = spells; cs.level = level; cs.accepted = accepted; - sendAndApply(&cs); + sendAndApply(cs); } void CGameHandler::giveHeroBonus(GiveBonus * bonus) diff --git a/test/scripting/LuaSpellEffectAPITest.cpp b/test/scripting/LuaSpellEffectAPITest.cpp index a144485a3..f9c4a1b7d 100644 --- a/test/scripting/LuaSpellEffectAPITest.cpp +++ b/test/scripting/LuaSpellEffectAPITest.cpp @@ -157,18 +157,18 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit) BattleStackMoved expected; BattleStackMoved actual; - auto checkMove = [&](BattleStackMoved * pack) + auto checkMove = [&](BattleStackMoved & pack) { - EXPECT_EQ(pack->stack, 42); - EXPECT_EQ(pack->teleporting, true); - EXPECT_EQ(pack->distance, 0); + EXPECT_EQ(pack.stack, 42); + EXPECT_EQ(pack.teleporting, true); + EXPECT_EQ(pack.distance, 0); std::vector toMove(1, hex2); - EXPECT_EQ(pack->tilesToMove, toMove); + EXPECT_EQ(pack.tilesToMove, toMove); }; - EXPECT_CALL(serverMock, apply(Matcher(_))).WillOnce(Invoke(checkMove)); + EXPECT_CALL(serverMock, apply(Matcher(_))).WillOnce(Invoke(checkMove)); context->callGlobal(&serverMock, "apply", params); } From 714de1861520979f69d3ae65b5e07d90cc1753db Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:41:36 +0200 Subject: [PATCH 274/726] fix 8th + portal of summoning in kingdom overview --- client/windows/CKingdomInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index ab163ba62..00fd01555 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -805,7 +805,7 @@ CTownItem::CTownItem(const CGTownInstance * Town) picture = std::make_shared(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6); openTown = std::make_shared(Rect(5, 6, 58, 64), town); - for(size_t i=0; icreatures.size(); i++) + for(size_t i=0; icreatures.size() && i(Point(401+37*(int)i, 78), town, (int)i, true, true)); available.push_back(std::make_shared(Point(48+37*(int)i, 78), town, (int)i, true, false)); From 758666d99debb5751c03d40dd6ab2387c0c319d2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:00:34 +0200 Subject: [PATCH 275/726] fix crash with 8th creature and portal of s. --- client/windows/CKingdomInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 00fd01555..107848055 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -866,7 +866,7 @@ void CTownItem::update() heroes->update(); - for (size_t i=0; icreatures.size(); i++) + for (size_t i=0; i(town->creatures.size()), GameConstants::CREATURES_PER_TOWN); i++) { growth[i]->update(); available[i]->update(); From 106dfdeb28e710644e930115c93f2c6807ef391b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:01:20 +0200 Subject: [PATCH 276/726] fix { and } in multiline --- client/windows/CMessage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index 51c0da65c..4a9138471 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -129,7 +129,7 @@ std::vector CMessage::breakText(std::string text, size_t maxLineWid if(wordBreak != ui32(-1)) { currPos = wordBreak; - if(text.substr(0, currPos).find('{') == std::string::npos) + if(boost::count(text.substr(0, currPos), '{') == boost::count(text.substr(0, currPos), '}')) { opened = false; color = ""; From 72da365d248753187a2c0c68ca8f1391f744a45d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:01:36 +0200 Subject: [PATCH 277/726] center QuickRecruitmentWindow --- client/windows/QuickRecruitmentWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/windows/QuickRecruitmentWindow.cpp b/client/windows/QuickRecruitmentWindow.cpp index ea512efc8..49f9be79d 100644 --- a/client/windows/QuickRecruitmentWindow.cpp +++ b/client/windows/QuickRecruitmentWindow.cpp @@ -166,4 +166,6 @@ QuickRecruitmentWindow::QuickRecruitmentWindow(const CGTownInstance * townd, Rec setButtons(); setCreaturePurchaseCards(); maxAllCards(cards); + + center(); } From cc480e6e466564ebffbf609c9cd3545b4397f7d0 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:20:18 +0200 Subject: [PATCH 278/726] textsize and textposition in OptionsTab --- client/lobby/OptionsTab.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 3b56e8433..cc28e07cf 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -835,9 +835,9 @@ OptionsTab::HandicapWindow::HandicapWindow() if(i == 0) { if(isIncome) - labels.push_back(std::make_shared(xPos, 35, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.jktext.32"))); + labels.push_back(std::make_shared(xPos, 38, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.jktext.32"))); else if(isGrowth) - labels.push_back(std::make_shared(xPos, 35, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.194"))); + labels.push_back(std::make_shared(xPos, 38, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.194"))); else anim.push_back(std::make_shared(AnimationPath::builtin("SMALRES"), GameResID(resource), 0, 15 + xPos + (j == 0 ? 10 : 0), 35)); } @@ -1037,12 +1037,12 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con } const auto & font = GH.renderHandler().loadFont(FONT_SMALL); - labelWhoCanPlay = std::make_shared(Rect(6, 23, 45, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); + labelWhoCanPlay = std::make_shared(Rect(6, 21, 45, 26), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; }; std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString(); - labelHandicap = std::make_shared(Rect(57, 24, 47, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, labelHandicapText); - handicap = std::make_shared(Rect(56, 24, 49, font->getLineHeight()*2), [](){ + labelHandicap = std::make_shared(Rect(55, 23, 46, 24), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, labelHandicapText); + handicap = std::make_shared(Rect(53, 23, 50, 24), [](){ if(!CSH->isHost()) return; From 2d4c0778b011eab03bf5dda9f84d4ace04d15dc4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:29:54 +0200 Subject: [PATCH 279/726] fix resource bar pos in kingdom overview --- client/windows/CKingdomInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 107848055..9363cd2aa 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -473,7 +473,7 @@ CKingdomInterface::CKingdomInterface() generateButtons(); statusbar = CGStatusBar::create(std::make_shared(ImagePath::builtin("KSTATBAR"), 10,pos.h - 45)); - resdatabar = std::make_shared(ImagePath::builtin("KRESBAR"), 7, 111+footerPos, 29, 5, 76, 81); + resdatabar = std::make_shared(ImagePath::builtin("KRESBAR"), 7, 111+footerPos, 29, 3, 76, 81); activateTab(persistentStorage["gui"]["lastKindomInterface"].Integer()); } From 1c6eaf63364b1033c6e88019cb9da89ae7393b75 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:37:07 +0200 Subject: [PATCH 280/726] fix player translation --- client/CServerHandler.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 373bbec3e..8c4bec843 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -141,7 +141,12 @@ void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen if(!playerNames.empty()) //if have custom set of player names - use it localPlayerNames = playerNames; else - localPlayerNames.push_back(settings["general"]["playerName"].String()); + { + std::string playerName = settings["general"]["playerName"].String(); + if(playerName == "Player") + playerName = CGI->generaltexth->translate("core.genrltxt.434"); + localPlayerNames.push_back(playerName); + } gameChat->resetMatchState(); lobbyClient->resetMatchState(); From a168b3aeaa8c3e018d6191d066c6e92083195228 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:49:55 +0200 Subject: [PATCH 281/726] remove unused var --- client/lobby/OptionsTab.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index cc28e07cf..e85db069f 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -1035,8 +1035,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con labelPlayerNameEdit = std::make_shared(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, false); labelPlayerNameEdit->setText(name); } - const auto & font = GH.renderHandler().loadFont(FONT_SMALL); - + labelWhoCanPlay = std::make_shared(Rect(6, 21, 45, 26), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; }; From 9e67e9616b1021c2309865eb388a1d809dd890fa Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:05:07 +0200 Subject: [PATCH 282/726] Center "Human or CPU" --- client/lobby/OptionsTab.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index e85db069f..002904f9f 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -1035,8 +1035,8 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con labelPlayerNameEdit = std::make_shared(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, false); labelPlayerNameEdit->setText(name); } - - labelWhoCanPlay = std::make_shared(Rect(6, 21, 45, 26), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); + + labelWhoCanPlay = std::make_shared(Rect(6, 21, 45, 26), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]); auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; }; std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString(); From 9977092cf48804f8ae96d45b5da4e374749aca28 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Oct 2024 20:05:45 +0000 Subject: [PATCH 283/726] Loading separate audio file in place of audio stream embedded in video --- client/media/CVideoHandler.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 9428c7428..b13c2dfc0 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -608,9 +608,8 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool stopOnKey) { CVideoInstance instance; - CAudioInstance audio; - auto extractedAudio = audio.extractAudio(name); + auto extractedAudio = getAudio(name); int audioHandle = CCS->soundh->playSound(extractedAudio); if (!instance.openInput(name)) @@ -684,6 +683,15 @@ std::unique_ptr CVideoPlayer::open(const VideoPath & name, float std::pair, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen) { + AudioPath audioPath = videoToOpen.toType(); + AudioPath audioPathVideoDir = audioPath.addPrefix("VIDEO/"); + + if(CResourceHandler::get()->existsResource(audioPath)) + return CResourceHandler::get()->load(audioPath)->readAll(); + + if(CResourceHandler::get()->existsResource(audioPathVideoDir)) + return CResourceHandler::get()->load(audioPathVideoDir)->readAll(); + CAudioInstance audio; return audio.extractAudio(videoToOpen); } From f7039435dab6cd6b1efe3b219772aa714c4cbf1a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:55:48 +0200 Subject: [PATCH 284/726] fix vmap name --- client/lobby/SelectionTab.cpp | 3 ++- client/lobby/SelectionTab.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 75b8181ee..cdbe983ec 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -809,6 +809,7 @@ void SelectionTab::parseMaps(const std::unordered_set & files) { auto mapInfo = std::make_shared(); mapInfo->mapInit(file.getName()); + mapInfo->name = mapInfo->getNameForList(); if (isMapSupported(*mapInfo)) allItems.push_back(mapInfo); @@ -988,6 +989,6 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0); labelName->setMaxWidth(185); } - labelName->setText(info->getNameForList()); + labelName->setText(info->name); labelName->setColor(color); } diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index 67e39047c..86c625a58 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -33,6 +33,7 @@ public: ElementInfo() : CMapInfo() { } ~ElementInfo() { } std::string folderName = ""; + std::string name = ""; bool isFolder = false; }; From a4b18f60d73ce1ad41e9a47c56f63c27c25505f2 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Tue, 8 Oct 2024 03:36:03 +0200 Subject: [PATCH 285/726] Update swedish.json Shortened some texts so that they will hopefully fit within the borders of the text boxes in the game. --- Mods/vcmi/config/vcmi/swedish.json | 103 +++++++++++++++-------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index 8998f5700..a1fb564f1 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -13,6 +13,8 @@ "vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig", "vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig", "vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN", + "vcmi.adventureMap.monsterMeleeType" : "närstrid", + "vcmi.adventureMap.monsterRangedType" : "fjärrstrid", "vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?", "vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!", @@ -21,7 +23,7 @@ "vcmi.adventureMap.playerAttacked" : "Spelare har blivit attackerad: %s", "vcmi.adventureMap.moveCostDetails" : "Förflyttningspoängs-kostnad: %TURNS tur(er) + %POINTS poäng - Återstående poäng: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Förflyttningspoängs-kostnad: %POINTS poäng - Återstående poäng: %REMAINING", - "vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!", "vcmi.capitalColors.0" : "Röd", @@ -135,14 +137,15 @@ "vcmi.lobby.pvp.coin.hover" : "Mynt", "vcmi.lobby.pvp.coin.help" : "Singla slant", "vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad", - "vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpmässig stad i chatten", - "vcmi.lobby.pvp.randomTownVs.hover" : "Slumpmässig stad vs.", - "vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpmässiga städer i chatten", + "vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpad stad i chatten", + "vcmi.lobby.pvp.randomTownVs.hover" : "Slumpad stad vs.", + "vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpade städer i chatten", "vcmi.lobby.pvp.versus" : "vs.", "vcmi.client.errors.invalidMap" : "{Ogiltig karta eller kampanj}\n\nStartade inte spelet! Vald karta eller kampanj kan vara ogiltig eller skadad. Orsak:\n%s", "vcmi.client.errors.missingCampaigns" : "{Saknade datafiler}\n\nKampanjernas datafiler hittades inte! Du kanske använder ofullständiga eller skadade Heroes 3-datafiler. Vänligen installera om speldata.", "vcmi.server.errors.disconnected" : "{Nätverksfel}\n\nAnslutningen till spelservern har förlorats!", + "vcmi.server.errors.playerLeft" : "{Spelare har lämnat}\n\n%s spelaren har kopplat bort sig från spelet!", //%s -> spelarens färg "vcmi.server.errors.existingProcess" : "En annan VCMI-serverprocess är igång. Vänligen avsluta den innan du startar ett nytt spel.", "vcmi.server.errors.modsToEnable" : "{Följande modd(ar) krävs}", "vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}", @@ -365,15 +368,15 @@ "vcmi.optionsTab.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer", "vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern", - "vcmi.optionsTab.chessFieldTurn.hover" : "Turomgångs-timern", + "vcmi.optionsTab.chessFieldTurn.hover" : "Tur-timern", "vcmi.optionsTab.chessFieldBattle.hover" : "Strids-timern", "vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-timern", - "vcmi.optionsTab.chessFieldBase.help" : "Används när {Turomgångs-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).", + "vcmi.optionsTab.chessFieldBase.help" : "Används när {Tur-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).", "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern} till nästa turomgång.", "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid går förlorad.", - "vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PVP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PVP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PVP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", + "vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PvP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PvP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PvP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", "vcmi.optionsTab.accumulate" : "Ackumulera", @@ -385,7 +388,7 @@ "vcmi.optionsTab.simturnsMax.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar eller tills en tillräckligt nära kontakt inträffar med en annan spelare", "vcmi.optionsTab.simturnsAI.help" : "{Simultana AI-turomgångar}\nExperimentellt alternativ. Tillåter AI-spelare att agera samtidigt som den mänskliga spelaren när simultana turomgångar är aktiverade.", - "vcmi.optionsTab.turnTime.select" : "Turtids-förinställningar", + "vcmi.optionsTab.turnTime.select" : "Timer-inställningar för turomgångar", "vcmi.optionsTab.turnTime.unlimited" : "Obegränsat med tid", "vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut", "vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter", @@ -393,22 +396,22 @@ "vcmi.optionsTab.turnTime.classic.10" : "Klassisk timer: 10 minuter", "vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter", "vcmi.optionsTab.turnTime.classic.30" : "Klassisk timer: 30 minuter", - "vcmi.optionsTab.turnTime.chess.20" : "Schack-timer: 20:00 + 10:00 + 02:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.16" : "Schack-timer: 16:00 + 08:00 + 01:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.8" : "Schack-timer: 08:00 + 04:00 + 01:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.4" : "Schack-timer: 04:00 + 02:00 + 00:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.2" : "Schack-timer: 02:00 + 01:00 + 00:15 + 00:00", - "vcmi.optionsTab.turnTime.chess.1" : "Schack-timer: 01:00 + 01:00 + 00:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.20" : "Schack: 20:00 + 10:00 + 02:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.16" : "Schack: 16:00 + 08:00 + 01:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.8" : "Schack: 08:00 + 04:00 + 01:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.4" : "Schack: 04:00 + 02:00 + 00:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.2" : "Schack: 02:00 + 01:00 + 00:15 + 00:00", + "vcmi.optionsTab.turnTime.chess.1" : "Schack: 01:00 + 01:00 + 00:00 + 00:00", - "vcmi.optionsTab.simturns.select" : "Välj förinställning för simultana/samtidiga turer", + "vcmi.optionsTab.simturns.select" : "Simultana/samtidiga turomgångsinställningar", "vcmi.optionsTab.simturns.none" : "Inga simultana/samtidiga turer", - "vcmi.optionsTab.simturns.tillContactMax" : "Simultantur: Fram till kontakt", - "vcmi.optionsTab.simturns.tillContact1" : "Simultantur: 1 vecka, bryt vid kontakt", - "vcmi.optionsTab.simturns.tillContact2" : "Simultantur: 2 veckor, bryt vid kontakt", - "vcmi.optionsTab.simturns.tillContact4" : "Simultantur: 1 månad, bryt vid kontakt", - "vcmi.optionsTab.simturns.blocked1" : "Simultantur: 1 vecka, kontakter blockerade", - "vcmi.optionsTab.simturns.blocked2" : "Simultantur: 2 veckor, kontakter blockerade", - "vcmi.optionsTab.simturns.blocked4" : "Simultantur: 1 månad, kontakter blockerade", + "vcmi.optionsTab.simturns.tillContactMax" : "Sam-tur: Fram till kontakt", + "vcmi.optionsTab.simturns.tillContact1" : "Sam-tur: 1 vecka, bryt vid kontakt", + "vcmi.optionsTab.simturns.tillContact2" : "Sam-tur: 2 veckor, bryt vid kontakt", + "vcmi.optionsTab.simturns.tillContact4" : "Sam-tur: 1 månad, bryt vid kontakt", + "vcmi.optionsTab.simturns.blocked1" : "Sam-tur: 1 vecka, kontakter blockerade", + "vcmi.optionsTab.simturns.blocked2" : "Sam-tur: 2 veckor, kontakter blockerade", + "vcmi.optionsTab.simturns.blocked4" : "Sam-tur: 1 månad, kontakter blockerade", // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language // Using this information, VCMI will automatically select correct plural form for every possible amount @@ -525,10 +528,10 @@ "core.bonus.AIR_IMMUNITY.description" : "Immun mot alla trollformler från skolan för luftmagi", "core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring", "core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender", - "core.bonus.BLOCKS_RETALIATION.name" : "Ingen motattack", + "core.bonus.BLOCKS_RETALIATION.name" : "Blockera närstrids-motattack", "core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera", - "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Ingen motattack på avstånd", - "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte göra en motattack/retaliering på avstånd genom att använda en distansattack", + "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Blockera fjärrstrids-motattack", + "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte retaliera på avstånd genom att använda en distansattack", "core.bonus.CATAPULT.name" : "Katapult", "core.bonus.CATAPULT.description" : "Attackerar belägringsmurar", "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})", @@ -538,21 +541,21 @@ "core.bonus.CHARGE_IMMUNITY.name" : "Galoppanfalls-immunitet", "core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall", "core.bonus.DARKNESS.name" : "I skydd av mörkret", - "core.bonus.DARKNESS.description" : "Skapar ett hölje av mörker med en ${val}-rutorsradie", + "core.bonus.DARKNESS.description" : "Skapar ett mörkerhölje med en rut-radie på ${val} som gäckar dina fiender", "core.bonus.DEATH_STARE.name" : "Dödsblick (${val}%)", - "core.bonus.DEATH_STARE.description" : "Varje förbandsenhet med 'Dödsblick' har ${val}% chans att döda den översta enheten i ett fiendeförband", + "core.bonus.DEATH_STARE.description" : "Varje 'Dödsblick' har ${val}% chans att döda den översta fiendeenheten", "core.bonus.DEFENSIVE_STANCE.name" : "Försvarshållning", - "core.bonus.DEFENSIVE_STANCE.description" : "Ger ytterligare +${val} till enhetens försvarsförmåga när du väljer att försvarar dig", + "core.bonus.DEFENSIVE_STANCE.description" : "När du väljer att försvara en enhet så får den +${val} extra försvar", "core.bonus.DESTRUCTION.name" : "Förintelse", "core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack", "core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Dödsstöt", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att ge dubbel basskada vid attack", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att utdela dubbel basskada vid attack", "core.bonus.DRAGON_NATURE.name" : "Drake", "core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur", "core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet", "core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi", "core.bonus.ENCHANTER.name" : "Förtrollare", - "core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje tur/omgång", + "core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje turomgång", "core.bonus.ENCHANTED.name" : "Förtrollad", "core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}", "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)", @@ -578,15 +581,15 @@ "core.bonus.GARGOYLE.name" : "Stenfigur", "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas", "core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar fysisk skada från både distans- och närstridsattacker", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från distans- och närstrids-attacker", "core.bonus.HATE.name" : "Hatar ${subtyp.varelse}", "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}", "core.bonus.HEALER.name" : "Helare", "core.bonus.HEALER.description" : "Helar/läker allierade enheter", "core.bonus.HP_REGENERATION.name" : "Självläkande", - "core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} träffpoäng (hälsa) varje runda", + "core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} hälsa (träffpoäng) varje runda", "core.bonus.JOUSTING.name" : "Galopperande ridanfall", - "core.bonus.JOUSTING.description" : "Orsakar +${val}% extra skada för varje ruta som enheten förflyttas innan attack", + "core.bonus.JOUSTING.description" : "Orsakar +${val}% mer skada för varje ruta som förflyttas innan attack", "core.bonus.KING.name" : "Kung", "core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre", "core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Förtrollningsimmunitet 1-${val}", @@ -594,13 +597,13 @@ "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad räckvidd för skjutning", "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Kan inte sikta på enheter längre bort än ${val} rutor", "core.bonus.LIFE_DRAIN.name" : "Dränerar livskraft (${val}%)", - "core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% träffpoäng (hälsa) av utdelad skada", + "core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% hälsa (träffpoäng) av utdelad skada", "core.bonus.MANA_CHANNELING.name" : "Kanalisera trollformelspoäng ${val}%", - "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av den mängd trollformelspoäng som fienden spenderar per trollformel i strid", + "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av fiendens spenderade trollformelspoäng i strid", "core.bonus.MANA_DRAIN.name" : "Dränera trollformelspoäng", "core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur", "core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)", - "core.bonus.MAGIC_MIRROR.description" : "Har ${val}% chans att reflektera (omdirigera) en offensiv trollformel på en fiendeenhet", + "core.bonus.MAGIC_MIRROR.description" : "${val}% chans att reflektera en offensiv trollformel på en fiendeenhet", "core.bonus.MAGIC_RESISTANCE.name" : "Magiskt motstånd (${val}%)", "core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel", "core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar", @@ -610,11 +613,11 @@ "core.bonus.NO_MELEE_PENALTY.name" : "Ingen närstridsbestraffning", "core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har ingen närstridsbestraffning", "core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan", - "core.bonus.NO_MORALE.description" : "Varelsen är immun mot moraliska effekter och har alltid neutral moral", + "core.bonus.NO_MORALE.description" : "Är immun mot moraliska effekter och har alltid neutral moral", "core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning", "core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur", "core.bonus.NON_LIVING.name" : "Icke levande", - "core.bonus.NON_LIVING.description" : "Immunitet mot många effekter som annars bara påverkar levande och odöda varelser", + "core.bonus.NON_LIVING.description" : "Påverkas inte av vissa effekter som levande/odöda gör", "core.bonus.RANDOM_SPELLCASTER.name" : "Slumpmässig besvärjare", "core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt", "core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd", @@ -624,21 +627,21 @@ "core.bonus.REBIRTH.name" : "Återfödelse (${val}%)", "core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden", "core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid", - "core.bonus.RETURN_AFTER_STRIKE.description" : "Efter att ha attackerat en fiendeenhet i närstrid återvänder enheten till rutan som den var placerad på innan den utförde sin närstridsattack", + "core.bonus.RETURN_AFTER_STRIKE.description" : "Efter närstridsattack återvänder den till sin ursprungliga ruta", "core.bonus.REVENGE.name" : "Hämnd", - "core.bonus.REVENGE.description" : "Orsakar extra skada baserat på angriparens förlorade träffpoäng (hälsa) i strid", + "core.bonus.REVENGE.description" : "Orsakar mer skada om den själv blivit skadad", "core.bonus.SHOOTER.name" : "Distans-attack", "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd", "core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten", - "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Denna varelses distans-attacker drabbar alla mål i ett litet område", + "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Dess distans-attacker drabbar alla mål i ett litet område", "core.bonus.SOUL_STEAL.name" : "Själtjuv", - "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna enheter för varje dödad fiendeenhet", + "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna för varje dödad fiendeenhet", "core.bonus.SPELLCASTER.name" : "Besvärjare", "core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}", "core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack", - "core.bonus.SPELL_AFTER_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} efter att den har attackerat", + "core.bonus.SPELL_AFTER_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} efter anfall", "core.bonus.SPELL_BEFORE_ATTACK.name" : "Besvärja före attack", - "core.bonus.SPELL_BEFORE_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} innan den attackerar", + "core.bonus.SPELL_BEFORE_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} före anfall", "core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Trolldoms-resistens", "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.", "core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet", @@ -659,14 +662,14 @@ "core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ", "core.bonus.UNDEAD.name" : "Odöd", "core.bonus.UNDEAD.description" : "Varelsen är odöd", - "core.bonus.UNLIMITED_RETALIATIONS.name" : "Obegränsat antal motattacker", - "core.bonus.UNLIMITED_RETALIATIONS.description" : "Kan slå tillbaka mot ett obegränsat antal attacker varje omgång", + "core.bonus.UNLIMITED_RETALIATIONS.name" : "Slår tillbaka varje gång", + "core.bonus.UNLIMITED_RETALIATIONS.description" : "Obegränsat antal motattacker", "core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet", - "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagi-skolan", + "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagiskolan", "core.bonus.WIDE_BREATH.name" : "Bred dödlig andedräkt", "core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)", "core.bonus.DISINTEGRATE.name" : "Desintegrerar", - "core.bonus.DISINTEGRATE.description" : "Ingen fysisk kropp finns kvar efter att enheten blivit besegrad i strid", + "core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar efter dödsögonblicket", "core.bonus.INVINCIBLE.name" : "Oövervinnerlig", "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting" } From 9492eab7c5d1212e6e662bb1d259289763037ffe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 8 Oct 2024 14:17:04 +0000 Subject: [PATCH 286/726] Finish encapsulation of PlayerLocalState class --- client/PlayerLocalState.cpp | 10 ++++++++++ client/PlayerLocalState.h | 22 ++++++++++++++-------- client/windows/CSpellWindow.cpp | 24 ++++++++++++++++++------ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index 541b9ff80..c9496fa18 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -23,6 +23,16 @@ PlayerLocalState::PlayerLocalState(CPlayerInterface & owner) { } +const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings() +{ + return spellbookSettings; +} + +void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSettings) +{ + spellbookSettings = newSettings; +} + void PlayerLocalState::saveHeroPaths(std::map & pathsMap) { for(auto & p : paths) diff --git a/client/PlayerLocalState.h b/client/PlayerLocalState.h index c6029ff1b..b4bd481eb 100644 --- a/client/PlayerLocalState.h +++ b/client/PlayerLocalState.h @@ -21,6 +21,15 @@ VCMI_LIB_NAMESPACE_END class CPlayerInterface; +struct PlayerSpellbookSetting +{ + //on which page we left spellbook + int spellbookLastPageBattle = 0; + int spellbookLastPageAdvmap = 0; + int spellbookLastTabBattle = 4; + int spellbookLastTabAdvmap = 4; +}; + /// Class that contains potentially serializeable state of a local player class PlayerLocalState { @@ -34,18 +43,12 @@ class PlayerLocalState std::vector wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones) std::vector ownedTowns; //our towns on the adventure map + PlayerSpellbookSetting spellbookSettings; + void saveHeroPaths(std::map & paths); void loadHeroPaths(std::map & paths); public: - struct SpellbookLastSetting - { - //on which page we left spellbook - int spellbookLastPageBattle = 0; - int spellbookLastPageAdvmap = 0; - int spellbookLastTabBattle = 4; - int spellbookLastTabAdvmap = 4; - } spellbookSettings; explicit PlayerLocalState(CPlayerInterface & owner); @@ -53,6 +56,9 @@ public: void setHeroAsleep(const CGHeroInstance * hero); void setHeroAwaken(const CGHeroInstance * hero); + const PlayerSpellbookSetting & getSpellbookSettings(); + void setSpellbookSettings(const PlayerSpellbookSetting & newSettings); + const std::vector & getOwnedTowns(); const CGTownInstance * getOwnedTown(size_t index); void addOwnedTown(const CGTownInstance * hero); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 778b7db6c..6f810a2fa 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -205,9 +205,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m } } - selectedTab = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap; + selectedTab = battleSpellsOnly ? myInt->localState->getSpellbookSettings().spellbookLastTabBattle : myInt->localState->getSpellbookSettings().spellbookLastTabAdvmap; schoolTab->setFrame(selectedTab, 0); - int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap; + int cp = battleSpellsOnly ? myInt->localState->getSpellbookSettings().spellbookLastPageBattle : myInt->localState->getSpellbookSettings().spellbookLastPageAdvmap; // spellbook last page battle index is not reset after battle, so this needs to stay here vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1)); setCurrentPage(cp); @@ -313,8 +313,18 @@ void CSpellWindow::processSpells() void CSpellWindow::fexitb() { - (myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab; - (myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage; + auto spellBookState = myInt->localState->getSpellbookSettings(); + if(myInt->battleInt) + { + spellBookState.spellbookLastTabBattle = selectedTab; + spellBookState.spellbookLastPageBattle = currentPage; + } + else + { + spellBookState.spellbookLastTabAdvmap = selectedTab; + spellBookState.spellbookLastPageAdvmap = currentPage; + } + myInt->localState->setSpellbookSettings(spellBookState); if(onSpellSelect) onSpellSelect(SpellID::NONE); @@ -619,8 +629,10 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition) auto guard = vstd::makeScopeGuard([this]() { - owner->myInt->localState->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab; - owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage; + auto spellBookState = owner->myInt->localState->getSpellbookSettings(); + spellBookState.spellbookLastTabAdvmap = owner->selectedTab; + spellBookState.spellbookLastPageAdvmap = owner->currentPage; + owner->myInt->localState->setSpellbookSettings(spellBookState); }); spells::detail::ProblemImpl problem; From e09525a9a0dfa385522e03be4a6d0e53ff200fe1 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Tue, 8 Oct 2024 21:30:50 +0200 Subject: [PATCH 287/726] Update swedish.json Shortened a few extra lines of text that were out of bounds. --- Mods/vcmi/config/vcmi/swedish.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index a1fb564f1..57f0bae33 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -224,8 +224,8 @@ "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Gränssnittsförbättringar}\n\nVälj mellan olika förbättringar av användargränssnittet. Till exempel en lättåtkomlig ryggsäcksknapp med mera. Avaktivera för att få en mer klassisk spelupplevelse.", "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Stor trollformelsbok", "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Stor trollformelsbok}\n\nAktiverar en större trollformelsbok som rymmer fler trollformler per sida (animeringen av sidbyte i den större trollformelsboken fungerar inte).", - "vcmi.systemOptions.audioMuteFocus.hover" : "Stänger av ljudet vid inaktivitet", - "vcmi.systemOptions.audioMuteFocus.help" : "{Stäng av ljud vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny tur/omgång.", + "vcmi.systemOptions.audioMuteFocus.hover" : "Tyst vid inaktivitet", + "vcmi.systemOptions.audioMuteFocus.help" : "{Tyst vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny turomgång.", "vcmi.adventureOptions.infoBarPick.hover" : "Visar textmeddelanden i infopanelen", "vcmi.adventureOptions.infoBarPick.help" : "{Infopanelsmeddelanden}\n\nNär det är möjligt kommer spelmeddelanden från besökande kartobjekt att visas i infopanelen istället för att dyka upp i ett separat fönster.", @@ -237,11 +237,11 @@ "vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.", "vcmi.adventureOptions.borderScroll.hover" : "Kantrullning", "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantering av varelser i infopanelen i nedre högra hörnet", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantera armén i nedre högra hörnet", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Dra kartan med vänster musknapp", + "vcmi.adventureOptions.leftButtonDrag.hover" : "V.klicksdragning", "vcmi.adventureOptions.leftButtonDrag.help" : "{Vänsterklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med vänster musknapp nedtryckt.", - "vcmi.adventureOptions.rightButtonDrag.hover" : "Dra kartan med höger musknapp", + "vcmi.adventureOptions.rightButtonDrag.hover" : "H.klicksdragning", "vcmi.adventureOptions.rightButtonDrag.help" : "{Högerklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med höger musknapp nedtryckt.", "vcmi.adventureOptions.smoothDragging.hover" : "Mjuk kartdragning", "vcmi.adventureOptions.smoothDragging.help" : "{Mjuk kartdragning}\n\nVid aktivering så har kartdragningen en modern rullningseffekt.", @@ -271,16 +271,16 @@ "vcmi.battleOptions.animationsSpeed1.help" : "Ställ in animationshastigheten till mycket långsam.", "vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.", "vcmi.battleOptions.animationsSpeed6.help" : "Ställ in animationshastigheten till omedelbar.", - "vcmi.battleOptions.movementHighlightOnHover.hover" : "Muspeka (hovra) för att avslöja förflyttningsräckvidd", - "vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.", + "vcmi.battleOptions.movementHighlightOnHover.hover" : "Avslöja förflyttningsräckvidd", + "vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka (hovra) för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.", "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Avslöja skyttars räckvidd", "vcmi.battleOptions.rangeLimitHighlightOnHover.help" : "{Muspeka för att avslöja skyttars räckvidd}\n\nVisar hur långt en enhets distansattack sträcker sig över slagfältet när du håller muspekaren över dem.", "vcmi.battleOptions.showStickyHeroInfoWindows.hover" : "Visa fönster med hjältars primärförmågor", "vcmi.battleOptions.showStickyHeroInfoWindows.help" : "{Visa fönster med hjältars primärförmågor}\n\nKommer alltid att visa ett fönster där du kan se dina hjältars primärförmågor (anfall, försvar, trollkonst, kunskap och trollformelpoäng).", "vcmi.battleOptions.skipBattleIntroMusic.hover" : "Hoppa över intromusik", "vcmi.battleOptions.skipBattleIntroMusic.help" : "{Hoppa över intromusik}\n\nTillåt åtgärder under intromusiken som spelas i början av varje strid.", - "vcmi.battleOptions.endWithAutocombat.hover" : "Slutför striden så fort som möjligt", - "vcmi.battleOptions.endWithAutocombat.help" : "{Slutför strid}\n\nAuto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.", + "vcmi.battleOptions.endWithAutocombat.hover" : "Snabbstrid (AI)", + "vcmi.battleOptions.endWithAutocombat.help" : "{Slutför striden så fort som möjligt}\n\nAI för auto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.", "vcmi.battleOptions.showQuickSpell.hover" : "Snabb åtkomst till dina trollformler", "vcmi.battleOptions.showQuickSpell.help" : "{Visa snabbtrollformels-panelen}\n\nVisar en snabbvalspanel vid sidan av stridsfönstret där du har snabb åtkomst till några av dina trollformler", From 679181c10386793baf12714a257e01b6cb02ca04 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 8 Oct 2024 19:55:51 +0000 Subject: [PATCH 288/726] Implemented serialization of local player state in json form --- CCallback.cpp | 10 ++ CCallback.h | 2 + client/CPlayerInterface.cpp | 2 + client/PlayerLocalState.cpp | 156 +++++++++++++++++++++---- client/PlayerLocalState.h | 8 +- lib/CMakeLists.txt | 1 + lib/CPlayerState.cpp | 10 +- lib/CPlayerState.h | 5 +- lib/networkPacks/NetPackVisitor.h | 2 + lib/networkPacks/NetPacksLib.cpp | 7 ++ lib/networkPacks/SaveLocalState.h | 27 +++++ lib/serializer/ESerializationVersion.h | 3 +- lib/serializer/RegisterTypes.h | 2 + server/CGameHandler.cpp | 3 + server/NetPacksServer.cpp | 8 ++ server/ServerNetPackVisitors.h | 1 + 16 files changed, 215 insertions(+), 32 deletions(-) create mode 100644 lib/networkPacks/SaveLocalState.h diff --git a/CCallback.cpp b/CCallback.cpp index 02f6d7a2d..3eeb6d238 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -25,6 +25,7 @@ #include "lib/UnlockGuard.h" #include "lib/battle/BattleInfo.h" #include "lib/networkPacks/PacksForServer.h" +#include "lib/networkPacks/SaveLocalState.h" bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where) { @@ -323,6 +324,15 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn sendRequest(pack); } +void CCallback::saveLocalState(const JsonNode & data) +{ + SaveLocalState state; + state.data = data; + state.player = *player; + + sendRequest(state); +} + void CCallback::save( const std::string &fname ) { cl->save(fname); diff --git a/CCallback.h b/CCallback.h index 39b9fc8ce..6e30299c6 100644 --- a/CCallback.h +++ b/CCallback.h @@ -100,6 +100,7 @@ public: virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0; virtual void eraseArtifactByClient(const ArtifactLocation & al)=0; virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0; + virtual void saveLocalState(const JsonNode & data)=0; virtual void endTurn()=0; virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith) virtual void setFormation(const CGHeroInstance * hero, EArmyFormation mode)=0; @@ -193,6 +194,7 @@ public: void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override; bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; + void saveLocalState(const JsonNode & data) override; void endTurn() override; void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override; void swapGarrisonHero(const CGTownInstance *town) override; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index c028b8aec..572924c09 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1333,6 +1333,8 @@ void CPlayerInterface::initializeHeroTownList() localState->addOwnedTown(town); } + localState->deserialize(*cb->getPlayerState(playerID)->playerLocalSettings); + if(adventureInt) adventureInt->onHeroChanged(nullptr); } diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index c9496fa18..69e8b1b5e 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -11,6 +11,7 @@ #include "PlayerLocalState.h" #include "../CCallback.h" +#include "../lib/json/JsonNode.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/pathfinder/CGPathNode.h" @@ -33,34 +34,10 @@ void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSe spellbookSettings = newSettings; } -void PlayerLocalState::saveHeroPaths(std::map & pathsMap) -{ - for(auto & p : paths) - { - if(p.second.nodes.size()) - pathsMap[p.first] = p.second.endPos(); - else - logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated()); - } -} - -void PlayerLocalState::loadHeroPaths(std::map & pathsMap) -{ - if(owner.cb) - { - for(auto & p : pathsMap) - { - CGPath path; - owner.cb->getPathsInfo(p.first)->getPath(path, p.second); - paths[p.first] = path; - logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size()); - } - } -} - void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path) { paths[h] = path; + syncronizeState(); } const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const @@ -80,6 +57,7 @@ bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destinatio if(!owner.cb->getPathsInfo(h)->getPath(path, destination)) { paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired) + syncronizeState(); return false; } @@ -103,6 +81,7 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h) { paths.erase(h); adventureInt->onHeroChanged(h); + syncronizeState(); } void PlayerLocalState::verifyPath(const CGHeroInstance * h) @@ -180,6 +159,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection) if (adventureInt && selection) adventureInt->onSelectionChanged(selection); + syncronizeState(); } bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const @@ -194,6 +174,7 @@ void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero) assert(!vstd::contains(sleepingHeroes, hero)); sleepingHeroes.push_back(hero); + syncronizeState(); } void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero) @@ -203,6 +184,7 @@ void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero) assert(vstd::contains(sleepingHeroes, hero)); vstd::erase(sleepingHeroes, hero); + syncronizeState(); } const std::vector & PlayerLocalState::getWanderingHeroes() @@ -225,6 +207,8 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero) if (currentSelection == nullptr) setSelection(hero); + + syncronizeState(); } void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) @@ -246,6 +230,8 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) if (currentSelection == nullptr && !ownedTowns.empty()) setSelection(ownedTowns.front()); + + syncronizeState(); } void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2) @@ -254,6 +240,8 @@ void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2) std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2)); adventureInt->onHeroOrderChanged(); + + syncronizeState(); } const std::vector & PlayerLocalState::getOwnedTowns() @@ -276,6 +264,8 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town) if (currentSelection == nullptr) setSelection(town); + + syncronizeState(); } void PlayerLocalState::removeOwnedTown(const CGTownInstance * town) @@ -292,6 +282,8 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town) if (currentSelection == nullptr && !ownedTowns.empty()) setSelection(ownedTowns.front()); + + syncronizeState(); } void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2) @@ -299,5 +291,119 @@ void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2) assert(ownedTowns[pos1] && ownedTowns[pos2]); std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2)); + syncronizeState(); + adventureInt->onTownOrderChanged(); } + +void PlayerLocalState::syncronizeState() +{ + JsonNode data; + serialize(data); + owner.cb->saveLocalState(data); +} + +void PlayerLocalState::serialize(JsonNode & dest) const +{ + dest.clear(); + + for (auto const * town : ownedTowns) + { + JsonNode record; + record["id"].Integer() = town->id; + dest["towns"].Vector().push_back(record); + } + + for (auto const * hero : wanderingHeroes) + { + JsonNode record; + record["id"].Integer() = hero->id; + if (vstd::contains(sleepingHeroes, hero)) + record["sleeping"].Bool() = true; + + if (paths.count(hero)) + { + record["path"]["x"].Integer() = paths.at(hero).lastNode().coord.x; + record["path"]["y"].Integer() = paths.at(hero).lastNode().coord.y; + record["path"]["z"].Integer() = paths.at(hero).lastNode().coord.z; + } + dest["heroes"].Vector().push_back(record); + } + dest["spellbook"]["pageBattle"].Integer() = spellbookSettings.spellbookLastPageBattle; + dest["spellbook"]["pageAdvmap"].Integer() = spellbookSettings.spellbookLastPageAdvmap; + dest["spellbook"]["tabBattle"].Integer() = spellbookSettings.spellbookLastTabBattle; + dest["spellbook"]["tabAdvmap"].Integer() = spellbookSettings.spellbookLastTabAdvmap; + + dest["currentSelection"].Integer() = currentSelection->id; +} + +void PlayerLocalState::deserialize(const JsonNode & source) +{ + // this method must be called after player state has been initialized + assert(currentSelection != nullptr); + assert(!ownedTowns.empty() || wanderingHeroes.empty()); + + auto oldHeroes = wanderingHeroes; + auto oldTowns = ownedTowns; + + paths.clear(); + sleepingHeroes.clear(); + wanderingHeroes.clear(); + ownedTowns.clear(); + + for (auto const & town : source["towns"].Vector()) + { + ObjectInstanceID objID(town["id"].Integer()); + const CGTownInstance * townPtr = owner.cb->getTown(objID); + + if (!townPtr) + continue; + + if (!vstd::contains(oldTowns, townPtr)) + continue; + + ownedTowns.push_back(townPtr); + vstd::erase(oldTowns, townPtr); + } + + for (auto const & hero : source["heroes"].Vector()) + { + ObjectInstanceID objID(hero["id"].Integer()); + const CGHeroInstance * heroPtr = owner.cb->getHero(objID); + + if (!heroPtr) + continue; + + if (!vstd::contains(oldHeroes, heroPtr)) + continue; + + wanderingHeroes.push_back(heroPtr); + vstd::erase(oldHeroes, heroPtr); + + if (hero["sleeping"].Bool()) + sleepingHeroes.push_back(heroPtr); + + if (hero["path"]["x"].isNumber() && hero["path"]["y"].isNumber() && hero["path"]["z"].isNumber()) + { + int3 pathTarget(hero["path"]["x"].Integer(), hero["path"]["y"].Integer(), hero["path"]["z"].Integer()); + setPath(heroPtr, pathTarget); + } + } + + spellbookSettings.spellbookLastPageBattle = source["spellbook"]["pageBattle"].Integer(); + spellbookSettings.spellbookLastPageAdvmap = source["spellbook"]["pageAdvmap"].Integer(); + spellbookSettings.spellbookLastTabBattle = source["spellbook"]["tabBattle"].Integer(); + spellbookSettings.spellbookLastTabAdvmap = source["spellbook"]["tabAdvmap"].Integer(); + + // append any owned heroes / towns that were not present in loaded state + wanderingHeroes.insert(wanderingHeroes.end(), oldHeroes.begin(), oldHeroes.end()); + ownedTowns.insert(ownedTowns.end(), oldTowns.begin(), oldTowns.end()); + +//FIXME: broken, anything that is selected in here will be overwritten on NewTurn pack +// ObjectInstanceID selectedObjectID(source["currentSelection"].Integer()); +// const CGObjectInstance * objectPtr = owner.cb->getObjInstance(selectedObjectID); +// const CArmedInstance * armyPtr = dynamic_cast(objectPtr); +// +// if (armyPtr) +// setSelection(armyPtr); +} diff --git a/client/PlayerLocalState.h b/client/PlayerLocalState.h index b4bd481eb..0cfda1f7a 100644 --- a/client/PlayerLocalState.h +++ b/client/PlayerLocalState.h @@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; class CGTownInstance; class CArmedInstance; +class JsonNode; struct CGPath; class int3; @@ -45,9 +46,7 @@ class PlayerLocalState PlayerSpellbookSetting spellbookSettings; - void saveHeroPaths(std::map & paths); - void loadHeroPaths(std::map & paths); - + void syncronizeState(); public: explicit PlayerLocalState(CPlayerInterface & owner); @@ -87,6 +86,9 @@ public: const CGTownInstance * getCurrentTown() const; const CArmedInstance * getCurrentArmy() const; + void serialize(JsonNode & dest) const; + void deserialize(const JsonNode & source); + /// Changes currently selected object void setSelection(const CArmedInstance *sel); }; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7bfb5225c..37cb28106 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -559,6 +559,7 @@ set(lib_MAIN_HEADERS networkPacks/PacksForServer.h networkPacks/SetRewardableConfiguration.h networkPacks/SetStackEffect.h + networkPacks/SaveLocalState.h networkPacks/StackLocation.h networkPacks/TradeItem.h diff --git a/lib/CPlayerState.cpp b/lib/CPlayerState.cpp index 2242d5381..2660377c6 100644 --- a/lib/CPlayerState.cpp +++ b/lib/CPlayerState.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "CPlayerState.h" +#include "json/JsonNode.h" #include "mapObjects/CGDwelling.h" #include "mapObjects/CGTownInstance.h" #include "mapObjects/CGHeroInstance.h" @@ -20,8 +21,13 @@ VCMI_LIB_NAMESPACE_BEGIN PlayerState::PlayerState() - : color(-1), human(false), cheated(false), enteredWinningCheatCode(false), - enteredLosingCheatCode(false), status(EPlayerStatus::INGAME) + : color(-1) + , playerLocalSettings(std::make_unique()) + , human(false) + , cheated(false) + , enteredWinningCheatCode(false) + , enteredLosingCheatCode(false) + , status(EPlayerStatus::INGAME) { setNodeType(PLAYER); } diff --git a/lib/CPlayerState.h b/lib/CPlayerState.h index 96a0ba790..411b0945f 100644 --- a/lib/CPlayerState.h +++ b/lib/CPlayerState.h @@ -16,7 +16,6 @@ #include "bonuses/CBonusSystemNode.h" #include "ResourceSet.h" #include "TurnTimerInfo.h" -#include "ConstTransitivePtr.h" VCMI_LIB_NAMESPACE_BEGIN @@ -66,6 +65,7 @@ public: std::vector quests; //store info about all received quests std::vector battleBonuses; //additional bonuses to be added during battle with neutrals std::map> costumesArtifacts; + std::unique_ptr playerLocalSettings; // Json with client-defined data, such as order of heroes or current hero paths. Not used by client/lib bool cheated; bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory @@ -116,6 +116,9 @@ public: h & status; h & turnTimer; + if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA) + h & *playerLocalSettings; + if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS) { h & ownedObjects; diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 007f30fa6..09a67e354 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -13,6 +13,7 @@ #include "PacksForClientBattle.h" #include "PacksForServer.h" #include "PacksForLobby.h" +#include "SaveLocalState.h" #include "SetRewardableConfiguration.h" #include "SetStackEffect.h" @@ -177,6 +178,7 @@ public: virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {} virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {} virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {} + virtual void visitSaveLocalState(SaveLocalState & pack) {} }; VCMI_LIB_NAMESPACE_END diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6e7229f98..a5a6900d7 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -12,6 +12,7 @@ #include "PacksForClient.h" #include "PacksForClientBattle.h" #include "PacksForServer.h" +#include "SaveLocalState.h" #include "SetRewardableConfiguration.h" #include "StackLocation.h" #include "PacksForLobby.h" @@ -92,6 +93,12 @@ bool CLobbyPackToServer::isForServer() const return true; } +void SaveLocalState::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSaveLocalState(*this); +} + + void PackageApplied::visitTyped(ICPackVisitor & visitor) { visitor.visitPackageApplied(*this); diff --git a/lib/networkPacks/SaveLocalState.h b/lib/networkPacks/SaveLocalState.h new file mode 100644 index 000000000..f7a8ec21a --- /dev/null +++ b/lib/networkPacks/SaveLocalState.h @@ -0,0 +1,27 @@ +/* + * SaveLocalState.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "NetPacksBase.h" + +#include "../json/JsonNode.h" + +struct DLL_LINKAGE SaveLocalState : public CPackForServer +{ + JsonNode data; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + h & data; + } +}; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index b4be54223..3f7845cff 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -62,6 +62,7 @@ enum class ESerializationVersion : int32_t REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects REGION_LABEL, // 864 - labels for campaign regions SPELL_RESEARCH, // 865 - spell research + LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data - CURRENT = SPELL_RESEARCH + CURRENT = LOCAL_PLAYER_STATE_DATA }; diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index f716c7be4..492d0e371 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -34,6 +34,7 @@ #include "../networkPacks/PacksForClientBattle.h" #include "../networkPacks/PacksForLobby.h" #include "../networkPacks/PacksForServer.h" +#include "../networkPacks/SaveLocalState.h" #include "../networkPacks/SetRewardableConfiguration.h" #include "../networkPacks/SetStackEffect.h" @@ -290,6 +291,7 @@ void registerTypes(Serializer &s) s.template registerType(240); s.template registerType(241); s.template registerType(242); + s.template registerType(243); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c2505b29e..9f287bac5 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4012,6 +4012,9 @@ bool CGameHandler::isBlockedByQueries(const CPackForServer *pack, PlayerColor pl if (dynamic_cast(pack) != nullptr) return false; + if (dynamic_cast(pack) != nullptr) + return false; + auto query = queries->topQuery(player); if (query && query->blocksPack(pack)) { diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index fe4607786..f97fdaa70 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -19,6 +19,7 @@ #include "queries/MapQueries.h" #include "../lib/IGameCallback.h" +#include "../lib/CPlayerState.h" #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/gameState/CGameState.h" @@ -389,6 +390,13 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack) result = gh.queryReply(pack.qid, pack.reply, pack.player); } +void ApplyGhNetPackVisitor::visitSaveLocalState(SaveLocalState & pack) +{ + gh.throwIfWrongPlayer(&pack); + *gh.gameState()->getPlayerState(pack.player)->playerLocalSettings = pack.data; + result = true; +} + void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) { gh.throwIfWrongPlayer(&pack); diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index ba94157cb..73feeec8f 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -62,4 +62,5 @@ public: void visitDigWithHero(DigWithHero & pack) override; void visitCastAdvSpell(CastAdvSpell & pack) override; void visitPlayerMessage(PlayerMessage & pack) override; + void visitSaveLocalState(SaveLocalState & pack) override; }; From c4859d51d0f028ab588dcd0b2866fec18c32e6dc Mon Sep 17 00:00:00 2001 From: Warzyw647 Date: Wed, 9 Oct 2024 00:42:04 +0200 Subject: [PATCH 289/726] Changed Commander Resistance type from Dwarf to Golem Only changed the bonus on level-ups The starting bonus on level 1 needs to be changed in mods --- server/CGameHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c2505b29e..273379c1e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -235,7 +235,8 @@ void CGameHandler::levelUpCommander (const CCommanderInstance * c, int skill) scp.accumulatedBonus.type = BonusType::STACKS_SPEED; break; case ECommander::SPELL_POWER: - scp.accumulatedBonus.type = BonusType::MAGIC_RESISTANCE; + scp.accumulatedBonus.type = BonusType::SPELL_DAMAGE_REDUCTION; + scp.accumulatedBonus.subtype = BonusSubtypeID(SpellSchool::ANY); scp.accumulatedBonus.val = difference (VLC->creh->skillLevels, c->secondarySkills, ECommander::RESISTANCE); sendAndApply(scp); //additional pack scp.accumulatedBonus.type = BonusType::CREATURE_SPELL_POWER; From 7847fc4bbaa6706bb5bfd2d5af91630ebeb39851 Mon Sep 17 00:00:00 2001 From: Evgeniy Meshcheryakov Date: Wed, 9 Oct 2024 11:27:43 +0300 Subject: [PATCH 290/726] Fix static linking --- launcher/CMakeLists.txt | 4 ++++ mapeditor/CMakeLists.txt | 4 ++++ vcmiqt/vcmiqt.h | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e2229ba19..a56893838 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -202,6 +202,10 @@ elseif(NOT APPLE_IOS) target_link_libraries(vcmilauncher SDL2::SDL2) endif() +if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER)) + target_compile_definitions(vcmilauncher PRIVATE VCMIQT_STATIC) +endif() + target_link_libraries(vcmilauncher vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmilauncher PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 33f6f6e67..e9f7ad6cf 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -217,6 +217,10 @@ if(APPLE) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmieditor) endif() +if(ENABLE_STATIC_LIBS OR NOT (ENABLE_EDITOR AND ENABLE_LAUNCHER)) + target_compile_definitions(vcmieditor PRIVATE VCMIQT_STATIC) +endif() + target_link_libraries(vcmieditor vcmi vcmiqt Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) target_include_directories(vcmieditor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/vcmiqt/vcmiqt.h b/vcmiqt/vcmiqt.h index a3cf5f8c0..4783eaaae 100644 --- a/vcmiqt/vcmiqt.h +++ b/vcmiqt/vcmiqt.h @@ -10,7 +10,7 @@ #include -#if VCMIQT_STATIC +#ifdef VCMIQT_STATIC # define VCMIQT_LINKAGE #elif defined(VCMIQT_SHARED) # define VCMIQT_LINKAGE Q_DECL_EXPORT From a8f8c3f4b14885c1b53115ccda84e6d87c41354c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 2 Oct 2024 16:40:06 +0000 Subject: [PATCH 291/726] Replaced most of accesses to CGObjectInstance::pos with anchorPoint() --- AI/Nullkiller/AIGateway.cpp | 4 +-- AI/Nullkiller/Goals/BuildThis.cpp | 2 +- AI/VCAI/Goals/FindObj.cpp | 2 +- AI/VCAI/VCAI.cpp | 12 +++---- client/HeroMovementController.cpp | 2 +- client/adventureMap/MapAudioPlayer.cpp | 6 ++-- client/mapView/MapRenderer.cpp | 4 +-- client/mapView/MapRendererContext.cpp | 4 +-- client/mapView/MapRendererContextState.cpp | 6 ++-- client/mapView/MapViewController.cpp | 2 +- client/mapView/mapHandler.cpp | 14 ++++---- client/windows/CQuestLog.cpp | 4 +-- lib/CGameInfoCallback.cpp | 2 +- lib/gameState/CGameState.cpp | 31 +++++++--------- lib/gameState/CGameStateCampaign.cpp | 4 +-- lib/mapObjects/CGCreature.cpp | 6 ++-- lib/mapObjects/CGHeroInstance.cpp | 4 +-- lib/mapObjects/CGObjectInstance.cpp | 37 +++++++++----------- lib/mapObjects/CGObjectInstance.h | 17 +++++---- lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/CGTownInstance.h | 2 -- lib/mapObjects/CQuest.cpp | 2 +- lib/mapObjects/IObjectInterface.cpp | 2 +- lib/mapObjects/IObjectInterface.h | 2 +- lib/mapObjects/MiscObjects.cpp | 22 ++++++------ lib/mapObjects/TownBuildingInstance.cpp | 4 +-- lib/mapObjects/TownBuildingInstance.h | 2 +- lib/mapping/CMap.cpp | 28 +++++++-------- lib/mapping/CMapOperation.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 4 +-- lib/mapping/MapFormatJson.cpp | 2 +- lib/networkPacks/NetPacksLib.cpp | 4 +-- lib/rmg/RmgObject.cpp | 14 ++++---- lib/rmg/modificators/QuestArtifactPlacer.cpp | 4 +-- lib/spells/AdventureSpellMechanics.cpp | 6 ++-- mapeditor/maphandler.cpp | 2 +- mapeditor/mapsettings/abstractsettings.cpp | 2 +- mapeditor/scenelayer.cpp | 12 +++---- server/CGameHandler.cpp | 9 +++-- server/NetPacksServer.cpp | 2 +- 40 files changed, 142 insertions(+), 150 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 3609182df..bf289681c 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -864,7 +864,7 @@ void AIGateway::makeTurn() void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { - LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString()); + LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString()); switch(obj->ID) { case Obj::TOWN: @@ -1455,7 +1455,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building) { auto name = t->town->buildings.at(building)->getNameTranslated(); - logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString()); + logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, building); //just do this; } diff --git a/AI/Nullkiller/Goals/BuildThis.cpp b/AI/Nullkiller/Goals/BuildThis.cpp index c9c89c0a9..4de43c060 100644 --- a/AI/Nullkiller/Goals/BuildThis.cpp +++ b/AI/Nullkiller/Goals/BuildThis.cpp @@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai) if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->pos.toString()); + ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString()); cb->buildBuilding(town, b); return; diff --git a/AI/VCAI/Goals/FindObj.cpp b/AI/VCAI/Goals/FindObj.cpp index 189a5c44b..288474eaa 100644 --- a/AI/VCAI/Goals/FindObj.cpp +++ b/AI/VCAI/Goals/FindObj.cpp @@ -46,7 +46,7 @@ TSubgoal FindObj::whatToDoToAchieve() } } } - if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is + if(o && ai->isAccessible(o->visitablePos())) //we don't use isAccessibleForHero as we don't know which hero it is return sptr(VisitObj(o->id.getNum())); else return sptr(Explore()); diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 3757c56d3..c72a2a655 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1032,7 +1032,7 @@ void VCAI::mainLoop() void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h) { - LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString()); + LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString()); switch(obj->ID) { case Obj::TOWN: @@ -1417,11 +1417,11 @@ void VCAI::wander(HeroPtr h) //TODO pick the truly best const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements); logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->getNameTranslated(), t->visitablePos().toString()); - int3 pos1 = h->pos; + int3 posBefore = h->visitablePos(); striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop //if out hero is stuck, we may need to request another hero to clear the way we see - if(pos1 == h->pos && h == primaryHero()) //hero can't move + if(posBefore == h->visitablePos() && h == primaryHero()) //hero can't move { if(canRecruitAnyHero(t)) recruitHero(t); @@ -1471,7 +1471,7 @@ void VCAI::wander(HeroPtr h) { auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); if(chosenObject != nullptr) - logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); + logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->anchorPos().toString()); } else logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name()); @@ -1995,7 +1995,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) void VCAI::buildStructure(const CGTownInstance * t, BuildingID building) { auto name = t->town->buildings.at(building)->getNameTranslated(); - logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString()); + logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, building); //just do this; } @@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g) if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->pos.toString()); + playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, b); throw goalFulfilledException(sptr(g)); } diff --git a/client/HeroMovementController.cpp b/client/HeroMovementController.cpp index d402413e5..2ee599017 100644 --- a/client/HeroMovementController.cpp +++ b/client/HeroMovementController.cpp @@ -375,7 +375,7 @@ void HeroMovementController::sendMovementRequest(const CGHeroInstance * h, const { updateMovementSound(h, currNode.coord, nextNode.coord, nextNode.action); - assert(h->pos.z == nextNode.coord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all + assert(h->anchorPos().z == nextNode.coord.z); // Z should change only if it's movement via teleporter and in this case this code shouldn't be executed at all logGlobal->trace("Requesting hero movement to %s", nextNode.coord.toString()); diff --git a/client/adventureMap/MapAudioPlayer.cpp b/client/adventureMap/MapAudioPlayer.cpp index bac234103..356b5577b 100644 --- a/client/adventureMap/MapAudioPlayer.cpp +++ b/client/adventureMap/MapAudioPlayer.cpp @@ -81,9 +81,9 @@ void MapAudioPlayer::addObject(const CGObjectInstance * obj) { for(int fy = 0; fy < obj->getHeight(); ++fy) { - int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z); + int3 currTile(obj->anchorPos().x - fx, obj->anchorPos().y - fy, obj->anchorPos().z); - if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile.x, currTile.y)) + if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile)) objects[currTile.z][currTile.x][currTile.y].push_back(obj->id); } } @@ -108,7 +108,7 @@ void MapAudioPlayer::addObject(const CGObjectInstance * obj) for(const auto & tile : tiles) { - int3 currTile = obj->pos + tile; + int3 currTile = obj->anchorPos() + tile; if(LOCPLINT->cb->isInTheMap(currTile)) objects[currTile.z][currTile.x][currTile.y].push_back(obj->id); diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 6688812a6..a274fe5ec 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -591,8 +591,8 @@ void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & targ if(context.objectTransparency(objectID, coordinates) > 0 && !context.isActiveHero(object)) { - visitable |= object->visitableAt(coordinates.x, coordinates.y); - blocking |= object->blockingAt(coordinates.x, coordinates.y); + visitable |= object->visitableAt(coordinates); + blocking |= object->blockingAt(coordinates); } } diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 239541ae5..291322f31 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -120,7 +120,7 @@ size_t MapRendererBaseContext::objectGroupIndex(ObjectInstanceID objectID) const Point MapRendererBaseContext::objectImageOffset(ObjectInstanceID objectID, const int3 & coordinates) const { const CGObjectInstance * object = getObject(objectID); - int3 offsetTiles(object->getPosition() - coordinates); + int3 offsetTiles(object->anchorPos() - coordinates); return Point(offsetTiles) * Point(32, 32); } @@ -498,7 +498,7 @@ size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates) { const auto * object = getObject(objectID); - if(!object->visitableAt(coordinates.x, coordinates.y)) + if(!object->visitableAt(coordinates)) continue; ObjectPosInfo info(object); diff --git a/client/mapView/MapRendererContextState.cpp b/client/mapView/MapRendererContextState.cpp index aa1a6ab0a..bb2632ce3 100644 --- a/client/mapView/MapRendererContextState.cpp +++ b/client/mapView/MapRendererContextState.cpp @@ -49,9 +49,9 @@ void MapRendererContextState::addObject(const CGObjectInstance * obj) { for(int fy = 0; fy < obj->getHeight(); ++fy) { - int3 currTile(obj->pos.x - fx, obj->pos.y - fy, obj->pos.z); + int3 currTile(obj->anchorPos().x - fx, obj->anchorPos().y - fy, obj->anchorPos().z); - if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile.x, currTile.y)) + if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile)) { auto & container = objects[currTile.z][currTile.x][currTile.y]; @@ -73,7 +73,7 @@ void MapRendererContextState::addMovingObject(const CGObjectInstance * object, c { for(int y = yFrom; y <= yDest; ++y) { - int3 currTile(x, y, object->pos.z); + int3 currTile(x, y, object->anchorPos().z); if(LOCPLINT->cb->isInTheMap(currTile)) { diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index ec33d6f5b..fa653725b 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -317,7 +317,7 @@ bool MapViewController::isEventVisible(const CGObjectInstance * obj, const Playe if(obj->isVisitable()) return context->isVisible(obj->visitablePos()); else - return context->isVisible(obj->pos); + return context->isVisible(obj->anchorPos()); } bool MapViewController::isEventVisible(const CGHeroInstance * obj, const int3 & from, const int3 & dest) diff --git a/client/mapView/mapHandler.cpp b/client/mapView/mapHandler.cpp index bd19e421a..ede4b3ea5 100644 --- a/client/mapView/mapHandler.cpp +++ b/client/mapView/mapHandler.cpp @@ -59,7 +59,7 @@ std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) cons for(const auto & object : map->objects) { - if(object && object->coveringAt(pos.x, pos.y) && object->pos.z == pos.z && object->isTile2Terrain()) + if(object && object->coveringAt(pos) && object->isTile2Terrain()) { result = object->getObjectName(); break; @@ -103,15 +103,15 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj for(const auto & aOffset : a->getBlockedOffsets()) { - int3 testTarget = a->pos + aOffset + int3(0, 1, 0); - if(b->blockingAt(testTarget.x, testTarget.y)) + int3 testTarget = a->anchorPos() + aOffset + int3(0, 1, 0); + if(b->blockingAt(testTarget)) bBlocksA += 1; } for(const auto & bOffset : b->getBlockedOffsets()) { - int3 testTarget = b->pos + bOffset + int3(0, 1, 0); - if(a->blockingAt(testTarget.x, testTarget.y)) + int3 testTarget = b->anchorPos() + bOffset + int3(0, 1, 0); + if(a->blockingAt(testTarget)) aBlocksB += 1; } @@ -126,8 +126,8 @@ bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObj return aBlocksB < bBlocksA; // object that don't have clear priority via tile blocking will appear based on their row - if(a->pos.y != b->pos.y) - return a->pos.y < b->pos.y; + if(a->anchorPos().y != b->anchorPos().y) + return a->anchorPos().y < b->anchorPos().y; // heroes should appear on top of objects on the same tile if(b->ID==Obj::HERO && a->ID!=Obj::HERO) diff --git a/client/windows/CQuestLog.cpp b/client/windows/CQuestLog.cpp index 2e18ef424..dbc39d439 100644 --- a/client/windows/CQuestLog.cpp +++ b/client/windows/CQuestLog.cpp @@ -78,7 +78,7 @@ void CQuestMinimap::addQuestMarks (const QuestInfo * q) int3 tile; if (q->obj) - tile = q->obj->pos; + tile = q->obj->visitablePos(); else tile = q->tile; @@ -104,7 +104,7 @@ void CQuestMinimap::update() void CQuestMinimap::iconClicked() { if(currentQuest->obj) - adventureInt->centerOnTile(currentQuest->obj->pos); + adventureInt->centerOnTile(currentQuest->obj->visitablePos()); //moveAdvMapSelection(); } diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 8e404e34c..fa5fe231d 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -539,7 +539,7 @@ EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) cons for(const auto & object : gs->map->objects) { - if(object && object->ID == Obj::HOLE && object->pos == tile) + if(object && object->ID == Obj::HOLE && object->anchorPos() == tile) return EDiggingStatus::TILE_OCCUPIED; } return getTile(tile)->getDiggingStatus(); diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index e35432cef..d0cd98fae 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -449,7 +449,7 @@ void CGameState::initGrailPosition() //remove tiles with holes for(auto & elem : map->objects) if(elem && elem->ID == Obj::HOLE) - allowedPos -= elem->pos; + allowedPos -= elem->anchorPos(); if(!allowedPos.empty()) { @@ -495,7 +495,7 @@ void CGameState::randomizeMapObjects() { for (int j = 0; j < object->getHeight() ; j++) { - int3 pos = object->pos - int3(i,j,0); + int3 pos = object->anchorPos() - int3(i,j,0); if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128; } } @@ -530,7 +530,7 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy { for(auto town : map->towns) { - if(town->getPosition() == townPos) + if(town->anchorPos() == townPos) { townPos = town->visitablePos(); break; @@ -545,8 +545,7 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy hero->setHeroType(heroTypeId); hero->tempOwner = playerColor; - hero->pos = townPos; - hero->pos += hero->getVisitableOffset(); + hero->setAnchorPos(townPos + hero->getVisitableOffset()); map->getEditManager()->insertObject(hero); } @@ -614,7 +613,7 @@ void CGameState::initHeroes() auto boat = dynamic_cast(handler->create(callback, nullptr)); handler->configureObject(boat, gs->getRandomGenerator()); - boat->pos = hero->pos; + boat->setAnchorPos(hero->anchorPos()); boat->appearance = handler->getTemplates().front(); boat->id = ObjectInstanceID(static_cast(gs->map->objects.size())); @@ -964,22 +963,18 @@ void CGameState::placeHeroesInTowns() { for(CGTownInstance * t : player.second.getTowns()) { - if(h->visitablePos().z != t->visitablePos().z) - continue; - - bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos().x, h->visitablePos().y); + bool heroOnTownBlockableTile = t->blockingAt(h->visitablePos()); // current hero position is at one of blocking tiles of current town // assume that this hero should be visiting the town (H3M format quirk) and move hero to correct position if (heroOnTownBlockableTile) { - int3 correctedPos = h->convertFromVisitablePos(t->visitablePos()); - map->removeBlockVisTiles(h); - h->pos = correctedPos; + int3 correctedPos = h->convertFromVisitablePos(t->visitablePos()); + h->setAnchorPos(correctedPos); map->addBlockVisTiles(h); - assert(t->visitableAt(h->visitablePos().x, h->visitablePos().y)); + assert(t->visitableAt(h->visitablePos())); } } } @@ -1001,7 +996,7 @@ void CGameState::initVisitingAndGarrisonedHeroes() if(h->visitablePos().z != t->visitablePos().z) continue; - if (t->visitableAt(h->visitablePos().x, h->visitablePos().y)) + if (t->visitableAt(h->visitablePos())) { assert(t->visitingHero == nullptr); t->setVisitingHero(h); @@ -1066,7 +1061,7 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand) for(auto &obj : map->objects) { //look only for objects covering given tile - if( !obj || obj->pos.z != tile.z || !obj->coveringAt(tile.x, tile.y)) + if( !obj || !obj->coveringAt(tile)) continue; auto customBattlefield = obj->getBattlefield(); @@ -1250,10 +1245,10 @@ bool CGameState::isVisible(const CGObjectInstance * obj, const std::optionalgetWidth(); ++fx) { - int3 pos = obj->pos + int3(-fx, -fy, 0); + int3 pos = obj->anchorPos() + int3(-fx, -fy, 0); if ( map->isInTheMap(pos) && - obj->coveringAt(pos.x, pos.y) && + obj->coveringAt(pos) && isVisible(pos, *player)) return true; } diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 703f97667..b4bf3678c 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -368,7 +368,7 @@ void CGameStateCampaign::replaceHeroesPlaceholders() heroToPlace->id = campaignHeroReplacement.heroPlaceholderId; if(heroPlaceholder->tempOwner.isValidPlayer()) heroToPlace->tempOwner = heroPlaceholder->tempOwner; - heroToPlace->pos = heroPlaceholder->pos; + heroToPlace->setAnchorPos(heroPlaceholder->anchorPos()); heroToPlace->type = heroToPlace->getHeroType().toHeroType(); heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front(); @@ -655,7 +655,7 @@ void CGameStateCampaign::initTowns() if (!owner->human) continue; - if (town->pos != pi.posOfMainTown) + if (town->anchorPos() != pi.posOfMainTown) continue; BuildingID newBuilding; diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index ffa7bf5a9..3ebead8cb 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -33,7 +33,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const if(stacks.empty()) { //should not happen... - logGlobal->error("Invalid stack at tile %s: subID=%d; id=%d", pos.toString(), getCreature(), id.getNum()); + logGlobal->error("Invalid stack at tile %s: subID=%d; id=%d", anchorPos().toString(), getCreature(), id.getNum()); return "INVALID_STACK"; } @@ -562,7 +562,7 @@ bool CGCreature::containsUpgradedStack() const float c = 5325.181015f; float d = 32788.727920f; - int val = static_cast(std::floor(a * pos.x + b * pos.y + c * pos.z + d)); + int val = static_cast(std::floor(a * visitablePos().x + b * visitablePos().y + c * visitablePos().z + d)); return ((val % 32768) % 100) < 50; } @@ -591,7 +591,7 @@ int CGCreature::getNumberOfStacks(const CGHeroInstance *hero) const ui32 c = 1943276003u; ui32 d = 3174620878u; - ui32 R1 = a * static_cast(pos.x) + b * static_cast(pos.y) + c * static_cast(pos.z) + d; + ui32 R1 = a * static_cast(visitablePos().x) + b * static_cast(visitablePos().y) + c * static_cast(visitablePos().z) + d; ui32 R2 = (R1 >> 16) & 0x7fff; int R4 = R2 % 100 + 1; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 53426d09b..080285a96 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1514,11 +1514,11 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, BonusSubtypeID if (visionsMultiplier > 0) vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present - const int distance = static_cast(target->pos.dist2d(visitablePos())); + const int distance = static_cast(target->anchorPos().dist2d(visitablePos())); //logGlobal->debug(boost::str(boost::format("Visions: dist %d, mult %d, range %d") % distance % visionsMultiplier % visionsRange)); - return (distance < visionsRange) && (target->pos.z == pos.z); + return (distance < visionsRange) && (target->anchorPos().z == anchorPos().z); } std::string CGHeroInstance::getHeroTypeName() const diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index 5cee078fe..c9d7b3eea 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -54,14 +54,14 @@ MapObjectSubID CGObjectInstance::getObjTypeIndex() const return subID; } -int3 CGObjectInstance::getPosition() const +int3 CGObjectInstance::anchorPos() const { return pos; } int3 CGObjectInstance::getTopVisiblePos() const { - return pos - appearance->getTopVisibleOffset(); + return anchorPos() - appearance->getTopVisibleOffset(); } void CGObjectInstance::setOwner(const PlayerColor & ow) @@ -69,6 +69,11 @@ void CGObjectInstance::setOwner(const PlayerColor & ow) tempOwner = ow; } +void CGObjectInstance::setAnchorPos(int3 newPos) +{ + pos = newPos; +} + int CGObjectInstance::getWidth() const { return appearance->getWidth(); @@ -79,32 +84,19 @@ int CGObjectInstance::getHeight() const return appearance->getHeight(); } -bool CGObjectInstance::visitableAt(int x, int y) const -{ - return appearance->isVisitableAt(pos.x - x, pos.y - y); -} -bool CGObjectInstance::blockingAt(int x, int y) const -{ - return appearance->isBlockedAt(pos.x - x, pos.y - y); -} - -bool CGObjectInstance::coveringAt(int x, int y) const -{ - return appearance->isVisibleAt(pos.x - x, pos.y - y); -} - bool CGObjectInstance::visitableAt(const int3 & testPos) const { - return pos.z == testPos.z && appearance->isVisitableAt(pos.x - testPos.x, pos.y - testPos.y); + return anchorPos().z == testPos.z && appearance->isVisitableAt(anchorPos().x - testPos.x, anchorPos().y - testPos.y); } + bool CGObjectInstance::blockingAt(const int3 & testPos) const { - return pos.z == testPos.z && appearance->isBlockedAt(pos.x - testPos.x, pos.y - testPos.y); + return anchorPos().z == testPos.z && appearance->isBlockedAt(anchorPos().x - testPos.x, anchorPos().y - testPos.y); } bool CGObjectInstance::coveringAt(const int3 & testPos) const { - return pos.z == testPos.z && appearance->isVisibleAt(pos.x - testPos.x, pos.y - testPos.y); + return anchorPos().z == testPos.z && appearance->isVisibleAt(anchorPos().x - testPos.x, anchorPos().y - testPos.y); } std::set CGObjectInstance::getBlockedPos() const @@ -115,7 +107,7 @@ std::set CGObjectInstance::getBlockedPos() const for(int h=0; hisBlockedAt(w, h)) - ret.insert(int3(pos.x - w, pos.y - h, pos.z)); + ret.insert(int3(anchorPos().x - w, anchorPos().y - h, anchorPos().z)); } } return ret; @@ -215,6 +207,8 @@ int CGObjectInstance::getSightRadius() const int3 CGObjectInstance::getVisitableOffset() const { + if (!isVisitable()) + throw std::runtime_error("Attempt to access visitable offset of a non-visitable object!"); return appearance->getVisitableOffset(); } @@ -313,6 +307,9 @@ void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const int3 CGObjectInstance::visitablePos() const { + if (!isVisitable()) + throw std::runtime_error("Attempt to access visitable position on a non-visitable object!"); + return pos - getVisitableOffset(); } diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index 130077ae1..b5fdd7142 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -28,8 +28,6 @@ using TObjectTypeHandler = std::shared_ptr; class DLL_LINKAGE CGObjectInstance : public IObjectInterface { public: - /// Position of bottom-right corner of object on map - int3 pos; /// Type of object, e.g. town, hero, creature. MapObjectID ID; /// Subtype of object, depends on type @@ -41,6 +39,9 @@ public: /// Defines appearance of object on map (animation, blocked tiles, blit order, etc) std::shared_ptr appearance; + /// Position of bottom-right corner of object on map + int3 pos; + std::string instanceName; std::string typeName; std::string subTypeName; @@ -62,21 +63,19 @@ public: return this->tempOwner; } void setOwner(const PlayerColor & ow); + void setAnchorPos(int3 pos); /** APPEARANCE ACCESSORS **/ int getWidth() const; //returns width of object graphic in tiles int getHeight() const; //returns height of object graphic in tiles int3 visitablePos() const override; - int3 getPosition() const override; + int3 anchorPos() const override; int3 getTopVisiblePos() const; - bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) - bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) - bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) - bool visitableAt(const int3 & pos) const; //returns true if object is visitable at location (x, y) (h3m pos) - bool blockingAt (const int3 & pos) const; //returns true if object is blocking location (x, y) (h3m pos) - bool coveringAt (const int3 & pos) const; //returns true if object covers with picture location (x, y) (h3m pos) + bool visitableAt(const int3 & pos) const; //returns true if object is visitable at location + bool blockingAt (const int3 & pos) const; //returns true if object is blocking location + bool coveringAt (const int3 & pos) const; //returns true if object covers with picture location std::set getBlockedPos() const; //returns set of positions blocked by this object const std::set & getBlockedOffsets() const; //returns set of relative positions blocked by this object diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 9d28aee82..614cd0477 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -954,7 +954,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const return town->buildings.at(buildingID)->resources; else { - logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), pos.toString(), buildingID.toEnum()); + logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), anchorPos().toString(), buildingID.toEnum()); return TResources(); } diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index fe143c6fd..f3b1f90fc 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -56,8 +56,6 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I std::set builtBuildings; public: - using CGDwelling::getPosition; - enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; CTownAndVisitingHero townAndVis; diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 7e1e00298..b49e169ea 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -614,7 +614,7 @@ void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const int CGSeerHut::checkDirection() const { - int3 cord = getCreatureToKill(false)->pos; + int3 cord = getCreatureToKill(false)->visitablePos(); if(static_cast(cord.x) / static_cast(cb->getMapSize().x) < 0.34) //north { if(static_cast(cord.y) / static_cast(cb->getMapSize().y) < 0.34) //northwest diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index 432b9e35d..80cbec7e4 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -145,7 +145,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit out.appendLocalString(EMetaText::ADVOB_TXT, 189); break; case NO_WATER: - logGlobal->error("Shipyard without water at tile %s! ", getObject()->getPosition().toString()); + logGlobal->error("Shipyard without water at tile %s! ", getObject()->anchorPos().toString()); return; } } diff --git a/lib/mapObjects/IObjectInterface.h b/lib/mapObjects/IObjectInterface.h index 9ccda2c7c..4398c5a11 100644 --- a/lib/mapObjects/IObjectInterface.h +++ b/lib/mapObjects/IObjectInterface.h @@ -47,7 +47,7 @@ public: virtual PlayerColor getOwner() const = 0; virtual int3 visitablePos() const = 0; - virtual int3 getPosition() const = 0; + virtual int3 anchorPos() const = 0; virtual void onHeroVisit(const CGHeroInstance * h) const; virtual void onHeroLeave(const CGHeroInstance * h) const; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 9956396f3..b8b95994d 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -111,7 +111,7 @@ void CGMine::initObj(vstd::RNG & rand) } else { - logGlobal->error("Abandoned mine at (%s) has no valid resource candidates!", pos.toString()); + logGlobal->error("Abandoned mine at (%s) has no valid resource candidates!", anchorPos().toString()); producedResource = GameResID::GOLD; } } @@ -510,11 +510,11 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const if(cb->isTeleportChannelImpassable(channel)) { - logGlobal->debug("Cannot find corresponding exit monolith for %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("Cannot find corresponding exit monolith for %d at %s", id.getNum(), anchorPos().toString()); td.impassable = true; } else if(getRandomExit(h) == ObjectInstanceID()) - logGlobal->debug("All exits blocked for monolith %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("All exits blocked for monolith %d at %s", id.getNum(), anchorPos().toString()); } else h->showInfoDialog(70); @@ -574,7 +574,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const if(cb->isTeleportChannelImpassable(channel)) { h->showInfoDialog(153);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. - logGlobal->debug("Cannot find exit subterranean gate for %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("Cannot find exit subterranean gate for %d at %s", id.getNum(), anchorPos().toString()); td.impassable = true; } else @@ -602,13 +602,13 @@ void CGSubterraneanGate::postInit(IGameCallback * cb) //matches subterranean gat auto * hlp = dynamic_cast(cb->gameState()->getObjInstance(obj->id)); if(hlp) - gatesSplit[hlp->pos.z].push_back(hlp); + gatesSplit[hlp->visitablePos().z].push_back(hlp); } //sort by position std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b) { - return a->pos < b->pos; + return a->visitablePos() < b->visitablePos(); }); auto assignToChannel = [&](CGSubterraneanGate * obj) @@ -631,7 +631,7 @@ void CGSubterraneanGate::postInit(IGameCallback * cb) //matches subterranean gat CGSubterraneanGate *checked = gatesSplit[1][j]; if(checked->channel != TeleportChannelID()) continue; - si32 hlp = checked->pos.dist2dSQ(objCurrent->pos); + si32 hlp = checked->visitablePos().dist2dSQ(objCurrent->visitablePos()); if(hlp < best.second) { best.first = j; @@ -657,11 +657,11 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const TeleportDialog td(h->id, channel); if(cb->isTeleportChannelImpassable(channel)) { - logGlobal->debug("Cannot find exit whirlpool for %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("Cannot find exit whirlpool for %d at %s", id.getNum(), anchorPos().toString()); td.impassable = true; } else if(getRandomExit(h) == ObjectInstanceID()) - logGlobal->debug("All exits are blocked for whirlpool %d at %s", id.getNum(), pos.toString()); + logGlobal->debug("All exits are blocked for whirlpool %d at %s", id.getNum(), anchorPos().toString()); if(!isProtected(h)) { @@ -1086,9 +1086,9 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const for(const auto & eye : eyes) { - cb->getTilesInRange (fw.tiles, eye->pos, 10, ETileVisibility::HIDDEN, h->tempOwner); + cb->getTilesInRange (fw.tiles, eye->visitablePos(), 10, ETileVisibility::HIDDEN, h->tempOwner); cb->sendAndApply(fw); - cv.pos = eye->pos; + cv.pos = eye->visitablePos(); cb->sendAndApply(cv); } diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index 04d2dfbe5..cb8f2661b 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -56,9 +56,9 @@ int3 TownBuildingInstance::visitablePos() const return town->visitablePos(); } -int3 TownBuildingInstance::getPosition() const +int3 TownBuildingInstance::anchorPos() const { - return town->getPosition(); + return town->anchorPos(); } TownRewardableBuildingInstance::TownRewardableBuildingInstance(IGameCallback *cb) diff --git a/lib/mapObjects/TownBuildingInstance.h b/lib/mapObjects/TownBuildingInstance.h index 8874793cf..015421448 100644 --- a/lib/mapObjects/TownBuildingInstance.h +++ b/lib/mapObjects/TownBuildingInstance.h @@ -38,7 +38,7 @@ public: const IOwnableObject * asOwnable() const override; int3 visitablePos() const override; - int3 getPosition() const override; + int3 anchorPos() const override; template void serialize(Handler &h) { diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 77af471b0..afd1d610d 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -232,22 +232,22 @@ CMap::~CMap() void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total) { - const int zVal = obj->pos.z; + const int zVal = obj->anchorPos().z; for(int fx = 0; fx < obj->getWidth(); ++fx) { - int xVal = obj->pos.x - fx; + int xVal = obj->anchorPos().x - fx; for(int fy = 0; fy < obj->getHeight(); ++fy) { - int yVal = obj->pos.y - fy; + int yVal = obj->anchorPos().y - fy; if(xVal>=0 && xVal < width && yVal>=0 && yVal < height) { TerrainTile & curt = terrain[zVal][xVal][yVal]; - if(total || obj->visitableAt(xVal, yVal)) + if(total || obj->visitableAt(int3(xVal, yVal, zVal))) { curt.visitableObjects -= obj; curt.visitable = curt.visitableObjects.size(); } - if(total || obj->blockingAt(xVal, yVal)) + if(total || obj->blockingAt(int3(xVal, yVal, zVal))) { curt.blockingObjects -= obj; curt.blocked = curt.blockingObjects.size(); @@ -259,22 +259,22 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total) void CMap::addBlockVisTiles(CGObjectInstance * obj) { - const int zVal = obj->pos.z; + const int zVal = obj->anchorPos().z; for(int fx = 0; fx < obj->getWidth(); ++fx) { - int xVal = obj->pos.x - fx; + int xVal = obj->anchorPos().x - fx; for(int fy = 0; fy < obj->getHeight(); ++fy) { - int yVal = obj->pos.y - fy; + int yVal = obj->anchorPos().y - fy; if(xVal>=0 && xVal < width && yVal >= 0 && yVal < height) { TerrainTile & curt = terrain[zVal][xVal][yVal]; - if(obj->visitableAt(xVal, yVal)) + if(obj->visitableAt(int3(xVal, yVal, zVal))) { curt.visitableObjects.push_back(obj); curt.visitable = true; } - if(obj->blockingAt(xVal, yVal)) + if(obj->blockingAt(int3(xVal, yVal, zVal))) { curt.blockingObjects.push_back(obj); curt.blocked = true; @@ -444,14 +444,14 @@ const CGObjectInstance * CMap::getObjectiveObjectFrom(const int3 & pos, Obj type bestMatch = object; else { - if (object->pos.dist2dSQ(pos) < bestMatch->pos.dist2dSQ(pos)) + if (object->anchorPos().dist2dSQ(pos) < bestMatch->anchorPos().dist2dSQ(pos)) bestMatch = object;// closer than one we already found } } } assert(bestMatch != nullptr); // if this happens - victory conditions or map itself is very, very broken - logGlobal->error("Will use %s from %s", bestMatch->getObjectName(), bestMatch->pos.toString()); + logGlobal->error("Will use %s from %s", bestMatch->getObjectName(), bestMatch->anchorPos().toString()); return bestMatch; } @@ -635,7 +635,7 @@ void CMap::addNewObject(CGObjectInstance * obj) void CMap::moveObject(CGObjectInstance * obj, const int3 & pos) { removeBlockVisTiles(obj); - obj->pos = pos; + obj->setAnchorPos(pos); addBlockVisTiles(obj); } @@ -803,7 +803,7 @@ void CMap::reindexObjects() if (lhs->isRemovable() && !rhs->isRemovable()) return false; - return lhs->pos.y < rhs->pos.y; + return lhs->anchorPos().y < rhs->anchorPos().y; }); // instanceNames don't change diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index 7aaf268fc..f6c8deb22 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -615,7 +615,7 @@ std::string CInsertObjectOperation::getLabel() const CMoveObjectOperation::CMoveObjectOperation(CMap* map, CGObjectInstance* obj, const int3& targetPosition) : CMapOperation(map), obj(obj), - initialPos(obj->pos), + initialPos(obj->anchorPos()), targetPos(targetPosition) { } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 1826d34ea..bfdb2a289 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -913,7 +913,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) if(!hero->artifactsWorn.empty() || !hero->artifactsInBackpack.empty()) { - logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroType().getNum(), hero->pos.toString()); + logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroType().getNum(), hero->anchorPos().toString()); hero->artifactsInBackpack.clear(); while(!hero->artifactsWorn.empty()) @@ -1651,7 +1651,7 @@ void CMapLoaderH3M::readObjects() if(!newObject) continue; - newObject->pos = mapPosition; + newObject->setAnchorPos(mapPosition); newObject->ID = objectTemplate->id; newObject->id = objectInstanceID; if(newObject->ID != Obj::HERO && newObject->ID != Obj::HERO_PLACEHOLDER && newObject->ID != Obj::PRISON) diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 753e235c7..b1a68b0ec 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1072,7 +1072,7 @@ void CMapLoaderJson::MapObjectLoader::construct() instance->id = ObjectInstanceID(static_cast(owner->map->objects.size())); instance->instanceName = jsonKey; - instance->pos = pos; + instance->setAnchorPos(pos); owner->map->addNewObject(instance); } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6e7229f98..9efe0025b 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1047,7 +1047,7 @@ void ChangeObjPos::applyGs(CGameState *gs) return; } gs->map->removeBlockVisTiles(obj); - obj->pos = nPos + obj->getVisitableOffset(); + obj->setAnchorPos(nPos + obj->getVisitableOffset()); gs->map->addBlockVisTiles(obj); } @@ -1467,7 +1467,7 @@ void GiveHero::applyGs(CGameState *gs) h->setOwner(player); h->setMovementPoints(h->movementPointsLimit(true)); - h->pos = h->convertFromVisitablePos(oldVisitablePos); + h->setAnchorPos(h->convertFromVisitablePos(oldVisitablePos)); gs->map->heroesOnMap.emplace_back(h); gs->getPlayerState(h->getOwner())->addOwnedObject(h); diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 1004072cb..691bb23b8 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -87,7 +87,7 @@ const rmg::Area & Object::Instance::getAccessibleArea() const void Object::Instance::setPosition(const int3 & position) { dPosition = position; - dObject.pos = dPosition + dParent.getPosition(); + dObject.setAnchorPos(dPosition + dParent.getPosition()); dBlockedAreaCache.clear(); dAccessibleAreaCache.clear(); @@ -96,21 +96,21 @@ void Object::Instance::setPosition(const int3 & position) void Object::Instance::setPositionRaw(const int3 & position) { - if(!dObject.pos.valid()) + if(!dObject.anchorPos().valid()) { - dObject.pos = dPosition + dParent.getPosition(); + dObject.setAnchorPos(dPosition + dParent.getPosition()); dBlockedAreaCache.clear(); dAccessibleAreaCache.clear(); dParent.clearCachedArea(); } - auto shift = position + dParent.getPosition() - dObject.pos; + auto shift = position + dParent.getPosition() - dObject.anchorPos(); dAccessibleAreaCache.translate(shift); dBlockedAreaCache.translate(shift); dPosition = position; - dObject.pos = dPosition + dParent.getPosition(); + dObject.setAnchorPos(dPosition + dParent.getPosition()); } void Object::Instance::setAnyTemplate(vstd::RNG & rng) @@ -497,12 +497,12 @@ void Object::Instance::finalize(RmgMap & map, vstd::RNG & rng) } if (dObject.isVisitable() && !map.isOnMap(dObject.visitablePos())) - throw rmgException(boost::str(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.pos.toString())); + throw rmgException(boost::str(boost::format("Visitable tile %s of object %d at %s is outside the map") % dObject.visitablePos().toString() % dObject.id % dObject.anchorPos().toString())); for(const auto & tile : dObject.getBlockedPos()) { if(!map.isOnMap(tile)) - throw rmgException(boost::str(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.pos.toString())); + throw rmgException(boost::str(boost::format("Tile %s of object %d at %s is outside the map") % tile.toString() % dObject.id % dObject.anchorPos().toString())); } for(const auto & tile : getBlockedArea().getTilesVector()) diff --git a/lib/rmg/modificators/QuestArtifactPlacer.cpp b/lib/rmg/modificators/QuestArtifactPlacer.cpp index 9117ab34c..31d9e5c05 100644 --- a/lib/rmg/modificators/QuestArtifactPlacer.cpp +++ b/lib/rmg/modificators/QuestArtifactPlacer.cpp @@ -112,7 +112,7 @@ void QuestArtifactPlacer::placeQuestArtifacts(vstd::RNG & rand) logGlobal->trace("Replacing %s at %s with the quest artifact %s", objectToReplace->getObjectName(), - objectToReplace->getPosition().toString(), + objectToReplace->anchorPos().toString(), VLC->artifacts()->getById(artifactToPlace)->getNameTranslated()); //Update appearance. Terrain is irrelevant. @@ -121,7 +121,7 @@ void QuestArtifactPlacer::placeQuestArtifacts(vstd::RNG & rand) auto templates = handler->getTemplates(); //artifactToReplace->appearance = templates.front(); newObj->appearance = templates.front(); - newObj->pos = objectToReplace->pos; + newObj->setAnchorPos(objectToReplace->anchorPos()); mapProxy->insertObject(newObj); mapProxy->removeObject(objectToReplace); break; diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index decfa7140..089c691b2 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -209,7 +209,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment if(b->hero || b->layer != EPathfindingLayer::SAIL) continue; //we're looking for unoccupied boat - double nDist = b->pos.dist2d(parameters.caster->getHeroCaster()->visitablePos()); + double nDist = b->visitablePos().dist2d(parameters.caster->getHeroCaster()->visitablePos()); if(!nearest || nDist < dist) //it's first boat or closer than previous { nearest = b; @@ -669,11 +669,11 @@ const CGTownInstance * TownPortalMechanics::findNearestTown(SpellCastEnvironment return nullptr; auto nearest = pool.cbegin(); //nearest town's iterator - si32 dist = (*nearest)->pos.dist2dSQ(parameters.caster->getHeroCaster()->pos); + si32 dist = (*nearest)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos()); for(auto i = nearest + 1; i != pool.cend(); ++i) { - si32 curDist = (*i)->pos.dist2dSQ(parameters.caster->getHeroCaster()->pos); + si32 curDist = (*i)->visitablePos().dist2dSQ(parameters.caster->getHeroCaster()->visitablePos()); if(curDist < dist) { diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index e42b84b78..c9b18ac32 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -379,7 +379,7 @@ void MapHandler::drawObjects(QPainter & painter, int x, int y, int z, const std: if(objData.objBitmap) { - auto pos = obj->getPosition(); + auto pos = obj->anchorPos(); painter.drawImage(QPoint(x * tileSize, y * tileSize), *objData.objBitmap, object.rect, Qt::AutoColor | Qt::NoOpaqueDetection); diff --git a/mapeditor/mapsettings/abstractsettings.cpp b/mapeditor/mapsettings/abstractsettings.cpp index a1f4afc43..f75a77685 100644 --- a/mapeditor/mapsettings/abstractsettings.cpp +++ b/mapeditor/mapsettings/abstractsettings.cpp @@ -115,7 +115,7 @@ std::string AbstractSettings::getMonsterName(const CMap & map, int objectIdx) std::string name; if(auto monster = dynamic_cast(map.objects[objectIdx].get())) { - name = boost::str(boost::format("%1% at %2%") % monster->getObjectName() % monster->getPosition().toString()); + name = boost::str(boost::format("%1% at %2%") % monster->getObjectName() % monster->anchorPos().toString()); } return name; } diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index 32713ebef..628fb932c 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -425,7 +425,7 @@ void ObjectsLayer::setDirty(const CGObjectInstance * object) { for(int i = 0; i < object->getWidth(); ++i) { - setDirty(object->getPosition().x - i, object->getPosition().y - j); + setDirty(object->anchorPos().x - i, object->anchorPos().y - j); } } } @@ -479,7 +479,7 @@ void SelectionObjectsLayer::draw() { if(obj != newObject) { - QRect bbox(obj->getPosition().x, obj->getPosition().y, 1, 1); + QRect bbox(obj->anchorPos().x, obj->anchorPos().y, 1, 1); for(auto & t : obj->getBlockedPos()) { QPoint topLeft(std::min(t.x, bbox.topLeft().x()), std::min(t.y, bbox.topLeft().y())); @@ -496,7 +496,7 @@ void SelectionObjectsLayer::draw() if(selectionMode == SelectionMode::MOVEMENT && (shift.x() || shift.y())) { painter.setOpacity(0.7); - auto newPos = QPoint(obj->getPosition().x, obj->getPosition().y) + shift; + auto newPos = QPoint(obj->anchorPos().x, obj->anchorPos().y) + shift; handler->drawObjectAt(painter, obj, newPos.x(), newPos.y()); } } @@ -517,7 +517,7 @@ CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGO if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj)) continue; - if(object.obj->visitableAt(x, y)) + if(object.obj->visitableAt(int3(x, y, scene->level))) { return const_cast(object.obj); } @@ -529,7 +529,7 @@ CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGO if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj)) continue; - if(object.obj->blockingAt(x, y)) + if(object.obj->blockingAt(int3(x, y, scene->level))) { return const_cast(object.obj); } @@ -541,7 +541,7 @@ CGObjectInstance * SelectionObjectsLayer::selectObjectAt(int x, int y, const CGO if(!object.obj || object.obj == ignore || lockedObjects.count(object.obj)) continue; - if(object.obj->coveringAt(x, y)) + if(object.obj->coveringAt(int3(x, y, scene->level))) { return const_cast(object.obj); } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index c2505b29e..ede0a9828 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -803,7 +803,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme return false; } - logGlobal->trace("Player %d (%s) wants to move hero %d from %s to %s", asker, asker.toString(), hid.getNum(), h->pos.toString(), dst.toString()); + logGlobal->trace("Player %d (%s) wants to move hero %d from %s to %s", asker, asker.toString(), hid.getNum(), h->anchorPos().toString(), dst.toString()); const int3 hmpos = h->convertToVisitablePos(dst); if (!gs->map->isInTheMap(hmpos)) @@ -902,7 +902,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme // should be called if hero changes tile but before applying TryMoveHero package auto leaveTile = [&]() { - for (CGObjectInstance *obj : gs->map->getTile(int3(h->pos.x-1, h->pos.y, h->pos.z)).visitableObjects) + for (CGObjectInstance *obj : gs->map->getTile(h->visitablePos()).visitableObjects) { obj->onHeroLeave(h); } @@ -4222,8 +4222,11 @@ CGObjectInstance * CGameHandler::createNewObject(const int3 & visitablePosition, else o->appearance = handler->getTemplates().front(); + if (o->isVisitable()) + o->setAnchorPos(visitablePosition + o->getVisitableOffset()); + else + o->setAnchorPos(visitablePosition); - o->pos = visitablePosition + o->getVisitableOffset(); return o; } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index fe4607786..7818e3e5a 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -283,7 +283,7 @@ void ApplyGhNetPackVisitor::visitTradeOnMarketplace(TradeOnMarketplace & pack) gh.throwAndComplain(&pack, "Can not trade - no hero!"); // TODO: check that object is actually being visited (e.g. Query exists) - if (!object->visitableAt(hero->visitablePos().x, hero->visitablePos().y)) + if (!object->visitableAt(hero->visitablePos())) gh.throwAndComplain(&pack, "Can not trade - object not visited!"); if (object->getOwner().isValidPlayer() && gh.getPlayerRelations(object->getOwner(), hero->getOwner()) == PlayerRelations::ENEMIES) From 0c03e0b7c7b303eba3df94e3b18af37281ca7b3b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 1 Oct 2024 18:50:23 +0000 Subject: [PATCH 292/726] Enable autodetection of upscaling filter --- client/renderSDL/ScreenHandler.cpp | 33 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index c8d99e6fa..30e0e8bbf 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -335,25 +335,22 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const if (filter != EUpscalingFilter::AUTO) return filter; - // for now - always fallback to no filter - return EUpscalingFilter::NONE; - // else - autoselect -// Point outputResolution = getRenderResolution(); -// Point logicalResolution = getPreferredLogicalResolution(); -// -// float scaleX = static_cast(outputResolution.x) / logicalResolution.x; -// float scaleY = static_cast(outputResolution.x) / logicalResolution.x; -// float scaling = std::min(scaleX, scaleY); -// -// if (scaling <= 1.0f) -// return EUpscalingFilter::NONE; -// if (scaling <= 2.0f) -// return EUpscalingFilter::XBRZ_2; -// if (scaling <= 3.0f) -// return EUpscalingFilter::XBRZ_3; -// -// return EUpscalingFilter::XBRZ_4; + Point outputResolution = getRenderResolution(); + Point logicalResolution = getPreferredLogicalResolution(); + + float scaleX = static_cast(outputResolution.x) / logicalResolution.x; + float scaleY = static_cast(outputResolution.x) / logicalResolution.x; + float scaling = std::min(scaleX, scaleY); + + if (scaling <= 1.001f) + return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz + if (scaling <= 2.001f) + return EUpscalingFilter::XBRZ_2; // resolutions below 1200p (including 1080p / FullHD) + if (scaling <= 3.001f) + return EUpscalingFilter::XBRZ_3; // resolutions below 2400p (including 1440p and 2160p / 4K) + + return EUpscalingFilter::XBRZ_4; // Only for massive displays, e.g. 8K } void ScreenHandler::selectUpscalingFilter() From 42adc9d39497e1a7b19c062ed3f16ac2738f6b0d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 1 Oct 2024 18:50:41 +0000 Subject: [PATCH 293/726] Enable auto-detection of UI scaling --- client/renderSDL/ScreenHandler.cpp | 16 ++++++++++++++++ config/schemas/settings.json | 13 +++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index 30e0e8bbf..d772e97e3 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -93,6 +93,22 @@ Point ScreenHandler::getPreferredLogicalResolution() const auto [minimalScaling, maximalScaling] = getSupportedScalingRange(); int userScaling = settings["video"]["resolution"]["scaling"].Integer(); + + if (userScaling == 0) // autodetection + { +#ifdef VCMI_MOBILE + // for mobiles - stay at maximum scaling unless we have large screen + // might be better to check screen DPI / physical dimensions, but way more complex, and may result in different edge cases, e.g. chromebooks / tv's + int preferredMinimalScaling = 200; +#else + // for PC - avoid downscaling if possible + int preferredMinimalScaling = 100; +#endif + // prefer a little below maximum - to give space for extended UI + int preferredMaximalScaling = maximalScaling * 10 / 12; + userScaling = std::max(std::min(maximalScaling, preferredMinimalScaling), preferredMaximalScaling); + } + int scaling = std::clamp(userScaling, minimalScaling, maximalScaling); Point logicalResolution = availableResolution * 100.0 / scaling; diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 276903873..1272c3ecb 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -195,17 +195,14 @@ "additionalProperties" : false, "required" : [ "width", "height", "scaling" ], "properties" : { - "width" : { "type" : "number" }, - "height" : { "type" : "number" }, - "scaling" : { "type" : "number" } - }, - "defaultIOS" : {"width" : 800, "height" : 600, "scaling" : 200 }, - "defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 }, - "default" : {"width" : 800, "height" : 600, "scaling" : 100 } + "width" : { "type" : "number", "default" : 800 }, + "height" : { "type" : "number", "default" : 600 }, + "scaling" : { "type" : "number", "default" : 0 } + } }, "reservedWidth" : { "type" : "number", - "defaultIOS" : 0.1, // iOS camera cutout / notch is excluded from available area by SDL + "defaultIOS" : 0.1, // iOS camera cutout / notch is not excluded from available area by SDL, handle it this way "default" : 0 }, "fullscreen" : { From 58d13fdce55204a1caf5e99a9c3e864810e3ec0e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 1 Oct 2024 19:40:12 +0000 Subject: [PATCH 294/726] Implemented scaling of hardware cursor --- client/gui/CGuiHandler.cpp | 4 ++-- client/gui/CursorHandler.cpp | 5 +++++ client/gui/CursorHandler.h | 1 + client/renderSDL/CursorHardware.cpp | 16 +++++++++++++--- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index ebacdc1b4..6608ca44d 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -250,8 +250,8 @@ void CGuiHandler::setStatusbar(std::shared_ptr newStatusBar) void CGuiHandler::onScreenResize(bool resolutionChanged) { if(resolutionChanged) - { screenHandler().onScreenResize(); - } + windows().onScreenResize(); + CCS->curh->onScreenResize(); } diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index eaa8cfd98..fbc5922f8 100644 --- a/client/gui/CursorHandler.cpp +++ b/client/gui/CursorHandler.cpp @@ -312,3 +312,8 @@ void CursorHandler::changeCursor(Cursor::ShowType newShowType) break; } } + +void CursorHandler::onScreenResize() +{ + cursor->setImage(getCurrentImage(), getPivotOffset()); +} diff --git a/client/gui/CursorHandler.h b/client/gui/CursorHandler.h index 539c577bd..acaccaaa8 100644 --- a/client/gui/CursorHandler.h +++ b/client/gui/CursorHandler.h @@ -182,6 +182,7 @@ public: void hide(); void show(); + void onScreenResize(); /// change cursor's positions to (x, y) void cursorMove(const int & x, const int & y); diff --git a/client/renderSDL/CursorHardware.cpp b/client/renderSDL/CursorHardware.cpp index cbcfaeac2..c043a899e 100644 --- a/client/renderSDL/CursorHardware.cpp +++ b/client/renderSDL/CursorHardware.cpp @@ -11,11 +11,14 @@ #include "StdInc.h" #include "CursorHardware.h" +#include "SDL_Extensions.h" + #include "../gui/CGuiHandler.h" #include "../render/IScreenHandler.h" #include "../render/Colors.h" #include "../render/IImage.h" -#include "SDL_Extensions.h" + +#include "../../lib/CConfigHandler.h" #include #include @@ -45,19 +48,26 @@ void CursorHardware::setVisible(bool on) void CursorHardware::setImage(std::shared_ptr image, const Point & pivotOffset) { - auto cursorSurface = CSDL_Ext::newSurface(image->dimensions() * GH.screenHandler().getScalingFactor()); + int cursorScalingPercent = settings["video"]["resolution"]["scaling"].Integer(); + Point cursorDimensions = image->dimensions() * GH.screenHandler().getScalingFactor(); + Point cursorDimensionsScaled = image->dimensions() * cursorScalingPercent / 100; + Point pivotOffsetScaled = pivotOffset * cursorScalingPercent / 100 / GH.screenHandler().getScalingFactor(); + + auto cursorSurface = CSDL_Ext::newSurface(cursorDimensions); CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY)); image->draw(cursorSurface, Point(0,0)); + auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y ); auto oldCursor = cursor; - cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y); + cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y); if (!cursor) logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError()); SDL_FreeSurface(cursorSurface); + SDL_FreeSurface(cursorSurfaceScaled); GH.dispatchMainThread([this, oldCursor](){ SDL_SetCursor(cursor); From daa706b8476ea01e38c1776b8b4d8968e9c65d11 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Oct 2024 13:00:25 +0000 Subject: [PATCH 295/726] Enable fullscreen as default --- config/schemas/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 1272c3ecb..4528650e6 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -207,7 +207,7 @@ }, "fullscreen" : { "type" : "boolean", - "default" : false + "default" : true }, "realFullscreen" : { "type" : "boolean", From 7d58f89992f0d25a65ac3b5c880ef4e52f5da391 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Oct 2024 13:00:49 +0000 Subject: [PATCH 296/726] Change default resolution from 800x600 to 1280x720 --- config/schemas/settings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 4528650e6..0d670baeb 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -195,9 +195,9 @@ "additionalProperties" : false, "required" : [ "width", "height", "scaling" ], "properties" : { - "width" : { "type" : "number", "default" : 800 }, - "height" : { "type" : "number", "default" : 600 }, - "scaling" : { "type" : "number", "default" : 0 } + "width" : { "type" : "number", "default" : 1280 }, + "height" : { "type" : "number", "default" : 720 }, + "scaling" : { "type" : "number", "default" : 0 } } }, "reservedWidth" : { From 68e5cff27631c74d6dd6905598babb2fa4a795b3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Oct 2024 19:34:28 +0000 Subject: [PATCH 297/726] Implement user-defined cursor scaling --- client/renderSDL/CursorHardware.cpp | 4 +++- config/schemas/settings.json | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/renderSDL/CursorHardware.cpp b/client/renderSDL/CursorHardware.cpp index c043a899e..b6e927023 100644 --- a/client/renderSDL/CursorHardware.cpp +++ b/client/renderSDL/CursorHardware.cpp @@ -48,7 +48,9 @@ void CursorHardware::setVisible(bool on) void CursorHardware::setImage(std::shared_ptr image, const Point & pivotOffset) { - int cursorScalingPercent = settings["video"]["resolution"]["scaling"].Integer(); + int videoScalingSettings = settings["video"]["resolution"]["scaling"].Integer(); + float cursorScalingSettings = settings["video"]["cursorScalingFactor"].Float(); + int cursorScalingPercent = videoScalingSettings * cursorScalingSettings; Point cursorDimensions = image->dimensions() * GH.screenHandler().getScalingFactor(); Point cursorDimensionsScaled = image->dimensions() * cursorScalingPercent / 100; Point pivotOffsetScaled = pivotOffset * cursorScalingPercent / 100 / GH.screenHandler().getScalingFactor(); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 0d670baeb..93ae2038f 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -184,6 +184,7 @@ "targetfps", "vsync", "fontsType", + "cursorScalingFactor", "fontScalingFactor", "upscalingFilter", "fontUpscalingFilter", @@ -253,6 +254,10 @@ "enum" : [ "auto", "original", "scalable" ], "default" : "auto" }, + "cursorScalingFactor" : { + "type" : "number", + "default" : 1 + }, "fontScalingFactor" : { "type" : "number", "default" : 1 From c8a0664b3c15d693e78eefd9d947f8efd3aa71ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Oct 2024 19:34:52 +0000 Subject: [PATCH 298/726] Adjust Launcher UI to account for changes in this branch --- launcher/settingsView/csettingsview_moc.cpp | 35 +- launcher/settingsView/csettingsview_moc.h | 4 + launcher/settingsView/csettingsview_moc.ui | 2318 ++++++++++--------- 3 files changed, 1220 insertions(+), 1137 deletions(-) diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 91788e7b6..94ca8fc6c 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -127,7 +127,12 @@ void CSettingsView::loadSettings() #endif fillValidScalingRange(); - ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float()); + ui->buttonScalingAuto->setChecked(settings["video"]["resolution"]["scaling"].Integer() == 0); + if (settings["video"]["resolution"]["scaling"].Integer() == 0) + ui->spinBoxInterfaceScaling->setValue(100); + else + ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float()); + ui->spinBoxFramerateLimit->setValue(settings["video"]["targetfps"].Float()); ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); ui->sliderReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100)); @@ -174,6 +179,7 @@ void CSettingsView::loadSettings() ui->sliderControllerSticksAcceleration->setValue(settings["input"]["controllerAxisScale"].Float() * 100); ui->lineEditGameLobbyHost->setText(QString::fromStdString(settings["lobby"]["hostname"].String())); ui->spinBoxNetworkPortLobby->setValue(settings["lobby"]["port"].Integer()); + ui->buttonVSync->setChecked(settings["video"]["vsync"].Bool()); if (settings["video"]["fontsType"].String() == "auto") ui->buttonFontAuto->setChecked(true); @@ -195,7 +201,6 @@ void CSettingsView::loadSettings() void CSettingsView::loadToggleButtonSettings() { setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool()); - setCheckbuttonState(ui->buttonVSync, settings["video"]["vsync"].Bool()); setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool()); setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool()); @@ -212,10 +217,15 @@ void CSettingsView::loadToggleButtonSettings() std::string cursorType = settings["video"]["cursor"].String(); int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType); setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex); + ui->sliderScalingCursor->setDisabled(cursorType == "software"); // Not supported + ui->labelScalingCursorValue->setDisabled(cursorType == "software"); // Not supported int fontScalingPercentage = settings["video"]["fontScalingFactor"].Float() * 100; ui->sliderScalingFont->setValue(fontScalingPercentage / 5); + int cursorScalingPercentage = settings["video"]["cursorScalingFactor"].Float() * 100; + ui->sliderScalingCursor->setValue(cursorScalingPercentage / 5); + } void CSettingsView::fillValidResolutions() @@ -494,6 +504,8 @@ void CSettingsView::on_buttonCursorType_toggled(bool value) Settings node = settings.write["video"]["cursor"]; node->String() = cursorTypesList[value ? 1 : 0]; updateCheckbuttonText(ui->buttonCursorType); + ui->sliderScalingCursor->setDisabled(value == 1); // Not supported + ui->labelScalingCursorValue->setDisabled(value == 1); // Not supported } void CSettingsView::loadTranslation() @@ -627,7 +639,6 @@ void CSettingsView::on_buttonVSync_toggled(bool value) Settings node = settings.write["video"]["vsync"]; node->Bool() = value; ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); - updateCheckbuttonText(ui->buttonVSync); } void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1) @@ -816,3 +827,21 @@ void CSettingsView::on_buttonValidationFull_clicked(bool checked) Settings node = settings.write["mods"]["validation"]; node->String() = "full"; } + +void CSettingsView::on_sliderScalingCursor_valueChanged(int value) +{ + int actualValuePercentage = value * 5; + ui->labelScalingCursorValue->setText(QString("%1%").arg(actualValuePercentage)); + Settings node = settings.write["video"]["cursorScalingFactor"]; + node->Float() = actualValuePercentage / 100.0; +} + +void CSettingsView::on_buttonScalingAuto_toggled(bool checked) +{ + ui->spinBoxInterfaceScaling->setDisabled(checked); + ui->spinBoxInterfaceScaling->setValue(100); + + Settings node = settings.write["video"]["resolution"]["scaling"]; + node->Integer() = checked ? 0 : 100; +} + diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 512762d0c..d05e7eb1e 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -97,6 +97,10 @@ private slots: void on_buttonValidationFull_clicked(bool checked); + void on_sliderScalingCursor_valueChanged(int value); + + void on_buttonScalingAuto_toggled(bool checked); + private: Ui::CSettingsView * ui; diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index c0082edd8..a974b5209 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -47,209 +47,119 @@ 0 - -800 + -126 729 - 1506 + 1503 - - + + + + + + 0 + 0 + + + + + + + true + + + + + + + Mods Validation + + + + + + + + Automatic + + + + + None + + + + + xBRZ x2 + + + + + xBRZ x3 + + + + + xBRZ x4 + + + + + + + + 500 + + + 2500 + + + 25 + + + 250 + + + 500 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 250 + + + + + + + Reset + + + + - - + + - 0 - - - 50 - - - 1 - - - 10 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Upscaling Filter - - - - - - - 100 - - 500 - - 10 - - - 100 - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 50 - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Additional repository - - - - - - - Ignore SSL errors - - - - - - - - Nearest - - - - - Linear - - - - - Automatic (Linear) - - - - - - - - VCMI Language - - - - - - - - 0 - 0 - - - - Automatic - - - true - - - true - - - buttonGroupFonts - - - - - - - - true - - - - Video - - - 5 - - - - - - - Show intro - - - - - - - Heroes III Translation - - - - - - - 0 - - 50 + 2000 - 1 + 250 - 10 - - - 0 + 250 Qt::Horizontal @@ -258,169 +168,11 @@ QSlider::TicksAbove - 10 + 250 - - - - - 0 - 0 - - - - Scalable - - - true - - - true - - - buttonGroupFonts - - - - - - - Touch Tap Tolerance - - - - - - - - - - - - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - - - - Renderer - - - - - - - - true - - - - Artificial Intelligence - - - 5 - - - - - - - Neutral AI in battles - - - - - - - VSync - - - - - - - Framerate Limit - - - - - - - empty = map name prefix - - - - - - - - true - - - - Input - Touchscreen - - - 5 - - - - - - - 0 - - - 50 - - - 1 - - - 10 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Relative Pointer Speed - - - - - - - + true @@ -442,8 +194,56 @@ - - + + + + Reserved screen area + + + + + + + + true + + + + General + + + 5 + + + + + + + Autosave + + + + + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + true + 0 @@ -456,26 +256,341 @@ true - - - - - - Long Touch Duration + + false - - - - - + + + + true + + + + 0 + 0 + + - Check on startup + Basic + + + true + + + false + + + buttonGroupValidation + + + + + + + Sound Volume - + + + + Haptic Feedback + + + + + + + Touch Tap Tolerance + + + + + + + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + true + + + + 0 + 0 + + + + Full + + + true + + + false + + + buttonGroupValidation + + + + + + + + true + + + + Input - Controller + + + 5 + + + + + + + 10 + + + 30 + + + 1 + + + 2 + + + 20 + + + 20 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 2 + + + + + + + Software Cursor + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + + + + true + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + Heroes III Translation + + + + + + + + true + + + + Artificial Intelligence + + + 5 + + + + + + + Default repository + + + + + + + + + + % + + + 50 + + + 400 + + + 10 + + + + + + + Relative Pointer Speed + + + + + + + Downscaling Filter + + + + + + + true + + + + 0 + 0 + + + + + + + true + + + false + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + Show intro + + + + + + + + 0 + 0 + + + + Automatic + + + true + + + true + + + buttonGroupFonts + + + + + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + @@ -494,38 +609,28 @@ - - - - - true - - + + - Input - Controller - - - 5 + Refresh now - - - - - 0 - 0 - - + + - - - - true + Adventure Map Allies - + + + + Neutral AI in battles + + + + 10 @@ -556,14 +661,547 @@ - - + + - Downscaling Filter + - + + + + Display index + + + + + + + Use Relative Pointer Mode + + + + + + + + + + Autocombat AI in battles + + + + + + + true + + + + 0 + 0 + + + + Off + + + true + + + false + + + buttonGroupValidation + + + + + + + + true + + + + Input - Mouse + + + 5 + + + + + + + Network port + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + true + + + + Audio + + + 5 + + + + + + + Additional repository + + + + + + + + 0 + 0 + + + + VSync + + + true + + + + + + + Adventure Map Enemies + + + + + + + Framerate Limit + + + + + + + Use scalable fonts + + + + + + + Renderer + + + + + + + + true + + + + Input - Touchscreen + + + 5 + + + + + + + 25 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 5 + + + + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + VCMI Language + + + + + + + Online Lobby address + + + + + + + Online Lobby port + + + + + + + Controller Click Tolerance + + + + + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + Nearest + + + + + Linear + + + + + Automatic (Linear) + + + + + + + + Ignore SSL errors + + + + + + + + + + Show Tutorial again + + + + + + + Sticks Acceleration + + + + + + + + true + + + + Video + + + 5 + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + + + + + + + Fullscreen + + + + + + + Mouse Click Tolerance + + + + + + + Enemy AI in battles + + + + + + + Autosave limit (0 = off) + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + Interface Scaling + + + + + + + Music Volume + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + + + + + + + + Cursor Scaling + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + 0 + 0 + + + + Scalable + + + true + + + true + + + buttonGroupFonts + + + + + + + + true + + + + Network + + + 5 + + + + + + + + true + + + + Miscellaneous + + + 5 + + + + + + + 100 + + + 500 + + + 10 + + + 100 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 50 + + + + Select display mode for game @@ -594,14 +1232,155 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - Use scalable fonts + + + + 20 + + + 1000 + + + 10 - + + + + Long Touch Duration + + + + + + + Check on startup + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + 100 + + + 300 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 25 + + + + + + + Font Scaling (experimental) + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + 0 + + + 50 + + + 1 + + + 10 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + 10 + + + + + + + empty = map name prefix + + + + @@ -623,47 +1402,8 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - - - - Automatic - - - - - None - - - - - xBRZ x2 - - - - - xBRZ x3 - - - - - xBRZ x4 - - - - - - - - Online Lobby port - - - - - + + 0 @@ -671,283 +1411,27 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Automatic true - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - - true - - + + - Network - - - 5 + Sticks Sensitivity - - + + - Refresh now + Upscaling Filter - - - - 500 - - - 2500 - - - 25 - - - 250 - - - 500 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 250 - - - - - - - Display index - - - - - - - 500 - - - 2000 - - - 250 - - - 250 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 250 - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Adventure Map Enemies - - - - - - - - - - true - - - - - - - Reset - - - - - - - Show Tutorial again - - - - - - - Fullscreen - - - - - - - Use Relative Pointer Mode - - - - - - - - - - - - - - Sticks Acceleration - - - - - - - Autosave limit (0 = off) - - - - - - - Mouse Click Tolerance - - - - - - - - true - - - - General - - - 5 - - - - - - - Online Lobby address - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - Interface Scaling - - - - - - - Default repository - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - Haptic Feedback - - - - - - @@ -955,449 +1439,15 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - - true - - - - Input - Mouse - - - 5 - - - - - - - 25 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 5 - - - - - - - 20 - - - 1000 - - - 10 - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - 100 - - - 300 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 25 - - - - - - - Enemy AI in battles - - - - - - Reserved screen area - - - - - - - % - - - 50 - - - 400 - - - 10 - - - - - - - Music Volume - - - - - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Autosave - - - - - - - 100 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - 10 - - - - - - - Network port - - - - - - - true - - - - 0 - 0 - - - - - - - true - - - false - - - - Resolution - - - - - 0 - 0 - - - - - - - true - - - - - - - Sound Volume - - - - - - - Controller Click Tolerance - - - - - - - Adventure Map Allies - - - - - - - - true - - - - Miscellaneous - - - 5 - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - - true - - - - Audio - - - 5 - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - Software Cursor - - - - - - - Autocombat AI in battles - - - - - - - - - - - - - - - - - true - - - - 0 - 0 - - - - - - - true - - - false - - - - - - - Font Scaling (experimental) - - - - - - - Sticks Sensitivity - - - - - - - Mods Validation - - - - - - - true - - - - 0 - 0 - - - - Off - - - true - - - false - - - buttonGroupValidation - - - - - - - true - - - - 0 - 0 - - - - Basic - - - true - - - false - - - buttonGroupValidation - - - - - - - true - - - - 0 - 0 - - - - Full - - - true - - - false - - - buttonGroupValidation - - + + From e442e71ed94bdfe256fba0544838aef9ad1170b3 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 9 Oct 2024 17:37:47 +0000 Subject: [PATCH 299/726] Fix handling of autoselected interface scaling by client --- client/render/IScreenHandler.h | 2 ++ client/renderSDL/CursorHardware.cpp | 2 +- client/renderSDL/ScreenHandler.cpp | 16 ++++++++++------ client/renderSDL/ScreenHandler.h | 2 ++ client/windows/settings/GeneralOptionsTab.cpp | 4 +--- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client/render/IScreenHandler.h b/client/render/IScreenHandler.h index 32abf7711..f3b6d5eee 100644 --- a/client/render/IScreenHandler.h +++ b/client/render/IScreenHandler.h @@ -44,6 +44,8 @@ public: /// Dimensions of logical output. Can be different if scaling is used virtual Point getLogicalResolution() const = 0; + virtual int getInterfaceScalingPercentage() const = 0; + virtual int getScalingFactor() const = 0; /// Window has focus diff --git a/client/renderSDL/CursorHardware.cpp b/client/renderSDL/CursorHardware.cpp index b6e927023..7c9daea4d 100644 --- a/client/renderSDL/CursorHardware.cpp +++ b/client/renderSDL/CursorHardware.cpp @@ -48,7 +48,7 @@ void CursorHardware::setVisible(bool on) void CursorHardware::setImage(std::shared_ptr image, const Point & pivotOffset) { - int videoScalingSettings = settings["video"]["resolution"]["scaling"].Integer(); + int videoScalingSettings = GH.screenHandler().getInterfaceScalingPercentage(); float cursorScalingSettings = settings["video"]["cursorScalingFactor"].Float(); int cursorScalingPercent = videoScalingSettings * cursorScalingSettings; Point cursorDimensions = image->dimensions() * GH.screenHandler().getScalingFactor(); diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index d772e97e3..7614335e8 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -84,12 +84,8 @@ Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const return result; } -Point ScreenHandler::getPreferredLogicalResolution() const +int ScreenHandler::getInterfaceScalingPercentage() const { - Point renderResolution = getRenderResolution(); - double reservedAreaWidth = settings["video"]["reservedWidth"].Float(); - Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y); - auto [minimalScaling, maximalScaling] = getSupportedScalingRange(); int userScaling = settings["video"]["resolution"]["scaling"].Integer(); @@ -110,9 +106,17 @@ Point ScreenHandler::getPreferredLogicalResolution() const } int scaling = std::clamp(userScaling, minimalScaling, maximalScaling); + return scaling; +} +Point ScreenHandler::getPreferredLogicalResolution() const +{ + Point renderResolution = getRenderResolution(); + double reservedAreaWidth = settings["video"]["reservedWidth"].Float(); + + int scaling = getInterfaceScalingPercentage(); + Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y); Point logicalResolution = availableResolution * 100.0 / scaling; - return logicalResolution; } diff --git a/client/renderSDL/ScreenHandler.h b/client/renderSDL/ScreenHandler.h index e15958b55..6a9026d7b 100644 --- a/client/renderSDL/ScreenHandler.h +++ b/client/renderSDL/ScreenHandler.h @@ -112,6 +112,8 @@ public: int getScalingFactor() const final; + int getInterfaceScalingPercentage() const final; + std::vector getSupportedResolutions() const final; std::vector getSupportedResolutions(int displayIndex) const; std::tuple getSupportedScalingRange() const final; diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index d8ead20f8..6571a3422 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -194,10 +194,8 @@ GeneralOptionsTab::GeneralOptionsTab() build(config); - const auto & currentResolution = settings["video"]["resolution"]; - std::shared_ptr scalingLabel = widget("scalingLabel"); - scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer())); + scalingLabel->setText(scalingToLabelString(GH.screenHandler().getInterfaceScalingPercentage())); std::shared_ptr longTouchLabel = widget("longTouchLabel"); if (longTouchLabel) From 598b2fb6c1e7edb6bdf038f7b2a5ae7b5cf39ea2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:06:34 +0200 Subject: [PATCH 300/726] show folder cases; translate new game --- client/lobby/SelectionTab.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 75b8181ee..f63ce7f22 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -480,11 +480,11 @@ void SelectionTab::filter(int size, bool selectFirst) if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList) { if(showRandom) - curFolder = "RANDOMMAPS/"; + curFolder = "RandomMaps/"; auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(elem->originalFileURI); - if((showRandom && baseFolder != "RANDOMMAPS") || (!showRandom && baseFolder == "RANDOMMAPS")) + if((showRandom && baseFolder != "RandomMaps") || (!showRandom && baseFolder == "RandomMaps")) continue; if(parentExists && !showRandom) @@ -715,7 +715,7 @@ void SelectionTab::selectFileName(std::string fname) selectAbs(-1); if(tabType == ESelectionScreen::saveGame && inputName->getText().empty()) - inputName->setText("NEWGAME"); + inputName->setText(CGI->generaltexth->translate("core.genrltxt.11")); } void SelectionTab::selectNewestFile() @@ -808,7 +808,7 @@ void SelectionTab::parseMaps(const std::unordered_set & files) try { auto mapInfo = std::make_shared(); - mapInfo->mapInit(file.getName()); + mapInfo->mapInit(file.getOriginalName()); if (isMapSupported(*mapInfo)) allItems.push_back(mapInfo); @@ -873,7 +873,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files { auto info = std::make_shared(); //allItems[i].date = std::asctime(std::localtime(&files[i].date)); - info->fileURI = file.getName(); + info->fileURI = file.getOriginalName(); info->campaignInit(); if(info->campaign) allItems.push_back(info); From a22de743237250c999d91b8d8d77b43fb4ce5546 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 09:50:56 +0000 Subject: [PATCH 301/726] Fixes for loading 1.5 mods in vcmi 1.6 --- lib/entities/faction/CTownHandler.cpp | 4 ++-- lib/mapObjectConstructors/CObjectClassesHandler.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 3ed640afc..09a99ed71 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/lib/entities/faction/CTownHandler.cpp @@ -883,8 +883,8 @@ void CTownHandler::beforeValidate(JsonNode & object) if (building.second.Struct().count("onVisitBonuses")) { building.second["configuration"]["visitMode"] = JsonNode("bonus"); - building.second["configuration"]["visitMode"]["rewards"][0]["message"] = building.second["description"]; - building.second["configuration"]["visitMode"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"]; + building.second["configuration"]["rewards"][0]["message"] = building.second["description"]; + building.second["configuration"]["rewards"][0]["bonuses"] = building.second["onVisitBonuses"]; } } } diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index de28a4a0f..f8939be58 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -207,8 +207,13 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin // Compatibility with 1.5 mods for 1.6. To be removed in 1.7 // Detect banks that use old format and load them using old bank hander - if (baseObject->id == Obj::CREATURE_BANK && entry.Struct().count("levels") && !entry.Struct().count("rewards")) - handler = "bank"; + if (baseObject->id == Obj::CREATURE_BANK) + { + if (entry.Struct().count("levels") && !entry.Struct().count("rewards")) + handler = "bank"; + else + handler = "configurable"; + } auto createdObject = handlerConstructors.at(handler)(); From 3dd4fa2528ad802040de42c6e259413c63ac0a1c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 5 Oct 2024 19:37:52 +0000 Subject: [PATCH 302/726] Reduce usage of pointers to VLC entities Final goal (of multiple PR's) is to remove all remaining pointers from serializeable game state, and replace them with either identifiers or with shared/unique pointers. CGTownInstance::town and CGHeroInstance::type members have been removed. Now this data is computed dynamically using subID member. VLC entity of a town can now be accessed via following methods: - getFactionID() returns ID of a faction - getFaction() returns pointer to a faction - getTown() returns pointer to a town VLC entity of a hero can now be accessed via following methods: - getHeroTypeID() returns ID of a hero - getHeroClassID() returns ID of a hero class - getHeroType() returns pointer to a hero - getHeroClass() returns pointer to a hero class --- AI/Nullkiller/AIGateway.cpp | 2 +- AI/Nullkiller/Analyzers/ArmyManager.cpp | 4 +- AI/Nullkiller/Analyzers/BuildAnalyzer.cpp | 10 +- AI/Nullkiller/Analyzers/HeroManager.cpp | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- AI/Nullkiller/Goals/BuildThis.cpp | 4 +- AI/VCAI/BuildingManager.cpp | 8 +- AI/VCAI/Goals/BuildThis.cpp | 2 +- AI/VCAI/Goals/GatherTroops.cpp | 8 +- AI/VCAI/MapObjectsEvaluator.cpp | 2 +- AI/VCAI/VCAI.cpp | 4 +- CCallback.cpp | 2 +- client/CPlayerInterface.cpp | 2 +- client/ClientCommandManager.cpp | 2 +- client/NetPacksClient.cpp | 2 +- client/adventureMap/CList.cpp | 2 +- client/battle/BattleInterfaceClasses.cpp | 10 +- client/battle/BattleSiegeController.cpp | 16 +-- client/widgets/MiscWidgets.cpp | 9 +- client/windows/CCastleInterface.cpp | 116 ++++++++--------- client/windows/CExchangeWindow.cpp | 4 +- client/windows/CHeroWindow.cpp | 6 +- client/windows/CKingdomInterface.cpp | 8 +- client/windows/CMarketWindow.cpp | 2 +- client/windows/GUIClasses.cpp | 10 +- client/windows/QuickRecruitmentWindow.cpp | 10 +- include/vcmi/Entity.h | 2 +- lib/BasicTypes.cpp | 2 +- lib/CCreatureHandler.cpp | 2 +- lib/CCreatureHandler.h | 2 +- lib/CCreatureSet.cpp | 4 +- lib/CCreatureSet.h | 2 +- lib/CGameInfoCallback.cpp | 6 +- lib/battle/CUnitState.cpp | 4 +- lib/battle/CUnitState.h | 2 +- lib/bonuses/Limiters.cpp | 6 +- lib/campaign/CampaignState.cpp | 8 +- lib/entities/faction/CFaction.cpp | 2 +- lib/entities/faction/CFaction.h | 2 +- lib/gameState/CGameState.cpp | 35 +++-- lib/gameState/CGameStateCampaign.cpp | 16 +-- lib/gameState/GameStatistics.cpp | 2 +- lib/gameState/InfoAboutArmy.cpp | 2 +- lib/gameState/TavernHeroesPool.cpp | 8 +- .../CommonConstructors.cpp | 8 +- .../CommonConstructors.h | 1 - lib/mapObjects/CArmedInstance.cpp | 2 +- lib/mapObjects/CGCreature.cpp | 35 ++--- lib/mapObjects/CGCreature.h | 3 +- lib/mapObjects/CGDwelling.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 113 ++++++++--------- lib/mapObjects/CGHeroInstance.h | 15 ++- lib/mapObjects/CGTownInstance.cpp | 120 +++++++++--------- lib/mapObjects/CGTownInstance.h | 28 ++-- lib/mapObjects/CQuest.cpp | 2 +- lib/mapObjects/TownBuildingInstance.cpp | 4 +- lib/mapping/CMap.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 12 +- lib/mapping/MapFormatJson.cpp | 10 +- lib/networkPacks/NetPacksLib.cpp | 4 +- lib/pathfinder/CPathfinder.cpp | 2 +- lib/rewardable/Limiter.cpp | 4 +- lib/rmg/modificators/ObjectManager.cpp | 2 +- lib/rmg/modificators/TownPlacer.cpp | 4 +- lib/rmg/modificators/TreasurePlacer.cpp | 12 +- lib/serializer/CSerializer.cpp | 2 +- lib/serializer/ESerializationVersion.h | 3 +- lib/spells/effects/Moat.cpp | 2 +- lib/spells/effects/Summon.cpp | 2 +- mapeditor/inspector/inspector.cpp | 43 ++----- mapeditor/inspector/townbuildingswidget.cpp | 2 +- mapeditor/inspector/towneventdialog.cpp | 4 +- mapeditor/mapcontroller.cpp | 6 +- mapeditor/playerparams.cpp | 6 +- mapeditor/validator.cpp | 10 +- scripting/lua/api/Creature.cpp | 2 +- server/CGameHandler.cpp | 44 +++---- server/processors/HeroPoolProcessor.cpp | 28 ++-- server/processors/NewTurnProcessor.cpp | 10 +- server/processors/PlayerMessageProcessor.cpp | 2 +- test/entity/CCreatureTest.cpp | 2 +- test/mock/mock_Creature.h | 2 +- test/mock/mock_battle_Unit.h | 2 +- 83 files changed, 445 insertions(+), 468 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index bf289681c..99172b213 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1454,7 +1454,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building) { - auto name = t->town->buildings.at(building)->getNameTranslated(); + auto name = t->getTown()->buildings.at(building)->getNameTranslated(); logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, building); //just do this; } diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index 5c2a7f4a8..e2bbbb1fc 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -144,7 +144,7 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, for(auto & slot : sortedSlots) { - alignmentMap[slot.creature->getFaction()] += slot.power; + alignmentMap[slot.creature->getFactionID()] += slot.power; } std::set allowedFactions; @@ -178,7 +178,7 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, for(auto & slot : sortedSlots) { - if(vstd::contains(allowedFactions, slot.creature->getFaction())) + if(vstd::contains(allowedFactions, slot.creature->getFactionID())) { auto slotID = newArmyInstance.getSlotFor(slot.creature->getId()); diff --git a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp index 4496aec3f..55a1ebeaf 100644 --- a/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp @@ -17,7 +17,7 @@ namespace NKAI void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) { - auto townInfo = developmentInfo.town->town; + auto townInfo = developmentInfo.town->getTown(); auto creatures = townInfo->creatures; auto buildings = townInfo->getAllBuildings(); @@ -31,7 +31,7 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo) } } - for(int level = 0; level < developmentInfo.town->town->creatures.size(); level++) + for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++) { logAi->trace("Checking dwelling level %d", level); BuildingInfo nextToBuild = BuildingInfo(); @@ -82,7 +82,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo) { for(auto & buildingID : buildingSet) { - if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID)) + if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID)) { developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID)); @@ -198,7 +198,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite( bool excludeDwellingDependencies) const { BuildingID building = toBuild; - auto townInfo = town->town; + auto townInfo = town->getTown(); const CBuilding * buildPtr = townInfo->buildings.at(building); const CCreature * creature = nullptr; @@ -327,7 +327,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const { for(auto tdi : developmentInfos) { - if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid)) + if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid)) return true; } diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index f64fb4811..b6938ec1f 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const { - auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId())); + auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID())); auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL); auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus)); auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL)); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 2d5839040..8e20c4e54 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1120,7 +1120,7 @@ public: uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const { - if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id)) + if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id)) return 0; auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID); diff --git a/AI/Nullkiller/Goals/BuildThis.cpp b/AI/Nullkiller/Goals/BuildThis.cpp index 4de43c060..414c8a03d 100644 --- a/AI/Nullkiller/Goals/BuildThis.cpp +++ b/AI/Nullkiller/Goals/BuildThis.cpp @@ -23,7 +23,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid) : ElementarGoal(Goals::BUILD_STRUCTURE) { buildingInfo = BuildingInfo( - tid->town->buildings.at(Bid), + tid->getTown()->buildings.at(Bid), nullptr, CreatureID::NONE, tid, @@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai) if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString()); + ai->playerID, town->getTown()->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString()); cb->buildBuilding(town, b); return; diff --git a/AI/VCAI/BuildingManager.cpp b/AI/VCAI/BuildingManager.cpp index 202661c33..60d971086 100644 --- a/AI/VCAI/BuildingManager.cpp +++ b/AI/VCAI/BuildingManager.cpp @@ -23,13 +23,13 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID return false; } - if (!vstd::contains(t->town->buildings, building)) + if (!vstd::contains(t->getTown()->buildings, building)) return false; // no such building in town if (t->hasBuilt(building)) //Already built? Shouldn't happen in general return true; - const CBuilding * buildPtr = t->town->buildings.at(building); + const CBuilding * buildPtr = t->getTown()->buildings.at(building); auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID) { @@ -51,7 +51,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID for (const auto & buildID : toBuild) { - const CBuilding * b = t->town->buildings.at(buildID); + const CBuilding * b = t->getTown()->buildings.at(buildID); EBuildingState canBuild = cb->canBuildStructure(t, buildID); if (canBuild == EBuildingState::ALLOWED) @@ -220,7 +220,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t) //at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling) std::vector extraBuildings; - for (auto buildingInfo : t->town->buildings) + for (auto buildingInfo : t->getTown()->buildings) { if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST) extraBuildings.push_back(buildingInfo.first); diff --git a/AI/VCAI/Goals/BuildThis.cpp b/AI/VCAI/Goals/BuildThis.cpp index ee1e3d41a..62e3a1649 100644 --- a/AI/VCAI/Goals/BuildThis.cpp +++ b/AI/VCAI/Goals/BuildThis.cpp @@ -56,7 +56,7 @@ TSubgoal BuildThis::whatToDoToAchieve() case EBuildingState::ALLOWED: case EBuildingState::NO_RESOURCES: { - auto res = town->town->buildings.at(BuildingID(bid))->resources; + auto res = town->getTown()->buildings.at(BuildingID(bid))->resources; return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources } break; diff --git a/AI/VCAI/Goals/GatherTroops.cpp b/AI/VCAI/Goals/GatherTroops.cpp index 275bef9f5..50b8fdce5 100644 --- a/AI/VCAI/Goals/GatherTroops.cpp +++ b/AI/VCAI/Goals/GatherTroops.cpp @@ -88,13 +88,13 @@ TGoalVec GatherTroops::getAllPossibleSubgoals() } auto creature = VLC->creatures()->getByIndex(objid); - if(t->getFaction() == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O + if(t->getFactionID() == creature->getFactionID()) //TODO: how to force AI to build unupgraded creatures? :O { auto tryFindCreature = [&]() -> std::optional> { - if(vstd::isValidIndex(t->town->creatures, creature->getLevel() - 1)) + if(vstd::isValidIndex(t->getTown()->creatures, creature->getLevel() - 1)) { - auto itr = t->town->creatures.begin(); + auto itr = t->getTown()->creatures.begin(); std::advance(itr, creature->getLevel() - 1); return make_optional(*itr); } @@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals() if(upgradeNumber < 0) continue; - BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->town->creatures.size()); + BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->getTown()->creatures.size()); if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction { solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid))); diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index 5536d8fd9..e430c1f08 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -69,7 +69,7 @@ std::optional MapObjectsEvaluator::getObjectValue(const CGObjectInstance * { //special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes auto hero = dynamic_cast(obj); - return getObjectValue(obj->ID, hero->type->heroClass->getIndex()); + return getObjectValue(obj->ID, hero->getHeroClassID()); } else if(obj->ID == Obj::PRISON) { diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index c72a2a655..f9ebb1657 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1994,7 +1994,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) void VCAI::buildStructure(const CGTownInstance * t, BuildingID building) { - auto name = t->town->buildings.at(building)->getNameTranslated(); + auto name = t->getTown()->buildings.at(building)->getNameTranslated(); logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, building); //just do this; } @@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g) if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED) { logAi->debug("Player %d will build %s in town of %s at %s", - playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString()); + playerID, t->getTown()->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString()); cb->buildBuilding(t, b); throw goalFulfilledException(sptr(g)); } diff --git a/CCallback.cpp b/CCallback.cpp index 3eeb6d238..72b6e8b78 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -319,7 +319,7 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn assert(townOrTavern); assert(hero); - HireHero pack(hero->getHeroType(), townOrTavern->id, nextHero); + HireHero pack(hero->getHeroTypeID(), townOrTavern->id, nextHero); pack.player = *player; sendRequest(pack); } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 572924c09..6133cdd64 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1138,7 +1138,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component const CGTownInstance * t = dynamic_cast(cb->getObj(obj)); if(t) { - auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); image->scaleTo(Point(35, 23)); images.push_back(image); } diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index a2a7176ef..775c9b806 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -453,7 +453,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe if(what == "hs") { for(const CGHeroInstance* h : LOCPLINT->cb->getHeroesInfo()) - if(h->type->getIndex() == id1) + if(h->getHeroTypeID().getNum() == id1) if(const CArtifactInstance* a = h->getArt(ArtifactPosition(id2))) printCommandMessage(a->nodeName()); } diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index d403120a7..91c8833b2 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -671,7 +671,7 @@ void ApplyClientNetPackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack) void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack) { CGHeroInstance *h = gs.map->heroesOnMap.back(); - if(h->getHeroType() != pack.hid) + if(h->getHeroTypeID() != pack.hid) { logNetwork->error("Something wrong with hero recruited!"); } diff --git a/client/adventureMap/CList.cpp b/client/adventureMap/CList.cpp index 5c46a510a..e730224b4 100644 --- a/client/adventureMap/CList.cpp +++ b/client/adventureMap/CList.cpp @@ -432,7 +432,7 @@ std::shared_ptr CTownList::CTownItem::genSelection() void CTownList::CTownItem::update() { - size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; + size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; picture->setFrame(iconIndex + 2); redraw(); diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 919118407..5cf6aa340 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -389,13 +389,13 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her { AnimationPath animationPath; - if(!hero->type->battleImage.empty()) - animationPath = hero->type->battleImage; + if(!hero->getHeroType()->battleImage.empty()) + animationPath = hero->getHeroType()->battleImage; else if(hero->gender == EHeroGender::FEMALE) - animationPath = hero->type->heroClass->imageBattleFemale; + animationPath = hero->getHeroClass()->imageBattleFemale; else - animationPath = hero->type->heroClass->imageBattleMale; + animationPath = hero->getHeroClass()->imageBattleMale; animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA); @@ -1027,7 +1027,7 @@ void StackQueue::update() int32_t StackQueue::getSiegeShooterIconID() { - return owner.siegeController->getSiegedTown()->town->faction->getIndex(); + return owner.siegeController->getSiegedTown()->getFactionID().getNum(); } std::optional StackQueue::getHoveredUnitIdIfAny() const diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index f64bc8ac4..2f3c4df5e 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -58,14 +58,14 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual }; }; - const std::string & prefix = town->town->clientInfo.siegePrefix; + const std::string & prefix = town->getTown()->clientInfo.siegePrefix; std::string addit = std::to_string(getImageIndex()); switch(what) { case EWallVisual::BACKGROUND_WALL: { - auto faction = town->town->faction->getIndex(); + auto faction = town->getFactionID(); if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD) return ImagePath::builtinTODO(prefix + "TPW1.BMP"); @@ -111,7 +111,7 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what) { - auto & ci = town->town->clientInfo; + auto & ci = town->getTown()->clientInfo; auto const & pos = ci.siegePositions[what]; if ( wallPieceImages[what] && pos.isValid()) @@ -120,7 +120,7 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis ImagePath BattleSiegeController::getBattleBackgroundName() const { - const std::string & prefix = town->town->clientInfo.siegePrefix; + const std::string & prefix = town->getTown()->clientInfo.siegePrefix; return ImagePath::builtinTODO(prefix + "BACK.BMP"); } @@ -130,8 +130,8 @@ bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) switch (what) { - case EWallVisual::MOAT: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid(); - case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid(); + case EWallVisual::MOAT: return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid(); + case EWallVisual::MOAT_BANK: return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid(); case EWallVisual::KEEP_BATTLEMENT: return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED; case EWallVisual::UPPER_BATTLEMENT: return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED; case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED; @@ -218,8 +218,8 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con if (posID != 0) { return { - town->town->clientInfo.siegePositions[posID].x, - town->town->clientInfo.siegePositions[posID].y + town->getTown()->clientInfo.siegePositions[posID].x, + town->getTown()->clientInfo.siegePositions[posID].y }; } diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index e7e7c655d..14cc7b14d 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -468,8 +468,8 @@ void CInteractableTownTooltip::init(const CGTownInstance * town) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); } }, [town]{ - if(!town->town->faction->getDescriptionTranslated().empty()) - CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); + if(!town->getFaction()->getDescriptionTranslated().empty()) + CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated()); }); fastMarket = std::make_shared(Rect(143, 31, 30, 34), []() { @@ -532,8 +532,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature) { OBJECT_CONSTRUCTION; - auto creatureID = creature->getCreature(); - int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex(); + int32_t creatureIconIndex = creature->getCreature()->getIconIndex(); creatureImage = std::make_shared(AnimationPath::builtin("TWCRPORT"), creatureIconIndex); creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11)); @@ -633,7 +632,7 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A pos.x+=x; pos.y+=y; - auto faction = cre->getFaction(); + auto faction = cre->getFactionID(); assert(CGI->townh->size() > faction); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 9729bd73c..aea486688 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -82,7 +82,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town // special animation frame manipulation for castle shipyard with and without ship // done due to .def used in special way, not to animate building - first image is for shipyard without citadel moat, 2nd image is for including moat - if(Town->town->faction->getId() == FactionID::CASTLE && Str->building && + if(Town->getFactionID() == FactionID::CASTLE && Str->building && (Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP)) { if(Town->hasBuilt(BuildingID::CITADEL)) @@ -107,7 +107,7 @@ const CBuilding * CBuildingRect::getBuilding() return nullptr; if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes) - return town->town->buildings.at(str->building->getBase()); + return town->getTown()->buildings.at(str->building->getBase()); return str->building; } @@ -156,7 +156,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition) return; BuildingID bid = getBuilding()->bid; - const CBuilding *bld = town->town->buildings.at(bid); + const CBuilding *bld = town->getTown()->buildings.at(bid); if (bid < BuildingID::DWELL_FIRST) { CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()), @@ -235,10 +235,10 @@ std::string CBuildingRect::getSubtitle()//hover text for building int bid = getBuilding()->bid; if (bid<30)//non-dwellings - only building name - return town->town->buildings.at(getBuilding()->bid)->getNameTranslated(); + return town->getTown()->buildings.at(getBuilding()->bid)->getNameTranslated(); else//dwellings - recruit %creature% { - auto & availableCreatures = town->creatures[(bid-30)%town->town->creatures.size()].second; + auto & availableCreatures = town->creatures[(bid-30)%town->getTown()->creatures.size()].second; if(availableCreatures.size()) { int creaID = availableCreatures.back();//taking last of available creatures @@ -566,7 +566,7 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town): { OBJECT_CONSTRUCTION; - background = std::make_shared(town->town->clientInfo.townBackground); + background = std::make_shared(town->getTown()->clientInfo.townBackground); background->needRefresh = true; background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE); pos.w = background->pos.w; @@ -602,7 +602,7 @@ void CCastleBuildings::recreate() } } - for(const CStructure * structure : town->town->clientInfo.structures) + for(const CStructure * structure : town->getTown()->clientInfo.structures) { if(!structure->building) { @@ -617,7 +617,7 @@ void CCastleBuildings::recreate() for(auto & entry : groups) { - const CBuilding * build = town->town->buildings.at(entry.first); + const CBuilding * build = town->getTown()->buildings.at(entry.first); const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b) { @@ -648,7 +648,7 @@ void CCastleBuildings::recreate() void CCastleBuildings::addBuilding(BuildingID building) { //FIXME: implement faster method without complete recreation of town - BuildingID base = town->town->buildings.at(building)->getBase(); + BuildingID base = town->getTown()->buildings.at(building)->getBase(); recreate(); @@ -687,7 +687,7 @@ void CCastleBuildings::buildingClicked(BuildingID building) BuildingID buildingToEnter = building; for(;;) { - const CBuilding *b = town->town->buildings.find(buildingToEnter)->second; + const CBuilding *b = town->getTown()->buildings.find(buildingToEnter)->second; if (buildingTryActivateCustomUI(buildingToEnter, building)) return; @@ -705,7 +705,7 @@ void CCastleBuildings::buildingClicked(BuildingID building) bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget) { logGlobal->trace("You've clicked on %d", (int)buildingToTest.toEnum()); - const CBuilding *b = town->town->buildings.at(buildingToTest); + const CBuilding *b = town->getTown()->buildings.at(buildingToTest); if (town->getWarMachineInBuilding(buildingToTest).hasValue()) { @@ -744,7 +744,7 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu } } - if (town->rewardableBuildings.count(buildingToTest) && town->town->buildings.at(buildingToTest)->manualHeroVisit) + if (town->rewardableBuildings.count(buildingToTest) && town->getTown()->buildings.at(buildingToTest)->manualHeroVisit) { enterRewardable(buildingToTest); return true; @@ -820,10 +820,10 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu return false; case BuildingSubID::PORTAL_OF_SUMMONING: - if (town->creatures[town->town->creatures.size()].second.empty())//No creatures + if (town->creatures[town->getTown()->creatures.size()].second.empty())//No creatures LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]); else - enterDwelling(town->town->creatures.size()); + enterDwelling(town->getTown()->creatures.size()); return true; case BuildingSubID::BANK: @@ -850,7 +850,7 @@ void CCastleBuildings::enterRewardable(BuildingID building) { MetaString message; message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s - message.replaceTextID(town->town->buildings.at(building)->getNameTextID()); + message.replaceTextID(town->getTown()->buildings.at(building)->getNameTextID()); LOCPLINT->showInfoDialog(message.toString()); } @@ -868,7 +868,7 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI const CGHeroInstance *hero = town->visitingHero; if(!hero) { - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(building)->second->getNameTranslated())); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->getTown()->buildings.find(building)->second->getNameTranslated())); return; } auto art = artifactID.toArtifact(); @@ -897,8 +897,8 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI void CCastleBuildings::enterBuilding(BuildingID building) { - std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building))); - LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps); + std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building))); + LOCPLINT->showInfoDialog( town->getTown()->buildings.find(building)->second->getDescriptionTranslated(), comps); } void CCastleBuildings::enterCastleGate() @@ -915,20 +915,20 @@ void CCastleBuildings::enterCastleGate() { const CGTownInstance *t = Town; if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is - t->town->faction->getId() == town->town->faction->getId() && //the town of the same faction + t->getFactionID() == town->getFactionID() && //the town of the same faction t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate { availableTowns.push_back(t->id.getNum());//add to the list if(settings["general"]["enableUiEnhancements"].Bool()) { - auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); + auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE); image->scaleTo(Point(35, 23)); images.push_back(image); } } } - auto gateIcon = std::make_shared(town->town->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window + auto gateIcon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window auto wnd = std::make_shared(availableTowns, gateIcon, CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41], std::bind (&CCastleInterface::castleTeleport, LOCPLINT->castleInt, _1), 0, images); wnd->onPopup = [availableTowns](int index) { CRClickPopup::createAndPush(LOCPLINT->cb->getObjInstance(ObjectInstanceID(availableTowns[index])), GH.getCursorPosition()); }; @@ -940,7 +940,7 @@ void CCastleBuildings::enterDwelling(int level) if (level < 0 || level >= town->creatures.size() || town->creatures[level].second.empty()) { assert(0); - logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->town->faction->getNameTranslated()); + logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->getFaction()->getNameTranslated()); return; } @@ -954,8 +954,8 @@ void CCastleBuildings::enterDwelling(int level) void CCastleBuildings::enterToTheQuickRecruitmentWindow() { const auto beginIt = town->creatures.cbegin(); - const auto afterLastIt = town->creatures.size() > town->town->creatures.size() - ? std::next(beginIt, town->town->creatures.size()) + const auto afterLastIt = town->creatures.size() > town->getTown()->creatures.size() + ? std::next(beginIt, town->getTown()->creatures.size()) : town->creatures.cend(); const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt, [](const auto & creatureInfo) { return creatureInfo.first > 0; }); @@ -967,8 +967,8 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow() void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades) { - std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building))); - std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated(); + std::vector> comps(1, std::make_shared(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building))); + std::string descr = town->getTown()->buildings.find(building)->second->getDescriptionTranslated(); std::string hasNotProduced; std::string hasProduced; @@ -977,10 +977,10 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID: bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND || (upgrades != BuildingID::NONE - && town->town->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND); + && town->getTown()->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND); if(upgrades != BuildingID::NONE) - descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated(); + descr += "\n\n"+town->getTown()->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated(); if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns { @@ -1056,7 +1056,7 @@ void CCastleBuildings::enterTownHall() void CCastleBuildings::openMagesGuild() { - auto mageGuildBackground = LOCPLINT->castleInt->town->town->clientInfo.guildBackground; + auto mageGuildBackground = LOCPLINT->castleInt->town->getTown()->clientInfo.guildBackground; GH.windows().createAndPushWindow(LOCPLINT->castleInt, mageGuildBackground); } @@ -1247,7 +1247,7 @@ CTownInfo::CTownInfo(int posX, int posY, const CGTownInstance * Town, bool townH return;//FIXME: suspicious statement, fix or comment picture = std::make_shared(AnimationPath::builtin("ITMCL.DEF"), town->fortLevel()-1); } - building = town->town->buildings.at(BuildingID(buildID)); + building = town->getTown()->buildings.at(BuildingID(buildID)); pos = picture->pos; } @@ -1322,7 +1322,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst recreateIcons(); if (!from) adventureInt->onAudioPaused(); - CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false); + CCS->musich->playMusicFromSet("faction", town->getFaction()->getJsonKey(), true, false); } CCastleInterface::~CCastleInterface() @@ -1403,7 +1403,7 @@ void CCastleInterface::removeBuilding(BuildingID bid) void CCastleInterface::recreateIcons() { OBJECT_CONSTRUCTION; - size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; + size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; icon->setFrame(iconIndex); TResources townIncome = town->dailyIncome(); @@ -1425,8 +1425,8 @@ void CCastleInterface::recreateIcons() if(town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); }, [this]{ - if(!town->town->faction->getDescriptionTranslated().empty()) - CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); + if(!town->getFaction()->getDescriptionTranslated().empty()) + CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated()); }); creainfo.clear(); @@ -1527,7 +1527,7 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance * -1, -1, -1, 0, 0, 1, 2, -1, 1, 1, -1, -1 }; - icon = std::make_shared(town->town->clientInfo.buildingsIcons, building->bid, 0, 2, 2); + icon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 2, 2); header = std::make_shared(AnimationPath::builtin("TPTHBAR"), panelIndex[static_cast(state)], 0, 1, 73); if(iconIndex[static_cast(state)] >=0) mark = std::make_shared(AnimationPath::builtin("TPTHCHK"), iconIndex[static_cast(state)], 0, 136, 56); @@ -1569,7 +1569,7 @@ void CHallInterface::CBuildingBox::showPopupWindow(const Point & cursorPosition) } CHallInterface::CHallInterface(const CGTownInstance * Town): - CWindowObject(PLAYER_COLORED | BORDERED, Town->town->clientInfo.hallBackground), + CWindowObject(PLAYER_COLORED | BORDERED, Town->getTown()->clientInfo.hallBackground), town(Town) { OBJECT_CONSTRUCTION; @@ -1581,10 +1581,10 @@ CHallInterface::CHallInterface(const CGTownInstance * Town): auto statusbarBackground = std::make_shared(background->getSurface(), barRect, 5, 556); statusbar = CGStatusBar::create(statusbarBackground); - title = std::make_shared(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated()); + title = std::make_shared(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->getTown()->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated()); exit = std::make_shared(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->hcommands[8]), [&](){close();}, EShortcut::GLOBAL_RETURN); - auto & boxList = town->town->clientInfo.hallSlots; + auto & boxList = town->getTown()->clientInfo.hallSlots; boxes.resize(boxList.size()); for(size_t row=0; rowwarn("Invalid building ID found in hallSlots of town '%s'", town->town->faction->getJsonKey() ); + logMod->warn("Invalid building ID found in hallSlots of town '%s'", town->getFaction()->getJsonKey() ); continue; } - const CBuilding * current = town->town->buildings.at(buildingID); + const CBuilding * current = town->getTown()->buildings.at(buildingID); if(town->hasBuilt(buildingID)) { building = current; @@ -1629,7 +1629,7 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin { OBJECT_CONSTRUCTION; - icon = std::make_shared(town->town->clientInfo.buildingsIcons, building->bid, 0, 125, 50); + icon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 125, 50); auto statusbarBackground = std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26); statusbar = CGStatusBar::create(statusbarBackground); @@ -1711,7 +1711,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state) { auto toStr = [&](const BuildingID build) -> std::string { - return town->town->buildings.at(build)->getNameTranslated(); + return town->getTown()->buildings.at(build)->getNameTranslated(); }; ret = CGI->generaltexth->allTexts[52]; @@ -1721,7 +1721,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state) case EBuildingState::MISSING_BASE: { std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase"); - ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->getNameTranslated()); + ret = boost::str(boost::format(msg) % town->getTown()->buildings.at(building->upgrade)->getNameTranslated()); break; } } @@ -1780,11 +1780,11 @@ CFortScreen::CFortScreen(const CGTownInstance * town): { OBJECT_CONSTRUCTION; ui32 fortSize = static_cast(town->creatures.size()); - if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty()) + if(fortSize > town->getTown()->creatures.size() && town->creatures.back().second.empty()) fortSize--; fortSize = std::min(fortSize, static_cast(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning - const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6)); + const CBuilding * fortBuilding = town->getTown()->buildings.at(BuildingID(town->fortLevel()+6)); title = std::make_shared(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated()); std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->getNameTranslated()); @@ -1810,7 +1810,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): for(ui32 i=0; itown->creatures.size()) + if(fortSize == town->getTown()->creatures.size()) { BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1); @@ -1839,7 +1839,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town): ImagePath CFortScreen::getBgName(const CGTownInstance * town) { ui32 fortSize = static_cast(town->creatures.size()); - if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty()) + if(fortSize > town->getTown()->creatures.size() && town->creatures.back().second.empty()) fortSize--; fortSize = std::min(fortSize, static_cast(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning @@ -1877,7 +1877,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance * if(getMyBuilding() != nullptr) { - buildingIcon = std::make_shared(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); + buildingIcon = std::make_shared(town->getTown()->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21); buildingName = std::make_shared(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152); if(town->hasBuilt(getMyBuilding()->bid)) @@ -1913,8 +1913,8 @@ const CCreature * CFortScreen::RecruitArea::getMyCreature() { if(!town->creatures.at(level).second.empty()) // built return town->creatures.at(level).second.back().toCreature(); - if(!town->town->creatures.at(level).empty()) // there are creatures on this level - return town->town->creatures.at(level).front().toCreature(); + if(!town->getTown()->creatures.at(level).empty()) // there are creatures on this level + return town->getTown()->creatures.at(level).front().toCreature(); return nullptr; } @@ -1922,17 +1922,17 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding() { BuildingID myID = BuildingID(BuildingID::getDwellingFromLevel(level, 0)); - if (level == town->town->creatures.size()) - return town->town->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING); + if (level == town->getTown()->creatures.size()) + return town->getTown()->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING); - if (!town->town->buildings.count(myID)) + if (!town->getTown()->buildings.count(myID)) return nullptr; - const CBuilding * build = town->town->buildings.at(myID); - while (town->town->buildings.count(myID)) + const CBuilding * build = town->getTown()->buildings.at(myID); + while (town->getTown()->buildings.count(myID)) { if (town->hasBuilt(myID)) - build = town->town->buildings.at(myID); + build = town->getTown()->buildings.at(myID); BuildingID::advanceDwelling(myID); } @@ -1972,7 +1972,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i { OBJECT_CONSTRUCTION; - window = std::make_shared(owner->town->town->clientInfo.guildWindow, 332, 76); + window = std::make_shared(owner->town->getTown()->clientInfo.guildWindow, 332, 76); resdatabar = std::make_shared(); resdatabar->moveBy(pos.topLeft(), true); @@ -2007,7 +2007,7 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID) const CGTownInstance * town = LOCPLINT->cb->getTown(townId); - for(size_t i=0; itown->mageLevel; i++) + for(size_t i=0; igetTown()->mageLevel; i++) { size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? for(size_t j=0; jsecSkills.size(); ++m) secSkillIcons[leftRight].push_back(std::make_shared(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88)); - specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); + specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); expImages[leftRight] = std::make_shared(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45); expValues[leftRight] = std::make_shared(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); @@ -151,7 +151,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, specialtyAreas[b] = std::make_shared(); specialtyAreas[b]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32)); specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27]; - specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated(); + specialtyAreas[b]->text = hero->getHeroType()->getSpecialtyDescriptionTranslated(); experienceAreas[b] = std::make_shared(); experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32)); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 36dd52d80..809f937f0 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -184,9 +184,9 @@ void CHeroWindow::update() name->setText(curHero->getNameTranslated()); title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->getClassNameTranslated()).str()); - specArea->text = curHero->type->getSpecialtyDescriptionTranslated(); - specImage->setFrame(curHero->type->imageIndex); - specName->setText(curHero->type->getSpecialtyNameTranslated()); + specArea->text = curHero->getHeroType()->getSpecialtyDescriptionTranslated(); + specImage->setFrame(curHero->getHeroType()->imageIndex); + specName->setText(curHero->getHeroType()->getSpecialtyNameTranslated()); tacticsButton = std::make_shared(Point(539, 483), AnimationPath::builtin("hsbtns8.def"), std::make_pair(heroscrn[26], heroscrn[31]), 0, EShortcut::HERO_TOGGLE_TACTICS); tacticsButton->addHoverText(EButtonState::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 9363cd2aa..6c99bbca6 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -300,7 +300,7 @@ int InfoBoxHeroData::getSubID() else return 0; case HERO_SPECIAL: - return hero->type->getIndex(); + return hero->getHeroTypeID().getNum(); case HERO_MANA: case HERO_EXPERIENCE: return 0; @@ -800,7 +800,7 @@ CTownItem::CTownItem(const CGTownInstance * Town) garr = std::make_shared(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS); heroes = std::make_shared(town, Point(244,6), Point(475,6), garr, false); - size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; + size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)]; picture = std::make_shared(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6); openTown = std::make_shared(Rect(5, 6, 58, 64), town); @@ -823,8 +823,8 @@ CTownItem::CTownItem(const CGTownInstance * Town) if(town->hasBuilt(BuildingID::TAVERN)) LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE); }, [&]{ - if(!town->town->faction->getDescriptionTranslated().empty()) - CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated()); + if(!town->getTown()->faction->getDescriptionTranslated().empty()) + CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated()); }); fastMarket = std::make_shared(Rect(153, 6, 65, 64), []() { diff --git a/client/windows/CMarketWindow.cpp b/client/windows/CMarketWindow.cpp index bf7661696..02b167575 100644 --- a/client/windows/CMarketWindow.cpp +++ b/client/windows/CMarketWindow.cpp @@ -192,7 +192,7 @@ std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const { for(const auto & buildingId : town->getBuildings()) { - if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode)) + if(const auto building = town->getTown()->buildings.at(buildingId); vstd::contains(building->marketModes, mode)) return building->getNameTranslated(); } } diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index e014d36a8..46406d4d4 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -522,9 +522,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func recruit->block(true); } if(LOCPLINT->castleInt) - videoPlayer = std::make_shared(Point(70, 56), LOCPLINT->castleInt->town->town->clientInfo.tavernVideo, false); + videoPlayer = std::make_shared(Point(70, 56), LOCPLINT->castleInt->town->getTown()->clientInfo.tavernVideo, false); else if(const auto * townObj = dynamic_cast(TavernObj)) - videoPlayer = std::make_shared(Point(70, 56), townObj->town->clientInfo.tavernVideo, false); + videoPlayer = std::make_shared(Point(70, 56), townObj->getTown()->clientInfo.tavernVideo, false); else videoPlayer = std::make_shared(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false); @@ -548,7 +548,7 @@ void CTavernWindow::addInvite() if(!inviteableHeroes.empty()) { - int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random + int imageIndex = heroToInvite ? heroToInvite->getIconIndex() : 156; // 156 => special id for random if(!heroToInvite) heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second; @@ -563,7 +563,7 @@ void CTavernWindow::recruitb() const CGHeroInstance *toBuy = (selected ? h2 : h1)->h; const CGObjectInstance *obj = tavernObj; - LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE); + LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroTypeID() : HeroTypeID::NONE); close(); } @@ -963,7 +963,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu if(auto town = dynamic_cast(_market)) { - auto faction = town->town->faction->getId(); + auto faction = town->getTown()->faction->getId(); titlePic = std::make_shared((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, building); } else if(auto uni = dynamic_cast(_market); uni->appearance) diff --git a/client/windows/QuickRecruitmentWindow.cpp b/client/windows/QuickRecruitmentWindow.cpp index 49f9be79d..cdbb80121 100644 --- a/client/windows/QuickRecruitmentWindow.cpp +++ b/client/windows/QuickRecruitmentWindow.cpp @@ -51,9 +51,9 @@ void QuickRecruitmentWindow::setCreaturePurchaseCards() { int availableAmount = getAvailableCreatures(); Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64); - for (int i = 0; i < town->town->creatures.size(); i++) + for (int i = 0; i < town->getTown()->creatures.size(); i++) { - if(!town->town->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) + if(!town->getTown()->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) { cards.push_back(std::make_shared(town->creatures[i].second, position, town->creatures[i].first, this)); position.x += 108; @@ -108,7 +108,7 @@ void QuickRecruitmentWindow::purchaseUnits() { int level = 0; int i = 0; - for(auto c : town->town->creatures) + for(auto c : town->getTown()->creatures) { for(auto c2 : c) if(c2 == selected->creatureOnTheCard->getId()) @@ -129,8 +129,8 @@ void QuickRecruitmentWindow::purchaseUnits() int QuickRecruitmentWindow::getAvailableCreatures() { int creaturesAmount = 0; - for (int i=0; i< town->town->creatures.size(); i++) - if(!town->town->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) + for (int i=0; i< town->getTown()->creatures.size(); i++) + if(!town->getTown()->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first) creaturesAmount++; return creaturesAmount; } diff --git a/include/vcmi/Entity.h b/include/vcmi/Entity.h index 406a879b5..b00a61525 100644 --- a/include/vcmi/Entity.h +++ b/include/vcmi/Entity.h @@ -26,7 +26,7 @@ class DLL_LINKAGE INativeTerrainProvider { public: virtual TerrainId getNativeTerrain() const = 0; - virtual FactionID getFaction() const = 0; + virtual FactionID getFactionID() const = 0; virtual bool isNativeTerrain(TerrainId terrain) const; }; diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index a294a14a2..fecc59c7d 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -38,7 +38,7 @@ TerrainId AFactionMember::getNativeTerrain() const //this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses //and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties. return getBonusBearer()->hasBonus(selectorNoTerrainPenalty, cachingStringNoTerrainPenalty) - ? TerrainId::ANY_TERRAIN : VLC->factions()->getById(getFaction())->getNativeTerrain(); + ? TerrainId::ANY_TERRAIN : getFactionID().toEntity(VLC)->getNativeTerrain(); } int32_t AFactionMember::magicResistance() const diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index f34608fd2..2eed440ed 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -117,7 +117,7 @@ int32_t CCreature::getHorde() const return hordeGrowth; } -FactionID CCreature::getFaction() const +FactionID CCreature::getFactionID() const { return FactionID(faction); } diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 7b09b8d65..0e6c8011b 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -127,7 +127,7 @@ public: std::string getNamePluralTextID() const override; std::string getNameSingularTextID() const override; - FactionID getFaction() const override; + FactionID getFactionID() const override; int32_t getIndex() const override; int32_t getIconIndex() const override; std::string getJsonKey() const override; diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 69040009d..5de5a3420 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -912,10 +912,10 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler) } } -FactionID CStackInstance::getFaction() const +FactionID CStackInstance::getFactionID() const { if(type) - return type->getFaction(); + return type->getFactionID(); return FactionID::NEUTRAL; } diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index d20fee553..75b377a11 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -106,7 +106,7 @@ public: //IConstBonusProvider const IBonusBearer* getBonusBearer() const override; //INativeTerrainProvider - FactionID getFaction() const override; + FactionID getFactionID() const override; virtual ui64 getPower() const; CCreature::CreatureQuantityId getQuantityID() const; diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index fa5fe231d..b6017df9d 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -381,7 +381,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero for(const auto & creature : VLC->creh->objects) { - if(creature->getFaction() == factionIndex && static_cast(creature->getAIValue()) > maxAIValue) + if(creature->getFactionID() == factionIndex && static_cast(creature->getAIValue()) > maxAIValue) { maxAIValue = creature->getAIValue(); mostStrong = creature.get(); @@ -575,10 +575,10 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu { ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", EBuildingState::TOWN_NOT_OWNED); - if(!t->town->buildings.count(ID)) + if(!t->getTown()->buildings.count(ID)) return EBuildingState::BUILDING_ERROR; - const CBuilding * building = t->town->buildings.at(ID); + const CBuilding * building = t->getTown()->buildings.at(ID); if(t->hasBuilt(ID)) //already built diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 131fc2a23..737a9813f 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -416,9 +416,9 @@ int32_t CUnitState::creatureIconIndex() const return unitType()->getIconIndex(); } -FactionID CUnitState::getFaction() const +FactionID CUnitState::getFactionID() const { - return unitType()->getFaction(); + return unitType()->getFactionID(); } int32_t CUnitState::getCasterUnitId() const diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index b5451ba89..9bb9570a1 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -253,7 +253,7 @@ public: void localInit(const IUnitEnvironment * env_); void serializeJson(JsonSerializeFormat & handler); - FactionID getFaction() const override; + FactionID getFactionID() const override; void afterAttack(bool ranged, bool counter); diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 4f1786a20..914081139 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -300,15 +300,15 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context) if(bearer) { if(faction != FactionID::DEFAULT) - return bearer->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; + return bearer->getFactionID() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; switch(context.b.source) { case BonusSource::CREATURE_ABILITY: - return bearer->getFaction() == context.b.sid.as().toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; + return bearer->getFactionID() == context.b.sid.as().toCreature()->getFactionID() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; case BonusSource::TOWN_STRUCTURE: - return bearer->getFaction() == context.b.sid.as().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; + return bearer->getFactionID() == context.b.sid.as().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //TODO: other sources of bonuses } diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index 132d6e904..89d4eed8c 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -351,14 +351,14 @@ void CampaignState::setCurrentMapAsConquered(std::vector heroe { JsonNode node = CampaignState::crossoverSerialize(hero); - if (reservedHeroes.count(hero->getHeroType())) + if (reservedHeroes.count(hero->getHeroTypeID())) { - logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroType(), hero->getNameTranslated()); - globalHeroPool[hero->getHeroType()] = node; + logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroTypeID(), hero->getNameTranslated()); + globalHeroPool[hero->getHeroTypeID()] = node; } else { - logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroType(), hero->getNameTranslated()); + logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroTypeID(), hero->getNameTranslated()); scenarioHeroPool[*currentMap].push_back(node); } } diff --git a/lib/entities/faction/CFaction.cpp b/lib/entities/faction/CFaction.cpp index 4ed5d1ad2..3ef88afae 100644 --- a/lib/entities/faction/CFaction.cpp +++ b/lib/entities/faction/CFaction.cpp @@ -92,7 +92,7 @@ FactionID CFaction::getId() const return FactionID(index); } -FactionID CFaction::getFaction() const +FactionID CFaction::getFactionID() const { return FactionID(index); } diff --git a/lib/entities/faction/CFaction.h b/lib/entities/faction/CFaction.h index 294a77529..27d6a2239 100644 --- a/lib/entities/faction/CFaction.h +++ b/lib/entities/faction/CFaction.h @@ -39,7 +39,7 @@ class DLL_LINKAGE CFaction : public Faction FactionID index = FactionID::NEUTRAL; - FactionID getFaction() const override; //This function should not be used + FactionID getFactionID() const override; //This function should not be used public: TerrainId nativeTerrain; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index d0cd98fae..7bba1770d 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -599,7 +599,7 @@ void CGameState::initHeroes() } hero->initHero(getRandomGenerator()); - map->allHeroes[hero->getHeroType().getNum()] = hero; + map->allHeroes[hero->getHeroTypeID().getNum()] = hero; } // generate boats for all heroes on water @@ -629,20 +629,20 @@ void CGameState::initHeroes() { auto * hero = dynamic_cast(obj.get()); hero->initHero(getRandomGenerator()); - map->allHeroes[hero->getHeroType().getNum()] = hero; + map->allHeroes[hero->getHeroTypeID().getNum()] = hero; } } std::set heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool for(auto ph : map->predefinedHeroes) { - if(!vstd::contains(heroesToCreate, ph->getHeroType())) + if(!vstd::contains(heroesToCreate, ph->getHeroTypeID())) continue; ph->initHero(getRandomGenerator()); heroesPool->addHeroToPool(ph); - heroesToCreate.erase(ph->type->getId()); + heroesToCreate.erase(ph->getHeroTypeID()); - map->allHeroes[ph->getHeroType().getNum()] = ph; + map->allHeroes[ph->getHeroTypeID().getNum()] = ph; } for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool @@ -756,12 +756,12 @@ void CGameState::initTownNames() for(auto & vti : map->towns) { - assert(vti->town); + assert(vti->getTown()); if(!vti->getNameTextID().empty()) continue; - FactionID faction = vti->getFaction(); + FactionID faction = vti->getFactionID(); if(availableNames.empty()) { @@ -798,8 +798,8 @@ void CGameState::initTowns() for (auto & vti : map->towns) { - assert(vti->town); - assert(vti->town->creatures.size() <= GameConstants::CREATURES_PER_TOWN); + assert(vti->getTown()); + assert(vti->getTown()->creatures.size() <= GameConstants::CREATURES_PER_TOWN); constexpr std::array basicDwellings = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7, BuildingID::DWELL_LVL_8 }; constexpr std::array upgradedDwellings = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP, BuildingID::DWELL_LVL_8_UP }; @@ -828,7 +828,7 @@ void CGameState::initTowns() vti->addBuilding(BuildingID::VILLAGE_HALL); //init hordes - for (int i = 0; i < vti->town->creatures.size(); i++) + for (int i = 0; i < vti->getTown()->creatures.size(); i++) { if(vti->hasBuilt(hordes[i])) //if we have horde for this level { @@ -894,7 +894,7 @@ void CGameState::initTowns() int sel = -1; for(ui32 ps=0;pspossibleSpells.size();ps++) - total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction()); + total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID()); if (total == 0) // remaining spells have 0 probability break; @@ -902,7 +902,7 @@ void CGameState::initTowns() auto r = getRandomGenerator().nextInt(total - 1); for(ui32 ps=0; pspossibleSpells.size();ps++) { - r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction()); + r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID()); if(r<0) { sel = ps; @@ -1655,18 +1655,13 @@ std::set CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow } for(auto hero : map->heroesOnMap) //heroes instances initialization - { - if(hero->type) - ret -= hero->type->getId(); - else - ret -= hero->getHeroType(); - } + ret -= hero->getHeroTypeID(); for(auto obj : map->objects) //prisons { auto * hero = dynamic_cast(obj.get()); if(hero && hero->ID == Obj::PRISON) - ret -= hero->getHeroType(); + ret -= hero->getHeroTypeID(); } return ret; @@ -1690,7 +1685,7 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const auto * hero = dynamic_cast(obj.get()); assert(hero); - if (hero->getHeroType() == hid) + if (hero->getHeroTypeID() == hid) return hero; } diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index b4bf3678c..2178da654 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -86,7 +86,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr .And(Selector::subtype()(BonusSubtypeID(g))) .And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); - hero.hero->getLocalBonus(sel)->val = hero.hero->type->heroClass->primarySkillInitial[g.getNum()]; + hero.hero->getLocalBonus(sel)->val = hero.hero->getHeroClass()->primarySkillInitial[g.getNum()]; } } } @@ -96,7 +96,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr //trimming sec skills for(auto & hero : campaignHeroReplacements) { - hero.hero->secSkills = hero.hero->type->secSkillsInit; + hero.hero->secSkills = hero.hero->getHeroType()->secSkillsInit; hero.hero->recreateSecondarySkillsBonuses(); } } @@ -240,7 +240,7 @@ void CGameStateCampaign::placeCampaignHeroes() for(auto & replacement : campaignHeroReplacements) if (replacement.heroPlaceholderId.hasValue()) - heroesToRemove.insert(replacement.hero->getHeroType()); + heroesToRemove.insert(replacement.hero->getHeroTypeID()); for(auto & heroID : heroesToRemove) { @@ -369,8 +369,8 @@ void CGameStateCampaign::replaceHeroesPlaceholders() if(heroPlaceholder->tempOwner.isValidPlayer()) heroToPlace->tempOwner = heroPlaceholder->tempOwner; heroToPlace->setAnchorPos(heroPlaceholder->anchorPos()); - heroToPlace->type = heroToPlace->getHeroType().toHeroType(); - heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front(); + heroToPlace->setHeroType(heroToPlace->getHeroTypeID()); + heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->getHeroTypeID())->getTemplates().front(); gameState->map->removeBlockVisTiles(heroPlaceholder, true); gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr; @@ -563,7 +563,7 @@ void CGameStateCampaign::initHeroes() { for (auto & hero : heroes) { - if (hero->getHeroType().getNum() == chosenBonus->info1) + if (hero->getHeroTypeID().getNum() == chosenBonus->info1) { giveCampaignBonusToHero(hero); break; @@ -662,7 +662,7 @@ void CGameStateCampaign::initTowns() if(gameState->scenarioOps->campState->formatVCMI()) newBuilding = BuildingID(chosenBonus->info1); else - newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings()); + newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFactionID(), town->getBuildings()); // Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2 while(true) @@ -675,7 +675,7 @@ void CGameStateCampaign::initTowns() town->addBuilding(newBuilding); - auto building = town->town->buildings.at(newBuilding); + auto building = town->getTown()->buildings.at(newBuilding); newBuilding = building->upgrade; } break; diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 237da8f54..6e1c95e85 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -381,7 +381,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps) for(const auto & t : ps->getTowns()) { built += t->getBuildings().size(); - for(const auto & b : t->town->buildings) + for(const auto & b : t->getTown()->buildings) if(!t->forbiddenBuildings.count(b.first)) total += 1; } diff --git a/lib/gameState/InfoAboutArmy.cpp b/lib/gameState/InfoAboutArmy.cpp index cc3bdc205..cb6ad5e95 100644 --- a/lib/gameState/InfoAboutArmy.cpp +++ b/lib/gameState/InfoAboutArmy.cpp @@ -115,7 +115,7 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe initFromArmy(h, detailed); - hclass = h->type->heroClass; + hclass = h->getHeroClass(); name = h->getNameTranslated(); portraitSource = h->getPortraitSource(); diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index bafaf28b0..44d085c13 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -25,7 +25,7 @@ std::map TavernHeroesPool::unusedHeroesFromPool() c { std::map pool = heroesPool; for(const auto & slot : currentTavern) - pool.erase(slot.hero->getHeroType()); + pool.erase(slot.hero->getHeroTypeID()); return pool; } @@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const { for (auto const & slot : currentTavern) { - if (slot.hero->getHeroType() == hero) + if (slot.hero->getHeroTypeID() == hero) return slot.role; } return TavernSlotRole::NONE; @@ -106,7 +106,7 @@ CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero) heroesPool.erase(hero); vstd::erase_if(currentTavern, [&](const TavernSlot & entry){ - return entry.hero->type->getId() == hero; + return entry.hero->getHeroTypeID() == hero; }); assert(result); @@ -138,7 +138,7 @@ void TavernHeroesPool::onNewDay() void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero) { - heroesPool[hero->getHeroType()] = hero; + heroesPool[hero->getHeroTypeID()] = hero; } void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set mask) diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index d90ac8ef2..421d2565f 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -96,7 +96,6 @@ bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, std void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const { - obj->town = faction->town; obj->tempOwner = PlayerColor::NEUTRAL; } @@ -144,7 +143,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std auto heroTest = [&](const HeroTypeID & id) { - return hero->type->getId() == id; + return hero->getHeroTypeID() == id; }; if(filters.count(templ->stringID)) @@ -154,11 +153,6 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std return false; } -void CHeroInstanceConstructor::initializeObject(CGHeroInstance * obj) const -{ - obj->type = nullptr; //FIXME: set to valid value. somehow. -} - void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const { diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index d7ce61f5e..2089acdf7 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -81,7 +81,6 @@ public: const CHeroClass * heroClass = nullptr; std::map> filters; - void initializeObject(CGHeroInstance * object) const override; void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; void afterLoadFinalization() override; diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index 5a8b0cae8..23d026cd9 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -78,7 +78,7 @@ void CArmedInstance::updateMoraleBonusFromArmy() const CStackInstance * inst = slot.second; const auto * creature = inst->getCreatureID().toEntity(VLC); - factions.insert(creature->getFaction()); + factions.insert(creature->getFactionID()); // Check for undead flag instead of faction (undead mummies are neutral) if (!hasUndead) { diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index 3ebead8cb..2d595bd5f 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -45,7 +45,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const else ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex); ms.appendRawString(" "); - ms.appendNamePlural(getCreature()); + ms.appendNamePlural(getCreatureID()); return ms.toString(); } @@ -57,7 +57,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const MetaString ms; ms.appendNumber(stacks.begin()->second->count); ms.appendRawString(" "); - ms.appendName(getCreature(), stacks.begin()->second->count); + ms.appendName(getCreatureID(), stacks.begin()->second->count); return ms.toString(); } else @@ -69,11 +69,11 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const std::string CGCreature::getMonsterLevelText() const { std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel"); - bool isRanged = VLC->creatures()->getById(getCreature())->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER); + bool isRanged = getCreature()->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER); std::string attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType"; std::string attackType = VLC->generaltexth->translate(attackTypeKey); - boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated()); - boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel())); + boost::replace_first(monsterLevel, "%TOWN", getCreature()->getFactionID().toEntity(VLC)->getNameTranslated()); + boost::replace_first(monsterLevel, "%LEVEL", std::to_string(getCreature()->getLevel())); boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType); return monsterLevel; } @@ -150,7 +150,7 @@ std::string CGCreature::getPopupText(PlayerColor player) const std::vector CGCreature::getPopupComponents(PlayerColor player) const { return { - Component(ComponentType::CREATURE, getCreature()) + Component(ComponentType::CREATURE, getCreatureID()) }; } @@ -182,7 +182,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT, 86); - ynd.text.replaceName(getCreature(), getStackCount(SlotID(0))); + ynd.text.replaceName(getCreatureID(), getStackCount(SlotID(0))); cb->showBlockingDialog(this, &ynd); break; } @@ -197,7 +197,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const std::string tmp = VLC->generaltexth->advobtxt[90]; boost::algorithm::replace_first(tmp, "%d", std::to_string(getStackCount(SlotID(0)))); boost::algorithm::replace_first(tmp, "%d", std::to_string(action)); - boost::algorithm::replace_first(tmp,"%s",VLC->creatures()->getById(getCreature())->getNamePluralTranslated()); + boost::algorithm::replace_first(tmp,"%s",getCreature()->getNamePluralTranslated()); ynd.text.appendRawString(tmp); cb->showBlockingDialog(this, &ynd); break; @@ -205,11 +205,16 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const } } -CreatureID CGCreature::getCreature() const +CreatureID CGCreature::getCreatureID() const { return CreatureID(getObjTypeIndex().getNum()); } +const CCreature * CGCreature::getCreature() const +{ + return getCreatureID().toCreature(); +} + void CGCreature::pickRandomObject(vstd::RNG & rand) { switch(ID.toEnum()) @@ -279,7 +284,7 @@ void CGCreature::initObj(vstd::RNG & rand) stacks[SlotID(0)]->setType(getCreature()); TQuantity &amount = stacks[SlotID(0)]->count; - const Creature * c = VLC->creatures()->getById(getCreature()); + const Creature * c = getCreature(); if(amount == 0) { amount = rand.nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax()); @@ -353,8 +358,8 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const for(const auto & elem : h->Slots()) { - bool isOurUpgrade = vstd::contains(getCreature().toCreature()->upgrades, elem.second->getCreatureID()); - bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreature()); + bool isOurUpgrade = vstd::contains(getCreature()->upgrades, elem.second->getCreatureID()); + bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreatureID()); if(isOurUpgrade || isOurDowngrade) count += elem.second->count; @@ -380,7 +385,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const if(diplomacy * 2 + sympathy + 1 >= character) { - int32_t recruitCost = VLC->creatures()->getById(getCreature())->getRecruitCost(EGameResID::GOLD); + int32_t recruitCost = getCreature()->getRecruitCost(EGameResID::GOLD); int32_t stackCount = getStackCount(SlotID(0)); return recruitCost * stackCount; //join for gold } @@ -493,7 +498,7 @@ void CGCreature::flee( const CGHeroInstance * h ) const BlockingDialog ynd(true,false); ynd.player = h->tempOwner; ynd.text.appendLocalString(EMetaText::ADVOB_TXT,91); - ynd.text.replaceName(getCreature(), getStackCount(SlotID(0))); + ynd.text.replaceName(getCreatureID(), getStackCount(SlotID(0))); cb->showBlockingDialog(this, &ynd); } @@ -513,7 +518,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult & { //merge stacks into one TSlots::const_iterator i; - const CCreature * cre = getCreature().toCreature(); + const CCreature * cre = getCreature(); for(i = stacks.begin(); i != stacks.end(); i++) { if(cre->isMyUpgrade(i->second->type)) diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index a8c32fb8b..daa554a2e 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -49,7 +49,8 @@ public: void newTurn(vstd::RNG & rand) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override; - CreatureID getCreature() const; + CreatureID getCreatureID() const; + const CCreature * getCreature() const; //stack formation depends on position, bool containsUpgradedStack() const; diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 51dd897e3..638480ed2 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -93,7 +93,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand) assert(linkedTown->ID == Obj::TOWN); if(linkedTown->ID==Obj::TOWN) - return linkedTown->getFaction(); + return linkedTown->getFactionID(); } if(!randomizationInfo->allowedFactions.empty()) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 080285a96..caaf5081a 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -116,9 +116,9 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain return static_cast(ret); } -FactionID CGHeroInstance::getFaction() const +FactionID CGHeroInstance::getFactionID() const { - return FactionID(type->heroClass->faction); + return getHeroClass()->faction; } const IBonusBearer* CGHeroInstance::getBonusBearer() const @@ -229,10 +229,10 @@ bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const if (getSecSkillLevel(which) > 0) return false; - if (type->heroClass->secSkillProbability.count(which) == 0) + if (getHeroClass()->secSkillProbability.count(which) == 0) return false; - if (type->heroClass->secSkillProbability.at(which) == 0) + if (getHeroClass()->secSkillProbability.at(which) == 0) return false; return true; @@ -282,7 +282,6 @@ int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) CGHeroInstance::CGHeroInstance(IGameCallback * cb) : CArmedInstance(cb), - type(nullptr), tacticFormationEnabled(false), inTownGarrison(false), moveDir(4), @@ -303,14 +302,30 @@ PlayerColor CGHeroInstance::getOwner() const return tempOwner; } -HeroTypeID CGHeroInstance::getHeroType() const +const CHeroClass * CGHeroInstance::getHeroClass() const { + return getHeroType()->heroClass; +} + +HeroClassID CGHeroInstance::getHeroClassID() const +{ + return getHeroType()->heroClass->getId(); +} + +const CHero * CGHeroInstance::getHeroType() const +{ + return getHeroTypeID().toHeroType(); +} + +HeroTypeID CGHeroInstance::getHeroTypeID() const +{ + if (ID == Obj::RANDOM_HERO) + return HeroTypeID::NONE; return HeroTypeID(getObjTypeIndex().getNum()); } void CGHeroInstance::setHeroType(HeroTypeID heroType) { - assert(type == nullptr); subID = heroType; } @@ -323,16 +338,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) void CGHeroInstance::initHero(vstd::RNG & rand) { assert(validTypes(true)); - if(!type) - type = getHeroType().toHeroType(); - + if (ID == Obj::HERO) - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex())->getTemplates().front(); if(!vstd::contains(spells, SpellID::PRESET)) { // hero starts with default spells - for(const auto & spellID : type->spells) + for(const auto & spellID : getHeroType()->spells) spells.insert(spellID); } else //remove placeholder @@ -341,7 +354,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand) if(!vstd::contains(spells, SpellID::SPELLBOOK_PRESET)) { // hero starts with default spellbook presence status - if(!getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) + if(!getArt(ArtifactPosition::SPELLBOOK) && getHeroType()->haveSpellBook) { auto artifact = ArtifactUtils::createArtifact(ArtifactID::SPELLBOOK); putArtifact(ArtifactPosition::SPELLBOOK, artifact); @@ -360,14 +373,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand) { for(int g=0; g(g), type->heroClass->primarySkillInitial[g]); + pushPrimSkill(static_cast(g), getHeroClass()->primarySkillInitial[g]); } } if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::NONE, -1)) //set secondary skills to default - secSkills = type->secSkillsInit; + secSkills = getHeroType()->secSkillsInit; if (gender == EHeroGender::DEFAULT) - gender = type->gender; + gender = getHeroType()->gender; setFormation(EArmyFormation::LOOSE); if (!stacksCount()) //standard army//initial army @@ -403,9 +416,9 @@ void CGHeroInstance::initHero(vstd::RNG & rand) addNewBonus(bonus); } - if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && type->heroClass->commander.hasValue()) + if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && getHeroClass()->commander.hasValue()) { - commander = new CCommanderInstance(type->heroClass->commander); + commander = new CCommanderInstance(getHeroClass()->commander); commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders commander->giveStackExp (exp); //after our exp is set } @@ -413,7 +426,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand) skillsInfo = SecondarySkillsInfo(); //copy active (probably growing) bonuses from hero prototype to hero object - for(const std::shared_ptr & b : type->specialty) + for(const std::shared_ptr & b : getHeroType()->specialty) addNewBonus(b); //initialize bonuses @@ -433,14 +446,14 @@ void CGHeroInstance::initArmy(vstd::RNG & rand, IArmyDescriptor * dst) auto stacksCountChances = cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES); int stacksCountInitRandomNumber = rand.nextInt(1, 100); - size_t maxStacksCount = std::min(stacksCountChances.size(), type->initialArmy.size()); + size_t maxStacksCount = std::min(stacksCountChances.size(), getHeroType()->initialArmy.size()); for(int stackNo=0; stackNo < maxStacksCount; stackNo++) { if (stacksCountInitRandomNumber > stacksCountChances[stackNo]) continue; - auto & stack = type->initialArmy[stackNo]; + auto & stack = getHeroType()->initialArmy[stackNo]; int count = rand.nextInt(stack.minAmount, stack.maxAmount); @@ -588,11 +601,11 @@ std::string CGHeroInstance::getMovementPointsTextIfOwner(PlayerColor player) con ui8 CGHeroInstance::maxlevelsToMagicSchool() const { - return type->heroClass->isMagicHero() ? 3 : 4; + return getHeroClass()->isMagicHero() ? 3 : 4; } ui8 CGHeroInstance::maxlevelsToWisdom() const { - return type->heroClass->isMagicHero() ? 3 : 6; + return getHeroClass()->isMagicHero() ? 3 : 6; } CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo(): @@ -617,11 +630,8 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand) { ID = Obj::HERO; subID = cb->gameState()->pickNextHeroType(getOwner()); - type = getHeroType().toHeroType(); - randomizeArmy(type->heroClass->faction); + randomizeArmy(getHeroClass()->faction); } - else - type = getHeroType().toHeroType(); auto oldSubID = subID; @@ -629,7 +639,7 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand) // after setType subID used to store unique hero identify id. Check issue 2277 for details // exclude prisons which should use appearance as set in map, via map editor or RMG if (ID != Obj::PRISON) - setType(ID, type->heroClass->getIndex()); + setType(ID, getHeroClass()->getIndex()); this->subID = oldSubID; } @@ -1041,7 +1051,7 @@ si32 CGHeroInstance::getManaNewTurn() const BoatId CGHeroInstance::getBoatType() const { - return BoatId(VLC->townh->getById(type->heroClass->faction)->getBoatType()); + return BoatId(VLC->townh->getById(getHeroClass()->faction)->getBoatType()); } void CGHeroInstance::getOutOffsets(std::vector &offsets) const @@ -1080,7 +1090,7 @@ void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val ) EAlignment CGHeroInstance::getAlignment() const { - return type->heroClass->getAlignment(); + return getHeroClass()->getAlignment(); } void CGHeroInstance::initExp(vstd::RNG & rand) @@ -1104,12 +1114,12 @@ HeroTypeID CGHeroInstance::getPortraitSource() const if (customPortraitSource.isValid()) return customPortraitSource; else - return getHeroType(); + return getHeroTypeID(); } int32_t CGHeroInstance::getIconIndex() const { - return VLC->heroTypes()->getById(getPortraitSource())->getIconIndex(); + return getPortraitSource().toEntity(VLC)->getIconIndex(); } std::string CGHeroInstance::getNameTranslated() const @@ -1126,15 +1136,15 @@ std::string CGHeroInstance::getClassNameTextID() const { if (isCampaignGem()) return "core.genrltxt.735"; - return type->heroClass->getNameTextID(); + return getHeroClass()->getNameTextID(); } std::string CGHeroInstance::getNameTextID() const { if (!nameCustomTextId.empty()) return nameCustomTextId; - if (type) - return type->getNameTextID(); + if (getHeroTypeID().hasValue()) + return getHeroType()->getNameTextID(); // FIXME: called by logging from some specialties (mods?) before type is set on deserialization // assert(0); @@ -1150,8 +1160,8 @@ std::string CGHeroInstance::getBiographyTextID() const { if (!biographyCustomTextId.empty()) return biographyCustomTextId; - if (type) - return type->getBiographyTextID(); + if (getHeroTypeID().hasValue()) + return getHeroType()->getBiographyTextID(); return ""; //for random hero } @@ -1349,11 +1359,11 @@ std::vector CGHeroInstance::getLevelUpProposedSecondarySkills(vs SecondarySkill selection; if (selectWisdom) - selection = type->heroClass->chooseSecSkill(intersect(options, wisdomList), rand); + selection = getHeroClass()->chooseSecSkill(intersect(options, wisdomList), rand); else if (selectSchool) - selection = type->heroClass->chooseSecSkill(intersect(options, schoolList), rand); + selection = getHeroClass()->chooseSecSkill(intersect(options, schoolList), rand); else - selection = type->heroClass->chooseSecSkill(options, rand); + selection = getHeroClass()->chooseSecSkill(options, rand); skills.push_back(selection); options.erase(selection); @@ -1384,7 +1394,7 @@ PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const { assert(gainsLevel()); const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL; - const auto & skillChances = isLowLevelHero ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; + const auto & skillChances = isLowLevelHero ? getHeroClass()->primarySkillLowLevel : getHeroClass()->primarySkillHighLevel; if (isCampaignYog()) { @@ -1524,25 +1534,15 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, BonusSubtypeID std::string CGHeroInstance::getHeroTypeName() const { if(ID == Obj::HERO || ID == Obj::PRISON) - { - if(type) - { - return type->getJsonKey(); - } - else - { - return getHeroType().toEntity(VLC)->getJsonKey(); - } - } + return getHeroType()->getJsonKey(); + return ""; } void CGHeroInstance::afterAddToMap(CMap * map) { if(ID != Obj::PRISON) - { map->heroesOnMap.emplace_back(this); - } } void CGHeroInstance::afterRemoveFromMap(CMap* map) { @@ -1729,8 +1729,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) if(!appearance) { // crossoverDeserialize - type = getHeroType().toHeroType(); - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClassID())->getTemplates().front(); } } @@ -1817,7 +1816,7 @@ bool CGHeroInstance::isCampaignYog() const if (!boost::starts_with(campaign, "DATA/YOG")) // "Birth of a Barbarian" return false; - if (getHeroType() != HeroTypeID::SOLMYR) // Yog (based on Solmyr) + if (getHeroTypeID() != HeroTypeID::SOLMYR) // Yog (based on Solmyr) return false; return true; @@ -1835,7 +1834,7 @@ bool CGHeroInstance::isCampaignGem() const if (!boost::starts_with(campaign, "DATA/GEM") && !boost::starts_with(campaign, "DATA/FINAL")) // "New Beginning" and "Unholy Alliance" return false; - if (getHeroType() != HeroTypeID::GEM) // Yog (based on Solmyr) + if (getHeroTypeID() != HeroTypeID::GEM) // Yog (based on Solmyr) return false; return true; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index d07885e70..ebb8261f8 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -72,7 +72,6 @@ public: ////////////////////////////////////////////////////////////////////////// - const CHero * type; TExpType exp; //experience points ui32 level; //current level of hero @@ -171,7 +170,7 @@ public: const IOwnableObject * asOwnable() const final; //INativeTerrainProvider - FactionID getFaction() const override; + FactionID getFactionID() const override; TerrainId getNativeTerrain() const override; int getLowestCreatureSpeed() const; si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day @@ -235,7 +234,11 @@ public: ////////////////////////////////////////////////////////////////////////// - HeroTypeID getHeroType() const; + const CHeroClass * getHeroClass() const; + HeroClassID getHeroClassID() const; + + const CHero * getHeroType() const; + HeroTypeID getHeroTypeID() const; void setHeroType(HeroTypeID type); void initHero(vstd::RNG & rand); @@ -352,7 +355,11 @@ public: h & skillsInfo; h & visitedTown; h & boat; - h & type; + if (h.version < Handler::Version::REMOVE_TOWN_PTR) + { + CHero * type = nullptr; + h & type; + } h & commander; h & visitedObjects; BONUS_TREE_DESERIALIZATION_FIX diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 614cd0477..50d9b8b35 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -45,7 +45,7 @@ int CGTownInstance::getSightRadius() const //returns sight distance for(const auto & bid : builtBuildings) { - auto height = town->buildings.at(bid)->height; + auto height = getTown()->buildings.at(bid)->height; if(ret < height) ret = height; } @@ -115,7 +115,7 @@ int CGTownInstance::mageGuildLevel() const int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present { - return town->hordeLvl.at(HID); + return getTown()->hordeLvl.at(HID); } int CGTownInstance::creatureGrowth(const int & level) const @@ -127,7 +127,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const { GrowthInfo ret; - if (level<0 || level >=town->creatures.size()) + if (level<0 || level >=getTown()->creatures.size()) return ret; if (creatures[level].second.empty()) return ret; //no dwelling @@ -151,11 +151,11 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const else if (hasBuilt(BuildingID::CITADEL)) ret.entries.emplace_back(subID, BuildingID::CITADEL, castleBonus = base / 2); - if(town->hordeLvl.at(0) == level)//horde 1 + if(getTown()->hordeLvl.at(0) == level)//horde 1 if(hasBuilt(BuildingID::HORDE_1)) ret.entries.emplace_back(subID, BuildingID::HORDE_1, creature->getHorde()); - if(town->hordeLvl.at(1) == level)//horde 2 + if(getTown()->hordeLvl.at(1) == level)//horde 2 if(hasBuilt(BuildingID::HORDE_2)) ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde()); @@ -209,11 +209,11 @@ int CGTownInstance::getDwellingBonus(const std::vector& creatureIds, TResources CGTownInstance::dailyIncome() const { TResources ret; - for(const auto & p : town->buildings) + for(const auto & p : getTown()->buildings) { BuildingID buildingUpgrade; - for(const auto & p2 : town->buildings) + for(const auto & p2 : getTown()->buildings) { if (p2.second->upgrade == p.first) { @@ -251,10 +251,10 @@ bool CGTownInstance::hasCapitol() const TownFortifications CGTownInstance::fortificationsLevel() const { - auto result = town->fortifications; + auto result = getTown()->fortifications; for (auto const & buildingID : builtBuildings) - result += town->buildings.at(buildingID)->fortifications; + result += getTown()->buildings.at(buildingID)->fortifications; if (result.wallsHealth == 0) return TownFortifications(); @@ -264,7 +264,6 @@ TownFortifications CGTownInstance::fortificationsLevel() const CGTownInstance::CGTownInstance(IGameCallback *cb): CGDwelling(cb), - town(nullptr), built(0), destroyed(0), identifier(0), @@ -379,17 +378,17 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const std::string CGTownInstance::getObjectName() const { - return getNameTranslated() + ", " + town->faction->getNameTranslated(); + return getNameTranslated() + ", " + getTown()->faction->getNameTranslated(); } bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const { - return town->getBuildingType(subId) != BuildingID::NONE; + return getTown()->getBuildingType(subId) != BuildingID::NONE; } void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand) { - for(const auto & kvp : town->buildings) + for(const auto & kvp : getTown()->buildings) { if(!kvp.second->rewardableObjectInfo.getParameters().isNull()) rewardableBuildings[kvp.first] = new TownRewardableBuildingInstance(this, kvp.second->bid, rand); @@ -457,8 +456,7 @@ void CGTownInstance::pickRandomObject(vstd::RNG & rand) assert(ID == Obj::TOWN); // just in case setType(ID, subID); - town = (*VLC->townh)[getFaction()]->town; - randomizeArmy(getFaction()); + randomizeArmy(getFactionID()); updateAppearance(); } @@ -467,19 +465,19 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures blockVisit = true; if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example - creatures.resize(town->creatures.size() + 1); + creatures.resize(getTown()->creatures.size() + 1); else - creatures.resize(town->creatures.size()); + creatures.resize(getTown()->creatures.size()); - for (int level = 0; level < town->creatures.size(); level++) + for (int level = 0; level < getTown()->creatures.size(); level++) { BuildingID buildID = BuildingID(BuildingID::getDwellingFromLevel(level, 0)); int upgradeNum = 0; - for (; town->buildings.count(buildID); upgradeNum++, BuildingID::advanceDwelling(buildID)) + for (; getTown()->buildings.count(buildID); upgradeNum++, BuildingID::advanceDwelling(buildID)) { - if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum) - creatures[level].second.push_back(town->creatures[level][upgradeNum]); + if (hasBuilt(buildID) && getTown()->creatures.at(level).size() > upgradeNum) + creatures[level].second.push_back(getTown()->creatures[level][upgradeNum]); } } initializeConfigurableBuildings(rand); @@ -623,9 +621,9 @@ void CGTownInstance::removeCapitols(const PlayerColor & owner) const if (hasCapitol()) // search if there's an older capitol { PlayerState* state = cb->gameState()->getPlayerState(owner); //get all towns owned by player - for (const auto & town : state->getTowns()) + for (const auto & otherTown : state->getTowns()) { - if (town != this && town->hasCapitol()) + if (otherTown != this && otherTown->hasCapitol()) { RazeStructures rs; rs.tid = id; @@ -648,7 +646,7 @@ void CGTownInstance::clearArmy() const BoatId CGTownInstance::getBoatType() const { - return town->faction->boatType; + return getTown()->faction->boatType; } int CGTownInstance::getMarketEfficiency() const @@ -703,7 +701,7 @@ void CGTownInstance::updateAppearance() std::string CGTownInstance::nodeName() const { - return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + getNameTranslated(); + return "Town (" + getTown()->faction->getNameTranslated() + ") of " + getNameTranslated(); } void CGTownInstance::deserializationFix() @@ -752,7 +750,7 @@ void CGTownInstance::recreateBuildingsBonuses() for(const auto & upgradeID : builtBuildings) { - const auto & upgrade = town->buildings.at(upgradeID); + const auto & upgrade = getTown()->buildings.at(upgradeID); if (upgrade->getBase() == bid && upgrade->upgradeReplacesBonuses) bonusesReplacedByUpgrade = true; } @@ -761,7 +759,7 @@ void CGTownInstance::recreateBuildingsBonuses() if (bonusesReplacedByUpgrade) continue; - auto building = town->buildings.at(bid); + auto building = getTown()->buildings.at(bid); if(building->buildingBonuses.empty()) continue; @@ -828,21 +826,6 @@ bool CGTownInstance::armedGarrison() const return !stacks.empty() || garrisonHero; } -const CTown * CGTownInstance::getTown() const -{ - if(ID == Obj::RANDOM_TOWN) - return VLC->townh->randomTown; - else - { - if(nullptr == town) - { - return (*VLC->townh)[getFaction()]->town; - } - else - return town; - } -} - int CGTownInstance::getTownLevel() const { // count all buildings that are not upgrades @@ -850,7 +833,7 @@ int CGTownInstance::getTownLevel() const for(const auto & bid : builtBuildings) { - if(town->buildings.at(bid)->upgrade == BuildingID::NONE) + if(getTown()->buildings.at(bid)->upgrade == BuildingID::NONE) level++; } return level; @@ -892,7 +875,7 @@ bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const { for(const auto & bid : builtBuildings) { - if(town->buildings.at(bid)->subId == buildingID) + if(getTown()->buildings.at(bid)->subId == buildingID) return true; } return false; @@ -905,7 +888,7 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) const { - if (townID == town->faction->getId() || townID == FactionID::ANY) + if (townID == getTown()->faction->getId() || townID == FactionID::ANY) return hasBuilt(buildingID); return false; } @@ -923,7 +906,7 @@ std::set CGTownInstance::availableModes() const std::set result; for (const auto & buildingID : builtBuildings) { - const auto * buildingPtr = town->buildings.at(buildingID).get(); + const auto * buildingPtr = getTown()->buildings.at(buildingID).get(); result.insert(buildingPtr->marketModes.begin(), buildingPtr->marketModes.end()); } @@ -950,8 +933,8 @@ std::set CGTownInstance::getBuildings() const TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const { - if (vstd::contains(town->buildings, buildingID)) - return town->buildings.at(buildingID)->resources; + if (vstd::contains(getTown()->buildings, buildingID)) + return getTown()->buildings.at(buildingID)->resources; else { logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), anchorPos().toString(), buildingID.toEnum()); @@ -962,7 +945,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & buildID, bool deep) const { - const CBuilding * building = town->buildings.at(buildID); + const CBuilding * building = getTown()->buildings.at(buildID); //TODO: find better solution to prevent infinite loops std::set processed; @@ -970,13 +953,13 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & std::function dependTest = [&](const BuildingID & id) -> CBuilding::TRequired::Variant { - if (town->buildings.count(id) == 0) + if (getTown()->buildings.count(id) == 0) { logMod->error("Invalid building ID %d in building dependencies!", id.getNum()); return CBuilding::TRequired::OperatorAll(); } - const CBuilding * build = town->buildings.at(id); + const CBuilding * build = getTown()->buildings.at(id); CBuilding::TRequired::OperatorAll requirements; if (!hasBuilt(id)) @@ -1001,7 +984,7 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & CBuilding::TRequired::OperatorAll requirements; if (building->upgrade != BuildingID::NONE) { - const CBuilding * upgr = town->buildings.at(building->upgrade); + const CBuilding * upgr = getTown()->buildings.at(building->upgrade); requirements.expressions.push_back(dependTest(upgr->bid)); processed.clear(); @@ -1151,14 +1134,27 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) } } -FactionID CGTownInstance::getFaction() const +const CFaction * CGTownInstance::getFaction() const { - return FactionID(subID.getNum()); + return getFactionID().toFaction(); +} + +const CTown * CGTownInstance::getTown() const +{ + if(ID == Obj::RANDOM_TOWN) + return VLC->townh->randomTown; + + return getFaction()->town; +} + +FactionID CGTownInstance::getFactionID() const +{ + return FactionID(subID.getNum()); } TerrainId CGTownInstance::getNativeTerrain() const { - return town->faction->getNativeTerrain(); + return getTown()->faction->getNativeTerrain(); } ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const @@ -1166,21 +1162,21 @@ ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const if (builtBuildings.count(building) == 0) return ArtifactID::NONE; - if (building == BuildingID::BLACKSMITH && town->warMachineDeprecated.hasValue()) - return town->warMachineDeprecated.toCreature()->warMachine; + if (building == BuildingID::BLACKSMITH && getTown()->warMachineDeprecated.hasValue()) + return getTown()->warMachineDeprecated.toCreature()->warMachine; - return town->buildings.at(building)->warMachine; + return getTown()->buildings.at(building)->warMachine; } bool CGTownInstance::isWarMachineAvailable(ArtifactID warMachine) const { for (auto const & buildingID : builtBuildings) - if (town->buildings.at(buildingID)->warMachine == warMachine) + if (getTown()->buildings.at(buildingID)->warMachine == warMachine) return true; if (builtBuildings.count(BuildingID::BLACKSMITH) && - town->warMachineDeprecated.hasValue() && - town->warMachineDeprecated.toCreature()->warMachine == warMachine) + getTown()->warMachineDeprecated.hasValue() && + getTown()->warMachineDeprecated.toCreature()->warMachine == warMachine) return true; return false; @@ -1200,7 +1196,7 @@ GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): co { MetaString formatter; formatter.appendRawString("%s %+d"); - formatter.replaceRawString((*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated()); + formatter.replaceRawString(FactionID(subID).toFaction()->town->buildings.at(building)->getNameTranslated()); formatter.replacePositiveNumber(count); description = formatter.toString(); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index f3b1f90fc..636f2bc5c 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -50,6 +50,7 @@ struct DLL_LINKAGE GrowthInfo class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader { + friend class CTownInstanceConstructor; std::string nameTextId; // name of town std::map convertOldBuildings(std::vector oldVector); @@ -59,7 +60,6 @@ public: enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; CTownAndVisitingHero townAndVis; - const CTown * town; si32 built; //how many buildings has been built this turn si32 destroyed; //how many buildings has been destroyed this turn ConstTransitivePtr garrisonHero, visitingHero; @@ -112,16 +112,21 @@ public: rewardableBuildings = convertOldBuildings(oldVector); } - if (h.saving) + if (h.version < Handler::Version::REMOVE_TOWN_PTR) { - CFaction * faction = town ? town->faction : nullptr; - h & faction; - } - else - { - CFaction * faction = nullptr; - h & faction; - town = faction ? faction->town : nullptr; + CTown * town = nullptr; + + if (h.saving) + { + CFaction * faction = town ? town->faction : nullptr; + h & faction; + } + else + { + CFaction * faction = nullptr; + h & faction; + town = faction ? faction->town : nullptr; + } } h & townAndVis; @@ -213,9 +218,10 @@ public: DamageRange getKeepDamageRange() const; const CTown * getTown() const; + const CFaction * getFaction() const; /// INativeTerrainProvider - FactionID getFaction() const override; + FactionID getFactionID() const override; TerrainId getNativeTerrain() const override; /// Returns ID of war machine that is produced by specified building or NONE if this is not built or if building does not produce war machines diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index b49e169ea..051349282 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -431,7 +431,7 @@ void CGSeerHut::setObjToKill() if(getCreatureToKill(true)) { - quest->stackToKill = getCreatureToKill(false)->getCreature(); + quest->stackToKill = getCreatureToKill(false)->getCreatureID(); assert(quest->stackToKill != CreatureID::NONE); quest->stackDirection = checkDirection(); } diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index cb8f2661b..2b485ef6d 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -73,14 +73,14 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance * void TownRewardableBuildingInstance::initObj(vstd::RNG & rand) { - assert(town && town->town); + assert(town && town->getTown()); configuration = generateConfiguration(rand); } Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const { Rewardable::Configuration result; - auto building = town->town->buildings.at(getBuildingType()); + auto building = town->getTown()->buildings.at(getBuildingType()); building->rewardableObjectInfo.configureObject(result, rand, cb); for(auto & rewardInfo : result.info) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index afd1d610d..57e3a4c24 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -302,7 +302,7 @@ void CMap::calculateGuardingGreaturePositions() CGHeroInstance * CMap::getHero(HeroTypeID heroID) { for(auto & elem : heroesOnMap) - if(elem->getHeroType() == heroID) + if(elem->getHeroTypeID() == heroID) return elem; return nullptr; } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index bfdb2a289..1fe7da29f 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -896,7 +896,7 @@ void CMapLoaderH3M::readPredefinedHeroes() } map->predefinedHeroes.emplace_back(hero); - logGlobal->debug("Map '%s': Hero predefined in map: %s", mapName, VLC->heroh->getById(hero->getHeroType())->getJsonKey()); + logGlobal->debug("Map '%s': Hero predefined in map: %s", mapName, hero->getHeroType()->getJsonKey()); } } @@ -913,7 +913,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) if(!hero->artifactsWorn.empty() || !hero->artifactsInBackpack.empty()) { - logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroType().getNum(), hero->anchorPos().toString()); + logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroTypeID().getNum(), hero->anchorPos().toString()); hero->artifactsInBackpack.clear(); while(!hero->artifactsWorn.empty()) @@ -1778,7 +1778,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec for(auto & elem : map->disposedHeroes) { - if(elem.heroId == object->getHeroType()) + if(elem.heroId == object->getHeroTypeID()) { object->nameCustomTextId = elem.name; object->customPortraitSource = elem.portrait; @@ -1788,7 +1788,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec bool hasName = reader->readBool(); if(hasName) - object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->getHeroType().getNum(), "name")); + object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->getHeroTypeID().getNum(), "name")); if(features.levelSOD) { @@ -1891,7 +1891,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec auto ps = object->getAllBonuses(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)), nullptr); if(ps->size()) { - logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroType().getNum() ); + logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroTypeID().getNum() ); for(const auto & b : *ps) object->removeBonus(b); } @@ -1904,7 +1904,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec } if (object->subID != MapObjectSubID()) - logGlobal->debug("Map '%s': Hero on map: %s at %s, owned by %s", mapName, VLC->heroh->getById(object->getHeroType())->getJsonKey(), mapPosition.toString(), object->getOwner().toString()); + logGlobal->debug("Map '%s': Hero on map: %s at %s, owned by %s", mapName, object->getHeroType()->getJsonKey(), mapPosition.toString(), object->getOwner().toString()); else logGlobal->debug("Map '%s': Hero on map: (random) at %s, owned by %s", mapName, mapPosition.toString(), object->getOwner().toString()); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index b1a68b0ec..0777defed 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -436,10 +436,8 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) if(hero->ID == Obj::HERO) { std::string temp; - if(hero->type) - temp = hero->type->getJsonKey(); - else - temp = hero->getHeroType().toEntity(VLC)->getJsonKey(); + if(hero->getHeroTypeID().hasValue()) + temp = hero->getHeroType()->getJsonKey(); handler.serializeString("type", temp); } @@ -1154,10 +1152,10 @@ void CMapLoaderJson::readObjects() auto * hero = dynamic_cast(object.get()); - if (debugHeroesOnMap.count(hero->getHeroType())) + if (debugHeroesOnMap.count(hero->getHeroTypeID())) logGlobal->error("Hero is already on the map at %s", hero->visitablePos().toString()); - debugHeroesOnMap.insert(hero->getHeroType()); + debugHeroesOnMap.insert(hero->getHeroTypeID()); } } diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 28c7a7ec9..6ea83ab8f 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1355,7 +1355,7 @@ void NewStructures::applyGs(CGameState *gs) for(const auto & id : bid) { - assert(t->town->buildings.at(id) != nullptr); + assert(t->getTown()->buildings.at(id) != nullptr); t->addBuilding(id); } t->updateAppearance(); @@ -1470,7 +1470,7 @@ void GiveHero::applyGs(CGameState *gs) auto oldVisitablePos = h->visitablePos(); gs->map->removeBlockVisTiles(h,true); - h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front(); + h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->getHeroClassID().getNum())->getTemplates().front(); h->setOwner(player); h->setMovementPoints(h->movementPointsLimit(true)); diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 41b7e7ee5..28f8fa178 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -292,7 +292,7 @@ TeleporterTilesVector CPathfinderHelper::getTeleportExits(const PathNodeInfo & s { auto * town = dynamic_cast(source.nodeObject); assert(town); - if (town && town->getFaction() == FactionID::INFERNO) + if (town && town->getFactionID() == FactionID::INFERNO) { /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo /// This may be handy if we allow to use teleportation to friendly towns diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index 563db49e1..384ffea92 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -173,10 +173,10 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const if(!players.empty() && !vstd::contains(players, hero->getOwner())) return false; - if(!heroes.empty() && !vstd::contains(heroes, hero->type->getId())) + if(!heroes.empty() && !vstd::contains(heroes, hero->getHeroTypeID())) return false; - if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->type->heroClass->getId())) + if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->getHeroClassID())) return false; diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index a15ef0937..c256a85c1 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -733,7 +733,7 @@ CGCreature * ObjectManager::chooseGuard(si32 strength, bool zoneGuard) continue; if(!cre->getAIValue()) //bug #2681 continue; - if(!vstd::contains(zone.getMonsterTypes(), cre->getFaction())) + if(!vstd::contains(zone.getMonsterTypes(), cre->getFactionID())) continue; if((static_cast(cre->getAIValue() * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < static_cast(cre->getAIValue()) * 100)) //at least one full monster. size between average size of given stack and 100 { diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index 0a3e412c4..1ab68779d 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -90,7 +90,7 @@ void TownPlacer::placeTowns(ObjectManager & manager) totalTowns++; //register MAIN town of zone only - map.registerZone(town->getFaction()); + map.registerZone(town->getFactionID()); if(player.isValidPlayer()) //configure info for owning player { @@ -213,7 +213,7 @@ void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player { //FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception //register MAIN town of zone - map.registerZone(town->getFaction()); + map.registerZone(town->getFactionID()); //first town in zone goes in the middle placeMainTown(manager, *town); } diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index eb1b3c60f..781458d43 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -51,7 +51,7 @@ void TreasurePlacer::process() // Add all native creatures for(auto const & cre : VLC->creh->objects) { - if(!cre->special && cre->getFaction() == zone.getTownType()) + if(!cre->special && cre->getFactionID() == zone.getTownType()) { creatures.push_back(cre.get()); } @@ -192,7 +192,7 @@ void TreasurePlacer::addPrisons() { // Hero can be used again auto* hero = dynamic_cast(obj); - prisonHeroPlacer->restoreDrawnHero(hero->getHeroType()); + prisonHeroPlacer->restoreDrawnHero(hero->getHeroTypeID()); }; oi.setTemplates(Obj::PRISON, 0, zone.getTerrainType()); @@ -236,9 +236,9 @@ void TreasurePlacer::addDwellings() continue; const auto * cre = creatures.front(); - if(cre->getFaction() == zone.getTownType()) + if(cre->getFactionID() == zone.getTownType()) { - auto nativeZonesCount = static_cast(map.getZoneCount(cre->getFaction())); + auto nativeZonesCount = static_cast(map.getZoneCount(cre->getFactionID())); ObjectInfo oi(dwellingType, secondaryID); setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID)); @@ -375,7 +375,7 @@ void TreasurePlacer::addPandoraBoxesWithCreatures() return obj; }; oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); - oi.value = static_cast(creature->getAIValue() * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())); + oi.value = static_cast(creature->getAIValue() * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFactionID())) / map.getTotalZoneCount())); oi.probability = 3; if(!oi.templates.empty()) addObjectToRandomPool(oi); @@ -555,7 +555,7 @@ void TreasurePlacer::addSeerHuts() oi.destroyObject = destroyObject; oi.probability = 3; oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); - oi.value = static_cast(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) - 4000) / 3); + oi.value = static_cast(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFactionID())) / map.getTotalZoneCount())) - 4000) / 3); if (oi.value > zone.getMaxTreasureValue()) { continue; diff --git a/lib/serializer/CSerializer.cpp b/lib/serializer/CSerializer.cpp index 23de9965e..f8311a95b 100644 --- a/lib/serializer/CSerializer.cpp +++ b/lib/serializer/CSerializer.cpp @@ -26,7 +26,7 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib) registerVectoredType(&gs->map->objects, [](const CGObjectInstance &obj){ return obj.id; }); registerVectoredType(&gs->map->allHeroes, - [](const CGHeroInstance &h){ return h.type->getId(); }); + [](const CGHeroInstance &h){ return h.getHeroType()->getId(); }); registerVectoredType(&gs->map->artInstances, [](const CArtifactInstance &artInst){ return artInst.getId(); }); registerVectoredType(&gs->map->quests, diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 3f7845cff..d0fe579e4 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -63,6 +63,7 @@ enum class ESerializationVersion : int32_t REGION_LABEL, // 864 - labels for campaign regions SPELL_RESEARCH, // 865 - spell research LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data + REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance - CURRENT = LOCAL_PLAYER_STATE_DATA + CURRENT = REMOVE_TOWN_PTR }; diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index 5be7c92fa..889644b88 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -88,7 +88,7 @@ void Moat::convertBonus(const Mechanics * m, std::vector & converted) con if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetFortifications().hasMoat) { - nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->town->buildings.at(BuildingID::CITADEL)->getUniqueTypeID()); + nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->getTown()->buildings.at(BuildingID::CITADEL)->getUniqueTypeID()); nb.source = BonusSource::TOWN_STRUCTURE; } else diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index 5f9c4d34b..e46cdc6da 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -79,7 +79,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const text.replaceNamePlural(elemental->creatureId()); - if(caster->type->gender == EHeroGender::FEMALE) + if(caster->gender == EHeroGender::FEMALE) text.replaceLocalString(EMetaText::GENERAL_TXT, 540); else text.replaceLocalString(EMetaText::GENERAL_TXT, 539); diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 30aa744a3..0fb5859ec 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -138,25 +138,11 @@ void Initializer::initialize(CGHeroInstance * o) o->subID = 0; o->tempOwner = PlayerColor::NEUTRAL; } - - if(o->ID == Obj::HERO) + + if(o->getHeroTypeID().hasValue()) { - for(auto const & t : VLC->heroh->objects) - { - if(t->heroClass->getId() == HeroClassID(o->subID)) - { - o->type = t.get(); - break; - } - } - } - - if(o->type) - { - // o->type = VLC->heroh->objects.at(o->subID); - - o->gender = o->type->gender; - o->randomizeArmy(o->type->heroClass->faction); + o->gender = o->getHeroType()->gender; + o->randomizeArmy(o->getFactionID()); } } @@ -304,7 +290,7 @@ void Inspector::updateProperties(CGHeroInstance * o) auto isPrison = o->ID == Obj::PRISON; addProperty("Owner", o->tempOwner, new OwnerDelegate(controller, isPrison), isPrison); //field is not editable for prison addProperty("Experience", o->exp, false); - addProperty("Hero class", o->type ? o->type->heroClass->getNameTranslated() : "", true); + addProperty("Hero class", o->getHeroClassID().hasValue() ? o->getHeroClass()->getNameTranslated() : "", true); { //Gender auto * delegate = new InspectorDelegate; @@ -319,20 +305,20 @@ void Inspector::updateProperties(CGHeroInstance * o) addProperty("Skills", PropertyEditorPlaceholder(), delegate, false); addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false); - if(o->type || o->ID == Obj::PRISON) + if(o->getHeroTypeID().hasValue() || o->ID == Obj::PRISON) { //Hero type auto * delegate = new InspectorDelegate; - for(int i = 0; i < VLC->heroh->objects.size(); ++i) + for(const auto & heroPtr : VLC->heroh->objects) { - if(controller.map()->allowedHeroes.count(HeroTypeID(i)) != 0) + if(controller.map()->allowedHeroes.count(heroPtr->getId()) != 0) { - if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex())) + if(o->ID == Obj::PRISON || heroPtr->heroClass->getIndex() == o->getHeroClassID()) { - delegate->options.push_back({QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str()), QVariant::fromValue(VLC->heroh->objects[i]->getId().getNum())}); + delegate->options.push_back({QObject::tr(heroPtr->getNameTranslated().c_str()), QVariant::fromValue(heroPtr->getIndex())}); } } } - addProperty("Hero type", o->type ? o->type->getNameTranslated() : "", delegate, false); + addProperty("Hero type", o->getHeroTypeID().hasValue() ? o->getHeroType()->getNameTranslated() : "", delegate, false); } } @@ -706,13 +692,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari for(auto const & t : VLC->heroh->objects) { if(t->getId() == value.toInt()) - { o->subID = value.toInt(); - o->type = t.get(); - } } - o->gender = o->type->gender; - o->randomizeArmy(o->type->heroClass->faction); + o->gender = o->getHeroType()->gender; + o->randomizeArmy(o->getHeroType()->heroClass->faction); updateProperties(); //updating other properties after change } } diff --git a/mapeditor/inspector/townbuildingswidget.cpp b/mapeditor/inspector/townbuildingswidget.cpp index a90bd654a..4bf95c5c8 100644 --- a/mapeditor/inspector/townbuildingswidget.cpp +++ b/mapeditor/inspector/townbuildingswidget.cpp @@ -331,7 +331,7 @@ void TownBuildingsDelegate::setEditorData(QWidget *editor, const QModelIndex &in { if(auto * ed = qobject_cast(editor)) { - auto * ctown = town.town; + auto * ctown = town.getTown(); if(!ctown) ctown = VLC->townh->randomTown; if(!ctown) diff --git a/mapeditor/inspector/towneventdialog.cpp b/mapeditor/inspector/towneventdialog.cpp index e56af191e..475c42edc 100644 --- a/mapeditor/inspector/towneventdialog.cpp +++ b/mapeditor/inspector/towneventdialog.cpp @@ -100,7 +100,7 @@ void TownEventDialog::initResources() void TownEventDialog::initBuildings() { - auto * ctown = town.town; + auto * ctown = town.getTown(); if (!ctown) ctown = VLC->townh->randomTown; if (!ctown) @@ -156,7 +156,7 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil void TownEventDialog::initCreatures() { auto creatures = params.value("creatures").toList(); - auto * ctown = town.town; + auto * ctown = town.getTown(); if (!ctown) ui->creaturesTable->setRowCount(GameConstants::CREATURES_PER_TOWN); else diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 5fc0c25ec..a30a90aa8 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -138,7 +138,7 @@ void MapController::repairMap(CMap * map) const // FIXME: How about custom scenarios where defeated hero cannot be hired again? - map->allowedHeroes.insert(nih->getHeroType()); + map->allowedHeroes.insert(nih->getHeroTypeID()); auto const & type = VLC->heroh->objects[nih->subID]; assert(type->heroClass); @@ -154,10 +154,6 @@ void MapController::repairMap(CMap * map) const nih->subTypeName = "prison"; nih->subID = 0; } - - if(obj->ID != Obj::RANDOM_HERO) - nih->type = type.get(); - if(nih->ID == Obj::HERO) //not prison nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); //fix spellbook diff --git a/mapeditor/playerparams.cpp b/mapeditor/playerparams.cpp index dc4cfee94..985c01d47 100644 --- a/mapeditor/playerparams.cpp +++ b/mapeditor/playerparams.cpp @@ -77,12 +77,10 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent) { if(auto town = dynamic_cast(controller.map()->objects[i].get())) { - auto * ctown = town->town; + auto * ctown = town->getTown(); if(!ctown) - { ctown = VLC->townh->randomTown; - town->town = ctown; - } + if(ctown && town->getOwner().getNum() == playerColor) { if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos) diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index fdab680b9..b4a98df30 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -122,13 +122,13 @@ std::set Validator::validate(const CMap * map) ++amountOfHeroes[ins->getOwner()]; } - if(ins->type) + if(ins->getHeroTypeID().hasValue()) { - if(map->allowedHeroes.count(ins->getHeroType()) == 0) - issues.insert({ tr("Hero %1 is prohibited by map settings").arg(ins->type->getNameTranslated().c_str()), false }); + if(map->allowedHeroes.count(ins->getHeroTypeID()) == 0) + issues.insert({ tr("Hero %1 is prohibited by map settings").arg(ins->getHeroType()->getNameTranslated().c_str()), false }); - if(!allHeroesOnMap.insert(ins->type).second) - issues.insert({ tr("Hero %1 has duplicate on map").arg(ins->type->getNameTranslated().c_str()), false }); + if(!allHeroesOnMap.insert(ins->getHeroType()).second) + issues.insert({ tr("Hero %1 has duplicate on map").arg(ins->getHeroType()->getNameTranslated().c_str()), false }); } else if(ins->ID != Obj::RANDOM_HERO) issues.insert({ tr("Hero %1 has an empty type and must be removed").arg(ins->instanceName.c_str()), true }); diff --git a/scripting/lua/api/Creature.cpp b/scripting/lua/api/Creature.cpp index 64c13c38b..5fca4601b 100644 --- a/scripting/lua/api/Creature.cpp +++ b/scripting/lua/api/Creature.cpp @@ -46,7 +46,7 @@ const std::vector CreatureProxy::REGISTER_CUSTOM = {"getLevel", LuaMethodWrapper::invoke, false}, {"getGrowth", LuaMethodWrapper::invoke, false}, {"getHorde", LuaMethodWrapper::invoke, false}, - {"getFaction", LuaMethodWrapper::invoke, false}, + {"getFactionID", LuaMethodWrapper::invoke, false}, {"getBaseAttack", LuaMethodWrapper::invoke, false}, {"getBaseDefense", LuaMethodWrapper::invoke, false}, diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 42e1961ab..1f9549fa0 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -162,7 +162,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero) hlu.player = hero->tempOwner; hlu.heroId = hero->id; hlu.primskill = primarySkill; - hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroType())); + hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroTypeID())); if (hlu.skills.size() == 0) { @@ -553,7 +553,7 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack for (auto & elem : gs->map->allHeroes) { if(elem) - heroPool->getHeroSkillsRandomGenerator(elem->getHeroType()); // init RMG seed + heroPool->getHeroSkillsRandomGenerator(elem->getHeroTypeID()); // init RMG seed } reinitScripting(); @@ -569,12 +569,12 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa return; } - if (forced || town->creatures.at(town->town->creatures.size()).second.empty())//we need to change creature + if (forced || town->creatures.at(town->getTown()->creatures.size()).second.empty())//we need to change creature { SetAvailableCreatures ssi; ssi.tid = town->id; ssi.creatures = town->creatures; - ssi.creatures[town->town->creatures.size()].second.clear();//remove old one + ssi.creatures[town->getTown()->creatures.size()].second.clear();//remove old one std::set availableCreatures; for (const auto & dwelling : p->getOwnedObjects()) @@ -590,13 +590,13 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa if (clear) { - ssi.creatures[town->town->creatures.size()].first = std::max(1, (creatureId.toEntity(VLC)->getGrowth())/2); + ssi.creatures[town->getTown()->creatures.size()].first = std::max(1, (creatureId.toEntity(VLC)->getGrowth())/2); } else { - ssi.creatures[town->town->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth(); + ssi.creatures[town->getTown()->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth(); } - ssi.creatures[town->town->creatures.size()].second.push_back(creatureId); + ssi.creatures[town->getTown()->creatures.size()].second.push_back(creatureId); sendAndApply(ssi); } } @@ -657,7 +657,7 @@ void CGameHandler::onNewTurn() PlayerColor player = t->tempOwner; if(t->hasBuilt(BuildingID::GRAIL) - && t->town->buildings.at(BuildingID::GRAIL)->height == CBuilding::HEIGHT_SKYSHIP) + && t->getTown()->buildings.at(BuildingID::GRAIL)->height == CBuilding::HEIGHT_SKYSHIP) { // Skyship, probably easier to handle same as Veil of darkness // do it every new day before veils @@ -1048,7 +1048,7 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui if (((h->getOwner() != t->getOwner()) && complain("Cannot teleport hero to another player")) - || (from->town->faction->getId() != t->town->faction->getId() + || (from->getFactionID() != t->getFactionID() && complain("Source town and destination town should belong to the same faction")) || ((!from || !from->hasBuilt(BuildingSubID::CASTLE_GATE)) @@ -1212,7 +1212,7 @@ void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vectorrewardableBuildings) { - if (!t->town->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first)) + if (!t->getTown()->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first)) buildingsToVisit.push_back(building.first); } @@ -2036,12 +2036,12 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, const CGTownInstance * t = getTown(tid); if(!t) COMPLAIN_RETF("No such town (ID=%s)!", tid); - if(!t->town->buildings.count(requestedID)) - COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->town->faction->getNameTranslated() % requestedID); + if(!t->getTown()->buildings.count(requestedID)) + COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->getFaction()->getNameTranslated() % requestedID); if(t->hasBuilt(requestedID)) - COMPLAIN_RETF("Building %s is already built in %s", t->town->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated()); + COMPLAIN_RETF("Building %s is already built in %s", t->getTown()->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated()); - const CBuilding * requestedBuilding = t->town->buildings.at(requestedID); + const CBuilding * requestedBuilding = t->getTown()->buildings.at(requestedID); //Vector with future list of built building and buildings in auto-mode that are not yet built. std::vector remainingAutoBuildings; @@ -2081,7 +2081,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, int level = BuildingID::getLevelFromDwelling(buildingID); int upgradeNumber = BuildingID::getUpgradedFromDwelling(buildingID); - if(upgradeNumber >= t->town->creatures.at(level).size()) + if(upgradeNumber >= t->getTown()->creatures.at(level).size()) { complain(boost::str(boost::format("Error encountered when building dwelling (bid=%s):" "no creature found (upgrade number %d, level %d!") @@ -2089,7 +2089,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, return; } - const CCreature * crea = t->town->creatures.at(level).at(upgradeNumber).toCreature(); + const CCreature * crea = t->getTown()->creatures.at(level).at(upgradeNumber).toCreature(); SetAvailableCreatures ssi; ssi.tid = t->id; @@ -2099,7 +2099,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, ssi.creatures[level].second.push_back(crea->getId()); sendAndApply(ssi); } - if(t->town->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING) + if(t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING) { setPortalDwelling(t); } @@ -2110,9 +2110,9 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, { auto isMageGuild = (buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1); auto isLibrary = isMageGuild ? false - : t->town->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY; + : t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY; - if(isMageGuild || isLibrary || (t->getFaction() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL)) + if(isMageGuild || isLibrary || (t->getFactionID() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL)) { if(t->visitingHero) giveSpells(t,t->visitingHero); @@ -2128,7 +2128,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID, }; //Init the vectors - for(auto & build : t->town->buildings) + for(auto & build : t->getTown()->buildings) { if(t->hasBuilt(build.first)) { @@ -2212,7 +2212,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) if(!t->hasBuilt(bid)) return false; - auto subID = t->town->buildings.at(bid)->subId; + auto subID = t->getTown()->buildings.at(bid)->subId; if(subID == BuildingSubID::EBuildingSubID::BANK) { @@ -2224,7 +2224,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid) return true; } - if (t->rewardableBuildings.count(bid) && t->visitingHero && t->town->buildings.at(bid)->manualHeroVisit) + if (t->rewardableBuildings.count(bid) && t->visitingHero && t->getTown()->buildings.at(bid)->manualHeroVisit) { std::vector buildingsToVisit; std::vector visitors; diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index fd26db5f5..44a50884a 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -47,8 +47,8 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player, // try to find "better" slot to overwrite // we want to avoid overwriting retreated heroes when tavern still has slot with random hero // as well as avoid overwriting surrendered heroes if we can overwrite retreated hero - auto roleLeft = heroesPool->getSlotRole(heroes[0]->getHeroType()); - auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroType()); + auto roleLeft = heroesPool->getSlotRole(heroes[0]->getHeroTypeID()); + auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroTypeID()); if (roleLeft > roleRight) return TavernHeroSlot::RANDOM; @@ -70,7 +70,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; - sah.hid = hero->getHeroType(); + sah.hid = hero->getHeroTypeID(); sah.replenishPoints = false; gameHandler->sendAndApply(sah); } @@ -82,9 +82,9 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns sah.slotID = selectSlotForRole(color, sah.roleID); sah.player = color; - sah.hid = hero->getHeroType(); + sah.hid = hero->getHeroTypeID(); sah.army.clearSlots(); - sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1); + sah.army.setCreature(SlotID(0), hero->getHeroType()->initialArmy.at(0).creature, 1); sah.replenishPoints = false; gameHandler->sendAndApply(sah); @@ -112,7 +112,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe if (newHero) { - sah.hid = newHero->getHeroType(); + sah.hid = newHero->getHeroTypeID(); if (giveArmy) { @@ -123,7 +123,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe { sah.roleID = TavernSlotRole::SINGLE_UNIT; sah.army.clearSlots(); - sah.army.setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1); + sah.army.setCreature(SlotID(0), newHero->getHeroType()->initialArmy[0].creature, 1); } } else @@ -208,7 +208,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy for(const auto & hero : recruitableHeroes) { - if(hero->getHeroType() == heroToRecruit) + if(hero->getHeroTypeID() == heroToRecruit) recruitedHero = hero; } @@ -221,7 +221,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy HeroRecruited hr; hr.tid = mapObject->id; - hr.hid = recruitedHero->getHeroType(); + hr.hid = recruitedHero->getHeroTypeID(); hr.player = player; hr.tile = recruitedHero->convertFromVisitablePos(targetPos ); if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat) @@ -258,14 +258,14 @@ std::vector HeroPoolProcessor::findAvailableClassesFor(const for(const auto & elem : heroesPool->unusedHeroesFromPool()) { - if (vstd::contains(result, elem.second->type->heroClass)) + if (vstd::contains(result, elem.second->getHeroClass())) continue; bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player); - bool heroClassBanned = elem.second->type->heroClass->tavernProbability(factionID) == 0; + bool heroClassBanned = elem.second->getHeroClass()->tavernProbability(factionID) == 0; if(heroAvailable && !heroClassBanned) - result.push_back(elem.second->type->heroClass); + result.push_back(elem.second->getHeroClass()); } return result; @@ -282,7 +282,7 @@ std::vector HeroPoolProcessor::findAvailableHeroesFor(const Pl assert(!vstd::contains(result, elem.second)); bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player); - bool heroClassMatches = elem.second->type->heroClass == heroClass; + bool heroClassMatches = elem.second->getHeroClass() == heroClass; if(heroAvailable && heroClassMatches) result.push_back(elem.second); @@ -318,7 +318,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo continue; bool hasSameClass = vstd::contains_if(currentTavern, [&](const CGHeroInstance * hero){ - return hero->type->heroClass == heroClass; + return hero->getHeroClass() == heroClass; }); if (hasSameClass) diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index a1c9c61b8..f87c380f4 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -95,10 +95,10 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town) // 1. Building exists in town (don't attempt to build Lvl 5 guild in Fortress // 2. Building was not built yet // othervice, silently ignore / skip it - if (town->town->buildings.count(i) && !town->hasBuilt(i)) + if (town->getTown()->buildings.count(i) && !town->hasBuilt(i)) { gameHandler->buildStructure(town->id, i, true); - iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), i)); + iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), i)); } } @@ -246,7 +246,7 @@ SetAvailableCreatures NewTurnProcessor::generateTownGrowth(const CGTownInstance sac.tid = t->id; sac.creatures = t->creatures; - for (int k=0; k < t->town->creatures.size(); k++) + for (int k=0; k < t->getTown()->creatures.size(); k++) { if (t->creatures.at(k).second.empty()) continue; @@ -345,7 +345,7 @@ void NewTurnProcessor::updateNeutralTownGarrison(const CGTownInstance * t, int c { const auto * creature = slot.second->type; - if (creature->getFaction() != t->getFaction()) + if (creature->getFactionID() != t->getFactionID()) continue; if (creature->getLevel() != tierToGrow) @@ -518,7 +518,7 @@ std::tuple NewTurnProcessor::pickWeekType(bool newMonth) { newMonster.second = VLC->creh->pickRandomMonster(gameHandler->getRandomGenerator()); } while (VLC->creh->objects[newMonster.second] && - (*VLC->townh)[VLC->creatures()->getById(newMonster.second)->getFaction()]->town == nullptr); // find first non neutral creature + (*VLC->townh)[VLC->creatures()->getById(newMonster.second)->getFactionID()]->town == nullptr); // find first non neutral creature return { EWeekType::BONUS_GROWTH, newMonster.second}; } diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 83183d478..6ebc78375 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -370,7 +370,7 @@ void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInst if (!town) return; - for (auto & build : town->town->buildings) + for (auto & build : town->getTown()->buildings) { if (!town->hasBuilt(build.first) && !build.second->getNameTranslated().empty() diff --git a/test/entity/CCreatureTest.cpp b/test/entity/CCreatureTest.cpp index 590754e73..3d963df09 100644 --- a/test/entity/CCreatureTest.cpp +++ b/test/entity/CCreatureTest.cpp @@ -100,7 +100,7 @@ TEST_F(CCreatureTest, DISABLED_JsonUpdate) EXPECT_EQ(subject->getFightValue(), 2420); EXPECT_EQ(subject->getLevel(), 6); - EXPECT_EQ(subject->getFaction().getNum(), 55); + EXPECT_EQ(subject->getFactionID().getNum(), 55); EXPECT_TRUE(subject->isDoubleWide()); } diff --git a/test/mock/mock_Creature.h b/test/mock/mock_Creature.h index 6dacd4815..cb51a595a 100644 --- a/test/mock/mock_Creature.h +++ b/test/mock/mock_Creature.h @@ -44,7 +44,7 @@ public: MOCK_CONST_METHOD0(getLevel, int32_t()); MOCK_CONST_METHOD0(getGrowth, int32_t()); MOCK_CONST_METHOD0(getHorde, int32_t()); - MOCK_CONST_METHOD0(getFaction, FactionID()); + MOCK_CONST_METHOD0(getFactionID, FactionID()); MOCK_CONST_METHOD0(getBaseAttack, int32_t()); MOCK_CONST_METHOD0(getBaseDefense, int32_t()); diff --git a/test/mock/mock_battle_Unit.h b/test/mock/mock_battle_Unit.h index 5f2b03bf0..7560bfdc8 100644 --- a/test/mock/mock_battle_Unit.h +++ b/test/mock/mock_battle_Unit.h @@ -82,7 +82,7 @@ public: MOCK_CONST_METHOD1(willMove, bool(int)); MOCK_CONST_METHOD1(waited, bool(int)); - MOCK_CONST_METHOD0(getFaction, FactionID()); + MOCK_CONST_METHOD0(getFactionID, FactionID()); MOCK_CONST_METHOD1(battleQueuePhase, battle::BattlePhases::Type(int)); From 77cd08d66a8f4487fa93d33dec8f6c2811820b9f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 12:31:06 +0000 Subject: [PATCH 303/726] Quick workaround for failing Android CI --- .github/workflows/github.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 99dcbd8b7..fc40fd23a 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -17,6 +17,7 @@ env: jobs: build: strategy: + fail-fast: false matrix: include: - platform: linux-qt6 From 15ad0440f7fc49aa616601d66f9907b7d8b04aa4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 12:38:32 +0000 Subject: [PATCH 304/726] Fix iOS build --- lib/networkPacks/SaveLocalState.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/networkPacks/SaveLocalState.h b/lib/networkPacks/SaveLocalState.h index f7a8ec21a..b6106fa47 100644 --- a/lib/networkPacks/SaveLocalState.h +++ b/lib/networkPacks/SaveLocalState.h @@ -13,6 +13,8 @@ #include "../json/JsonNode.h" +VCMI_LIB_NAMESPACE_BEGIN + struct DLL_LINKAGE SaveLocalState : public CPackForServer { JsonNode data; @@ -25,3 +27,5 @@ struct DLL_LINKAGE SaveLocalState : public CPackForServer h & data; } }; + +VCMI_LIB_NAMESPACE_END From da32b8b58f72175ddd821836c16447303b8a650e Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 10 Oct 2024 17:33:54 +0200 Subject: [PATCH 305/726] Restore compatibility with removal of getFaction() --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 2 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 010c8df78..aa7d4b23f 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -87,7 +87,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const score *= score / minScoreToHireMain; } score *= (hero->getArmyCost() + currentArmyValue); - if (hero->type->heroClass->faction == town->getFaction()) + if (hero->getFactionID() == town->getFactionID()) score *= 1.5; if (vstd::isAlmostZero(visitability)) score *= 30 * town->getTownLevel(); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 45824c9ca..713e36131 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1198,7 +1198,7 @@ void AINodeStorage::calculateTownPortal( } if (targetTown->visitingHero - && (targetTown->visitingHero.get()->getFaction() != actor->hero->getFaction() + && (targetTown->visitingHero.get()->getFactionID() != actor->hero->getFactionID() || targetTown->getUpperArmy()->stacksCount())) continue; From 341ef633a5a4bf9b8d6da595c120f295b4f73fe1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 16:05:50 +0000 Subject: [PATCH 306/726] Fixes missing names of saves and custom campaigns --- client/lobby/SelectionTab.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 71604a58d..8c72c087c 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -829,6 +829,7 @@ void SelectionTab::parseSaves(const std::unordered_set & files) { auto mapInfo = std::make_shared(); mapInfo->saveInit(file); + mapInfo->name = mapInfo->getNameForList(); // Filter out other game modes bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == EStartMode::CAMPAIGN; @@ -875,6 +876,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files auto info = std::make_shared(); //allItems[i].date = std::asctime(std::localtime(&files[i].date)); info->fileURI = file.getOriginalName(); + info->name = info->getNameForList(); info->campaignInit(); if(info->campaign) allItems.push_back(info); From 6adaffa2c2393311969cd8860c657713b3009968 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 10 Oct 2024 18:52:25 +0200 Subject: [PATCH 307/726] Update SelectionTab.cpp Fix for save-game-list having no names. --- client/lobby/SelectionTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 71604a58d..4a447922d 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -989,6 +989,6 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0); labelName->setMaxWidth(185); } - labelName->setText(info->name); + labelName->setText(info->getNameForList()); labelName->setColor(color); } From eb94c9f0bee4a7e165fa51539ee6800035c00455 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 10 Oct 2024 18:53:28 +0200 Subject: [PATCH 308/726] Update PriorityEvaluator.cpp AI will be more aggressive when defending their territory. And more aggressive means more willing to take losses while fighting. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 1ae243055..1e616b875 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1345,7 +1345,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) else { float score = 0; - float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25; + float maxWillingToLose = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0) ? 1 : 0.25; bool arriveNextWeek = false; if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7) From f3a6d4a93f80f32b30fa2591a78a2f5211dd372c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:01:25 +0200 Subject: [PATCH 309/726] copy name after init --- client/lobby/SelectionTab.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 8c72c087c..2cf811129 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -874,10 +874,9 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files for(auto & file : files) { auto info = std::make_shared(); - //allItems[i].date = std::asctime(std::localtime(&files[i].date)); info->fileURI = file.getOriginalName(); - info->name = info->getNameForList(); info->campaignInit(); + info->name = info->getNameForList(); if(info->campaign) allItems.push_back(info); } From dfff1eae261c651ea47af37fd16f488ff412120f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 21:16:41 +0000 Subject: [PATCH 310/726] Fix crash on losing a hero --- client/PlayerLocalState.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index 69e8b1b5e..519c39c94 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -219,7 +219,12 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) if (hero == currentSelection) { auto const * nextHero = getNextWanderingHero(hero); - setSelection(nextHero); + if (nextHero) + setSelection(nextHero); + else if (!ownedTowns.empty()) + setSelection(ownedTowns.front()); + else + setSelection(nullptr); } vstd::erase(wanderingHeroes, hero); @@ -334,7 +339,8 @@ void PlayerLocalState::serialize(JsonNode & dest) const dest["spellbook"]["tabBattle"].Integer() = spellbookSettings.spellbookLastTabBattle; dest["spellbook"]["tabAdvmap"].Integer() = spellbookSettings.spellbookLastTabAdvmap; - dest["currentSelection"].Integer() = currentSelection->id; + if (currentSelection) + dest["currentSelection"].Integer() = currentSelection->id; } void PlayerLocalState::deserialize(const JsonNode & source) From 6bed497f2c706b594bb2d50bb0094afa3d245705 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 21:18:43 +0000 Subject: [PATCH 311/726] Fix mod validation reporting failure for well-formed mods --- lib/json/JsonUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 6ede7be82..0388d03e3 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -241,6 +241,7 @@ JsonNode JsonUtils::assembleFromFiles(const JsonNode & files, bool & isValid) } else { + isValid = true; return files; } } From d5663c8948d9301351a13a4330463bd618dcb663 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 21:19:56 +0000 Subject: [PATCH 312/726] Fix broken layout in "Mod Validation" option --- launcher/settingsView/csettingsview_moc.ui | 52 +++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index a974b5209..10ac9d634 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -47,7 +47,7 @@ 0 - -126 + -797 729 1503 @@ -261,31 +261,6 @@ - - - - true - - - - 0 - 0 - - - - Basic - - - true - - - false - - - buttonGroupValidation - - - @@ -1449,6 +1424,31 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use + + + + true + + + + 0 + 0 + + + + Basic + + + true + + + false + + + buttonGroupValidation + + + From b56b6c14968eb244697a82a33e18993c2c025de1 Mon Sep 17 00:00:00 2001 From: heroesiiifan <77574150+heroesiiifan@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:44:08 +0000 Subject: [PATCH 313/726] Update campaignMedia.json --- config/campaignMedia.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/campaignMedia.json b/config/campaignMedia.json index c54185837..c0a78ce61 100644 --- a/config/campaignMedia.json +++ b/config/campaignMedia.json @@ -70,7 +70,7 @@ //Playing with Fire "H3ABpf1.smk", //PlayingWithFire_a "H3ABpf2.smk", //PlayingWithFire_b - "3ABpf3.smk", //PlayingWithFire_c + "H3ABpf3.smk", //PlayingWithFire_c "H3ABpf4.smk", //PlayingWithFire_end //Shadow of Death Campaigns //Birth of a Barbarian From cb34b7ab507d09ed8b4d6e3eafe284e238e89c5f Mon Sep 17 00:00:00 2001 From: altiereslima Date: Thu, 10 Oct 2024 19:58:42 -0300 Subject: [PATCH 314/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 7e1a5c174..d8388fe1f 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -61,6 +61,13 @@ "vcmi.spellBook.search" : "Procurar...", + "vcmi.spellResearch.canNotAfford" : "Você não pode se dar ao luxo de substituir {%SPELL1} por {%SPELL2}. Mas você ainda pode descartar este feitiço e continuar a pesquisa de feitiços.", + "vcmi.spellResearch.comeAgain" : "A pesquisa já foi realizada hoje. Volte amanhã.", + "vcmi.spellResearch.pay" : "Gostaria de substituir {%SPELL1} por {%SPELL2}? Ou descartar este feitiço e continuar a pesquisa de feitiços?", + "vcmi.spellResearch.research" : "Pesquisar este Feitiço", + "vcmi.spellResearch.skip" : "Pular este Feitiço", + "vcmi.spellResearch.abort" : "Abortar", + "vcmi.mainMenu.serverConnecting" : "Conectando...", "vcmi.mainMenu.serverAddressEnter" : "Insira o endereço:", "vcmi.mainMenu.serverConnectionFailed" : "Falha ao conectar", @@ -343,6 +350,12 @@ "vcmi.heroWindow.openCommander.help" : "Mostra detalhes sobre o comandante deste herói.", "vcmi.heroWindow.openBackpack.hover" : "Abrir janela da mochila de artefatos", "vcmi.heroWindow.openBackpack.help" : "Abre a janela que facilita o gerenciamento da mochila de artefatos.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Ordenar por custo", + "vcmi.heroWindow.sortBackpackByCost.help" : "Ordenar artefatos na mochila por custo.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Ordenar por espaço", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Ordenar artefatos na mochila por espaço equipado.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Ordenar por classe", + "vcmi.heroWindow.sortBackpackByClass.help" : "Ordenar artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia", "vcmi.tavernWindow.inviteHero" : "Convidar herói", From a8e84c55f673d60add8531ac8500ee9b3c7fe860 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 10 Oct 2024 20:31:11 +0000 Subject: [PATCH 315/726] Fix some of the new warnings from sonarcloud --- AI/BattleAI/AttackPossibility.cpp | 2 +- AI/Nullkiller/AIGateway.cpp | 4 +- .../Pathfinding/AIPathfinderConfig.cpp | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- AI/Nullkiller/Pathfinding/Actors.h | 2 +- client/Client.h | 2 +- client/PlayerLocalState.cpp | 2 +- client/PlayerLocalState.h | 2 +- client/media/CVideoHandler.cpp | 2 +- client/render/AssetGenerator.cpp | 2 +- client/renderSDL/CTrueTypeFont.cpp | 16 +++---- client/widgets/TextControls.cpp | 16 +++---- client/windows/CCastleInterface.cpp | 44 +++++++++---------- client/windows/GUIClasses.cpp | 2 +- client/windows/GUIClasses.h | 2 +- client/windows/InfoWindows.cpp | 5 --- client/windows/InfoWindows.h | 1 - lib/CPlayerState.cpp | 2 +- lib/IGameCallback.h | 2 +- lib/json/JsonValidator.cpp | 2 +- lib/modding/ContentTypeHandler.cpp | 4 +- lib/texts/TextLocalizationContainer.cpp | 8 ++-- server/CGameHandler.cpp | 2 +- server/CGameHandler.h | 2 +- server/CVCMIServer.cpp | 15 +++---- server/queries/VisitQueries.cpp | 3 +- test/mock/mock_IGameCallback.h | 2 +- 27 files changed, 66 insertions(+), 84 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index c22b7ccad..c55de09b2 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -58,7 +58,7 @@ void DamageCache::buildObstacleDamageCache(std::shared_ptr hb, return u->alive() && !u->isTurret() && u->getPosition().isValid(); }); - std::shared_ptr inner = std::make_shared(hb->env, hb); + auto inner = std::make_shared(hb->env, hb); for(auto stack : stacks) { diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 99172b213..d8d5e8aed 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -649,12 +649,12 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vectordangerEvaluator->evaluateDanger(target, hero.get()); auto ratio = static_cast(danger) / hero->getTotalStrength(); - answer = 1; + answer = true; if(topObj->id != goalObjectID && nullkiller->dangerEvaluator->evaluateDanger(topObj) > 0) { // no if we do not aim to visit this object - answer = 0; + answer = false; } logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name(), ratio); diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index 98bcddcec..7bb43fabb 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -13,7 +13,7 @@ #include "Rules/AIMovementAfterDestinationRule.h" #include "Rules/AIMovementToDestinationRule.h" #include "Rules/AIPreviousNodeRule.h" -#include "../Engine//Nullkiller.h" +#include "../Engine/Nullkiller.h" #include "../../../lib/pathfinder/CPathfinder.h" diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index ae6b6446e..2c1a35ec0 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -182,7 +182,7 @@ ExchangeResult HeroActor::tryExchangeNoLock(const ChainActor * specialActor, con return &actor == specialActor; }); - result.actor = &(dynamic_cast(result.actor)->specialActors[index]); + result.actor = &(dynamic_cast(result.actor)->specialActors.at(index)); return result; } diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index 4451bda24..1f653fbd3 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -113,7 +113,7 @@ public: static const int SPECIAL_ACTORS_COUNT = 7; private: - ChainActor specialActors[SPECIAL_ACTORS_COUNT]; + std::array specialActors; std::unique_ptr exchangeMap; void setupSpecialActors(); diff --git a/client/Client.h b/client/Client.h index f8c7eaf12..ce1276b06 100644 --- a/client/Client.h +++ b/client/Client.h @@ -158,7 +158,7 @@ public: friend class CBattleCallback; //handling players actions void changeSpells(const CGHeroInstance * hero, bool give, const std::set & spells) override {}; - void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override {}; + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector & spells, bool accepted) override {}; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}; void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {}; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {}; diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index 69e8b1b5e..28d83fff4 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -24,7 +24,7 @@ PlayerLocalState::PlayerLocalState(CPlayerInterface & owner) { } -const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings() +const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings() const { return spellbookSettings; } diff --git a/client/PlayerLocalState.h b/client/PlayerLocalState.h index 0cfda1f7a..3372b6052 100644 --- a/client/PlayerLocalState.h +++ b/client/PlayerLocalState.h @@ -55,7 +55,7 @@ public: void setHeroAsleep(const CGHeroInstance * hero); void setHeroAwaken(const CGHeroInstance * hero); - const PlayerSpellbookSetting & getSpellbookSettings(); + const PlayerSpellbookSetting & getSpellbookSettings() const; void setSpellbookSettings(const PlayerSpellbookSetting & newSettings); const std::vector & getOwnedTowns(); diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index b13c2dfc0..b028e8c57 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -545,7 +545,7 @@ std::pair, si64> CAudioInstance::extractAudio(const Vide frameSamplesBuffer.resize(std::max(frameSamplesBuffer.size(), bytesToRead)); uint8_t * frameSamplesPtr = frameSamplesBuffer.data(); - int result = swr_convert(swr_ctx, &frameSamplesPtr, frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples); + int result = swr_convert(swr_ctx, &frameSamplesPtr, frame->nb_samples, const_cast(frame->data), frame->nb_samples); if (result < 0) throwFFmpegError(result); diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 3d0d346e2..d887528e0 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -158,7 +158,7 @@ void AssetGenerator::createPlayerColoredBackground(const PlayerColor & player) assert(player.isValidPlayer()); if (!player.isValidPlayer()) { - logGlobal->error("Unable to colorize to invalid player color %d!", static_cast(player.getNum())); + logGlobal->error("Unable to colorize to invalid player color %d!", player.getNum()); return; } diff --git a/client/renderSDL/CTrueTypeFont.cpp b/client/renderSDL/CTrueTypeFont.cpp index dcf3661d6..ddff8347e 100644 --- a/client/renderSDL/CTrueTypeFont.cpp +++ b/client/renderSDL/CTrueTypeFont.cpp @@ -64,9 +64,9 @@ int CTrueTypeFont::getFontStyle(const JsonNode &config) const CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig): data(loadData(fontConfig)), font(loadFont(fontConfig), TTF_CloseFont), - dropShadow(!fontConfig["noShadow"].Bool()), + blended(true), outline(fontConfig["outline"].Bool()), - blended(true) + dropShadow(!fontConfig["noShadow"].Bool()) { assert(font); @@ -95,14 +95,14 @@ size_t CTrueTypeFont::getLineHeightScaled() const return TTF_FontHeight(font.get()); } -size_t CTrueTypeFont::getGlyphWidthScaled(const char *data) const +size_t CTrueTypeFont::getGlyphWidthScaled(const char *text) const { - return getStringWidthScaled(std::string(data, TextOperations::getUnicodeCharacterSize(*data))); + return getStringWidthScaled(std::string(text, TextOperations::getUnicodeCharacterSize(*text))); } -bool CTrueTypeFont::canRepresentCharacter(const char * data) const +bool CTrueTypeFont::canRepresentCharacter(const char * text) const { - uint32_t codepoint = TextOperations::getUnicodeCodepoint(data, TextOperations::getUnicodeCharacterSize(*data)); + uint32_t codepoint = TextOperations::getUnicodeCodepoint(text, TextOperations::getUnicodeCharacterSize(*text)); #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) return TTF_GlyphIsProvided32(font.get(), codepoint); #elif SDL_TTF_VERSION_ATLEAST(2, 0, 12) @@ -114,10 +114,10 @@ bool CTrueTypeFont::canRepresentCharacter(const char * data) const #endif } -size_t CTrueTypeFont::getStringWidthScaled(const std::string & data) const +size_t CTrueTypeFont::getStringWidthScaled(const std::string & text) const { int width; - TTF_SizeUTF8(font.get(), data.c_str(), &width, nullptr); + TTF_SizeUTF8(font.get(), text.c_str(), &width, nullptr); return width; } diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 8975fd469..2fa440955 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -309,7 +309,7 @@ void CMultiLineLabel::splitText(const std::string & Txt, bool redrawAfter) lines.clear(); const auto & fontPtr = GH.renderHandler().loadFont(font); - int lineHeight = static_cast(fontPtr->getLineHeight()); + int lineHeight = fontPtr->getLineHeight(); lines = CMessage::breakText(Txt, pos.w, font); @@ -330,16 +330,16 @@ Rect CMultiLineLabel::getTextLocation() return pos; const auto & fontPtr = GH.renderHandler().loadFont(font); - Point textSize(pos.w, fontPtr->getLineHeight() * (int)lines.size()); - Point textOffset(pos.w - textSize.x, pos.h - textSize.y); + Point textSizeComputed(pos.w, fontPtr->getLineHeight() * lines.size()); //FIXME: how is this different from textSize member? + Point textOffset(pos.w - textSizeComputed.x, pos.h - textSizeComputed.y); switch(alignment) { - case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSize); - case ETextAlignment::TOPCENTER: return Rect(pos.topLeft(), textSize); - case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize); - case ETextAlignment::CENTERRIGHT: return Rect(pos.topLeft() + Point(textOffset.x, textOffset.y / 2), textSize); - case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize); + case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSizeComputed); + case ETextAlignment::TOPCENTER: return Rect(pos.topLeft(), textSizeComputed); + case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSizeComputed); + case ETextAlignment::CENTERRIGHT: return Rect(pos.topLeft() + Point(textOffset.x, textOffset.y / 2), textSizeComputed); + case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSizeComputed); } assert(0); return Rect(); diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index aea486688..28984167b 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2007,10 +2007,10 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID) const CGTownInstance * town = LOCPLINT->cb->getTown(townId); - for(size_t i=0; igetTown()->mageLevel; i++) + for(uint32_t i=0; igetTown()->mageLevel; i++) { - size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm? - for(size_t j=0; jspellsAtLevel(i+1,false); //spell at level with -1 hmmm? + for(uint32_t j=0; jmageGuildLevel() && town->spells[i].size()>j) spells.push_back(std::make_shared(positions[i][j], town->spells[i][j].toSpell(), townId)); @@ -2063,30 +2063,26 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) resComps.push_back(std::make_shared(ComponentType::RESOURCE, i->resType, i->resVal, CComponent::ESize::medium)); } - auto showSpellResearchDialog = [this, resComps, town, cost, newSpell](){ - std::vector>> pom; - for(int i = 0; i < 3; i++) - pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr); + std::vector>> pom; + for(int i = 0; i < 3; i++) + pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr); - auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); - boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated()); - boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated()); - auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); + auto text = CGI->generaltexth->translate(LOCPLINT->cb->getResourceAmount().canAfford(cost) ? "vcmi.spellResearch.pay" : "vcmi.spellResearch.canNotAfford"); + boost::replace_first(text, "%SPELL1", spell->id.toSpell()->getNameTranslated()); + boost::replace_first(text, "%SPELL2", newSpell.toSpell()->getNameTranslated()); + auto temp = std::make_shared(text, LOCPLINT->playerID, resComps, pom); - temp->buttons[0]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/accept"))); - temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); - temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); - temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); - temp->buttons[1]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/reroll"))); - temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); - temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); - temp->buttons[2]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/close"))); - temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); + temp->buttons[0]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/accept"))); + temp->buttons[0]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, true); }); + temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.research")); }); + temp->buttons[0]->setEnabled(LOCPLINT->cb->getResourceAmount().canAfford(cost)); + temp->buttons[1]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/reroll"))); + temp->buttons[1]->addCallback([this, town](){ LOCPLINT->cb->spellResearch(town, spell->id, false); }); + temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.skip")); }); + temp->buttons[2]->setOverlay(std::make_shared(ImagePath::builtin("spellResearch/close"))); + temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(CGI->generaltexth->translate("vcmi.spellResearch.abort")); }); - GH.windows().pushWindow(temp); - }; - - showSpellResearchDialog(); + GH.windows().pushWindow(temp); } else LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared(ComponentType::SPELL, spell->id)); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 46406d4d4..e8d89dfc4 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1611,7 +1611,7 @@ void CObjectListWindow::keyPressed(EShortcut key) changeSelection(sel); } -VideoWindow::VideoWindow(VideoPath video, ImagePath rim, bool showBackground, float scaleFactor, std::function closeCb) +VideoWindow::VideoWindow(const VideoPath & video, const ImagePath & rim, bool showBackground, float scaleFactor, const std::function & closeCb) : CWindowObject(BORDERED | SHADOW_DISABLED | NEEDS_ANIMATED_BACKGROUND), closeCb(closeCb) { OBJECT_CONSTRUCTION; diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 5e58ad6d2..883c812db 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -513,7 +513,7 @@ class VideoWindow : public CWindowObject void exit(bool skipped); public: - VideoWindow(VideoPath video, ImagePath rim, bool showBackground, float scaleFactor, std::function closeCb); + VideoWindow(const VideoPath & video, const ImagePath & rim, bool showBackground, float scaleFactor, const std::function & closeCb); void clickPressed(const Point & cursorPosition) override; void keyPressed(EShortcut key) override; diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index cabeeef13..2719c08d5 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -187,11 +187,6 @@ bool CRClickPopup::isPopupWindow() const return true; } -void CRClickPopup::close() -{ - WindowBase::close(); -} - void CRClickPopup::createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo & comps) { PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index a06416934..81b661339 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -64,7 +64,6 @@ public: class CRClickPopup : public WindowBase { public: - void close() override; bool isPopupWindow() const override; static std::shared_ptr createCustomInfoWindow(Point position, const CGObjectInstance * specific); diff --git a/lib/CPlayerState.cpp b/lib/CPlayerState.cpp index 2660377c6..069be91c1 100644 --- a/lib/CPlayerState.cpp +++ b/lib/CPlayerState.cpp @@ -22,9 +22,9 @@ VCMI_LIB_NAMESPACE_BEGIN PlayerState::PlayerState() : color(-1) - , playerLocalSettings(std::make_unique()) , human(false) , cheated(false) + , playerLocalSettings(std::make_unique()) , enteredWinningCheatCode(false) , enteredLosingCheatCode(false) , status(EPlayerStatus::INGAME) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index fc1f104c3..1de95812d 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -94,7 +94,7 @@ public: virtual void showInfoDialog(InfoWindow * iw) = 0; virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells)=0; - virtual void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted)=0; + virtual void setResearchedSpells(const CGTownInstance * town, int level, const std::vector & spells, bool accepted)=0; virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0; virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; virtual void setOwner(const CGObjectInstance * objid, PlayerColor owner)=0; diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index 0114f113d..3cb89846f 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -74,7 +74,7 @@ static int getLevenshteinDistance(const std::string & s, const std::string & t) /// Searches for keys similar to 'target' in 'candidates' map /// Returns closest match or empty string if no suitable candidates are found -static std::string findClosestMatch(JsonMap candidates, std::string target) +static std::string findClosestMatch(const JsonMap & candidates, const std::string & target) { // Maximum distance at which we can consider strings to be similar // If strings have more different symbols than this number then it is not a typo, but a completely different word diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 0a9acb0f4..efe2feeda 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -197,8 +197,8 @@ void ContentTypeHandler::afterLoadFinalization() std::set conflictingMods; std::set resolvedConflicts; - for (auto const & conflictModData : conflictModData.Struct()) - conflictingMods.insert(conflictModData.first); + for (auto const & conflictModEntry: conflictModData.Struct()) + conflictingMods.insert(conflictModEntry.first); for (auto const & modID : conflictingMods) resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); diff --git a/lib/texts/TextLocalizationContainer.cpp b/lib/texts/TextLocalizationContainer.cpp index f45898cf7..1e446defc 100644 --- a/lib/texts/TextLocalizationContainer.cpp +++ b/lib/texts/TextLocalizationContainer.cpp @@ -152,11 +152,9 @@ void TextLocalizationContainer::exportAllTexts(std::map spells, bool accepted) +void CGameHandler::setResearchedSpells(const CGTownInstance * town, int level, const std::vector & spells, bool accepted) { SetResearchedSpells cs; cs.tid = town->id; diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 4448cbd22..9d896f758 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -107,7 +107,7 @@ public: //from IGameCallback //do sth void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override; - void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override; + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector & spells, bool accepted) override; bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override; void setOwner(const CGObjectInstance * obj, PlayerColor owner) override; void giveExperience(const CGHeroInstance * hero, TExpType val) override; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index c0753d2a1..7eda3a733 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -299,17 +299,12 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & con std::shared_ptr c = findConnection(connection); // player may have already disconnected via clientDisconnected call - if (c) + if (c && gh && getState() == EServerState::GAMEPLAY) { - //clientDisconnected(c); - - if(gh && getState() == EServerState::GAMEPLAY) - { - LobbyClientDisconnected lcd; - lcd.c = c; - lcd.clientId = c->connectionID; - handleReceivedPack(lcd); - } + LobbyClientDisconnected lcd; + lcd.c = c; + lcd.clientId = c->connectionID; + handleReceivedPack(lcd); } } diff --git a/server/queries/VisitQueries.cpp b/server/queries/VisitQueries.cpp index 36ff95291..f5f34fbb3 100644 --- a/server/queries/VisitQueries.cpp +++ b/server/queries/VisitQueries.cpp @@ -50,7 +50,6 @@ void MapObjectVisitQuery::onRemoval(PlayerColor color) { gh->objectVisitEnded(visitingHero, players.front()); - //TODO or should it be destructor? //Can object visit affect 2 players and what would be desired behavior? if(removeObjectAfterVisit) gh->removeObject(visitedObject, color); @@ -78,7 +77,7 @@ void TownBuildingVisitQuery::onAdded(PlayerColor color) while (!visitedBuilding.empty() && owner->topQuery(color).get() == this) { visitingHero = visitedBuilding.back().hero; - auto * building = visitedTown->rewardableBuildings.at(visitedBuilding.back().building); + const auto * building = visitedTown->rewardableBuildings.at(visitedBuilding.back().building); building->onHeroVisit(visitingHero); visitedBuilding.pop_back(); } diff --git a/test/mock/mock_IGameCallback.h b/test/mock/mock_IGameCallback.h index af5dd1171..3ae574345 100644 --- a/test/mock/mock_IGameCallback.h +++ b/test/mock/mock_IGameCallback.h @@ -44,7 +44,7 @@ public: void showInfoDialog(InfoWindow * iw) override {} void changeSpells(const CGHeroInstance * hero, bool give, const std::set &spells) override {} - void setResearchedSpells(const CGTownInstance * town, int level, const std::vector spells, bool accepted) override {} + void setResearchedSpells(const CGTownInstance * town, int level, const std::vector & spells, bool accepted) override {} bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;} void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {} void setOwner(const CGObjectInstance * objid, PlayerColor owner) override {} From 0c6233c77f74027876744f922ef0158333910e3a Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:02:30 +0200 Subject: [PATCH 316/726] Update swedish.json Added new code and trimmed some texts. --- Mods/vcmi/config/vcmi/swedish.json | 239 +++++++++++++++-------------- 1 file changed, 126 insertions(+), 113 deletions(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index 57f0bae33..e9145d40f 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -40,12 +40,12 @@ "vcmi.heroOverview.secondarySkills" : "Sekundärförmågor", "vcmi.heroOverview.spells" : "Trollformler", - "vcmi.radialWheel.mergeSameUnit" : "Slå samman samma varelser", + "vcmi.radialWheel.mergeSameUnit" : "Slå samman varelser av samma sort", "vcmi.radialWheel.fillSingleUnit" : "Fyll på med enstaka varelser", "vcmi.radialWheel.splitSingleUnit" : "Dela av en enda varelse", "vcmi.radialWheel.splitUnitEqually" : "Dela upp varelser lika", "vcmi.radialWheel.moveUnit" : "Flytta varelser till en annan armé", - "vcmi.radialWheel.splitUnit" : "Dela upp varelse till en annan ruta", + "vcmi.radialWheel.splitUnit" : "Dela upp varelseförband till en annan ruta", "vcmi.radialWheel.heroGetArmy" : "Hämta armé från annan hjälte", "vcmi.radialWheel.heroSwapArmy" : "Byt armé med annan hjälte", @@ -54,13 +54,20 @@ "vcmi.radialWheel.heroSwapArtifacts" : "Byt artefakter med annan hjälte", "vcmi.radialWheel.heroDismiss" : "Avfärda hjälten", - "vcmi.radialWheel.moveTop" : "Flytta till toppen", + "vcmi.radialWheel.moveTop" : "Flytta längst upp", "vcmi.radialWheel.moveUp" : "Flytta upp", "vcmi.radialWheel.moveDown" : "Flytta nedåt", - "vcmi.radialWheel.moveBottom" : "Flytta till botten", + "vcmi.radialWheel.moveBottom" : "Flytta längst ner", "vcmi.spellBook.search" : "sök...", + "vcmi.spellResearch.canNotAfford" : "Du har inte råd att byta ut '{%SPELL1}' med '{%SPELL2}'. Du kan fortfarande göra dig av med den här trollformeln och forska vidare.", + "vcmi.spellResearch.comeAgain" : "Forskningen har redan gjorts idag. Kom tillbaka imorgon.", + "vcmi.spellResearch.pay" : "Vill du byta ut '{%SPELL1}' med '{%SPELL2}'? Eller vill du göra dig av med den valda trollformeln och forska vidare?", + "vcmi.spellResearch.research" : "Forska fram denna trollformel", + "vcmi.spellResearch.skip" : "Strunta i denna trollformel", + "vcmi.spellResearch.abort" : "Avbryt", + "vcmi.mainMenu.serverConnecting" : "Ansluter...", "vcmi.mainMenu.serverAddressEnter" : "Ange adress:", "vcmi.mainMenu.serverConnectionFailed" : "Misslyckades med att ansluta", @@ -343,6 +350,12 @@ "vcmi.heroWindow.openCommander.help" : "Visar detaljer om befälhavaren för den här hjälten.", "vcmi.heroWindow.openBackpack.hover" : "Öppna artefaktryggsäcksfönster", "vcmi.heroWindow.openBackpack.help" : "Öppnar fönster som gör det enklare att hantera artefaktryggsäcken.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Sortera efter kostnad", + "vcmi.heroWindow.sortBackpackByCost.help" : "Sorterar artefakter i ryggsäcken efter kostnad.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Sortera efter plats", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Sorterar artefakter i ryggsäcken efter utrustad plats.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Sortera efter klass", + "vcmi.heroWindow.sortBackpackByClass.help" : "Sorterar artefakter i ryggsäcken efter artefaktklass (skatt, mindre, större, relik)", "vcmi.tavernWindow.inviteHero" : "Bjud in hjälte", @@ -355,8 +368,8 @@ "vcmi.creatureWindow.returnArtifact.hover" : "Återlämna artefakt", "vcmi.creatureWindow.returnArtifact.help" : "Klicka på den här knappen för att lägga tillbaka artefakten i hjältens ryggsäck.", - "vcmi.questLog.hideComplete.hover" : "Gömmer alla slutförda uppdrag", - "vcmi.questLog.hideComplete.help" : "Dölj alla slutförda uppdrag.", + "vcmi.questLog.hideComplete.hover" : "Dölj alla slutförda uppdrag", + "vcmi.questLog.hideComplete.help" : "Gömmer undan alla slutförda uppdrag.", "vcmi.randomMapTab.widgets.randomTemplate" : "(Slumpmässig)", "vcmi.randomMapTab.widgets.templateLabel" : "Mall", @@ -365,22 +378,22 @@ "vcmi.randomMapTab.widgets.roadTypesLabel" : "Vägtyper", "vcmi.optionsTab.turnOptions.hover" : "Turomgångsalternativ", - "vcmi.optionsTab.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer", + "vcmi.optionsTab.turnOptions.help" : "Ställ in turomgångs-timern och samtidiga turomgångar", "vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern", "vcmi.optionsTab.chessFieldTurn.hover" : "Tur-timern", "vcmi.optionsTab.chessFieldBattle.hover" : "Strids-timern", "vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-timern", - "vcmi.optionsTab.chessFieldBase.help" : "Används när {Tur-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).", - "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern} till nästa turomgång.", + "vcmi.optionsTab.chessFieldBase.help" : "Används när {Tur-timern} når 0. Ställs in i början av spelet. Vid 0 avslutas turomgången (pågående strid avslutas med förlust).", + "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern}.", "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid går förlorad.", "vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PvP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PvP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PvP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr dina enheter i PvP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr dina enheter i PvP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.", "vcmi.optionsTab.accumulate" : "Ackumulera", - "vcmi.optionsTab.simturnsTitle" : "Simultana turomgångar", + "vcmi.optionsTab.simturnsTitle" : "Samtidiga turomgångar", "vcmi.optionsTab.simturnsMin.hover" : "Åtminstone i", "vcmi.optionsTab.simturnsMax.hover" : "Som mest i", "vcmi.optionsTab.simturnsAI.hover" : "(Experimentell) Simultana AI-turomgångar", @@ -388,7 +401,7 @@ "vcmi.optionsTab.simturnsMax.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar eller tills en tillräckligt nära kontakt inträffar med en annan spelare", "vcmi.optionsTab.simturnsAI.help" : "{Simultana AI-turomgångar}\nExperimentellt alternativ. Tillåter AI-spelare att agera samtidigt som den mänskliga spelaren när simultana turomgångar är aktiverade.", - "vcmi.optionsTab.turnTime.select" : "Timer-inställningar för turomgångar", + "vcmi.optionsTab.turnTime.select" : "Ställ in turomgångs-timer", "vcmi.optionsTab.turnTime.unlimited" : "Obegränsat med tid", "vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut", "vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter", @@ -403,15 +416,15 @@ "vcmi.optionsTab.turnTime.chess.2" : "Schack: 02:00 + 01:00 + 00:15 + 00:00", "vcmi.optionsTab.turnTime.chess.1" : "Schack: 01:00 + 01:00 + 00:00 + 00:00", - "vcmi.optionsTab.simturns.select" : "Simultana/samtidiga turomgångsinställningar", - "vcmi.optionsTab.simturns.none" : "Inga simultana/samtidiga turer", - "vcmi.optionsTab.simturns.tillContactMax" : "Sam-tur: Fram till kontakt", - "vcmi.optionsTab.simturns.tillContact1" : "Sam-tur: 1 vecka, bryt vid kontakt", - "vcmi.optionsTab.simturns.tillContact2" : "Sam-tur: 2 veckor, bryt vid kontakt", - "vcmi.optionsTab.simturns.tillContact4" : "Sam-tur: 1 månad, bryt vid kontakt", - "vcmi.optionsTab.simturns.blocked1" : "Sam-tur: 1 vecka, kontakter blockerade", - "vcmi.optionsTab.simturns.blocked2" : "Sam-tur: 2 veckor, kontakter blockerade", - "vcmi.optionsTab.simturns.blocked4" : "Sam-tur: 1 månad, kontakter blockerade", + "vcmi.optionsTab.simturns.select" : "Ställ in samtidiga turomgångar", + "vcmi.optionsTab.simturns.none" : "Inga samtidiga turomgångar", + "vcmi.optionsTab.simturns.tillContactMax" : "Samtur: Fram till närkontakt", + "vcmi.optionsTab.simturns.tillContact1" : "Samtur: 1 vecka (bryts vid närkontakt)", + "vcmi.optionsTab.simturns.tillContact2" : "Samtur: 2 veckor (bryts vid närkontakt)", + "vcmi.optionsTab.simturns.tillContact4" : "Samtur: 1 månad (bryts vid närkontakt)", + "vcmi.optionsTab.simturns.blocked1" : "Samtur: 1 vecka (närkontakt blockerad)", + "vcmi.optionsTab.simturns.blocked2" : "Samtur: 2 veckor (närkontakt blockerad)", + "vcmi.optionsTab.simturns.blocked4" : "Samtur: 1 månad (närkontakt blockerad)", // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language // Using this information, VCMI will automatically select correct plural form for every possible amount @@ -521,155 +534,155 @@ "core.seerhut.quest.reachDate.visit.5" : "Stängt fram till %s.", "core.bonus.ADDITIONAL_ATTACK.name" : "Dubbelslag", - "core.bonus.ADDITIONAL_ATTACK.description" : "Attackerar två gånger", + "core.bonus.ADDITIONAL_ATTACK.description" : "Attackerar två gånger.", "core.bonus.ADDITIONAL_RETALIATION.name" : "Ytterligare motattacker", - "core.bonus.ADDITIONAL_RETALIATION.description" : "Kan slå tillbaka ${val} extra gånger", + "core.bonus.ADDITIONAL_RETALIATION.description" : "Kan slå tillbaka ${val} extra gånger.", "core.bonus.AIR_IMMUNITY.name" : "Luft-immunitet", - "core.bonus.AIR_IMMUNITY.description" : "Immun mot alla trollformler från skolan för luftmagi", + "core.bonus.AIR_IMMUNITY.description" : "Immun mot alla luftmagi-trollformler.", "core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring", - "core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender", - "core.bonus.BLOCKS_RETALIATION.name" : "Blockera närstrids-motattack", - "core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera", - "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Blockera fjärrstrids-motattack", - "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte retaliera på avstånd genom att använda en distansattack", - "core.bonus.CATAPULT.name" : "Katapult", - "core.bonus.CATAPULT.description" : "Attackerar belägringsmurar", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description" : "Minskar trollformelkostnaden för hjälten med ${val}", + "core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender.", + "core.bonus.BLOCKS_RETALIATION.name" : "Retaliera ej i närstrid", + "core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera.", + "core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Retaliera ej på avstånd", + "core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte retaliera på avstånd.", + "core.bonus.CATAPULT.name" : "Katapult-attack", + "core.bonus.CATAPULT.description" : "Attackerar belägringsmurar.", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska magikostnad (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description" : "Minskar magikostnaden för hjälten med ${val}.", "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name" : "Magisk dämpare (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Ökar trollformelkostnaden för fiendens trollformler med ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Ökar fiendens magikostnad med ${val}.", "core.bonus.CHARGE_IMMUNITY.name" : "Galoppanfalls-immunitet", - "core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall", + "core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares galopperande ridanfall.", "core.bonus.DARKNESS.name" : "I skydd av mörkret", - "core.bonus.DARKNESS.description" : "Skapar ett mörkerhölje med en rut-radie på ${val} som gäckar dina fiender", + "core.bonus.DARKNESS.description" : "Skapar ett mörkerhölje med rutradien ${val}.", "core.bonus.DEATH_STARE.name" : "Dödsblick (${val}%)", - "core.bonus.DEATH_STARE.description" : "Varje 'Dödsblick' har ${val}% chans att döda den översta fiendeenheten", + "core.bonus.DEATH_STARE.description" : "Varje dödsblick har ${val}% chans att döda.", "core.bonus.DEFENSIVE_STANCE.name" : "Försvarshållning", - "core.bonus.DEFENSIVE_STANCE.description" : "När du väljer att försvara en enhet så får den +${val} extra försvar", + "core.bonus.DEFENSIVE_STANCE.description" : "+${val} extra försvar när du försvarar dig.", "core.bonus.DESTRUCTION.name" : "Förintelse", - "core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack", + "core.bonus.DESTRUCTION.description" : "${val}% chans att ta död på fler efter attack.", "core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Dödsstöt", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att utdela dubbel basskada vid attack", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "${val}% chans till dubbel basskada vid attack.", "core.bonus.DRAGON_NATURE.name" : "Drake", - "core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur", + "core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur.", "core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet", - "core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi", + "core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla jordmagi-trollformler.", "core.bonus.ENCHANTER.name" : "Förtrollare", - "core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje turomgång", + "core.bonus.ENCHANTER.description" : "Kastar mass-${subtype.spell} varje turomgång.", "core.bonus.ENCHANTED.name" : "Förtrollad", - "core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}", + "core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}.", "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)", - "core.bonus.ENEMY_ATTACK_REDUCTION.description" : "När du blir attackerad ignoreras ${val}% av angriparens attack", + "core.bonus.ENEMY_ATTACK_REDUCTION.description" : "Ignorerar ${val}% av angriparens attack.", "core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Förbigå försvar (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "När du attackerar ignoreras ${val}% av försvararens försvar", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "Din attack ignorerar ${val}% av fiendens försvar.", "core.bonus.FIRE_IMMUNITY.name" : "Eld-immunitet", - "core.bonus.FIRE_IMMUNITY.description" : "Immun mot alla trollformler från skolan för eldmagi", + "core.bonus.FIRE_IMMUNITY.description" : "Immun mot alla eldmagi-trollformler.", "core.bonus.FIRE_SHIELD.name" : "Eldsköld (${val}%)", - "core.bonus.FIRE_SHIELD.description" : "Reflekterar en del av närstridsskadorna", + "core.bonus.FIRE_SHIELD.description" : "Reflekterar en del av närstridsskadorna.", "core.bonus.FIRST_STRIKE.name" : "Första slaget", - "core.bonus.FIRST_STRIKE.description" : "Denna varelse gör en motattack innan den blir attackerad", + "core.bonus.FIRST_STRIKE.description" : "Retalierar innan den blir attackerad.", "core.bonus.FEAR.name" : "Rädsla", - "core.bonus.FEAR.description" : "Orsakar rädsla på ett fiendeförband", + "core.bonus.FEAR.description" : "Orsakar rädsla på ett fiendeförband.", "core.bonus.FEARLESS.name" : "Orädd", - "core.bonus.FEARLESS.description" : "Immun mot rädsla", + "core.bonus.FEARLESS.description" : "Immun mot rädsla.", "core.bonus.FEROCITY.name" : "Vildsint", - "core.bonus.FEROCITY.description" : "Attackerar ${val} extra gång(er) om någon dödas", + "core.bonus.FEROCITY.description" : "+${val} extra attack(er) om någon dödas.", "core.bonus.FLYING.name" : "Flygande", - "core.bonus.FLYING.description" : "Flyger vid förflyttning (ignorerar hinder)", + "core.bonus.FLYING.description" : "Flyger vid förflyttning (ignorerar hinder).", "core.bonus.FREE_SHOOTING.name" : "Skjut på nära håll", - "core.bonus.FREE_SHOOTING.description" : "Kan använda distansattacker på närstridsavstånd", + "core.bonus.FREE_SHOOTING.description" : "Använd distansattacker på närstridsavstånd.", "core.bonus.GARGOYLE.name" : "Stenfigur", - "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas", + "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas.", "core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från distans- och närstrids-attacker", - "core.bonus.HATE.name" : "Hatar ${subtyp.varelse}", - "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från inkommande attacker.", + "core.bonus.HATE.name" : "Hatar: ${subtype.creature}", + "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtype.creature}.", "core.bonus.HEALER.name" : "Helare", - "core.bonus.HEALER.description" : "Helar/läker allierade enheter", + "core.bonus.HEALER.description" : "Helar/läker allierade enheter.", "core.bonus.HP_REGENERATION.name" : "Självläkande", - "core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} hälsa (träffpoäng) varje runda", + "core.bonus.HP_REGENERATION.description" : "Återfår ${val} hälsa (träffpoäng) varje runda.", "core.bonus.JOUSTING.name" : "Galopperande ridanfall", - "core.bonus.JOUSTING.description" : "Orsakar +${val}% mer skada för varje ruta som förflyttas innan attack", + "core.bonus.JOUSTING.description" : "+${val}% skada per rutförflyttning före attack.", "core.bonus.KING.name" : "Kung", - "core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre", - "core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Förtrollningsimmunitet 1-${val}", - "core.bonus.LEVEL_SPELL_IMMUNITY.description" : "Immun mot trollformler på nivå 1-${val}", - "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad räckvidd för skjutning", - "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Kan inte sikta på enheter längre bort än ${val} rutor", - "core.bonus.LIFE_DRAIN.name" : "Dränerar livskraft (${val}%)", - "core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% hälsa (träffpoäng) av utdelad skada", - "core.bonus.MANA_CHANNELING.name" : "Kanalisera trollformelspoäng ${val}%", - "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av fiendens spenderade trollformelspoäng i strid", - "core.bonus.MANA_DRAIN.name" : "Dränera trollformelspoäng", - "core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur", + "core.bonus.KING.description" : "Sårbar för Dräpar-nivå ${val} eller högre.", + "core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Trolldomsimmunitet 1-${val}", + "core.bonus.LEVEL_SPELL_IMMUNITY.description" : "Immun mot nivå 1-${val}-trollformler.", + "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad skjuträckvidd", + "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Skjuträckvidd: ${val} rutor.", + "core.bonus.LIFE_DRAIN.name" : "Dränera livskraft (${val}%)", + "core.bonus.LIFE_DRAIN.description" : "Dränera ${val}% hälsa av den vållade skadan.", + "core.bonus.MANA_CHANNELING.name" : "Kanalisera magi (${val}%)", + "core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av fiendens spenderade mana.", + "core.bonus.MANA_DRAIN.name" : "Dränera mana", + "core.bonus.MANA_DRAIN.description" : "Dränerar ${val} mana från fienden varje tur.", "core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)", - "core.bonus.MAGIC_MIRROR.description" : "${val}% chans att reflektera en offensiv trollformel på en fiendeenhet", + "core.bonus.MAGIC_MIRROR.description" : "${val}% chans att reflektera skadliga trollformler.", "core.bonus.MAGIC_RESISTANCE.name" : "Magiskt motstånd (${val}%)", - "core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel", - "core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar", - "core.bonus.MIND_IMMUNITY.description" : "Immun mot förtrollningar som påverkar dina sinnen", - "core.bonus.NO_DISTANCE_PENALTY.name" : "Ingen avståndsbestraffning", - "core.bonus.NO_DISTANCE_PENALTY.description" : "Gör full skada på vilket avstånd som helst i strid", - "core.bonus.NO_MELEE_PENALTY.name" : "Ingen närstridsbestraffning", - "core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har ingen närstridsbestraffning", + "core.bonus.MAGIC_RESISTANCE.description" : "${val}% chans att motstå en skadlig trollformel.", + "core.bonus.MIND_IMMUNITY.name" : "Immun mot sinnesmagi", + "core.bonus.MIND_IMMUNITY.description" : "Immun mot magi som påverkar dina sinnen.", + "core.bonus.NO_DISTANCE_PENALTY.name" : "Långdistansskytt", + "core.bonus.NO_DISTANCE_PENALTY.description" : "Gör full skada på alla avstånd i strid.", + "core.bonus.NO_MELEE_PENALTY.name" : "Närstridsspecialist", + "core.bonus.NO_MELEE_PENALTY.description" : "Ingen närstridsbestraffning.", "core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan", - "core.bonus.NO_MORALE.description" : "Är immun mot moraliska effekter och har alltid neutral moral", + "core.bonus.NO_MORALE.description" : "Immun mot moral-effekter (neutral moral).", "core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning", - "core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur", + "core.bonus.NO_WALL_PENALTY.description" : "Gör full skada mot fiender bakom en mur.", "core.bonus.NON_LIVING.name" : "Icke levande", - "core.bonus.NON_LIVING.description" : "Påverkas inte av vissa effekter som levande/odöda gör", + "core.bonus.NON_LIVING.description" : "Immunitet mot många effekter.", "core.bonus.RANDOM_SPELLCASTER.name" : "Slumpmässig besvärjare", - "core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt", + "core.bonus.RANDOM_SPELLCASTER.description" : "Kastar trollformler som väljs slumpmässigt.", "core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd", - "core.bonus.RANGED_RETALIATION.description" : "Kan retaliera/motattackera på avstånd", - "core.bonus.RECEPTIVE.name" : "Mottaglig", - "core.bonus.RECEPTIVE.description" : "Ingen immunitet mot vänliga besvärjelser", + "core.bonus.RANGED_RETALIATION.description" : "Kan retaliera/motattackera på avstånd.", + "core.bonus.RECEPTIVE.name" : "Magiskt mottaglig", + "core.bonus.RECEPTIVE.description" : "Ingen immunitet mot vänliga trollformler.", "core.bonus.REBIRTH.name" : "Återfödelse (${val}%)", - "core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden", + "core.bonus.REBIRTH.description" : "${val}% återuppväcks efter döden.", "core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid", - "core.bonus.RETURN_AFTER_STRIKE.description" : "Efter närstridsattack återvänder den till sin ursprungliga ruta", + "core.bonus.RETURN_AFTER_STRIKE.description" : "Återvänder till sin ruta efter attack.", "core.bonus.REVENGE.name" : "Hämnd", - "core.bonus.REVENGE.description" : "Orsakar mer skada om den själv blivit skadad", + "core.bonus.REVENGE.description" : "Extra skada om den själv blivit skadad.", "core.bonus.SHOOTER.name" : "Distans-attack", - "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd", + "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd.", "core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten", - "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Dess distans-attacker drabbar alla mål i ett litet område", + "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Distans-attack drabbar alla inom räckhåll.", "core.bonus.SOUL_STEAL.name" : "Själtjuv", - "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna för varje dödad fiendeenhet", + "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna/dödad fiende.", "core.bonus.SPELLCASTER.name" : "Besvärjare", - "core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}", + "core.bonus.SPELLCASTER.description" : "Kan kasta: ${subtype.spell}.", "core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack", - "core.bonus.SPELL_AFTER_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} efter anfall", + "core.bonus.SPELL_AFTER_ATTACK.description" : "${val}% chans för '${subtype.spell}' efter attack.", "core.bonus.SPELL_BEFORE_ATTACK.name" : "Besvärja före attack", - "core.bonus.SPELL_BEFORE_ATTACK.description" : "Har ${val}% chans att kasta ${subtype.spell} före anfall", + "core.bonus.SPELL_BEFORE_ATTACK.description" : "${val}% chans för '${subtype.spell}' före attack.", "core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Trolldoms-resistens", - "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.", + "core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Reducerar magisk-skada med ${val}%.", "core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet", - "core.bonus.SPELL_IMMUNITY.description" : "Immun mot ${subtype.spell}", - "core.bonus.SPELL_LIKE_ATTACK.name" : "Trolldomsliknande attack", - "core.bonus.SPELL_LIKE_ATTACK.description" : "Attackerar med ${subtype.spell}", + "core.bonus.SPELL_IMMUNITY.description" : "Immun mot '${subtype.spell}'.", + "core.bonus.SPELL_LIKE_ATTACK.name" : "Magisk attack", + "core.bonus.SPELL_LIKE_ATTACK.description" : "Attackerar med '${subtype.spell}'.", "core.bonus.SPELL_RESISTANCE_AURA.name" : "Motståndsaura", - "core.bonus.SPELL_RESISTANCE_AURA.description" : "Närbelägna förband får ${val}% magi-resistens", + "core.bonus.SPELL_RESISTANCE_AURA.description" : "Närbelägna förband får ${val}% magi-resistens.", "core.bonus.SUMMON_GUARDIANS.name" : "Åkalla väktare", - "core.bonus.SUMMON_GUARDIANS.description" : "I början av striden åkallas ${subtype.creature} (${val}%)", + "core.bonus.SUMMON_GUARDIANS.description" : "Vid strid åkallas: ${subtype.creature} ${val}%.", "core.bonus.SYNERGY_TARGET.name" : "Synergibar", - "core.bonus.SYNERGY_TARGET.description" : "Denna varelse är sårbar för synergieffekt", + "core.bonus.SYNERGY_TARGET.description" : "Denna varelse är sårbar för synergieffekt.", "core.bonus.TWO_HEX_ATTACK_BREATH.name" : "Dödlig andedräkt", - "core.bonus.TWO_HEX_ATTACK_BREATH.description" : "Andningsattack (2 rutors räckvidd)", + "core.bonus.TWO_HEX_ATTACK_BREATH.description" : "Andningsattack (2 rutors räckvidd).", "core.bonus.THREE_HEADED_ATTACK.name" : "Trehövdad attack", - "core.bonus.THREE_HEADED_ATTACK.description" : "Attackerar tre angränsande enheter", + "core.bonus.THREE_HEADED_ATTACK.description" : "Attackerar tre angränsande enheter.", "core.bonus.TRANSMUTATION.name" : "Transmutation", - "core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ", + "core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet.", "core.bonus.UNDEAD.name" : "Odöd", - "core.bonus.UNDEAD.description" : "Varelsen är odöd", + "core.bonus.UNDEAD.description" : "Varelsen är odöd.", "core.bonus.UNLIMITED_RETALIATIONS.name" : "Slår tillbaka varje gång", - "core.bonus.UNLIMITED_RETALIATIONS.description" : "Obegränsat antal motattacker", + "core.bonus.UNLIMITED_RETALIATIONS.description" : "Obegränsat antal motattacker.", "core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet", - "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagiskolan", + "core.bonus.WATER_IMMUNITY.description" : "Immun mot alla vattenmagi-trollformler.", "core.bonus.WIDE_BREATH.name" : "Bred dödlig andedräkt", - "core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)", + "core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor).", "core.bonus.DISINTEGRATE.name" : "Desintegrerar", - "core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar efter dödsögonblicket", + "core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar på slagfältet.", "core.bonus.INVINCIBLE.name" : "Oövervinnerlig", - "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting" + "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting." } From 115439f16acd512c7f4fd81240615dc9b5f0c3be Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Oct 2024 15:01:18 +0000 Subject: [PATCH 317/726] Fix town events not actually giving offered creatures --- server/processors/NewTurnProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index f87c380f4..a850cb99f 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -116,6 +116,8 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town) iw.components.emplace_back(ComponentType::CREATURE, town->creatures.at(i).second.back(), event.creatures.at(i)); } } + + gameHandler->sendAndApply(sac); //show dialog } gameHandler->sendAndApply(iw); //show dialog } From e221cdccabc38e8b95c71e77ee7a6e0cdf6eb2d8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Oct 2024 15:01:43 +0000 Subject: [PATCH 318/726] Fix initialization of hero type in map editor --- lib/mapObjects/CGHeroInstance.cpp | 6 +++++- mapeditor/inspector/inspector.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index caaf5081a..b31ff4190 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -309,7 +309,11 @@ const CHeroClass * CGHeroInstance::getHeroClass() const HeroClassID CGHeroInstance::getHeroClassID() const { - return getHeroType()->heroClass->getId(); + auto heroType = getHeroTypeID(); + if (heroType.hasValue()) + return getHeroType()->heroClass->getId(); + else + return HeroClassID(); } const CHero * CGHeroInstance::getHeroType() const diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 0fb5859ec..4ad9c5eaf 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -139,6 +139,18 @@ void Initializer::initialize(CGHeroInstance * o) o->tempOwner = PlayerColor::NEUTRAL; } + if(o->ID == Obj::HERO) + { + for(auto const & t : VLC->heroh->objects) + { + if(t->heroClass->getId() == HeroClassID(o->subID)) + { + o->subID = t->getId(); + break; + } + } + } + if(o->getHeroTypeID().hasValue()) { o->gender = o->getHeroType()->gender; From d1e7cb72c24a6d8ccd32526cc9fa1251d76810e8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Oct 2024 15:02:14 +0000 Subject: [PATCH 319/726] Fix game gang on random map generation --- lib/mapObjects/CGObjectInstance.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index c9d7b3eea..aa902a452 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -208,7 +208,7 @@ int CGObjectInstance::getSightRadius() const int3 CGObjectInstance::getVisitableOffset() const { if (!isVisitable()) - throw std::runtime_error("Attempt to access visitable offset of a non-visitable object!"); + logGlobal->debug("Attempt to access visitable offset on a non-visitable object!"); return appearance->getVisitableOffset(); } @@ -308,7 +308,7 @@ void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const int3 CGObjectInstance::visitablePos() const { if (!isVisitable()) - throw std::runtime_error("Attempt to access visitable position on a non-visitable object!"); + logGlobal->debug("Attempt to access visitable position on a non-visitable object!"); return pos - getVisitableOffset(); } From c55a75cc4e6fbf450cc9d72bc5e332dd23c23c8b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Oct 2024 15:02:48 +0000 Subject: [PATCH 320/726] Remove copy-pasted code in rewardable town building leading to hero not registered as visitor --- lib/mapObjects/CRewardableObject.cpp | 14 +------------- lib/mapObjects/TownBuildingInstance.cpp | 16 +--------------- lib/rewardable/Interface.cpp | 17 +++++++++++++++++ lib/rewardable/Interface.h | 2 ++ 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 80a9a5603..9c0d67378 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -95,19 +95,7 @@ void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int3 } else { - if (answer == 0) - return; //Player refused - - if(answer > 0 && answer - 1 < configuration.info.size()) - { - auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT); - markAsVisited(hero); - grantReward(list[answer - 1], hero); - } - else - { - throw std::runtime_error("Unhandled choice"); - } + onBlockingDialogAnswered(hero, answer); } } diff --git a/lib/mapObjects/TownBuildingInstance.cpp b/lib/mapObjects/TownBuildingInstance.cpp index 2b485ef6d..8b4a40e5c 100644 --- a/lib/mapObjects/TownBuildingInstance.cpp +++ b/lib/mapObjects/TownBuildingInstance.cpp @@ -131,21 +131,7 @@ void TownRewardableBuildingInstance::heroLevelUpDone(const CGHeroInstance *hero) void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const { - if(answer == 0) - return; // player refused - - if(visitors.find(hero->id) != visitors.end()) - return; // query not for this building - - if(answer > 0 && answer-1 < configuration.info.size()) - { - auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT); - grantReward(list[answer - 1], hero); - } - else - { - throw std::runtime_error("Unhandled choice"); - } + onBlockingDialogAnswered(hero, answer); } void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInstance * hero) const diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index 0634ba7c0..f67250efe 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -366,4 +366,21 @@ void Rewardable::Interface::doHeroVisit(const CGHeroInstance *h) const } } +void Rewardable::Interface::onBlockingDialogAnswered(const CGHeroInstance * hero, int32_t answer) const +{ + if (answer == 0) + return; //Player refused + + if(answer > 0 && answer - 1 < configuration.info.size()) + { + auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT); + markAsVisited(hero); + grantReward(list[answer - 1], hero); + } + else + { + throw std::runtime_error("Unhandled choice"); + } +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rewardable/Interface.h b/lib/rewardable/Interface.h index 5354fffaf..3fe764637 100644 --- a/lib/rewardable/Interface.h +++ b/lib/rewardable/Interface.h @@ -48,6 +48,8 @@ protected: virtual void markAsVisited(const CGHeroInstance * hero) const = 0; virtual void markAsScouted(const CGHeroInstance * hero) const = 0; virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero) const = 0; + + void onBlockingDialogAnswered(const CGHeroInstance * hero, int32_t answer) const; public: /// filters list of visit info and returns rewards that can be granted to current hero From c838f5d0c24286505fdd47b4a1b31c6010dc7b73 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Fri, 11 Oct 2024 20:27:18 +0200 Subject: [PATCH 321/726] Update BattleEvaluator.cpp Restored spell-damage-calculations for units that would die from spells. --- AI/BattleAI/BattleEvaluator.cpp | 50 ++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 2d2e59a79..ce70e7bce 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -731,7 +731,55 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack) ps.value = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state); } - + //! Some units may be dead alltogether. So if they existed before but not now, we know they were killed by the spell + for (const auto& unit : all) + { + if (!unit->isValidTarget()) + continue; + bool isDead = true; + for (const auto& remainingUnit : allUnits) + { + if (remainingUnit->unitId() == unit->unitId()) + isDead = false; + } + if (isDead) + { + auto newHealth = 0; + auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); + if (oldHealth != newHealth) + { + auto damage = std::abs(oldHealth - newHealth); + auto originalDefender = cb->getBattle(battleID)->battleGetUnitByID(unit->unitId()); + auto dpsReduce = AttackPossibility::calculateDamageReduce( + nullptr, + originalDefender && originalDefender->alive() ? originalDefender : unit, + damage, + innerCache, + state); + auto ourUnit = unit->unitSide() == side ? 1 : -1; + auto goodEffect = newHealth > oldHealth ? 1 : -1; + if (ourUnit * goodEffect == 1) + { + if (ourUnit && goodEffect && (unit->isClone() || unit->isGhost())) + continue; + ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier(); + } + else + ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier(); +#if BATTLE_TRACE_LEVEL >= 1 + logAi->trace( + "Spell %s to %d affects %s (%d), dps: %2f oldHealth: %d newHealth: %d", + ps.spell->getNameTranslated(), + ps.dest.at(0).hexValue.hex, + unit->creatureId().toCreature()->getNameSingularTranslated(), + unit->getCount(), + dpsReduce, + oldHealth, + newHealth); +#endif + } + } + } for(const auto & unit : allUnits) { if(!unit->isValidTarget(true)) From b67b43c811c8eebc6292fc48f3e9092b610e73c4 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sat, 12 Oct 2024 12:50:02 +0800 Subject: [PATCH 322/726] do lupdate and update Chinese translation --- Mods/vcmi/config/vcmi/chinese.json | 2 +- launcher/translation/chinese.ts | 185 ++++++++++++++--------- launcher/translation/czech.ts | 185 ++++++++++++++--------- launcher/translation/english.ts | 219 ++++++++++++++++----------- launcher/translation/french.ts | 185 ++++++++++++++--------- launcher/translation/german.ts | 185 ++++++++++++++--------- launcher/translation/polish.ts | 185 ++++++++++++++--------- launcher/translation/portuguese.ts | 189 ++++++++++++++--------- launcher/translation/russian.ts | 235 +++++++++++++++++------------ launcher/translation/spanish.ts | 185 ++++++++++++++--------- launcher/translation/swedish.ts | 193 ++++++++++++++--------- launcher/translation/ukrainian.ts | 185 ++++++++++++++--------- launcher/translation/vietnamese.ts | 185 ++++++++++++++--------- 13 files changed, 1417 insertions(+), 901 deletions(-) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index a36a923ce..44960a60a 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -581,7 +581,7 @@ "core.bonus.GARGOYLE.description": "不能被复活或治疗", "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "减少伤害 (${val}%)", "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "减少从远程和近战中遭受的物理伤害", - "core.bonus.HATE.name": "${subtype.creature}的死敌", + "core.bonus.HATE.name": "憎恨${subtype.creature}", "core.bonus.HATE.description": "对${subtype.creature}造成额外${val}%伤害", "core.bonus.HEALER.name": "治疗者", "core.bonus.HEALER.description": "可以治疗友军单位", diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index 11e156178..c80f1cfaa 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -639,267 +639,310 @@ Install successfully downloaded? CSettingsView + Off 关闭 - + Artificial Intelligence 人工智能 - + Interface Scaling 界面缩放 - + Neutral AI in battles 战场中立生物AI - + Enemy AI in battles 战场敌方玩家AI - + Additional repository 额外仓库 - + Downscaling Filter 图像缩小过滤器 - + Adventure Map Allies 冒险地图友方玩家 - + Online Lobby port 在线大厅端口 - + Autocombat AI in battles 自动战斗AI - + Sticks Sensitivity 摇杆灵敏度 - + Automatic (Linear) 自动(线性) - + Haptic Feedback 触觉反馈 - + Software Cursor 软件指针 - + + + Automatic 自动 - + + Mods Validation + 模组验证 + + + None - + xBRZ x2 xBRZ x2 - + xBRZ x3 xBRZ x3 - + xBRZ x4 xBRZ x4 - + + Full + 完备 + + + Use scalable fonts 使用可缩放字体 - + Online Lobby address 在线大厅地址 - + + Cursor Scaling + 指针缩放 + + + + Scalable + 可缩放字体 + + + + Miscellaneous + 杂项 + + + + Font Scaling (experimental) + 字体缩放(测试中) + + + + Original + 原始字体 + + + Upscaling Filter 图像放大过滤器 - + + Basic + 基本 + + + Use Relative Pointer Mode 使用相对指针模式 - + Nearest 最邻近 - + Linear 线性 - + Input - Touchscreen 输入 - 触屏 - + Adventure Map Enemies 冒险地图敌方玩家 - + Show Tutorial again 重新显示教程 - + Reset 重置 - + Network 网络 - + Audio 音频 - + Relative Pointer Speed 相对指针速度 - + Music Volume 音乐音量 - + Ignore SSL errors 忽略SSL错误 - + Input - Mouse 输入 - 鼠标 - + Long Touch Duration 长按触屏间隔 - + % % - + Controller Click Tolerance 控制器按键灵敏度 - + Touch Tap Tolerance 触屏点击灵敏度 - + Input - Controller 输入 - 控制器 - + Sound Volume 音效音量 - + Windowed 窗口化 - + Borderless fullscreen 无边框全屏 - + Exclusive fullscreen 独占全屏 - + Autosave limit (0 = off) 自动保存限制 (0 = 不限制) - + Framerate Limit 帧率限制 - + Autosave prefix 自动保存文件名前缀 - + Mouse Click Tolerance 鼠标点击灵敏度 - + Sticks Acceleration 摇杆加速度 - + empty = map name prefix 空 = 地图名称前缀 - + Refresh now 立即刷新 - + Default repository 默认仓库 - + Renderer 渲染器 @@ -909,7 +952,7 @@ Install successfully downloaded? 开启 - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -926,93 +969,93 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use 独占全屏模式 - 游戏会运行在一个覆盖全部屏幕的窗口,使用和你选择的分辨率。 - + Reserved screen area 保留屏幕区域 - + Heroes III Translation 发布版本里找不到这个项,不太清楚意义 英雄无敌3翻译 - + Check on startup 启动时检查更新 - + Fullscreen 全屏 - + General 通用设置 - + VCMI Language VCMI语言 - + Resolution 分辨率 - + Autosave 自动存档 - + VSync 垂直同步 - + Display index 显示器序号 - + Network port 网络端口 - + Video 视频设置 - + Show intro 显示开场动画 - + Active 激活 - + Disabled 禁用 - + Enable 启用 - + Not Installed 未安装 - + Install 安装 diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index f3a1225b1..1a47abdaf 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -634,267 +634,310 @@ Nainstalovat úspěšně stažené? CSettingsView + Off Vypnuto - + Artificial Intelligence Umělá inteligence - + Interface Scaling Škálování rozhraní - + Neutral AI in battles Neutrální AI v bitvách - + Enemy AI in battles Nepřátelská AI v bitvách - + Additional repository Další repozitáře - + Downscaling Filter - + Adventure Map Allies Spojenci na mapě světa - + Online Lobby port Port online předsíně - + Autocombat AI in battles AI automatického boje v bitvách - + Sticks Sensitivity Citlivost páček - + Automatic (Linear) - + Haptic Feedback Zpětná odezva - + Software Cursor Softwarový kurzor - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address Adresa online předsíně - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Filtr škálování - + + Basic + + + + Use Relative Pointer Mode Použít režim relativního ukazatele - + Nearest Nejbližší - + Linear Lineární - + Input - Touchscreen Vstup - dotyková obrazovka - + Adventure Map Enemies Nepřátelé na mapě světa - + Show Tutorial again - + Reset - + Network Síť - + Audio Zvuk - + Relative Pointer Speed Relativní rychlost myši - + Music Volume Hlasitost hudby - + Ignore SSL errors Ignorovat chyby SSL - + Input - Mouse Vstup - Myš - + Long Touch Duration Doba dlouhého podržení - + % % - + Controller Click Tolerance Odchylka klepnutí ovladače - + Touch Tap Tolerance Odchylka klepnutí dotykem - + Input - Controller Vstup - ovladač - + Sound Volume Hlasitost zvuků - + Windowed V okně - + Borderless fullscreen Celá obrazovka bez okrajů - + Exclusive fullscreen Exkluzivní celá obrazovka - + Autosave limit (0 = off) Limit aut. uložení (0=vypnuto) - + Framerate Limit Omezení snímků za sekundu - + Autosave prefix Předpona aut. uložení - + Mouse Click Tolerance Odchylka klepnutí myší - + Sticks Acceleration Zrychlení páček - + empty = map name prefix prázná = předpona - název mapy - + Refresh now Obnovit nyní - + Default repository Výchozí repozitář - + Renderer Vykreslovač @@ -904,7 +947,7 @@ Nainstalovat úspěšně stažené? Zapnuto - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -921,92 +964,92 @@ Celá obrazovka bez okrajů- hra poběží v okně, které zakryje vaši celou Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybrané rozlišení. - + Reserved screen area Vyhrazená část obrazovky - + Heroes III Translation Překlad Heroes III - + Check on startup Zkontrolovat při zapnutí - + Fullscreen Celá obrazovka - + General Všeobecné - + VCMI Language Jazyk VCMI - + Resolution Rozlišení - + Autosave Automatické uložení - + VSync VSync - + Display index - + Network port Síťový port - + Video Zobrazení - + Show intro Zobrazit intro - + Active Aktivní - + Disabled Zakázáno - + Enable Povolit - + Not Installed Nenainstalováno - + Install Instalovat diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index 604274ac0..0114b4501 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -626,267 +626,310 @@ Install successfully downloaded? CSettingsView + Off - + Artificial Intelligence - + Interface Scaling - + Neutral AI in battles - + Enemy AI in battles - + Additional repository - + Adventure Map Allies - + Online Lobby port - + Autocombat AI in battles - + Sticks Sensitivity - + Automatic (Linear) - + Haptic Feedback - + Software Cursor - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address - - Upscaling Filter + + Cursor Scaling - - Use Relative Pointer Mode - - - - - Nearest - - - - - Linear - - - - - Input - Touchscreen - - - - - Adventure Map Enemies + + Scalable + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + + Upscaling Filter + + + + + Basic + + + + + Use Relative Pointer Mode + + + + + Nearest + + + + + Linear + + + + + Input - Touchscreen + + + + + Adventure Map Enemies + + + + Show Tutorial again - + Reset - + Network - + Audio - + Relative Pointer Speed - + Music Volume - + Ignore SSL errors - + Input - Mouse - + Long Touch Duration - + % - + Controller Click Tolerance - + Touch Tap Tolerance - + Input - Controller - + Sound Volume - + Windowed - + Borderless fullscreen - + Exclusive fullscreen - + Autosave limit (0 = off) - + Downscaling Filter - + Framerate Limit - + Autosave prefix - + Mouse Click Tolerance - + Sticks Acceleration - + empty = map name prefix - + Refresh now - + Default repository - + Renderer @@ -896,7 +939,7 @@ Install successfully downloaded? - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -907,92 +950,92 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Reserved screen area - + Heroes III Translation - + Check on startup - + Fullscreen - + General - + VCMI Language - + Resolution - + Autosave - + VSync - + Display index - + Network port - + Video - + Show intro - + Active - + Disabled - + Enable - + Not Installed - + Install diff --git a/launcher/translation/french.ts b/launcher/translation/french.ts index 04ab48984..bd7f7dc30 100644 --- a/launcher/translation/french.ts +++ b/launcher/translation/french.ts @@ -639,12 +639,13 @@ Installer les téchargements réussis? CSettingsView + Off Désactivé - + Artificial Intelligence Intelligence Artificielle @@ -654,187 +655,229 @@ Installer les téchargements réussis? Activé - + Enemy AI in battles IA ennemie dans les batailles - + Default repository Dépôt par défaut - + VSync Synchronisation verticalle - + Online Lobby port Port de la salle d'attente en ligne - + Autocombat AI in battles IA de combat automatique dans les batailles - + Sticks Sensitivity Sensibilité au batons - + Automatic (Linear) Automatique (Linéaire) - + Haptic Feedback Retour Tactile - + Software Cursor Curseur Logiciel - + + + Automatic Automatique - + + Mods Validation + + + + None Aucun - + xBRZ x2 xBRZ x2 - + xBRZ x3 xBRZ x3 - + xBRZ x4 xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address Adresse de la salle d'attente en ligne - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Filtre d'Agrandissement - + + Basic + + + + Use Relative Pointer Mode Utiliser le Mode de Pointeur Relatif - + Nearest Le plus Proche - + Linear Linéaire - + Input - Touchscreen Entrée - Écran tactile - + Network Réseau - + Downscaling Filter Filtre de Rétrécissement - + Show Tutorial again Remontrer le Didacticiel - + Reset Réinitialiser - + Audio Audio - + Relative Pointer Speed Vitesse de Pointeur Relatif - + Music Volume Volume de la Musique - + Ignore SSL errors Ignorer les erreurs SSL - + Input - Mouse Entrée - Sourie - + Long Touch Duration Durée de Touche Prolongée - + % % - + Controller Click Tolerance Tolérance au Clic de Contrôleur - + Touch Tap Tolerance Tolérance à la Frappe de Touche - + Input - Controller Entrée - Contrôleur - + Sound Volume Volume du Son - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -851,167 +894,167 @@ Mode fenêtré sans bord - le jeu s"exécutera dans une fenêtre qui couvre Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écran et utilisera la résolution sélectionnée. - + Windowed Fenêtré - + Borderless fullscreen Fenêtré sans bord - + Exclusive fullscreen Plein écran exclusif - + Reserved screen area Zone d'écran réservée - + Neutral AI in battles IA neutre dans les batailles - + Autosave limit (0 = off) Limite de sauvegarde auto (0 = désactivé) - + Adventure Map Enemies Ennemis de la carte d"aventure - + Autosave prefix Préfix de sauvegarde auto. - + empty = map name prefix vide = prefix du nom de carte - + Interface Scaling Mise à l"échelle de l"interface - + Framerate Limit Limite de fréquence d"images - + Renderer Moteur de rendu - + Heroes III Translation Traduction de Heroes III - + Adventure Map Allies Alliés de la carte d"aventure - + Additional repository Dépôt supplémentaire - + Check on startup Vérifier au démarrage - + Mouse Click Tolerance Tolérance au Clic de Sourie - + Sticks Acceleration Accelération de Bâton - + Refresh now Actualiser maintenant - + Fullscreen Plein écran - + General Général - + VCMI Language Langue de VCMI - + Resolution Résolution - + Autosave Sauvegarde automatique - + Display index Index d'affichage - + Network port Port de réseau - + Video Vidéo - + Show intro Montrer l'intro - + Active Actif - + Disabled Désactivé - + Enable Activé - + Not Installed Pas Installé - + Install Installer diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index 659788841..90c01cbb3 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -634,267 +634,310 @@ Installation erfolgreich heruntergeladen? CSettingsView + Off Aus - + Artificial Intelligence Künstliche Intelligenz - + Interface Scaling Skalierung der Benutzeroberfläche - + Neutral AI in battles Neutrale KI in Kämpfen - + Enemy AI in battles Gegnerische KI in Kämpfen - + Additional repository Zusätzliches Repository - + Downscaling Filter - + Adventure Map Allies Abenteuerkarte Verbündete - + Online Lobby port Online-Lobby-Port - + Autocombat AI in battles Autokampf-KI in Kämpfen - + Sticks Sensitivity Sticks Empfindlichkeit - + Automatic (Linear) - + Haptic Feedback Haptisches Feedback - + Software Cursor Software-Cursor - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address Adresse der Online-Lobby - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Hochskalierungsfilter - + + Basic + + + + Use Relative Pointer Mode Relativen Zeigermodus verwenden - + Nearest Nearest - + Linear Linear - + Input - Touchscreen Eingabe - Touchscreen - + Adventure Map Enemies Abenteuerkarte Feinde - + Show Tutorial again Zeige Tutorial erneut - + Reset Zurücksetzen - + Network Netzwerk - + Audio Audio - + Relative Pointer Speed Relative Zeigergeschwindigkeit - + Music Volume Musik Lautstärke - + Ignore SSL errors SSL-Fehler ignorieren - + Input - Mouse Eingabe - Maus - + Long Touch Duration Dauer der Berührung für "lange Berührung" - + % % - + Controller Click Tolerance Toleranz bei Controller Klick - + Touch Tap Tolerance Toleranz bei Berührungen - + Input - Controller Eingabe - Controller - + Sound Volume Sound-Lautstärke - + Windowed Fenstermodus - + Borderless fullscreen Randloser Vollbildmodus - + Exclusive fullscreen Exklusiver Vollbildmodus - + Autosave limit (0 = off) Limit für Autospeicherung (0 = aus) - + Framerate Limit Limit der Bildrate - + Autosave prefix Präfix für Autospeicherung - + Mouse Click Tolerance Toleranz bei Mausklick - + Sticks Acceleration Sticks Beschleunigung - + empty = map name prefix leer = Kartenname als Präfix - + Refresh now Jetzt aktualisieren - + Default repository Standard Repository - + Renderer Renderer @@ -904,7 +947,7 @@ Installation erfolgreich heruntergeladen? An - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -921,92 +964,92 @@ Randloser Fenstermodus - das Spiel läuft in einem Fenster, das den gesamten Bil Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwendet die gewählte Auflösung. - + Reserved screen area Reservierter Bildschirmbereich - + Heroes III Translation Heroes III Übersetzung - + Check on startup Beim Start prüfen - + Fullscreen Vollbild - + General Allgemein - + VCMI Language VCMI-Sprache - + Resolution Auflösung - + Autosave Autospeichern - + VSync VSync - + Display index Anzeige-Index - + Network port Netzwerk-Port - + Video Video - + Show intro Intro anzeigen - + Active Aktiv - + Disabled Deaktiviert - + Enable Aktivieren - + Not Installed Nicht installiert - + Install Installieren diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index c30a428c1..43b478497 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -634,267 +634,310 @@ Zainstalować pomyślnie pobrane? CSettingsView + Off Wyłączony - + Artificial Intelligence Sztuczna Inteligencja - + Interface Scaling Skala interfejsu - + Neutral AI in battles AI bitewne jednostek neutralnych - + Enemy AI in battles AI bitewne wrogów - + Additional repository Dodatkowe repozytorium - + Downscaling Filter - + Adventure Map Allies AI sojuszników mapy przygody - + Online Lobby port Port lobby online - + Autocombat AI in battles AI szybkiej walki - + Sticks Sensitivity Czułość gałek - + Automatic (Linear) - + Haptic Feedback Wibracje - + Software Cursor Kursor programowy - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address Adres lobby online - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Filtr wyostrzający - + + Basic + + + + Use Relative Pointer Mode Użyj relatywnego trybu kursora - + Nearest Najbliższych - + Linear Liniowy - + Input - Touchscreen Sterowanie - Ekran dotykowy - + Adventure Map Enemies AI wrogów mapy przygody - + Show Tutorial again Pokaż ponownie samouczek - + Reset Zresetuj - + Network Sieć - + Audio Dźwięk i muzyka - + Relative Pointer Speed Prędkość kursora w trybie relatywnym - + Music Volume Głośność muzyki - + Ignore SSL errors Ignoruj błędy SSL - + Input - Mouse Sterowanie - Mysz - + Long Touch Duration Czas do długiego dotyku - + % % - + Controller Click Tolerance Tolerancja na kliknięcia poza elementami (kontroler) - + Touch Tap Tolerance Tolerancja na nietrafianie dotykiem w elementy - + Input - Controller Sterowanie - Kontroler - + Sound Volume Głośność dźwięku - + Windowed Okno - + Borderless fullscreen Pełny ekran (tryb okna) - + Exclusive fullscreen Pełny ekran klasyczny - + Autosave limit (0 = off) Limit autozapisów (0 = brak) - + Framerate Limit Limit FPS - + Autosave prefix Przedrostek autozapisu - + Mouse Click Tolerance Tolerancja na kliknięcia poza elementami (mysz) - + Sticks Acceleration Przyspieszenie gałek - + empty = map name prefix puste = przedrostek z nazwy mapy - + Refresh now Odśwież - + Default repository Domyślne repozytorium - + Renderer Renderer @@ -904,7 +947,7 @@ Zainstalować pomyślnie pobrane? Włączony - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -921,92 +964,92 @@ Pełny ekran w trybie okna - gra uruchomi się w oknie przysłaniającym cały e Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybranej przez ciebie rozdzielczości ekranu. - + Reserved screen area Zarezerwowany obszar ekranu - + Heroes III Translation Tłumaczenie Heroes III - + Check on startup Sprawdzaj przy uruchomieniu - + Fullscreen Pełny ekran - + General Ogólne - + VCMI Language Język VCMI - + Resolution Rozdzielczość - + Autosave Autozapis - + VSync Synchronizacja pionowa (VSync) - + Display index Numer wyświetlacza - + Network port Port sieciowy - + Video Obraz - + Show intro Pokaż intro - + Active Aktywny - + Disabled Wyłączone - + Enable Włącz - + Not Installed Nie zainstalowano - + Install Zainstaluj diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index 0f4108637..a95662eca 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -634,267 +634,310 @@ O download da instalação foi bem-sucedido? CSettingsView + Off Desativado - + Artificial Intelligence Inteligência artificial - + Interface Scaling Escala da interface - + Neutral AI in battles IA neutra nas batalhas - + Enemy AI in battles IA inimiga em batalhas - + Additional repository Repositório adicional - + Adventure Map Allies Aliados do mapa de aventura - + Online Lobby port Porta da sala de espera on-line - + Autocombat AI in battles IA de combate automático nas batalhas - + Sticks Sensitivity Sensibilidade dos analógicos - + Automatic (Linear) Automático (linear) - + Haptic Feedback Resposta tátil - + Software Cursor Cursor por software - + + + Automatic Automático - + + Mods Validation + + + + None Nenhum - + xBRZ x2 xBRZ x2 - + xBRZ x3 xBRZ x3 - + xBRZ x4 xBRZ x4 - - Use scalable fonts - Usar fontes escaláveis + + Full + - + + Use scalable fonts + Usar fontes escaláveis + + + Online Lobby address Endereço da sala de espera on-line - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Filtro de aumento de escala - + + Basic + + + + Use Relative Pointer Mode Usar modo de ponteiro relativo - + Nearest Mais próximo - + Linear Linear - + Input - Touchscreen Entrada - tela de toque - + Adventure Map Enemies Inimigos do mapa de aventura - + Show Tutorial again Mostrar o tutorial novamente - + Reset Redefinir - + Network Linear - + Audio Áudio - + Relative Pointer Speed Velocidade do ponteiro relativo - + Music Volume Volume da música - + Ignore SSL errors Ignorar erros SSL - + Input - Mouse Entrada - mouse - + Long Touch Duration Duração do toque longo - + % % - + Controller Click Tolerance Tolerância de clique do controle - + Touch Tap Tolerance Tolerância de toque tátil - + Input - Controller Entrada - controle - + Sound Volume Volume do som - + Windowed Janela - + Borderless fullscreen Tela cheia sem bordas - + Exclusive fullscreen Tela cheia exclusiva - + Autosave limit (0 = off) Limite de salvamento automático (0 = sem limite) - + Downscaling Filter Filtro de redução de escala - + Framerate Limit Limite de taxa de quadros - + Autosave prefix Prefixo do salvamento automático - + Mouse Click Tolerance Tolerância de clique do mouse - + Sticks Acceleration Aceleração dos analógicos - + empty = map name prefix vazio = prefixo do mapa - + Refresh now Atualizar - + Default repository Repositório padrão - + Renderer Renderizador @@ -904,7 +947,7 @@ O download da instalação foi bem-sucedido? Ativado - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -921,92 +964,92 @@ Modo de janela sem bordas - o jogo será executado em uma janela que cobre toda Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolução selecionada. - + Reserved screen area Área de tela reservada - + Heroes III Translation Tradução do Heroes III - + Check on startup Verificar na inicialização - + Fullscreen Tela cheia - + General Geral - + VCMI Language Idioma do VCMI - + Resolution Resolução - + Autosave Salvamento automático - + VSync VSync - + Display index Índice de exibição - + Network port Porta de rede - + Video Vídeo - + Show intro Mostrar introdução - + Active Ativo - + Disabled Desativado - + Enable Ativar - + Not Installed Não instalado - + Install Instalar diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index b63ae0386..20a45663b 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -626,11 +626,12 @@ Install successfully downloaded? CSettingsView - + Interface Scaling + Off Отключено @@ -641,292 +642,334 @@ Install successfully downloaded? Включено - + Neutral AI in battles - + Enemy AI in battles - + Additional repository - + Check on startup Проверять при запуске - + Fullscreen Полноэкранный режим - + General Общее - + VCMI Language Язык VCMI - + Artificial Intelligence Искусственный интеллект - + Adventure Map Allies - + Refresh now - + Adventure Map Enemies - + Online Lobby port - + Autocombat AI in battles - + Sticks Sensitivity - + Automatic (Linear) - + Haptic Feedback - + Software Cursor - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address - - Upscaling Filter + + Cursor Scaling - - Use Relative Pointer Mode - - - - - VSync - - - - - Nearest - - - - - Linear - - - - - Input - Touchscreen - - - - - Network - - - - - Downscaling Filter + + Scalable + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + + Upscaling Filter + + + + + Basic + + + + + Use Relative Pointer Mode + + + + + VSync + + + + + Nearest + + + + + Linear + + + + + Input - Touchscreen + + + + + Network + + + + + Downscaling Filter + + + + Show Tutorial again - + Reset - + Audio - + Relative Pointer Speed - + Music Volume - + Ignore SSL errors - + Input - Mouse - + Long Touch Duration - + % - + Controller Click Tolerance - + Touch Tap Tolerance - + Input - Controller - + Sound Volume - + Windowed - + Borderless fullscreen - + Exclusive fullscreen - + Reserved screen area - + Autosave limit (0 = off) - + Framerate Limit - + Autosave prefix - + Mouse Click Tolerance - + Sticks Acceleration - + empty = map name prefix - + Default repository - + Renderer - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -937,62 +980,62 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Heroes III Translation Перевод Героев III - + Resolution Разрешение экрана - + Autosave Автосохранение - + Display index Дисплей - + Network port Сетевой порт - + Video Графика - + Show intro Вступление - + Active Активен - + Disabled Отключен - + Enable Включить - + Not Installed Не установлен - + Install Установить diff --git a/launcher/translation/spanish.ts b/launcher/translation/spanish.ts index 0b853743b..a1e9e19f6 100644 --- a/launcher/translation/spanish.ts +++ b/launcher/translation/spanish.ts @@ -633,267 +633,310 @@ Instalar lo correctamente descargado? CSettingsView + Off Desactivado - + Artificial Intelligence Inteligencia Artificial - + Interface Scaling Escala de la interfaz - + Neutral AI in battles IA neutral en batallas - + Enemy AI in battles IA enemiga en batallas - + Additional repository Repositorio adicional - + Downscaling Filter - + Adventure Map Allies Aliados en el Mapa de aventuras - + Online Lobby port - + Autocombat AI in battles - + Sticks Sensitivity - + Automatic (Linear) - + Haptic Feedback - + Software Cursor - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter - + + Basic + + + + Use Relative Pointer Mode - + Nearest - + Linear - + Input - Touchscreen - + Adventure Map Enemies Enemigos en el Mapa de aventuras - + Show Tutorial again - + Reset - + Network - + Audio - + Relative Pointer Speed - + Music Volume - + Ignore SSL errors - + Input - Mouse - + Long Touch Duration - + % - + Controller Click Tolerance - + Touch Tap Tolerance - + Input - Controller - + Sound Volume - + Windowed Ventana - + Borderless fullscreen Ventana completa sin bordes - + Exclusive fullscreen Pantalla completa - + Autosave limit (0 = off) Límite de autosaves (0 = sin límite) - + Framerate Limit Límite de fotogramas - + Autosave prefix Prefijo autoguardado - + Mouse Click Tolerance - + Sticks Acceleration - + empty = map name prefix Vacio = prefijo del mapa - + Refresh now Actualizar - + Default repository Repositorio por defecto - + Renderer Render @@ -903,62 +946,62 @@ Instalar lo correctamente descargado? Activado - + Heroes III Translation Traducción de Heroes III - + Reserved screen area Área de pantalla reservada - + Fullscreen Pantalla completa - + General General - + VCMI Language Idioma de VCMI - + Resolution Resolución - + Autosave Autoguardado - + VSync Sincronización vertical - + Display index Mostrar índice - + Network port Puerto de red - + Video Vídeo - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -975,37 +1018,37 @@ Ventana sin bordes - el juego se ejecutará en una ventana que cubre completamen Pantalla completa - el juego cubrirá la totalidad de la pantalla y utilizará la resolución seleccionada. - + Show intro Mostrar introducción - + Check on startup Comprovar al inicio - + Active Activado - + Disabled Desactivado - + Enable Activar - + Not Installed No Instalado - + Install Instalar diff --git a/launcher/translation/swedish.ts b/launcher/translation/swedish.ts index deb1fd13b..f92834d02 100644 --- a/launcher/translation/swedish.ts +++ b/launcher/translation/swedish.ts @@ -634,12 +634,13 @@ Installation framgångsrikt nedladdad? CSettingsView + Off Inaktiverad - + Artificial Intelligence Artificiell intelligens @@ -649,187 +650,229 @@ Installation framgångsrikt nedladdad? Aktiverad - + Enemy AI in battles Fiendens AI i strider - + Default repository Standard-repositorie - + VSync Vertikal-synkronisering (VSync) - + Online Lobby port Port-numret till online-väntrummet - + Autocombat AI in battles Automatiska AI-strider - + Sticks Sensitivity Spak-känslighet - + Automatic (Linear) Automatisk (linjär) - + Haptic Feedback Haptisk återkoppling (vibrationer i kontrollen) - + Software Cursor Programvarustyrd muspekare - + + + Automatic Automatisk - + + Mods Validation + + + + None Inget - + xBRZ x2 xBRZ x2 - + xBRZ x3 xBRZ x3 - + xBRZ x4 xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address Adressen till online-väntrummet - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Uppskalnings-filter - + + Basic + + + + Use Relative Pointer Mode Använd läge för relativ muspekare - + Nearest Närmast - + Linear Linjär - + Input - Touchscreen Ingång/indata - Pekskärm - + Network Nätverk - + Downscaling Filter Nerskalnings-filter - + Show Tutorial again Visa handledningen/övningsgenomgången igen - + Reset Återställ - + Audio Ljud - + Relative Pointer Speed Relativ pekarhastighet - + Music Volume Musikvolym - + Ignore SSL errors Ignorera SSL-fel - + Input - Mouse Ingång/indata - Mus - + Long Touch Duration Utökad beröringslängd - + % % - + Controller Click Tolerance Tolerans för klick på styrenhet - + Touch Tap Tolerance Tolerans för pektryck - + Input - Controller Ingång/indata - Kontroll - + Sound Volume Ljudvolym - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -846,167 +889,167 @@ Kantlöst fönsterläge - spelet körs i ett fönster som täcker hela din skär Exklusivt helskärmsläge - spelet kommer att täcka hela skärmen och använda den valda upplösningen. - + Windowed Fönsterläge - + Borderless fullscreen Kantlös helskärm - + Exclusive fullscreen Exklusiv helskärm - + Reserved screen area Reserverat skärmområde - + Neutral AI in battles Neutralt AI i strider - + Autosave limit (0 = off) Antal platser för automatisk-sparning (0 = inaktiverad) - + Adventure Map Enemies Fiender på äventyskartan - + Autosave prefix Prefix för automatisk-sparning - + empty = map name prefix tomt = kartnamnsprefix - + Interface Scaling Gränssnittsskalning - + Framerate Limit Gräns ​​för bildhastighet - + Renderer Renderingsmotor - + Heroes III Translation Heroes III - Översättning - + Adventure Map Allies Allierade på äventyrskartan - + Additional repository Ytterligare repositorier - + Check on startup Kontrollera vid uppstart - + Mouse Click Tolerance Musklickstolerans - + Sticks Acceleration Styrspaks-acceleration - + Refresh now Uppdatera nu - + Fullscreen Helskärm - + General Allmänt - + VCMI Language VCMI-språk - + Resolution Upplösning - + Autosave Auto-spara - + Display index Visa index - + Network port Nätverksport - + Video Video - + Show intro Visa intro - + Active Aktiv - + Disabled Inaktiverad - + Enable Aktivera - + Not Installed Inte installerad - + Install Installera @@ -1151,7 +1194,7 @@ Exklusivt helskärmsläge - spelet kommer att täcka hela skärmen och använda If you own Heroes III on gog.com you can download backup offline installer from gog.com, and VCMI will import Heroes III data using offline installer. Offline installer consists of two parts, .exe and .bin. Make sure you download both of them. - Om du äger Heroes III från GOG.com kan du ladda ner backup offline-installationsprogrammet från 'GOG.com'. VCMI kommer att importera Heroes III-data med hjälp av offline-installationsprogrammet. Offline-installationsprogrammet består av två delar, en '.exe'- och en '.bin'fil. Se till att ladda ner båda. + Om du äger Heroes III från GOG.com kan du ladda ner backup offline-installationsprogrammet från 'GOG.com'. VCMI kommer att importera Heroes III-data med hjälp av offline-installationsprogrammet. Offline-installationsprogrammet består av två delar, en '.exe'- och en '.bin'fil. Se till att ladda ner båda. @@ -1166,12 +1209,12 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b VCMI on Github - VCMI på 'Github' + VCMI på 'Github' VCMI on Discord - VCMI på 'Discord' + VCMI på 'Discord' @@ -1245,7 +1288,7 @@ Heroes® of Might and Magic® III HD stöds för närvarande inte! Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher - Du kan välja att installera ytterligare moddar, antingen nu eller vid ett senare tillfälle med hjälp av 'VCMI Launchern' + Du kan välja att installera ytterligare moddar, antingen nu eller vid ett senare tillfälle med hjälp av 'VCMI Launchern' diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index ddfe4a510..efdf8ea08 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -634,267 +634,310 @@ Install successfully downloaded? CSettingsView + Off Ні - + Artificial Intelligence Штучний інтелект - + Interface Scaling Масштабування інтерфейсу - + Neutral AI in battles Нейтральний ШІ в боях - + Enemy AI in battles Ворожий ШІ в боях - + Additional repository Додатковий репозиторій - + Downscaling Filter - + Adventure Map Allies Союзники на мапі пригод - + Online Lobby port Порт онлайн лобі - + Autocombat AI in battles ШІ автобою - + Sticks Sensitivity Чутливість стиків - + Automatic (Linear) - + Haptic Feedback - + Software Cursor Програмний курсор - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address Адреса онлайн-лобі - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter Фільтр масштабування - + + Basic + + + + Use Relative Pointer Mode Режим відносного вказівника - + Nearest Найближчий - + Linear Лінійний - + Input - Touchscreen Введення - Сенсорний екран - + Adventure Map Enemies Вороги на мапі пригод - + Show Tutorial again Повторно показати навчання - + Reset Скинути - + Network Мережа - + Audio Аудіо - + Relative Pointer Speed Швидкість відносного вказівника - + Music Volume Гучність музики - + Ignore SSL errors Ігнорувати помилки SSL - + Input - Mouse Введення - Миша - + Long Touch Duration Тривалість довгого дотику - + % % - + Controller Click Tolerance Допуск на натискання контролера - + Touch Tap Tolerance Допуск на натискання дотиком - + Input - Controller Введення - Контролер - + Sound Volume Гучність звуку - + Windowed У вікні - + Borderless fullscreen Повноекранне вікно - + Exclusive fullscreen Повноекранний (ексклюзивно) - + Autosave limit (0 = off) Кількість автозбережень - + Framerate Limit Обмеження частоти кадрів - + Autosave prefix Префікс назв автозбережень - + Mouse Click Tolerance Допуск кліків миші - + Sticks Acceleration Прискорення стиків - + empty = map name prefix (використовувати назву карти) - + Refresh now Оновити зараз - + Default repository Стандартний репозиторій - + Renderer Рендерер @@ -904,7 +947,7 @@ Install successfully downloaded? Так - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -921,92 +964,92 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use Повноекранний ексклюзивний режим - гра займатиме весь екран і використовуватиме вибрану роздільну здатність. - + Reserved screen area Зарезервована зона екрану - + Heroes III Translation Переклад Heroes III - + Check on startup Перевіряти на старті - + Fullscreen Повноекранний режим - + General Загальні налаштування - + VCMI Language Мова VCMI - + Resolution Роздільна здатність - + Autosave Автозбереження - + VSync Вертикальна синхронізація - + Display index Дісплей - + Network port Мережевий порт - + Video Графіка - + Show intro Вступні відео - + Active Активні - + Disabled Деактивований - + Enable Активувати - + Not Installed Не встановлено - + Install Встановити diff --git a/launcher/translation/vietnamese.ts b/launcher/translation/vietnamese.ts index c5263d4e9..0f96f2242 100644 --- a/launcher/translation/vietnamese.ts +++ b/launcher/translation/vietnamese.ts @@ -626,267 +626,310 @@ Install successfully downloaded? CSettingsView + Off Tắt - + Artificial Intelligence Trí tuệ nhân tạo - + Interface Scaling Phóng đại giao diện - + Neutral AI in battles Máy hoang dã trong trận đánh - + Enemy AI in battles Máy đối thủ trong trận đánh - + Additional repository Nguồn bổ sung - + Downscaling Filter - + Adventure Map Allies Máy liên minh ở bản đồ phiêu lưu - + Online Lobby port - + Autocombat AI in battles - + Sticks Sensitivity - + Automatic (Linear) - + Haptic Feedback - + Software Cursor - + + + Automatic - + + Mods Validation + + + + None - + xBRZ x2 - + xBRZ x3 - + xBRZ x4 - + + Full + + + + Use scalable fonts - + Online Lobby address - + + Cursor Scaling + + + + + Scalable + + + + + Miscellaneous + + + + + Font Scaling (experimental) + + + + + Original + + + + Upscaling Filter - + + Basic + + + + Use Relative Pointer Mode - + Nearest - + Linear - + Input - Touchscreen - + Adventure Map Enemies Máy đối thủ ở bản đồ phiêu lưu - + Show Tutorial again - + Reset - + Network - + Audio - + Relative Pointer Speed - + Music Volume - + Ignore SSL errors - + Input - Mouse - + Long Touch Duration - + % - + Controller Click Tolerance - + Touch Tap Tolerance - + Input - Controller - + Sound Volume - + Windowed Cửa sổ - + Borderless fullscreen Toàn màn hình không viền - + Exclusive fullscreen Toàn màn hình riêng biệt - + Autosave limit (0 = off) Giới hạn lưu tự động (0 = không giới hạn) - + Framerate Limit Giới hạn khung hình - + Autosave prefix Thêm tiền tố vào lưu tự động - + Mouse Click Tolerance - + Sticks Acceleration - + empty = map name prefix Rỗng = tên bản đồ - + Refresh now Làm mới - + Default repository Nguồn mặc định - + Renderer @@ -896,7 +939,7 @@ Install successfully downloaded? Bật - + Select display mode for game Windowed - game will run inside a window that covers part of your screen @@ -913,92 +956,92 @@ Toàn màn hình không viền - Trò chơi chạy toàn màn hình, dùng chung Toàn màn hình riêng biệt - Trò chơi chạy toàn màn hình và dùng độ phân giải được chọn. - + Reserved screen area Diện tích màn hình dành riêng - + Heroes III Translation Bản dịch Heroes III - + Check on startup Kiểm tra khi khởi động - + Fullscreen Toàn màn hình - + General Chung - + VCMI Language Ngôn ngữ VCMI - + Resolution Độ phân giải - + Autosave Tự động lưu - + VSync - + Display index Mục hiện thị - + Network port Cổng mạng - + Video Phim ảnh - + Show intro Hiện thị giới thiệu - + Active Bật - + Disabled Tắt - + Enable Bật - + Not Installed Chưa cài đặt - + Install Cài đặt From d4d3ddf6850ddc945728577ff1a65af38605a291 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:33:46 +0300 Subject: [PATCH 323/726] fused artifacts --- client/ArtifactsUIController.cpp | 2 +- .../Entities_Format/Artifact_Format.md | 3 +++ lib/CArtHandler.cpp | 23 ++++++++++++++++--- lib/CArtHandler.h | 7 +++++- lib/CArtifactInstance.cpp | 5 ++++ lib/CArtifactInstance.h | 1 + lib/networkPacks/NetPacksLib.cpp | 16 ++++++++----- lib/networkPacks/PacksForClient.h | 6 ++--- server/CGameHandler.cpp | 5 +++- 9 files changed, 53 insertions(+), 15 deletions(-) diff --git a/client/ArtifactsUIController.cpp b/client/ArtifactsUIController.cpp index 73461a58a..30374ebc2 100644 --- a/client/ArtifactsUIController.cpp +++ b/client/ArtifactsUIController.cpp @@ -102,7 +102,7 @@ bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const if(hero->tempOwner != LOCPLINT->playerID) return false; - if(art->isCombined()) + if(art->hasParts()) { if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1)) return false; diff --git a/docs/modders/Entities_Format/Artifact_Format.md b/docs/modders/Entities_Format/Artifact_Format.md index 928496dc7..83b1adca2 100644 --- a/docs/modders/Entities_Format/Artifact_Format.md +++ b/docs/modders/Entities_Format/Artifact_Format.md @@ -67,6 +67,9 @@ In order to make functional artifact you also need: "artifact2", "artifact3" ], + + // Optional, by default is false. Set to true if components are supposed to be fused. + "fusedComponents" : true, // Creature id to use on battle field. If set, this artifact is war machine "warMachine" : "some.creature" diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 07b83dec3..f96e17f83 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -61,6 +61,21 @@ const std::vector & CCombinedArtifact::getPartOf() const return partOf; } +void CCombinedArtifact::setFused(bool isFused) +{ + fused = isFused; +} + +bool CCombinedArtifact::isFused() const +{ + return fused; +} + +bool CCombinedArtifact::hasParts() const +{ + return isCombined() && !isFused(); +} + bool CScrollArtifact::isScroll() const { return static_cast(this)->getId() == ArtifactID::SPELL_SCROLL; @@ -203,7 +218,7 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b auto artCanBePutAt = [this, simpleArtCanBePutAt](const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) -> bool { - if(isCombined()) + if(hasParts()) { if(!simpleArtCanBePutAt(artSet, slot, assumeDestRemoved)) return false; @@ -606,11 +621,11 @@ void CArtHandler::loadType(CArtifact * art, const JsonNode & node) const void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) { - if (!node["components"].isNull()) + if(!node["components"].isNull()) { for(const auto & component : node["components"].Vector()) { - VLC->identifiers()->requestIdentifier("artifact", component, [=](si32 id) + VLC->identifiers()->requestIdentifier("artifact", component, [this, art](int32_t id) { // when this code is called both combinational art as well as component are loaded // so it is safe to access any of them @@ -619,6 +634,8 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) }); } } + if(!node["fusedComponents"].isNull()) + art->setFused(node["fusedComponents"].Bool()); } void CArtHandler::makeItCreatureArt(CArtifact * a, bool onlyCreature) diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index e82f43147..f0bf7d8af 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -46,14 +46,19 @@ namespace ArtBearer class DLL_LINKAGE CCombinedArtifact { protected: - CCombinedArtifact() = default; + CCombinedArtifact() : fused(false) {}; std::vector constituents; // Artifacts IDs a combined artifact consists of, or nullptr. std::vector partOf; // Reverse map of constituents - combined arts that include this art + bool fused; + public: bool isCombined() const; const std::vector & getConstituents() const; const std::vector & getPartOf() const; + void setFused(bool isFused); + bool isFused() const; + bool hasParts() const; }; class DLL_LINKAGE CScrollArtifact diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index 6f191e46b..f65e6d95b 100644 --- a/lib/CArtifactInstance.cpp +++ b/lib/CArtifactInstance.cpp @@ -44,6 +44,11 @@ bool CCombinedArtifactInstance::isPart(const CArtifactInstance * supposedPart) c return false; } +bool CCombinedArtifactInstance::hasParts() const +{ + return !partsInfo.empty(); +} + const std::vector & CCombinedArtifactInstance::getPartsInfo() const { return partsInfo; diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index f679f8b44..6ff0bdbe0 100644 --- a/lib/CArtifactInstance.h +++ b/lib/CArtifactInstance.h @@ -38,6 +38,7 @@ public: void addPart(CArtifactInstance * art, const ArtifactPosition & slot); // Checks if supposed part inst is part of this combined art inst bool isPart(const CArtifactInstance * supposedPart) const; + bool hasParts() const; const std::vector & getPartsInfo() const; void addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap); diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6ea83ab8f..f44e6411e 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1806,6 +1806,7 @@ void AssembledArtifact::applyGs(CGameState *gs) assert(hero); const auto transformedArt = hero->getArt(al.slot); assert(transformedArt); + const auto builtArt = artId.toArtifact(); assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(hero, transformedArt->getTypeId()), [=](const CArtifact * art)->bool { return art->getId() == builtArt->getId(); @@ -1832,7 +1833,7 @@ void AssembledArtifact::applyGs(CGameState *gs) // Find a slot for combined artifact al.slot = transformedArtSlot; - for(const auto slot : slotsInvolved) + for(const auto & slot : slotsInvolved) { if(ArtifactUtils::isSlotEquipment(transformedArtSlot)) { @@ -1855,15 +1856,18 @@ void AssembledArtifact::applyGs(CGameState *gs) } // Delete parts from hero - for(const auto slot : slotsInvolved) + for(const auto & slot : slotsInvolved) { const auto constituentInstance = hero->getArt(slot); gs->map->removeArtifactInstance(*hero, slot); - if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot) - combinedArt->addPart(constituentInstance, slot); - else - combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST); + if(!combinedArt->artType->isFused()) + { + if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot) + combinedArt->addPart(constituentInstance, slot); + else + combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST); + } } // Put new combined artifacts diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index b9f678ba6..0413113dc 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -1108,8 +1108,8 @@ struct DLL_LINKAGE BulkMoveArtifacts : CArtifactOperationPack struct DLL_LINKAGE AssembledArtifact : CArtifactOperationPack { - ArtifactLocation al; //where assembly will be put - const CArtifact * builtArt; + ArtifactLocation al; + ArtifactID artId; void applyGs(CGameState * gs) override; @@ -1118,7 +1118,7 @@ struct DLL_LINKAGE AssembledArtifact : CArtifactOperationPack template void serialize(Handler & h) { h & al; - h & builtArt; + h & artId; } }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1f9549fa0..ab933a4f8 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2913,7 +2913,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a AssembledArtifact aa; aa.al = dstLoc; - aa.builtArt = combinedArt; + aa.artId = assembleTo; sendAndApply(aa); } else @@ -2921,6 +2921,9 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a if(!destArtifact->isCombined()) COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is not a combined artifact!"); + if(!destArtifact->hasParts()) + COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is fused combined artifact!"); + if(ArtifactUtils::isSlotBackpack(artifactSlot) && !ArtifactUtils::isBackpackFreeSlots(hero, destArtifact->artType->getConstituents().size() - 1)) COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble but backpack is full!"); From 11eaed9fef7705c4935df7ebdd1eb48fa103eab1 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:01:14 +0200 Subject: [PATCH 324/726] code review --- client/adventureMap/AdventureMapShortcuts.cpp | 26 ++++++------------- lib/CGameInfoCallback.cpp | 11 ++++++++ lib/CGameInfoCallback.h | 2 ++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index f3ab04933..d5c5ce321 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -467,29 +467,19 @@ void AdventureMapShortcuts::search(bool next) { // get all relevant objects std::vector visitableObjInstances; - int3 mapSizes = LOCPLINT->cb->getMapSize(); - for(int x = 0; x < mapSizes.x; x++) - for(int y = 0; y < mapSizes.y; y++) - for(int z = 0; z < mapSizes.z; z++) - for(auto & obj : LOCPLINT->cb->getVisitableObjs(int3(x, y, z), false)) - if(obj->ID != MapObjectID::MONSTER && obj->ID != MapObjectID::EVENT && obj->ID != MapObjectID::HERO) - visitableObjInstances.push_back(obj->id); + for(auto & obj : LOCPLINT->cb->getAllVisitableObjs()) + if(obj->ID != MapObjectID::MONSTER && obj->ID != MapObjectID::HERO && obj->ID != MapObjectID::TOWN) + visitableObjInstances.push_back(obj->id); - // count of elements for each group + // count of elements for each group (map is already sorted) std::map mapObjCount; for(auto & obj : visitableObjInstances) - mapObjCount[{ VLC->objtypeh->getObjectName(LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex()) }]++; + mapObjCount[{ LOCPLINT->cb->getObjInstance(obj)->getObjectName() }]++; - // sort by name + // convert to vector for indexed access std::vector> textCountList; for (auto itr = mapObjCount.begin(); itr != mapObjCount.end(); ++itr) textCountList.push_back(*itr); - std::sort(textCountList.begin(), textCountList.end(), - [=](std::pair& a, std::pair& b) - { - return a.first < b.first; - } - ); // get pos of last selection int lastSel = 0; @@ -510,7 +500,7 @@ void AdventureMapShortcuts::search(bool next) // filter for matching objects std::vector selVisitableObjInstances; for(auto & obj : visitableObjInstances) - if(selObj == VLC->objtypeh->getObjectName(LOCPLINT->cb->getObjInstance(obj)->getObjGroupIndex(), LOCPLINT->cb->getObjInstance(obj)->getObjTypeIndex())) + if(selObj == LOCPLINT->cb->getObjInstance(obj)->getObjectName()) selVisitableObjInstances.push_back(obj); if(searchPos + 1 < selVisitableObjInstances.size() && searchLast == selObj) @@ -520,7 +510,7 @@ void AdventureMapShortcuts::search(bool next) auto objInst = LOCPLINT->cb->getObjInstance(selVisitableObjInstances[searchPos]); owner.centerOnObject(objInst); - searchLast = VLC->objtypeh->getObjectName(objInst->getObjGroupIndex(), objInst->getObjTypeIndex()); + searchLast = objInst->getObjectName(); }; if(next) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 8e404e34c..9837848db 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -479,6 +479,17 @@ std::vector CGameInfoCallback::getVisitableObjs(int3 return ret; } + +std::vector> CGameInfoCallback::getAllVisitableObjs() const +{ + std::vector> ret; + for(auto & obj : gs->map->objects) + if(obj->isVisitable() && obj->ID != Obj::EVENT && getTile(obj->pos, false)) + ret.push_back(obj); + + return ret; +} + const CGObjectInstance * CGameInfoCallback::getTopObj (int3 pos) const { return vstd::backOrNull(getVisitableObjs(pos)); diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 91c51b5a7..25f15d53e 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -11,6 +11,7 @@ #include "int3.h" #include "ResourceSet.h" // for Res +#include "ConstTransitivePtr.h" #define ASSERT_IF_CALLED_WITH_PLAYER if(!getPlayerID()) {logGlobal->error(BOOST_CURRENT_FUNCTION); assert(0);} @@ -189,6 +190,7 @@ public: const CGObjectInstance * getObj(ObjectInstanceID objid, bool verbose = true) const override; virtual std::vector getBlockingObjs(int3 pos)const; std::vector getVisitableObjs(int3 pos, bool verbose = true) const override; + std::vector> getAllVisitableObjs() const; virtual std::vector getFlaggableObjects(int3 pos) const; virtual const CGObjectInstance * getTopObj (int3 pos) const; virtual PlayerColor getOwner(ObjectInstanceID heroID) const; From f7e3641c04db1d4abf38467ab8e487bfc48eaca8 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:21:26 +0200 Subject: [PATCH 325/726] fix shortcut bug; add center of selected item --- client/windows/GUIClasses.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 7727afd56..d6ceea6bf 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1487,6 +1487,9 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, std::share images(images) { OBJECT_CONSTRUCTION; + + addUsedEvents(KEYBOARD); + items.reserve(_items.size()); for(int id : _items) @@ -1494,7 +1497,7 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, std::share itemsVisible = items; init(titleWidget_, _title, _descr, searchBoxEnabled); - list->scrollTo(initialSelection); + list->scrollTo(initialSelection - 4); // -4 is for centering (list have 9 elements) } CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images, bool searchBoxEnabled) @@ -1504,6 +1507,9 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, st images(images) { OBJECT_CONSTRUCTION; + + addUsedEvents(KEYBOARD); + items.reserve(_items.size()); for(size_t i=0; i<_items.size(); i++) @@ -1511,7 +1517,7 @@ CObjectListWindow::CObjectListWindow(const std::vector & _items, st itemsVisible = items; init(titleWidget_, _title, _descr, searchBoxEnabled); - list->scrollTo(initialSelection); + list->scrollTo(initialSelection - 4); // -4 is for centering (list have 9 elements) } void CObjectListWindow::init(std::shared_ptr titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled) @@ -1636,7 +1642,7 @@ void CObjectListWindow::keyPressed(EShortcut key) } vstd::abetween(sel, 0, itemsVisible.size()-1); - list->scrollTo(sel); + list->scrollTo(sel - 4); // -4 is for centering (list have 9 elements) changeSelection(sel); } From 640ef0ea1944fccd8a03542779b0354e6d390451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Zaj=C4=85c?= <4561094+ToRRent1812@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:57:58 +0200 Subject: [PATCH 326/726] Update polish.json Added new stuff --- Mods/vcmi/config/vcmi/polish.json | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/vcmi/polish.json index 2f8d0ce87..7f8840169 100644 --- a/Mods/vcmi/config/vcmi/polish.json +++ b/Mods/vcmi/config/vcmi/polish.json @@ -12,6 +12,9 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Przytłaczający", "vcmi.adventureMap.monsterThreat.levels.10" : "Śmiertelny", "vcmi.adventureMap.monsterThreat.levels.11" : "Nie do pokonania", + "vcmi.adventureMap.monsterLevel" : "\n\n%Jednostka %ATTACK_TYPE %LEVEL poziomu z miasta %TOWN", + "vcmi.adventureMap.monsterMeleeType" : "Walcząca wręcz", + "vcmi.adventureMap.monsterRangedType" : "Dystansowa", "vcmi.adventureMap.confirmRestartGame" : "Czy na pewno chcesz zrestartować grę?", "vcmi.adventureMap.noTownWithMarket" : "Brak dostępnego targowiska!", @@ -58,6 +61,13 @@ "vcmi.spellBook.search" : "szukaj...", + "vcmi.spellResearch.canNotAfford" : "Nie stać Cię na zastąpienie {%SPELL1} przez {%SPELL2}, ale za to możesz odrzucić ten czar i kontynuować badania.", + "vcmi.spellResearch.comeAgain" : "Badania zostały już przeprowadzone dzisiaj. Wróć jutro.", + "vcmi.spellResearch.pay" : "Czy chcesz zastąpić {%SPELL1} czarem {%SPELL2}? Czy odrzucić ten czar i kontynuować badania?", + "vcmi.spellResearch.research" : "Zamień zaklęcia", + "vcmi.spellResearch.skip" : "Kontynuuj badania", + "vcmi.spellResearch.abort" : "Anuluj", + "vcmi.mainMenu.serverConnecting" : "Łączenie...", "vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:", "vcmi.mainMenu.serverConnectionFailed" : "Połączenie nie powiodło się", @@ -142,6 +152,7 @@ "vcmi.client.errors.invalidMap" : "{Błędna mapa lub kampania}\n\nNie udało się stworzyć gry! Wybrana mapa lub kampania jest niepoprawna lub uszkodzona. Powód:\n%s", "vcmi.client.errors.missingCampaigns" : "{Brakujące pliki gry}\n\nPliki kampanii nie zostały znalezione! Możliwe że używasz niekompletnych lub uszkodzonych plików Heroes 3. Spróbuj ponownej instalacji plików gry.", "vcmi.server.errors.disconnected" : "{Błąd sieciowy}\n\nUtracono połączenie z serwerem!", + "vcmi.server.errors.playerLeft" : "{Rozłączenie z graczem}\n\n%s opuścił rozgrywkę!", //%s -> player color "vcmi.server.errors.existingProcess" : "Inny proces 'vcmiserver' został już uruchomiony, zakończ go nim przejdziesz dalej", "vcmi.server.errors.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}", "vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}", @@ -235,8 +246,10 @@ "vcmi.adventureOptions.borderScroll.help" : "{Przewijanie na brzegu mapy}\n\nPrzewijanie mapy przygody gdy kursor najeżdża na brzeg okna gry. Może być wyłączone poprzez przytrzymanie klawisza CTRL.", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Zarządzanie armią w panelu informacyjnym", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Zarządzanie armią w panelu informacyjnym}\n\nPozwala zarządzać jednostkami w panelu informacyjnym, zamiast przełączać między domyślnymi informacjami.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Przeciąganie mapy lewym kliknięciem", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Przeciąganie lewym", "vcmi.adventureOptions.leftButtonDrag.help" : "{Przeciąganie mapy lewym kliknięciem}\n\nUmożliwia przesuwanie mapy przygody poprzez przeciąganie myszy z wciśniętym lewym przyciskiem.", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Przeciąganie prawym", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Przeciąganie mapy prawym kliknięciem}\n\nUmożliwia przesuwanie mapy przygody poprzez przeciąganie myszy z wciśniętym prawym przyciskiem.", "vcmi.adventureOptions.smoothDragging.hover" : "'Pływające' przeciąganie mapy", "vcmi.adventureOptions.smoothDragging.help" : "{'Pływające' przeciąganie mapy}\n\nPrzeciąganie mapy następuje ze stopniowo zanikającym przyspieszeniem.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Pomiń efekty zanikania", @@ -337,6 +350,12 @@ "vcmi.heroWindow.openCommander.help" : "Wyświetla informacje o dowódcy przynależącym do tego bohatera", "vcmi.heroWindow.openBackpack.hover" : "Otwórz okno sakwy", "vcmi.heroWindow.openBackpack.help" : "Otwiera okno pozwalające łatwiej zarządzać artefaktami w sakwie", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Sortuj wg. wartości", + "vcmi.heroWindow.sortBackpackByCost.help" : "Sortuj artefakty w sakwie według wartości", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Sortuj wg. miejsc", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Sortuj artefakty w sakwie według umiejscowienia na ciele", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Sortuj wg. jakości", + "vcmi.heroWindow.sortBackpackByClass.help" : "Sortuj artefakty w sakwie według jakości: Skarb, Pomniejszy, Potężny, Relikt", "vcmi.tavernWindow.inviteHero" : "Zaproś bohatera", @@ -663,5 +682,7 @@ "core.bonus.WIDE_BREATH.name": "Szerokie zionięcie", "core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)", "core.bonus.DISINTEGRATE.name": "Rozpadanie", - "core.bonus.DISINTEGRATE.description": "Po śmierci nie pozostaje żaden trup" + "core.bonus.DISINTEGRATE.description": "Po śmierci nie pozostaje żaden trup", + "core.bonus.INVINCIBLE.name": "Niezwyciężony", + "core.bonus.INVINCIBLE.description": "Nic nie może mieć na niego wpływu" } From 4bdc5031866cd909f0fcdbd8044a3e4a010b86ea Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 13 Oct 2024 08:28:37 +0000 Subject: [PATCH 327/726] Workaround for prison hero reset on loading map in editor --- mapeditor/mapcontroller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index a30a90aa8..fc68f4602 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -151,8 +151,8 @@ void MapController::repairMap(CMap * map) const if(obj->ID == Obj::PRISON) { nih->typeName = "prison"; - nih->subTypeName = "prison"; - nih->subID = 0; + //nih->subTypeName = "prison"; + //nih->subID = 0; } if(nih->ID == Obj::HERO) //not prison nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); From 31095248ab49ac74f5ad740ba886f5c856e10261 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 13 Oct 2024 13:05:50 +0000 Subject: [PATCH 328/726] Removed typeName and subtypeName properties from CGObjectInstance --- AI/Nullkiller/Goals/CaptureObject.h | 2 +- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 2 +- AI/Nullkiller/Pathfinding/Actors.cpp | 2 +- .../AObjectTypeHandler.cpp | 2 -- lib/mapObjects/CGObjectInstance.cpp | 10 +++++++++ lib/mapObjects/CGObjectInstance.h | 13 +++++++---- lib/mapping/CMap.cpp | 2 +- lib/rmg/modificators/ObstaclePlacer.cpp | 2 +- lib/serializer/ESerializationVersion.h | 3 ++- mapeditor/inspector/inspector.cpp | 2 -- mapeditor/mapcontroller.cpp | 22 +------------------ test/map/MapComparer.cpp | 4 ++-- 12 files changed, 29 insertions(+), 37 deletions(-) diff --git a/AI/Nullkiller/Goals/CaptureObject.h b/AI/Nullkiller/Goals/CaptureObject.h index e219e37ec..2073cd2fe 100644 --- a/AI/Nullkiller/Goals/CaptureObject.h +++ b/AI/Nullkiller/Goals/CaptureObject.h @@ -31,7 +31,7 @@ namespace Goals { objid = obj->id.getNum(); tile = obj->visitablePos(); - name = obj->typeName; + name = obj->getTypeName(); } bool operator==(const CaptureObject & other) const override; diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index 8fe4851b2..a12489e99 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -30,7 +30,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * #if NKAI_TRACE_LEVEL >= 1 targetName = obj->getObjectName() + tile.toString(); #else - targetName = obj->typeName + tile.toString(); + targetName = obj->getTypeName() + tile.toString(); #endif } else diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index ae6b6446e..145283d48 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -440,7 +440,7 @@ int DwellingActor::getInitialTurn(bool waitForGrowth, int dayOfWeek) std::string DwellingActor::toString() const { - return dwelling->typeName + dwelling->visitablePos().toString(); + return dwelling->getTypeName() + dwelling->visitablePos().toString(); } CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth) diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.cpp b/lib/mapObjectConstructors/AObjectTypeHandler.cpp index c431900aa..d842d9953 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.cpp +++ b/lib/mapObjectConstructors/AObjectTypeHandler.cpp @@ -133,8 +133,6 @@ void AObjectTypeHandler::preInitObject(CGObjectInstance * obj) const { obj->ID = Obj(type); obj->subID = subtype; - obj->typeName = typeName; - obj->subTypeName = getJsonKey(); obj->blockVisit = blockVisit; obj->removable = removable; } diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index aa902a452..5c4da7da0 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -192,6 +192,16 @@ TObjectTypeHandler CGObjectInstance::getObjectHandler() const return VLC->objtypeh->getHandlerFor(ID, subID); } +std::string CGObjectInstance::getTypeName() const +{ + return getObjectHandler()->getTypeName(); +} + +std::string CGObjectInstance::getSubtypeName() const +{ + return getObjectHandler()->getSubTypeName(); +} + void CGObjectInstance::setPropertyDer( ObjProperty what, ObjPropertyID identifier ) {} diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index b5fdd7142..8df5bbba1 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -43,8 +43,6 @@ public: int3 pos; std::string instanceName; - std::string typeName; - std::string subTypeName; CGObjectInstance(IGameCallback *cb); ~CGObjectInstance() override; @@ -52,6 +50,9 @@ public: MapObjectID getObjGroupIndex() const override; MapObjectSubID getObjTypeIndex() const override; + std::string getTypeName() const; + std::string getSubtypeName() const; + /// "center" tile from which the sight distance is calculated int3 getSightCenter() const; /// If true hero can visit this object only from neighbouring tiles and can't stand on this object @@ -142,8 +143,12 @@ public: template void serialize(Handler &h) { h & instanceName; - h & typeName; - h & subTypeName; + if (h.version < Handler::Version::REMOVE_OBJECT_TYPENAME) + { + std::string unused; + h & unused; + h & unused; + } h & pos; h & ID; subID.serializeIdentifier(h, ID); diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 57e3a4c24..bf16f913b 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -608,7 +608,7 @@ void CMap::setUniqueInstanceName(CGObjectInstance * obj) auto uid = uidCounter++; boost::format fmt("%s_%d"); - fmt % obj->typeName % uid; + fmt % obj->getTypeName() % uid; obj->instanceName = fmt.str(); } diff --git a/lib/rmg/modificators/ObstaclePlacer.cpp b/lib/rmg/modificators/ObstaclePlacer.cpp index 456ddbeb0..5447a256e 100644 --- a/lib/rmg/modificators/ObstaclePlacer.cpp +++ b/lib/rmg/modificators/ObstaclePlacer.cpp @@ -153,7 +153,7 @@ void ObstaclePlacer::postProcess(const rmg::Object & object) riverManager = zone.getModificator(); if(riverManager) { - const auto objTypeName = object.instances().front()->object().typeName; + const auto objTypeName = object.instances().front()->object().getTypeName(); if(objTypeName == "mountain") riverManager->riverSource().unite(object.getArea()); else if(objTypeName == "lake") diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index d0fe579e4..7b4270bc9 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -64,6 +64,7 @@ enum class ESerializationVersion : int32_t SPELL_RESEARCH, // 865 - spell research LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance + REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance - CURRENT = REMOVE_TOWN_PTR + CURRENT = REMOVE_OBJECT_TYPENAME }; diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 4ad9c5eaf..d8daa2162 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -470,8 +470,6 @@ void Inspector::updateProperties() addProperty("ID", obj->ID.getNum()); addProperty("SubID", obj->subID); addProperty("InstanceName", obj->instanceName); - addProperty("TypeName", obj->typeName); - addProperty("SubTypeName", obj->subTypeName); if(obj->ID != Obj::HERO_PLACEHOLDER && !dynamic_cast(obj)) { diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index fc68f4602..8aa581a36 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -112,13 +112,6 @@ void MapController::repairMap(CMap * map) const allImpactedObjects.insert(allImpactedObjects.end(), map->predefinedHeroes.begin(), map->predefinedHeroes.end()); for(auto obj : allImpactedObjects) { - //setup proper names (hero name will be fixed later - if(obj->ID != Obj::HERO && obj->ID != Obj::PRISON && (obj->typeName.empty() || obj->subTypeName.empty())) - { - auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID); - obj->typeName = handler->getTypeName(); - obj->subTypeName = handler->getSubTypeName(); - } //fix flags if(obj->getOwner() == PlayerColor::UNFLAGGABLE) { @@ -142,18 +135,7 @@ void MapController::repairMap(CMap * map) const auto const & type = VLC->heroh->objects[nih->subID]; assert(type->heroClass); - //TODO: find a way to get proper type name - if(obj->ID == Obj::HERO) - { - nih->typeName = "hero"; - nih->subTypeName = type->heroClass->getJsonKey(); - } - if(obj->ID == Obj::PRISON) - { - nih->typeName = "prison"; - //nih->subTypeName = "prison"; - //nih->subID = 0; - } + if(nih->ID == Obj::HERO) //not prison nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front(); //fix spellbook @@ -568,8 +550,6 @@ bool MapController::canPlaceObject(int level, CGObjectInstance * newObj, QString if(newObj->ID == Obj::GRAIL && objCounter >= 1) //special case for grail { - auto typeName = QString::fromStdString(newObj->typeName); - auto subTypeName = QString::fromStdString(newObj->subTypeName); error = QObject::tr("There can only be one grail object on the map."); return false; //maplimit reached } diff --git a/test/map/MapComparer.cpp b/test/map/MapComparer.cpp index 28ace85c2..8d1744520 100644 --- a/test/map/MapComparer.cpp +++ b/test/map/MapComparer.cpp @@ -203,8 +203,8 @@ void MapComparer::compareObject(const CGObjectInstance * actual, const CGObjectI EXPECT_EQ(actual->instanceName, expected->instanceName); EXPECT_EQ(typeid(actual).name(), typeid(expected).name());//todo: remove and use just comparison - std::string actualFullID = boost::str(boost::format("%s(%d)|%s(%d) %d") % actual->typeName % actual->ID % actual->subTypeName % actual->subID % actual->tempOwner); - std::string expectedFullID = boost::str(boost::format("%s(%d)|%s(%d) %d") % expected->typeName % expected->ID % expected->subTypeName % expected->subID % expected->tempOwner); + std::string actualFullID = boost::str(boost::format("(%d)|(%d) %d") % actual->ID % actual->subID % actual->tempOwner); + std::string expectedFullID = boost::str(boost::format("(%d)|(%d) %d") % expected->ID % expected->subID % expected->tempOwner); EXPECT_EQ(actualFullID, expectedFullID); From 184d8de82e16ae8f2568c9fa9305e4e246a422ee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 13 Oct 2024 13:06:07 +0000 Subject: [PATCH 329/726] Fix typo in assertion --- client/PlayerLocalState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index 519c39c94..2b347cb27 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -347,7 +347,7 @@ void PlayerLocalState::deserialize(const JsonNode & source) { // this method must be called after player state has been initialized assert(currentSelection != nullptr); - assert(!ownedTowns.empty() || wanderingHeroes.empty()); + assert(!ownedTowns.empty() || !wanderingHeroes.empty()); auto oldHeroes = wanderingHeroes; auto oldTowns = ownedTowns; From ad7f0416a70b7118f651ed2f9a134bd2779c0663 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 13 Oct 2024 13:06:31 +0000 Subject: [PATCH 330/726] Added overloaded version of getObjectHandler to CGHeroInstance --- lib/gameState/CGameStateCampaign.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 10 +++++++++- lib/mapObjects/CGHeroInstance.h | 2 ++ lib/mapObjects/CGObjectInstance.cpp | 7 +++++-- lib/mapObjects/CGObjectInstance.h | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 2178da654..c7fe97e0b 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -370,7 +370,7 @@ void CGameStateCampaign::replaceHeroesPlaceholders() heroToPlace->tempOwner = heroPlaceholder->tempOwner; heroToPlace->setAnchorPos(heroPlaceholder->anchorPos()); heroToPlace->setHeroType(heroToPlace->getHeroTypeID()); - heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->getHeroTypeID())->getTemplates().front(); + heroToPlace->appearance = heroToPlace->getObjectHandler()->getTemplates().front(); gameState->map->removeBlockVisTiles(heroPlaceholder, true); gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index b31ff4190..f0ca7a91a 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -339,12 +339,20 @@ void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) initHero(rand); } +TObjectTypeHandler CGHeroInstance::getObjectHandler() const +{ + if (ID == Obj::HERO) + return VLC->objtypeh->getHandlerFor(ID, getHeroClass()->getIndex()); + else // prison or random hero + return VLC->objtypeh->getHandlerFor(ID, 0); +} + void CGHeroInstance::initHero(vstd::RNG & rand) { assert(validTypes(true)); if (ID == Obj::HERO) - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex())->getTemplates().front(); + appearance = getObjectHandler()->getTemplates().front(); if(!vstd::contains(spells, SpellID::PRESET)) { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index ebb8261f8..3b971b933 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -307,6 +307,8 @@ public: std::string getHoverText(PlayerColor player) const override; std::string getMovementPointsTextIfOwner(PlayerColor player) const; + TObjectTypeHandler getObjectHandler() const override; + void afterAddToMap(CMap * map) override; void afterRemoveFromMap(CMap * map) override; diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index 5c4da7da0..7d53bfbb9 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -360,8 +360,11 @@ void CGObjectInstance::serializeJson(JsonSerializeFormat & handler) //only save here, loading is handled by map loader if(handler.saving) { - handler.serializeString("type", typeName); - handler.serializeString("subtype", subTypeName); + std::string ourTypeName = getTypeName(); + std::string ourSubtypeName = getSubtypeName(); + + handler.serializeString("type", ourTypeName); + handler.serializeString("subtype", ourSubtypeName); handler.serializeInt("x", pos.x); handler.serializeInt("y", pos.y); diff --git a/lib/mapObjects/CGObjectInstance.h b/lib/mapObjects/CGObjectInstance.h index 8df5bbba1..c201dd4db 100644 --- a/lib/mapObjects/CGObjectInstance.h +++ b/lib/mapObjects/CGObjectInstance.h @@ -101,7 +101,7 @@ public: std::optional getVisitSound(vstd::RNG & rng) const; std::optional getRemovalSound(vstd::RNG & rng) const; - TObjectTypeHandler getObjectHandler() const; + virtual TObjectTypeHandler getObjectHandler() const; /** VIRTUAL METHODS **/ From 10ad0fc760341da62295b3790d9b6c0c16326f96 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Oct 2024 16:30:16 +0000 Subject: [PATCH 331/726] Split CHeroHandler.cpp/.h into 1 file per class All parts of CHeroHandler.cpp are now in lib/entities/hero Adjusted includes to use new paths No functionality changes --- AI/Nullkiller/AIGateway.cpp | 1 - AI/Nullkiller/AIUtility.cpp | 1 - AI/Nullkiller/Analyzers/HeroManager.cpp | 1 - AI/VCAI/AIUtility.cpp | 1 - AI/VCAI/MapObjectsEvaluator.cpp | 1 - AI/VCAI/VCAI.cpp | 1 - CCallback.cpp | 1 - client/CPlayerInterface.cpp | 1 - client/ClientCommandManager.cpp | 1 - client/NetPacksClient.cpp | 1 - client/adventureMap/CList.cpp | 1 - client/battle/BattleInterface.cpp | 1 - client/battle/BattleInterfaceClasses.cpp | 3 +- client/lobby/CBonusSelection.cpp | 2 +- client/lobby/CSelectionBase.cpp | 1 - client/lobby/OptionsTab.cpp | 3 +- client/mainmenu/CCampaignScreen.cpp | 1 - client/render/Graphics.cpp | 1 - client/widgets/CComponent.cpp | 9 +- client/widgets/markets/CMarketBase.cpp | 2 +- client/windows/CCreatureWindow.cpp | 2 +- client/windows/CExchangeWindow.cpp | 2 +- client/windows/CHeroOverview.cpp | 3 +- client/windows/CHeroWindow.cpp | 4 +- client/windows/CKingdomInterface.cpp | 2 +- client/windows/GUIClasses.cpp | 2 +- lib/CCreatureSet.cpp | 2 +- lib/CHeroHandler.h | 221 --------- lib/CMakeLists.txt | 11 +- lib/IGameCallback.cpp | 2 +- lib/StartInfo.cpp | 2 +- lib/VCMI_Lib.cpp | 3 +- lib/battle/BattleInfo.cpp | 1 - lib/battle/CObstacleInstance.cpp | 1 - lib/bonuses/Bonus.cpp | 12 +- lib/bonuses/Limiters.cpp | 1 - lib/constants/EntityIdentifiers.cpp | 3 +- lib/entities/faction/CTownHandler.cpp | 2 +- lib/entities/hero/CHero.cpp | 114 +++++ lib/entities/hero/CHero.h | 87 ++++ lib/entities/hero/CHeroClass.cpp | 126 +++++ lib/entities/hero/CHeroClass.h | 85 ++++ lib/entities/hero/CHeroClassHandler.cpp | 226 +++++++++ lib/entities/hero/CHeroClassHandler.h | 37 ++ lib/{ => entities/hero}/CHeroHandler.cpp | 429 +----------------- lib/entities/hero/CHeroHandler.h | 59 +++ lib/entities/hero/EHeroGender.h | 21 + lib/gameState/CGameState.cpp | 3 +- lib/gameState/CGameStateCampaign.cpp | 4 +- lib/gameState/GameStatistics.cpp | 1 - lib/gameState/InfoAboutArmy.cpp | 4 +- lib/gameState/TavernHeroesPool.cpp | 1 - lib/json/JsonRandom.cpp | 5 +- .../CommonConstructors.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 3 +- lib/mapObjects/CGHeroInstance.h | 2 +- lib/mapObjects/CQuest.cpp | 2 +- lib/mapping/CMap.cpp | 2 +- lib/mapping/CMapHeader.cpp | 2 +- lib/mapping/CMapInfo.cpp | 1 - lib/mapping/MapFormatH3M.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 2 +- lib/mapping/MapIdentifiersH3M.cpp | 1 - lib/modding/ContentTypeHandler.cpp | 3 +- lib/networkPacks/NetPacksLib.cpp | 1 - lib/rewardable/Interface.cpp | 2 +- lib/rewardable/Limiter.cpp | 1 - lib/rmg/CMapGenerator.cpp | 3 +- lib/serializer/CSerializer.cpp | 2 +- lib/serializer/RegisterTypes.h | 1 - lib/serializer/SerializerReflection.cpp | 1 + lib/spells/BonusCaster.cpp | 2 +- lib/spells/ISpellMechanics.cpp | 1 - lib/spells/effects/Summon.cpp | 1 - lib/texts/TextLocalizationContainer.cpp | 2 +- mapeditor/graphics.cpp | 2 +- mapeditor/inspector/inspector.cpp | 3 +- mapeditor/inspector/portraitwidget.cpp | 2 +- mapeditor/inspector/questwidget.cpp | 6 +- mapeditor/inspector/rewardswidget.cpp | 6 +- mapeditor/mapcontroller.cpp | 3 +- mapeditor/maphandler.cpp | 1 - mapeditor/mapsettings/abstractsettings.cpp | 1 - mapeditor/mapsettings/mapsettings.cpp | 6 +- mapeditor/validator.cpp | 2 +- server/CGameHandler.cpp | 2 +- server/CVCMIServer.cpp | 3 +- server/processors/HeroPoolProcessor.cpp | 3 +- server/processors/PlayerMessageProcessor.cpp | 2 +- test/entity/CHeroClassTest.cpp | 2 +- test/entity/CHeroTest.cpp | 2 +- 91 files changed, 867 insertions(+), 730 deletions(-) delete mode 100644 lib/CHeroHandler.h create mode 100644 lib/entities/hero/CHero.cpp create mode 100644 lib/entities/hero/CHero.h create mode 100644 lib/entities/hero/CHeroClass.cpp create mode 100644 lib/entities/hero/CHeroClass.h create mode 100644 lib/entities/hero/CHeroClassHandler.cpp create mode 100644 lib/entities/hero/CHeroClassHandler.h rename lib/{ => entities/hero}/CHeroHandler.cpp (50%) create mode 100644 lib/entities/hero/CHeroHandler.h create mode 100644 lib/entities/hero/EHeroGender.h diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 99172b213..a6f75d68d 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -17,7 +17,6 @@ #include "../../lib/mapObjects/ObjectTemplate.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/IGameSettings.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/serializer/CTypeList.h" diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 3e89e2ba2..a5daee212 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -14,7 +14,6 @@ #include "../../lib/UnlockGuard.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/mapObjects/MapObjects.h" #include "../../lib/mapping/CMapDefines.h" #include "../../lib/gameState/QuestInfo.h" diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index b6938ec1f..1f4b97c0c 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -11,7 +11,6 @@ #include "../StdInc.h" #include "../Engine/Nullkiller.h" #include "../../../lib/mapObjects/MapObjects.h" -#include "../../../lib/CHeroHandler.h" #include "../../../lib/IGameSettings.h" namespace NKAI diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index a7c4c2f7a..63a64c43b 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -15,7 +15,6 @@ #include "../../lib/UnlockGuard.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CQuest.h" #include "../../lib/mapping/CMapDefines.h" diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index e430c1f08..fa038725e 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -12,7 +12,6 @@ #include "../../lib/GameConstants.h" #include "../../lib/VCMI_Lib.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/mapObjects/CompoundMapObjectID.h" #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index f9ebb1657..462023437 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -20,7 +20,6 @@ #include "../../lib/mapObjects/MapObjects.h" #include "../../lib/mapObjects/ObjectTemplate.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/IGameSettings.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/bonuses/Limiters.h" diff --git a/CCallback.cpp b/CCallback.cpp index 72b6e8b78..61c741cc8 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -18,7 +18,6 @@ #include "lib/mapObjects/CGHeroInstance.h" #include "lib/mapObjects/CGTownInstance.h" #include "lib/texts/CGeneralTextHandler.h" -#include "lib/CHeroHandler.h" #include "lib/CArtHandler.h" #include "lib/GameConstants.h" #include "lib/CPlayerState.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 6133cdd64..abf5abb9a 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -67,7 +67,6 @@ #include "../lib/CConfigHandler.h" #include "../lib/texts/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" #include "../lib/CRandomGenerator.h" #include "../lib/CStack.h" diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 775c9b806..203ea1c68 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -36,7 +36,6 @@ #include "../lib/modding/CModHandler.h" #include "../lib/modding/ContentTypeHandler.h" #include "../lib/modding/ModUtility.h" -#include "../lib/CHeroHandler.h" #include "../lib/VCMIDirs.h" #include "../lib/logging/VisualLogger.h" #include "../lib/serializer/Connection.h" diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 91c8833b2..1c94e0d7d 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -32,7 +32,6 @@ #include "../lib/filesystem/FileInfo.h" #include "../lib/serializer/Connection.h" #include "../lib/texts/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/VCMI_Lib.h" #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" diff --git a/client/adventureMap/CList.cpp b/client/adventureMap/CList.cpp index e730224b4..121853931 100644 --- a/client/adventureMap/CList.cpp +++ b/client/adventureMap/CList.cpp @@ -29,7 +29,6 @@ #include "../render/Colors.h" #include "../../lib/texts/CGeneralTextHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/IGameSettings.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index cea4410d5..3017eff04 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -39,7 +39,6 @@ #include "../../lib/CStack.h" #include "../../lib/CConfigHandler.h" #include "../../lib/texts/CGeneralTextHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForClientBattle.h" diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 5cf6aa340..add607dad 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -50,10 +50,11 @@ #include "../../lib/CStack.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" +#include "../../lib/entities/hero/CHeroClass.h" +#include "../../lib/entities/hero/CHero.h" #include "../../lib/gameState/InfoAboutArmy.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/texts/TextOperations.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/StartInfo.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/networkPacks/PacksForClientBattle.h" diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index d1db2d671..32ceab982 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -41,7 +41,6 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/CSkillHandler.h" #include "../../lib/StartInfo.h" #include "../../lib/entities/building/CBuilding.h" @@ -49,6 +48,7 @@ #include "../../lib/entities/faction/CFaction.h" #include "../../lib/entities/faction/CTown.h" #include "../../lib/entities/faction/CTownHandler.h" +#include "../../lib/entities/hero/CHeroHandler.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/texts/CGeneralTextHandler.h" diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 9a767f5f7..13d79d672 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -43,7 +43,6 @@ #include "../render/IFont.h" #include "../render/IRenderHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/CRandomGenerator.h" #include "../../lib/CThreadHelper.h" #include "../../lib/filesystem/Filesystem.h" diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 002904f9f..3aa94542e 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -39,12 +39,13 @@ #include "../../lib/entities/faction/CFaction.h" #include "../../lib/entities/faction/CTown.h" #include "../../lib/entities/faction/CTownHandler.h" +#include "../../lib/entities/hero/CHeroHandler.h" +#include "../../lib/entities/hero/CHeroClass.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/networkPacks/PacksForLobby.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/CArtHandler.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/mapping/CMapInfo.h" #include "../../lib/mapping/CMapHeader.h" diff --git a/client/mainmenu/CCampaignScreen.cpp b/client/mainmenu/CCampaignScreen.cpp index 4644ba18d..1957ce197 100644 --- a/client/mainmenu/CCampaignScreen.cpp +++ b/client/mainmenu/CCampaignScreen.cpp @@ -37,7 +37,6 @@ #include "../../lib/spells/CSpellHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CSkillHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/campaign/CampaignHandler.h" diff --git a/client/render/Graphics.cpp b/client/render/Graphics.cpp index 0c88f508a..9792e83e7 100644 --- a/client/render/Graphics.cpp +++ b/client/render/Graphics.cpp @@ -28,7 +28,6 @@ #include "../lib/modding/CModHandler.h" #include "../lib/modding/ModScope.h" #include "../lib/VCMI_Lib.h" -#include "../lib/CHeroHandler.h" #include diff --git a/client/widgets/CComponent.cpp b/client/widgets/CComponent.cpp index f02ba67b9..80b800492 100644 --- a/client/widgets/CComponent.cpp +++ b/client/widgets/CComponent.cpp @@ -12,9 +12,6 @@ #include "Images.h" -#include -#include - #include "../gui/CGuiHandler.h" #include "../gui/CursorHandler.h" #include "../gui/TextAlignment.h" @@ -29,7 +26,6 @@ #include "../CGameInfo.h" #include "../../lib/ArtifactUtils.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/entities/building/CBuilding.h" #include "../../lib/entities/faction/CFaction.h" #include "../../lib/entities/faction/CTown.h" @@ -42,6 +38,11 @@ #include "../../lib/CArtHandler.h" #include "../../lib/CArtifactInstance.h" +#include +#include +#include +#include + CComponent::CComponent(ComponentType Type, ComponentSubType Subtype, std::optional Val, ESize imageSize, EFonts font) { init(Type, Subtype, Val, imageSize, font, ""); diff --git a/client/widgets/markets/CMarketBase.cpp b/client/widgets/markets/CMarketBase.cpp index 02574dc67..8c6d78ec8 100644 --- a/client/widgets/markets/CMarketBase.cpp +++ b/client/widgets/markets/CMarketBase.cpp @@ -23,9 +23,9 @@ #include "../../../CCallback.h" +#include "../../../lib/entities/hero/CHeroHandler.h" #include "../../../lib/texts/CGeneralTextHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -#include "../../../lib/CHeroHandler.h" #include "../../../lib/mapObjects/CGMarket.h" CMarketBase::CMarketBase(const IMarket * market, const CGHeroInstance * hero) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 6c5612d5d..293324c3e 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -31,7 +31,7 @@ #include "../../lib/CStack.h" #include "../../lib/CBonusTypeHandler.h" #include "../../lib/IGameSettings.h" -#include "../../lib/CHeroHandler.h" +#include "../../lib/entities/hero/CHeroHandler.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/networkPacks/ArtifactLocation.h" #include "../../lib/texts/CGeneralTextHandler.h" diff --git a/client/windows/CExchangeWindow.cpp b/client/windows/CExchangeWindow.cpp index 0bd8d74a3..197ef9795 100644 --- a/client/windows/CExchangeWindow.cpp +++ b/client/windows/CExchangeWindow.cpp @@ -29,8 +29,8 @@ #include "../../CCallback.h" -#include "../lib/CHeroHandler.h" #include "../lib/CSkillHandler.h" +#include "../lib/entities/hero/CHeroHandler.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/texts/CGeneralTextHandler.h" diff --git a/client/windows/CHeroOverview.cpp b/client/windows/CHeroOverview.cpp index b1719df4d..6043aefac 100644 --- a/client/windows/CHeroOverview.cpp +++ b/client/windows/CHeroOverview.cpp @@ -24,9 +24,10 @@ #include "../widgets/GraphicalPrimitiveCanvas.h" #include "../../lib/IGameSettings.h" +#include "../../lib/entities/hero/CHeroHandler.h" +#include "../../lib/entities/hero/CHeroClass.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/CSkillHandler.h" #include "../../lib/spells/CSpellHandler.h" diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 809f937f0..9599fb567 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -35,11 +35,11 @@ #include "../lib/ArtifactUtils.h" #include "../lib/CArtHandler.h" #include "../lib/CConfigHandler.h" +#include "../lib/entities/hero/CHeroHandler.h" #include "../lib/texts/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/CSkillHandler.h" #include "../lib/mapObjects/CGHeroInstance.h" -#include "../../lib/networkPacks/ArtifactLocation.h" +#include "../lib/networkPacks/ArtifactLocation.h" void CHeroSwitcher::clickPressed(const Point & cursorPosition) { diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 6c99bbca6..0d754741f 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -33,8 +33,8 @@ #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" +#include "../../lib/entities/hero/CHeroHandler.h" #include "../../lib/texts/CGeneralTextHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/IGameSettings.h" #include "../../lib/CSkillHandler.h" #include "../../lib/StartInfo.h" diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 46406d4d4..32d485a1f 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -44,6 +44,7 @@ #include "../lib/entities/building/CBuilding.h" #include "../lib/entities/faction/CTownHandler.h" +#include "../lib/entities/hero/CHeroHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/mapObjectConstructors/CommonConstructors.h" #include "../lib/mapObjects/CGHeroInstance.h" @@ -53,7 +54,6 @@ #include "../lib/gameState/SThievesGuildInfo.h" #include "../lib/gameState/TavernHeroesPool.h" #include "../lib/texts/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/IGameSettings.h" #include "ConditionalWait.h" #include "../lib/CRandomGenerator.h" diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 5de5a3420..3a9d971dc 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -15,12 +15,12 @@ #include "CCreatureHandler.h" #include "VCMI_Lib.h" #include "IGameSettings.h" +#include "entities/hero/CHeroHandler.h" #include "mapObjects/CGHeroInstance.h" #include "modding/ModScope.h" #include "IGameCallback.h" #include "texts/CGeneralTextHandler.h" #include "spells/CSpellHandler.h" -#include "CHeroHandler.h" #include "IBonusTypeHandler.h" #include "serializer/JsonSerializeFormat.h" diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h deleted file mode 100644 index c2bd9d220..000000000 --- a/lib/CHeroHandler.h +++ /dev/null @@ -1,221 +0,0 @@ -/* - * CHeroHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include -#include - -#include "ConstTransitivePtr.h" -#include "GameConstants.h" -#include "bonuses/Bonus.h" -#include "bonuses/BonusList.h" -#include "IHandlerBase.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace vstd -{ -class RNG; -} - -class CHeroClass; -class CGHeroInstance; -struct BattleHex; -class JsonNode; -class JsonSerializeFormat; -class BattleField; - -enum class EHeroGender : int8_t -{ - DEFAULT = -1, // from h3m, instance has same gender as hero type - MALE = 0, - FEMALE = 1, -}; - -class DLL_LINKAGE CHero : public HeroType -{ - friend class CHeroHandler; - - HeroTypeID ID; - std::string identifier; - std::string modScope; - -public: - struct InitialArmyStack - { - ui32 minAmount; - ui32 maxAmount; - CreatureID creature; - }; - si32 imageIndex = 0; - - std::vector initialArmy; - - const CHeroClass * heroClass = nullptr; - std::vector > secSkillsInit; //initial secondary skills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert) - BonusList specialty; - std::set spells; - bool haveSpellBook = false; - bool special = false; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes - bool onlyOnWaterMap; // hero will be placed only if the map contains water - bool onlyOnMapWithoutWater; // hero will be placed only if the map does not contain water - EHeroGender gender = EHeroGender::MALE; // default sex: 0=male, 1=female - - /// Graphics - std::string iconSpecSmall; - std::string iconSpecLarge; - std::string portraitSmall; - std::string portraitLarge; - AnimationPath battleImage; - - CHero(); - virtual ~CHero(); - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - HeroTypeID getId() const override; - void registerIcons(const IconRegistar & cb) const override; - - std::string getNameTranslated() const override; - std::string getBiographyTranslated() const override; - std::string getSpecialtyNameTranslated() const override; - std::string getSpecialtyDescriptionTranslated() const override; - std::string getSpecialtyTooltipTranslated() const override; - - std::string getNameTextID() const override; - std::string getBiographyTextID() const override; - std::string getSpecialtyNameTextID() const override; - std::string getSpecialtyDescriptionTextID() const override; - std::string getSpecialtyTooltipTextID() const override; - - void updateFrom(const JsonNode & data); - void serializeJson(JsonSerializeFormat & handler); -}; - -class DLL_LINKAGE CHeroClass : public HeroClass -{ - friend class CHeroClassHandler; - HeroClassID id; // use getId instead - std::string modScope; - std::string identifier; // use getJsonKey instead - -public: - enum EClassAffinity - { - MIGHT, - MAGIC - }; - - //double aggression; // not used in vcmi. - FactionID faction; - ui8 affinity; // affinity, using EClassAffinity enum - - // default chance for hero of specific class to appear in tavern, if field "tavern" was not set - // resulting chance = sqrt(town.chance * heroClass.chance) - ui32 defaultTavernChance; - - CreatureID commander; - - std::vector primarySkillInitial; // initial primary skills - std::vector primarySkillLowLevel; // probability (%) of getting point of primary skill when getting level - std::vector primarySkillHighLevel;// same for high levels (> 10) - - std::map secSkillProbability; //probabilities of gaining secondary skills (out of 112), in id order - - std::map selectionProbability; //probability of selection in towns - - AnimationPath imageBattleMale; - AnimationPath imageBattleFemale; - std::string imageMapMale; - std::string imageMapFemale; - - CHeroClass(); - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - HeroClassID getId() const override; - void registerIcons(const IconRegistar & cb) const override; - - std::string getNameTranslated() const override; - std::string getNameTextID() const override; - - bool isMagicHero() const; - SecondarySkill chooseSecSkill(const std::set & possibles, vstd::RNG & rand) const; //picks secondary skill out from given possibilities - - void updateFrom(const JsonNode & data); - void serializeJson(JsonSerializeFormat & handler); - - EAlignment getAlignment() const; - - int tavernProbability(FactionID faction) const; -}; - -class DLL_LINKAGE CHeroClassHandler : public CHandlerBase -{ - void fillPrimarySkillData(const JsonNode & node, CHeroClass * heroClass, PrimarySkill pSkill) const; - -public: - std::vector loadLegacyData() override; - - void afterLoadFinalization() override; - - ~CHeroClassHandler(); - -protected: - const std::vector & getTypeNames() const override; - std::shared_ptr loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) override; - -}; - -class DLL_LINKAGE CHeroHandler : public CHandlerBase -{ - /// expPerLEvel[i] is amount of exp needed to reach level i; - /// consists of 196 values. Any higher levels require experience larger that TExpType can hold - std::vector expPerLevel; - - /// helpers for loading to avoid huge load functions - void loadHeroArmy(CHero * hero, const JsonNode & node) const; - void loadHeroSkills(CHero * hero, const JsonNode & node) const; - void loadHeroSpecialty(CHero * hero, const JsonNode & node); - - void loadExperience(); - - std::vector> callAfterLoadFinalization; - -public: - ui32 level(TExpType experience) const; //calculates level corresponding to given experience amount - TExpType reqExp(ui32 level) const; //calculates experience required for given level - ui32 maxSupportedLevel() const; - - std::vector loadLegacyData() override; - - void beforeValidate(JsonNode & object) override; - void loadObject(std::string scope, std::string name, const JsonNode & data) override; - void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; - void afterLoadFinalization() override; - - CHeroHandler(); - ~CHeroHandler(); - - std::set getDefaultAllowed() const; - -protected: - const std::vector & getTypeNames() const override; - std::shared_ptr loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) override; -}; - -VCMI_LIB_NAMESPACE_END diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 37cb28106..0db2f9e50 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -87,6 +87,10 @@ set(lib_MAIN_SRCS entities/faction/CFaction.cpp entities/faction/CTown.cpp entities/faction/CTownHandler.cpp + entities/hero/CHero.cpp + entities/hero/CHeroClass.cpp + entities/hero/CHeroClassHandler.cpp + entities/hero/CHeroHandler.cpp events/ApplyDamage.cpp events/GameResumed.cpp @@ -270,7 +274,6 @@ set(lib_MAIN_SRCS CCreatureSet.cpp CGameInfoCallback.cpp CGameInterface.cpp - CHeroHandler.cpp CPlayerState.cpp CRandomGenerator.cpp CScriptingModule.cpp @@ -458,6 +461,11 @@ set(lib_MAIN_HEADERS entities/faction/CFaction.h entities/faction/CTown.h entities/faction/CTownHandler.h + entities/hero/CHero.h + entities/hero/CHeroClass.h + entities/hero/CHeroClassHandler.h + entities/hero/CHeroHandler.h + entities/hero/EHeroGender.h events/ApplyDamage.h events/GameResumed.h @@ -683,7 +691,6 @@ set(lib_MAIN_HEADERS CCreatureSet.h CGameInfoCallback.h CGameInterface.h - CHeroHandler.h ConstTransitivePtr.h Color.h CPlayerState.h diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index fa73d0101..cb32dcb50 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "IGameCallback.h" -#include "CHeroHandler.h" // for CHeroHandler #include "spells/CSpellHandler.h"// for CSpell #include "CSkillHandler.h"// for CSkill #include "CBonusTypeHandler.h" @@ -20,6 +19,7 @@ #include "bonuses/Propagators.h" #include "bonuses/Updaters.h" #include "entities/building/CBuilding.h" +#include "entities/hero/CHero.h" #include "networkPacks/ArtifactLocation.h" #include "serializer/CLoadFile.h" #include "serializer/CSaveFile.h" diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index b1abfe7b6..f6ca7d6cc 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -11,10 +11,10 @@ #include "StartInfo.h" #include "texts/CGeneralTextHandler.h" -#include "CHeroHandler.h" #include "VCMI_Lib.h" #include "entities/faction/CFaction.h" #include "entities/faction/CTownHandler.h" +#include "entities/hero/CHeroHandler.h" #include "rmg/CMapGenOptions.h" #include "mapping/CMapInfo.h" #include "campaign/CampaignState.h" diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index c516947bd..91d3da34b 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -14,7 +14,6 @@ #include "CArtHandler.h" #include "CBonusTypeHandler.h" #include "CCreatureHandler.h" -#include "CHeroHandler.h" #include "CConfigHandler.h" #include "RoadHandler.h" #include "RiverHandler.h" @@ -23,6 +22,8 @@ #include "spells/effects/Registry.h" #include "CSkillHandler.h" #include "entities/faction/CTownHandler.h" +#include "entities/hero/CHeroClassHandler.h" +#include "entities/hero/CHeroHandler.h" #include "texts/CGeneralTextHandler.h" #include "modding/CModHandler.h" #include "modding/CModInfo.h" diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 5bbebc49c..8a8eb633c 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -15,7 +15,6 @@ #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" #include "../CStack.h" -#include "../CHeroHandler.h" #include "../entities/building/TownFortifications.h" #include "../filesystem/Filesystem.h" #include "../mapObjects/CGTownInstance.h" diff --git a/lib/battle/CObstacleInstance.cpp b/lib/battle/CObstacleInstance.cpp index 098d980a2..6bb5c65f3 100644 --- a/lib/battle/CObstacleInstance.cpp +++ b/lib/battle/CObstacleInstance.cpp @@ -9,7 +9,6 @@ */ #include "StdInc.h" #include "CObstacleInstance.h" -#include "../CHeroHandler.h" #include "../ObstacleHandler.h" #include "../VCMI_Lib.h" diff --git a/lib/bonuses/Bonus.cpp b/lib/bonuses/Bonus.cpp index 2f1cb519b..a11e794f4 100644 --- a/lib/bonuses/Bonus.cpp +++ b/lib/bonuses/Bonus.cpp @@ -14,18 +14,18 @@ #include "Updaters.h" #include "Propagators.h" -#include "../VCMI_Lib.h" -#include "../spells/CSpellHandler.h" +#include "../CArtHandler.h" #include "../CCreatureHandler.h" #include "../CCreatureSet.h" -#include "../CHeroHandler.h" -#include "../texts/CGeneralTextHandler.h" #include "../CSkillHandler.h" -#include "../CArtHandler.h" #include "../TerrainHandler.h" -#include "../constants/StringConstants.h" +#include "../VCMI_Lib.h" #include "../battle/BattleInfo.h" +#include "../constants/StringConstants.h" +#include "../entities/hero/CHero.h" #include "../modding/ModUtility.h" +#include "../spells/CSpellHandler.h" +#include "../texts/CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 914081139..0fa15f9d4 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -17,7 +17,6 @@ #include "../spells/CSpellHandler.h" #include "../CCreatureHandler.h" #include "../CCreatureSet.h" -#include "../CHeroHandler.h" #include "../texts/CGeneralTextHandler.h" #include "../CSkillHandler.h" #include "../CStack.h" diff --git a/lib/constants/EntityIdentifiers.cpp b/lib/constants/EntityIdentifiers.cpp index 599920523..2c3f183c5 100644 --- a/lib/constants/EntityIdentifiers.cpp +++ b/lib/constants/EntityIdentifiers.cpp @@ -29,12 +29,13 @@ #include "modding/IdentifierStorage.h" #include "modding/ModScope.h" #include "VCMI_Lib.h" -#include "CHeroHandler.h" #include "CArtHandler.h"//todo: remove #include "CCreatureHandler.h"//todo: remove #include "spells/CSpellHandler.h" //todo: remove #include "CSkillHandler.h"//todo: remove #include "entities/faction/CFaction.h" +#include "entities/hero/CHero.h" +#include "entities/hero/CHeroClass.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "constants/StringConstants.h" #include "texts/CGeneralTextHandler.h" diff --git a/lib/entities/faction/CTownHandler.cpp b/lib/entities/faction/CTownHandler.cpp index 09a99ed71..91d05852b 100644 --- a/lib/entities/faction/CTownHandler.cpp +++ b/lib/entities/faction/CTownHandler.cpp @@ -13,9 +13,9 @@ #include "CTown.h" #include "CFaction.h" #include "../building/CBuilding.h" +#include "../hero/CHeroClassHandler.h" #include "../../CCreatureHandler.h" -#include "../../CHeroHandler.h" #include "../../IGameSettings.h" #include "../../TerrainHandler.h" #include "../../VCMI_Lib.h" diff --git a/lib/entities/hero/CHero.cpp b/lib/entities/hero/CHero.cpp new file mode 100644 index 000000000..4b9b363f1 --- /dev/null +++ b/lib/entities/hero/CHero.cpp @@ -0,0 +1,114 @@ +/* + * CHero.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "CHero.h" + +#include "../../VCMI_Lib.h" +#include "../../texts/CGeneralTextHandler.h" + +VCMI_LIB_NAMESPACE_BEGIN + +CHero::CHero() = default; +CHero::~CHero() = default; + +int32_t CHero::getIndex() const +{ + return ID.getNum(); +} + +int32_t CHero::getIconIndex() const +{ + return imageIndex; +} + +std::string CHero::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string CHero::getModScope() const +{ + return modScope; +} + +HeroTypeID CHero::getId() const +{ + return ID; +} + +std::string CHero::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CHero::getBiographyTranslated() const +{ + return VLC->generaltexth->translate(getBiographyTextID()); +} + +std::string CHero::getSpecialtyNameTranslated() const +{ + return VLC->generaltexth->translate(getSpecialtyNameTextID()); +} + +std::string CHero::getSpecialtyDescriptionTranslated() const +{ + return VLC->generaltexth->translate(getSpecialtyDescriptionTextID()); +} + +std::string CHero::getSpecialtyTooltipTranslated() const +{ + return VLC->generaltexth->translate(getSpecialtyTooltipTextID()); +} + +std::string CHero::getNameTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "name").get(); +} + +std::string CHero::getBiographyTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "biography").get(); +} + +std::string CHero::getSpecialtyNameTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "specialty", "name").get(); +} + +std::string CHero::getSpecialtyDescriptionTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "specialty", "description").get(); +} + +std::string CHero::getSpecialtyTooltipTextID() const +{ + return TextIdentifier("hero", modScope, identifier, "specialty", "tooltip").get(); +} + +void CHero::registerIcons(const IconRegistar & cb) const +{ + cb(getIconIndex(), 0, "UN32", iconSpecSmall); + cb(getIconIndex(), 0, "UN44", iconSpecLarge); + cb(getIconIndex(), 0, "PORTRAITSLARGE", portraitLarge); + cb(getIconIndex(), 0, "PORTRAITSSMALL", portraitSmall); +} + +void CHero::updateFrom(const JsonNode & data) +{ + //todo: CHero::updateFrom +} + +void CHero::serializeJson(JsonSerializeFormat & handler) +{ + +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/entities/hero/CHero.h b/lib/entities/hero/CHero.h new file mode 100644 index 000000000..1b96cc341 --- /dev/null +++ b/lib/entities/hero/CHero.h @@ -0,0 +1,87 @@ +/* + * CHero.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include + +#include "EHeroGender.h" + +#include "../../bonuses/BonusList.h" +#include "../../constants/EntityIdentifiers.h" +#include "../../filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE CHero : public HeroType +{ + friend class CHeroHandler; + + HeroTypeID ID; + std::string identifier; + std::string modScope; + +public: + struct InitialArmyStack + { + ui32 minAmount; + ui32 maxAmount; + CreatureID creature; + }; + si32 imageIndex = 0; + + std::vector initialArmy; + + const CHeroClass * heroClass = nullptr; + + //initial secondary skills; first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert) + std::vector> secSkillsInit; + + BonusList specialty; + std::set spells; + bool haveSpellBook = false; + bool special = false; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes + bool onlyOnWaterMap; // hero will be placed only if the map contains water + bool onlyOnMapWithoutWater; // hero will be placed only if the map does not contain water + EHeroGender gender = EHeroGender::MALE; // default sex: 0=male, 1=female + + /// Graphics + std::string iconSpecSmall; + std::string iconSpecLarge; + std::string portraitSmall; + std::string portraitLarge; + AnimationPath battleImage; + + CHero(); + virtual ~CHero(); + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + HeroTypeID getId() const override; + void registerIcons(const IconRegistar & cb) const override; + + std::string getNameTranslated() const override; + std::string getBiographyTranslated() const override; + std::string getSpecialtyNameTranslated() const override; + std::string getSpecialtyDescriptionTranslated() const override; + std::string getSpecialtyTooltipTranslated() const override; + + std::string getNameTextID() const override; + std::string getBiographyTextID() const override; + std::string getSpecialtyNameTextID() const override; + std::string getSpecialtyDescriptionTextID() const override; + std::string getSpecialtyTooltipTextID() const override; + + void updateFrom(const JsonNode & data); + void serializeJson(JsonSerializeFormat & handler); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/entities/hero/CHeroClass.cpp b/lib/entities/hero/CHeroClass.cpp new file mode 100644 index 000000000..da6355e70 --- /dev/null +++ b/lib/entities/hero/CHeroClass.cpp @@ -0,0 +1,126 @@ +/* + * CHeroClass.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "CHeroClass.h" + +#include "../faction/CFaction.h" + +#include "../../VCMI_Lib.h" +#include "../../texts/CGeneralTextHandler.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +SecondarySkill CHeroClass::chooseSecSkill(const std::set & possibles, vstd::RNG & rand) const //picks secondary skill out from given possibilities +{ + assert(!possibles.empty()); + + if (possibles.size() == 1) + return *possibles.begin(); + + int totalProb = 0; + for(const auto & possible : possibles) + if (secSkillProbability.count(possible) != 0) + totalProb += secSkillProbability.at(possible); + + if (totalProb == 0) // may trigger if set contains only banned skills (0 probability) + return *RandomGeneratorUtil::nextItem(possibles, rand); + + auto ran = rand.nextInt(totalProb - 1); + for(const auto & possible : possibles) + { + if (secSkillProbability.count(possible) != 0) + ran -= secSkillProbability.at(possible); + + if(ran < 0) + return possible; + } + + assert(0); // should not be possible + return *possibles.begin(); +} + +bool CHeroClass::isMagicHero() const +{ + return affinity == MAGIC; +} + +int CHeroClass::tavernProbability(FactionID targetFaction) const +{ + auto it = selectionProbability.find(targetFaction); + if (it != selectionProbability.end()) + return it->second; + return 0; +} + +EAlignment CHeroClass::getAlignment() const +{ + return faction.toEntity(VLC)->getAlignment(); +} + +int32_t CHeroClass::getIndex() const +{ + return id.getNum(); +} + +int32_t CHeroClass::getIconIndex() const +{ + return getIndex(); +} + +std::string CHeroClass::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string CHeroClass::getModScope() const +{ + return modScope; +} + +HeroClassID CHeroClass::getId() const +{ + return id; +} + +void CHeroClass::registerIcons(const IconRegistar & cb) const +{ + +} + +std::string CHeroClass::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +std::string CHeroClass::getNameTextID() const +{ + return TextIdentifier("heroClass", modScope, identifier, "name").get(); +} + +void CHeroClass::updateFrom(const JsonNode & data) +{ + //TODO: CHeroClass::updateFrom +} + +void CHeroClass::serializeJson(JsonSerializeFormat & handler) +{ + +} + +CHeroClass::CHeroClass(): + faction(0), + affinity(0), + defaultTavernChance(0) +{ +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/entities/hero/CHeroClass.h b/lib/entities/hero/CHeroClass.h new file mode 100644 index 000000000..0d4edc5a6 --- /dev/null +++ b/lib/entities/hero/CHeroClass.h @@ -0,0 +1,85 @@ +/* + * CHeroClass.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include + +#include "../../constants/EntityIdentifiers.h" +#include "../../constants/Enumerations.h" +#include "../../filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace vstd +{ +class RNG; +} + +class DLL_LINKAGE CHeroClass : public HeroClass +{ + friend class CHeroClassHandler; + HeroClassID id; // use getId instead + std::string modScope; + std::string identifier; // use getJsonKey instead + +public: + enum EClassAffinity + { + MIGHT, + MAGIC + }; + + //double aggression; // not used in vcmi. + FactionID faction; + ui8 affinity; // affinity, using EClassAffinity enum + + // default chance for hero of specific class to appear in tavern, if field "tavern" was not set + // resulting chance = sqrt(town.chance * heroClass.chance) + ui32 defaultTavernChance; + + CreatureID commander; + + std::vector primarySkillInitial; // initial primary skills + std::vector primarySkillLowLevel; // probability (%) of getting point of primary skill when getting level + std::vector primarySkillHighLevel; // same for high levels (> 10) + + std::map secSkillProbability; //probabilities of gaining secondary skills (out of 112), in id order + + std::map selectionProbability; //probability of selection in towns + + AnimationPath imageBattleMale; + AnimationPath imageBattleFemale; + std::string imageMapMale; + std::string imageMapFemale; + + CHeroClass(); + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + HeroClassID getId() const override; + void registerIcons(const IconRegistar & cb) const override; + + std::string getNameTranslated() const override; + std::string getNameTextID() const override; + + bool isMagicHero() const; + SecondarySkill chooseSecSkill(const std::set & possibles, vstd::RNG & rand) const; //picks secondary skill out from given possibilities + + void updateFrom(const JsonNode & data); + void serializeJson(JsonSerializeFormat & handler); + + EAlignment getAlignment() const; + + int tavernProbability(FactionID faction) const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/entities/hero/CHeroClassHandler.cpp b/lib/entities/hero/CHeroClassHandler.cpp new file mode 100644 index 000000000..e9207a229 --- /dev/null +++ b/lib/entities/hero/CHeroClassHandler.cpp @@ -0,0 +1,226 @@ +/* + * CHeroClassHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "CHeroClassHandler.h" + +#include "CHeroClass.h" + +#include "../faction/CTown.h" +#include "../faction/CTownHandler.h" + +#include "../../CSkillHandler.h" +#include "../../IGameSettings.h" +#include "../../VCMI_Lib.h" +#include "../../constants/StringConstants.h" +#include "../../json/JsonNode.h" +#include "../../mapObjectConstructors/AObjectTypeHandler.h" +#include "../../mapObjectConstructors/CObjectClassesHandler.h" +#include "../../modding/IdentifierStorage.h" +#include "../../texts/CGeneralTextHandler.h" +#include "../../texts/CLegacyConfigParser.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * heroClass, PrimarySkill pSkill) const +{ + const auto & skillName = NPrimarySkill::names[pSkill.getNum()]; + auto currentPrimarySkillValue = static_cast(node["primarySkills"][skillName].Integer()); + int primarySkillLegalMinimum = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[pSkill.getNum()]; + + if(currentPrimarySkillValue < primarySkillLegalMinimum) + { + logMod->error("Hero class '%s' has incorrect initial value '%d' for skill '%s'. Value '%d' will be used instead.", + heroClass->getNameTranslated(), currentPrimarySkillValue, skillName, primarySkillLegalMinimum); + currentPrimarySkillValue = primarySkillLegalMinimum; + } + heroClass->primarySkillInitial.push_back(currentPrimarySkillValue); + heroClass->primarySkillLowLevel.push_back(static_cast(node["lowLevelChance"][skillName].Float())); + heroClass->primarySkillHighLevel.push_back(static_cast(node["highLevelChance"][skillName].Float())); +} + +const std::vector & CHeroClassHandler::getTypeNames() const +{ + static const std::vector typeNames = { "heroClass" }; + return typeNames; +} + +std::shared_ptr CHeroClassHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); + + std::string affinityStr[2] = { "might", "magic" }; + + auto heroClass = std::make_shared(); + + heroClass->id = HeroClassID(index); + heroClass->identifier = identifier; + heroClass->modScope = scope; + heroClass->imageBattleFemale = AnimationPath::fromJson(node["animation"]["battle"]["female"]); + heroClass->imageBattleMale = AnimationPath::fromJson(node["animation"]["battle"]["male"]); + //MODS COMPATIBILITY FOR 0.96 + heroClass->imageMapFemale = node["animation"]["map"]["female"].String(); + heroClass->imageMapMale = node["animation"]["map"]["male"].String(); + + VLC->generaltexth->registerString(scope, heroClass->getNameTextID(), node["name"].String()); + + if (vstd::contains(affinityStr, node["affinity"].String())) + { + heroClass->affinity = vstd::find_pos(affinityStr, node["affinity"].String()); + } + else + { + logGlobal->error("Mod '%s', hero class '%s': invalid affinity '%s'! Expected 'might' or 'magic'!", scope, identifier, node["affinity"].String()); + heroClass->affinity = CHeroClass::MIGHT; + } + + fillPrimarySkillData(node, heroClass.get(), PrimarySkill::ATTACK); + fillPrimarySkillData(node, heroClass.get(), PrimarySkill::DEFENSE); + fillPrimarySkillData(node, heroClass.get(), PrimarySkill::SPELL_POWER); + fillPrimarySkillData(node, heroClass.get(), PrimarySkill::KNOWLEDGE); + + auto percentSumm = std::accumulate(heroClass->primarySkillLowLevel.begin(), heroClass->primarySkillLowLevel.end(), 0); + if(percentSumm <= 0) + logMod->error("Hero class %s has wrong lowLevelChance values: must be above zero!", heroClass->identifier, percentSumm); + + percentSumm = std::accumulate(heroClass->primarySkillHighLevel.begin(), heroClass->primarySkillHighLevel.end(), 0); + if(percentSumm <= 0) + logMod->error("Hero class %s has wrong highLevelChance values: must be above zero!", heroClass->identifier, percentSumm); + + for(auto skillPair : node["secondarySkills"].Struct()) + { + int probability = static_cast(skillPair.second.Integer()); + VLC->identifiers()->requestIdentifier(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID) + { + heroClass->secSkillProbability[skillID] = probability; + }); + } + + VLC->identifiers()->requestIdentifier ("creature", node["commander"], + [=](si32 commanderID) + { + heroClass->commander = CreatureID(commanderID); + }); + + heroClass->defaultTavernChance = static_cast(node["defaultTavern"].Float()); + for(const auto & tavern : node["tavern"].Struct()) + { + int value = static_cast(tavern.second.Float()); + + VLC->identifiers()->requestIdentifier(tavern.second.getModScope(), "faction", tavern.first, + [=](si32 factionID) + { + heroClass->selectionProbability[FactionID(factionID)] = value; + }); + } + + VLC->identifiers()->requestIdentifier("faction", node["faction"], + [=](si32 factionID) + { + heroClass->faction.setNum(factionID); + }); + + VLC->identifiers()->requestIdentifier(scope, "object", "hero", [=](si32 index) + { + JsonNode classConf = node["mapObject"]; + classConf["heroClass"].String() = identifier; + if (!node["compatibilityIdentifiers"].isNull()) + classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"]; + classConf.setModScope(scope); + VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex()); + }); + + return heroClass; +} + +std::vector CHeroClassHandler::loadLegacyData() +{ + size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_HERO_CLASS); + + objects.resize(dataSize); + std::vector h3Data; + h3Data.reserve(dataSize); + + CLegacyConfigParser parser(TextPath::builtin("DATA/HCTRAITS.TXT")); + + parser.endLine(); // header + parser.endLine(); + + for (size_t i=0; i set selection probability if it was not set before in tavern entries + for(auto & heroClass : objects) + { + for(auto & faction : VLC->townh->objects) + { + if (!faction->town) + continue; + if (heroClass->selectionProbability.count(faction->getId())) + continue; + + auto chance = static_cast(heroClass->defaultTavernChance * faction->town->defaultTavernChance); + heroClass->selectionProbability[faction->getId()] = static_cast(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it + } + + // set default probabilities for gaining secondary skills where not loaded previously + for(int skillID = 0; skillID < VLC->skillh->size(); skillID++) + { + if(heroClass->secSkillProbability.count(skillID) == 0) + { + const CSkill * skill = (*VLC->skillh)[SecondarySkill(skillID)]; + logMod->trace("%s: no probability for %s, using default", heroClass->identifier, skill->getJsonKey()); + heroClass->secSkillProbability[skillID] = skill->gainChance[heroClass->affinity]; + } + } + } + + for(const auto & hc : objects) + { + if(!hc->imageMapMale.empty()) + { + JsonNode templ; + templ["animation"].String() = hc->imageMapMale; + VLC->objtypeh->getHandlerFor(Obj::HERO, hc->getIndex())->addTemplate(templ); + } + } +} + +CHeroClassHandler::~CHeroClassHandler() = default; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/entities/hero/CHeroClassHandler.h b/lib/entities/hero/CHeroClassHandler.h new file mode 100644 index 000000000..e04a6dbf3 --- /dev/null +++ b/lib/entities/hero/CHeroClassHandler.h @@ -0,0 +1,37 @@ +/* + * CHeroClassHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include + +#include "CHeroClass.h" // convenience include - users of handler generally also use its entity + +#include "../../IHandlerBase.h" +#include "../../constants/EntityIdentifiers.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE CHeroClassHandler : public CHandlerBase +{ + void fillPrimarySkillData(const JsonNode & node, CHeroClass * heroClass, PrimarySkill pSkill) const; + +public: + std::vector loadLegacyData() override; + + void afterLoadFinalization() override; + + ~CHeroClassHandler(); + +protected: + const std::vector & getTypeNames() const override; + std::shared_ptr loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/CHeroHandler.cpp b/lib/entities/hero/CHeroHandler.cpp similarity index 50% rename from lib/CHeroHandler.cpp rename to lib/entities/hero/CHeroHandler.cpp index 820f8404d..8b05df0eb 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/entities/hero/CHeroHandler.cpp @@ -10,427 +10,22 @@ #include "StdInc.h" #include "CHeroHandler.h" -#include "filesystem/Filesystem.h" -#include "VCMI_Lib.h" -#include "constants/StringConstants.h" -#include "battle/BattleHex.h" -#include "CCreatureHandler.h" -#include "IGameSettings.h" -#include "CSkillHandler.h" -#include "BattleFieldHandler.h" -#include "bonuses/Limiters.h" -#include "bonuses/Updaters.h" -#include "entities/faction/CFaction.h" -#include "entities/faction/CTown.h" -#include "entities/faction/CTownHandler.h" -#include "json/JsonBonus.h" -#include "json/JsonUtils.h" -#include "mapObjectConstructors/AObjectTypeHandler.h" -#include "mapObjectConstructors/CObjectClassesHandler.h" -#include "modding/IdentifierStorage.h" -#include "texts/CGeneralTextHandler.h" -#include "texts/CLegacyConfigParser.h" +#include "CHero.h" -#include +#include "../../VCMI_Lib.h" +#include "../../constants/StringConstants.h" +#include "../../CCreatureHandler.h" +#include "../../IGameSettings.h" +#include "../../bonuses/Limiters.h" +#include "../../bonuses/Updaters.h" +#include "../../json/JsonBonus.h" +#include "../../json/JsonUtils.h" +#include "../../modding/IdentifierStorage.h" +#include "../../texts/CGeneralTextHandler.h" +#include "../../texts/CLegacyConfigParser.h" VCMI_LIB_NAMESPACE_BEGIN -CHero::CHero() = default; -CHero::~CHero() = default; - -int32_t CHero::getIndex() const -{ - return ID.getNum(); -} - -int32_t CHero::getIconIndex() const -{ - return imageIndex; -} - -std::string CHero::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string CHero::getModScope() const -{ - return modScope; -} - -HeroTypeID CHero::getId() const -{ - return ID; -} - -std::string CHero::getNameTranslated() const -{ - return VLC->generaltexth->translate(getNameTextID()); -} - -std::string CHero::getBiographyTranslated() const -{ - return VLC->generaltexth->translate(getBiographyTextID()); -} - -std::string CHero::getSpecialtyNameTranslated() const -{ - return VLC->generaltexth->translate(getSpecialtyNameTextID()); -} - -std::string CHero::getSpecialtyDescriptionTranslated() const -{ - return VLC->generaltexth->translate(getSpecialtyDescriptionTextID()); -} - -std::string CHero::getSpecialtyTooltipTranslated() const -{ - return VLC->generaltexth->translate(getSpecialtyTooltipTextID()); -} - -std::string CHero::getNameTextID() const -{ - return TextIdentifier("hero", modScope, identifier, "name").get(); -} - -std::string CHero::getBiographyTextID() const -{ - return TextIdentifier("hero", modScope, identifier, "biography").get(); -} - -std::string CHero::getSpecialtyNameTextID() const -{ - return TextIdentifier("hero", modScope, identifier, "specialty", "name").get(); -} - -std::string CHero::getSpecialtyDescriptionTextID() const -{ - return TextIdentifier("hero", modScope, identifier, "specialty", "description").get(); -} - -std::string CHero::getSpecialtyTooltipTextID() const -{ - return TextIdentifier("hero", modScope, identifier, "specialty", "tooltip").get(); -} - -void CHero::registerIcons(const IconRegistar & cb) const -{ - cb(getIconIndex(), 0, "UN32", iconSpecSmall); - cb(getIconIndex(), 0, "UN44", iconSpecLarge); - cb(getIconIndex(), 0, "PORTRAITSLARGE", portraitLarge); - cb(getIconIndex(), 0, "PORTRAITSSMALL", portraitSmall); -} - -void CHero::updateFrom(const JsonNode & data) -{ - //todo: CHero::updateFrom -} - -void CHero::serializeJson(JsonSerializeFormat & handler) -{ - -} - - -SecondarySkill CHeroClass::chooseSecSkill(const std::set & possibles, vstd::RNG & rand) const //picks secondary skill out from given possibilities -{ - assert(!possibles.empty()); - - if (possibles.size() == 1) - return *possibles.begin(); - - int totalProb = 0; - for(const auto & possible : possibles) - if (secSkillProbability.count(possible) != 0) - totalProb += secSkillProbability.at(possible); - - if (totalProb == 0) // may trigger if set contains only banned skills (0 probability) - return *RandomGeneratorUtil::nextItem(possibles, rand); - - auto ran = rand.nextInt(totalProb - 1); - for(const auto & possible : possibles) - { - if (secSkillProbability.count(possible) != 0) - ran -= secSkillProbability.at(possible); - - if(ran < 0) - return possible; - } - - assert(0); // should not be possible - return *possibles.begin(); -} - -bool CHeroClass::isMagicHero() const -{ - return affinity == MAGIC; -} - -int CHeroClass::tavernProbability(FactionID targetFaction) const -{ - auto it = selectionProbability.find(targetFaction); - if (it != selectionProbability.end()) - return it->second; - return 0; -} - -EAlignment CHeroClass::getAlignment() const -{ - return VLC->factions()->getById(faction)->getAlignment(); -} - -int32_t CHeroClass::getIndex() const -{ - return id.getNum(); -} - -int32_t CHeroClass::getIconIndex() const -{ - return getIndex(); -} - -std::string CHeroClass::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string CHeroClass::getModScope() const -{ - return modScope; -} - -HeroClassID CHeroClass::getId() const -{ - return id; -} - -void CHeroClass::registerIcons(const IconRegistar & cb) const -{ - -} - -std::string CHeroClass::getNameTranslated() const -{ - return VLC->generaltexth->translate(getNameTextID()); -} - -std::string CHeroClass::getNameTextID() const -{ - return TextIdentifier("heroClass", modScope, identifier, "name").get(); -} - -void CHeroClass::updateFrom(const JsonNode & data) -{ - //TODO: CHeroClass::updateFrom -} - -void CHeroClass::serializeJson(JsonSerializeFormat & handler) -{ - -} - -CHeroClass::CHeroClass(): - faction(0), - affinity(0), - defaultTavernChance(0) -{ -} - -void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * heroClass, PrimarySkill pSkill) const -{ - const auto & skillName = NPrimarySkill::names[pSkill.getNum()]; - auto currentPrimarySkillValue = static_cast(node["primarySkills"][skillName].Integer()); - int primarySkillLegalMinimum = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[pSkill.getNum()]; - - if(currentPrimarySkillValue < primarySkillLegalMinimum) - { - logMod->error("Hero class '%s' has incorrect initial value '%d' for skill '%s'. Value '%d' will be used instead.", - heroClass->getNameTranslated(), currentPrimarySkillValue, skillName, primarySkillLegalMinimum); - currentPrimarySkillValue = primarySkillLegalMinimum; - } - heroClass->primarySkillInitial.push_back(currentPrimarySkillValue); - heroClass->primarySkillLowLevel.push_back(static_cast(node["lowLevelChance"][skillName].Float())); - heroClass->primarySkillHighLevel.push_back(static_cast(node["highLevelChance"][skillName].Float())); -} - -const std::vector & CHeroClassHandler::getTypeNames() const -{ - static const std::vector typeNames = { "heroClass" }; - return typeNames; -} - -std::shared_ptr CHeroClassHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - assert(!scope.empty()); - - std::string affinityStr[2] = { "might", "magic" }; - - auto heroClass = std::make_shared(); - - heroClass->id = HeroClassID(index); - heroClass->identifier = identifier; - heroClass->modScope = scope; - heroClass->imageBattleFemale = AnimationPath::fromJson(node["animation"]["battle"]["female"]); - heroClass->imageBattleMale = AnimationPath::fromJson(node["animation"]["battle"]["male"]); - //MODS COMPATIBILITY FOR 0.96 - heroClass->imageMapFemale = node["animation"]["map"]["female"].String(); - heroClass->imageMapMale = node["animation"]["map"]["male"].String(); - - VLC->generaltexth->registerString(scope, heroClass->getNameTextID(), node["name"].String()); - - if (vstd::contains(affinityStr, node["affinity"].String())) - { - heroClass->affinity = vstd::find_pos(affinityStr, node["affinity"].String()); - } - else - { - logGlobal->error("Mod '%s', hero class '%s': invalid affinity '%s'! Expected 'might' or 'magic'!", scope, identifier, node["affinity"].String()); - heroClass->affinity = CHeroClass::MIGHT; - } - - fillPrimarySkillData(node, heroClass.get(), PrimarySkill::ATTACK); - fillPrimarySkillData(node, heroClass.get(), PrimarySkill::DEFENSE); - fillPrimarySkillData(node, heroClass.get(), PrimarySkill::SPELL_POWER); - fillPrimarySkillData(node, heroClass.get(), PrimarySkill::KNOWLEDGE); - - auto percentSumm = std::accumulate(heroClass->primarySkillLowLevel.begin(), heroClass->primarySkillLowLevel.end(), 0); - if(percentSumm <= 0) - logMod->error("Hero class %s has wrong lowLevelChance values: must be above zero!", heroClass->identifier, percentSumm); - - percentSumm = std::accumulate(heroClass->primarySkillHighLevel.begin(), heroClass->primarySkillHighLevel.end(), 0); - if(percentSumm <= 0) - logMod->error("Hero class %s has wrong highLevelChance values: must be above zero!", heroClass->identifier, percentSumm); - - for(auto skillPair : node["secondarySkills"].Struct()) - { - int probability = static_cast(skillPair.second.Integer()); - VLC->identifiers()->requestIdentifier(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID) - { - heroClass->secSkillProbability[skillID] = probability; - }); - } - - VLC->identifiers()->requestIdentifier ("creature", node["commander"], - [=](si32 commanderID) - { - heroClass->commander = CreatureID(commanderID); - }); - - heroClass->defaultTavernChance = static_cast(node["defaultTavern"].Float()); - for(const auto & tavern : node["tavern"].Struct()) - { - int value = static_cast(tavern.second.Float()); - - VLC->identifiers()->requestIdentifier(tavern.second.getModScope(), "faction", tavern.first, - [=](si32 factionID) - { - heroClass->selectionProbability[FactionID(factionID)] = value; - }); - } - - VLC->identifiers()->requestIdentifier("faction", node["faction"], - [=](si32 factionID) - { - heroClass->faction.setNum(factionID); - }); - - VLC->identifiers()->requestIdentifier(scope, "object", "hero", [=](si32 index) - { - JsonNode classConf = node["mapObject"]; - classConf["heroClass"].String() = identifier; - if (!node["compatibilityIdentifiers"].isNull()) - classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"]; - classConf.setModScope(scope); - VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex()); - }); - - return heroClass; -} - -std::vector CHeroClassHandler::loadLegacyData() -{ - size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_HERO_CLASS); - - objects.resize(dataSize); - std::vector h3Data; - h3Data.reserve(dataSize); - - CLegacyConfigParser parser(TextPath::builtin("DATA/HCTRAITS.TXT")); - - parser.endLine(); // header - parser.endLine(); - - for (size_t i=0; i set selection probability if it was not set before in tavern entries - for(auto & heroClass : objects) - { - for(auto & faction : VLC->townh->objects) - { - if (!faction->town) - continue; - if (heroClass->selectionProbability.count(faction->getId())) - continue; - - auto chance = static_cast(heroClass->defaultTavernChance * faction->town->defaultTavernChance); - heroClass->selectionProbability[faction->getId()] = static_cast(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it - } - - // set default probabilities for gaining secondary skills where not loaded previously - for(int skillID = 0; skillID < VLC->skillh->size(); skillID++) - { - if(heroClass->secSkillProbability.count(skillID) == 0) - { - const CSkill * skill = (*VLC->skillh)[SecondarySkill(skillID)]; - logMod->trace("%s: no probability for %s, using default", heroClass->identifier, skill->getJsonKey()); - heroClass->secSkillProbability[skillID] = skill->gainChance[heroClass->affinity]; - } - } - } - - for(const auto & hc : objects) - { - if(!hc->imageMapMale.empty()) - { - JsonNode templ; - templ["animation"].String() = hc->imageMapMale; - VLC->objtypeh->getHandlerFor(Obj::HERO, hc->getIndex())->addTemplate(templ); - } - } -} - -CHeroClassHandler::~CHeroClassHandler() = default; - CHeroHandler::~CHeroHandler() = default; CHeroHandler::CHeroHandler() diff --git a/lib/entities/hero/CHeroHandler.h b/lib/entities/hero/CHeroHandler.h new file mode 100644 index 000000000..d62911599 --- /dev/null +++ b/lib/entities/hero/CHeroHandler.h @@ -0,0 +1,59 @@ +/* + * CHeroHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include + +#include "CHero.h" // convenience include - users of handler generally also use its entity + + +#include "../../GameConstants.h" +#include "../../IHandlerBase.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE CHeroHandler : public CHandlerBase +{ + /// expPerLEvel[i] is amount of exp needed to reach level i; + /// consists of 196 values. Any higher levels require experience larger that TExpType can hold + std::vector expPerLevel; + + /// helpers for loading to avoid huge load functions + void loadHeroArmy(CHero * hero, const JsonNode & node) const; + void loadHeroSkills(CHero * hero, const JsonNode & node) const; + void loadHeroSpecialty(CHero * hero, const JsonNode & node); + + void loadExperience(); + + std::vector> callAfterLoadFinalization; + +public: + ui32 level(TExpType experience) const; //calculates level corresponding to given experience amount + TExpType reqExp(ui32 level) const; //calculates experience required for given level + ui32 maxSupportedLevel() const; + + std::vector loadLegacyData() override; + + void beforeValidate(JsonNode & object) override; + void loadObject(std::string scope, std::string name, const JsonNode & data) override; + void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; + void afterLoadFinalization() override; + + CHeroHandler(); + ~CHeroHandler(); + + std::set getDefaultAllowed() const; + +protected: + const std::vector & getTypeNames() const override; + std::shared_ptr loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index) override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/entities/hero/EHeroGender.h b/lib/entities/hero/EHeroGender.h new file mode 100644 index 000000000..2b9dfb61f --- /dev/null +++ b/lib/entities/hero/EHeroGender.h @@ -0,0 +1,21 @@ +/* + * EHeroGender.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + + enum class EHeroGender : int8_t +{ + DEFAULT = -1, // from h3m, instance has same gender as hero type + MALE = 0, + FEMALE = 1, +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 7bba1770d..1ef3d9a12 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -18,7 +18,6 @@ #include "../ArtifactUtils.h" #include "../texts/CGeneralTextHandler.h" -#include "../CHeroHandler.h" #include "../CPlayerState.h" #include "../CStopWatch.h" #include "../IGameSettings.h" @@ -30,6 +29,8 @@ #include "../campaign/CampaignState.h" #include "../constants/StringConstants.h" #include "../entities/faction/CTownHandler.h" +#include "../entities/hero/CHero.h" +#include "../entities/hero/CHeroClass.h" #include "../filesystem/ResourcePath.h" #include "../json/JsonBonus.h" #include "../json/JsonUtils.h" diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 2178da654..80fc536f7 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -16,6 +16,8 @@ #include "../campaign/CampaignState.h" #include "../entities/building/CBuilding.h" #include "../entities/building/CBuildingHandler.h" +#include "../entities/hero/CHeroClass.h" +#include "../entities/hero/CHero.h" #include "../mapping/CMapEditManager.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" @@ -23,13 +25,13 @@ #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../StartInfo.h" -#include "../CHeroHandler.h" #include "../mapping/CMap.h" #include "../ArtifactUtils.h" #include "../CPlayerState.h" #include "../serializer/CMemorySerializer.h" #include +#include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index 6e1c95e85..a6ee5041c 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -14,7 +14,6 @@ #include "../VCMIDirs.h" #include "CGameState.h" #include "TerrainHandler.h" -#include "CHeroHandler.h" #include "StartInfo.h" #include "HighScore.h" #include "../mapObjects/CGHeroInstance.h" diff --git a/lib/gameState/InfoAboutArmy.cpp b/lib/gameState/InfoAboutArmy.cpp index cb6ad5e95..54e8528a8 100644 --- a/lib/gameState/InfoAboutArmy.cpp +++ b/lib/gameState/InfoAboutArmy.cpp @@ -12,7 +12,9 @@ #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" -#include "../CHeroHandler.h" + +#include +#include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index 44d085c13..3c0dfadbf 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -11,7 +11,6 @@ #include "TavernHeroesPool.h" #include "../mapObjects/CGHeroInstance.h" -#include "../CHeroHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index 57f7a97a2..bfc65fa9d 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -13,6 +13,8 @@ #include #include +#include +#include #include "JsonBonus.h" @@ -23,8 +25,9 @@ #include "../CCreatureSet.h" #include "../spells/CSpellHandler.h" #include "../CSkillHandler.h" -#include "../CHeroHandler.h" #include "../IGameCallback.h" +#include "../entities/hero/CHero.h" +#include "../entities/hero/CHeroClass.h" #include "../gameState/CGameState.h" #include "../mapObjects/IObjectInterface.h" #include "../modding/IdentifierStorage.h" diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 421d2565f..b8194937c 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -11,7 +11,6 @@ #include "CommonConstructors.h" #include "../texts/CGeneralTextHandler.h" -#include "../CHeroHandler.h" #include "../IGameCallback.h" #include "../json/JsonRandom.h" #include "../constants/StringConstants.h" @@ -19,6 +18,7 @@ #include "../VCMI_Lib.h" #include "../entities/faction/CTownHandler.h" +#include "../entities/hero/CHeroClass.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGMarket.h" #include "../mapObjects/CGTownInstance.h" diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index b31ff4190..e7825b324 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -17,7 +17,6 @@ #include "../texts/CGeneralTextHandler.h" #include "../ArtifactUtils.h" -#include "../CHeroHandler.h" #include "../TerrainHandler.h" #include "../RoadHandler.h" #include "../IGameSettings.h" @@ -31,6 +30,8 @@ #include "../StartInfo.h" #include "CGTownInstance.h" #include "../entities/faction/CTownHandler.h" +#include "../entities/hero/CHeroHandler.h" +#include "../entities/hero/CHeroClass.h" #include "../battle/CBattleInfoEssentials.h" #include "../campaign/CampaignState.h" #include "../json/JsonBonus.h" diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index ebb8261f8..aafad435c 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -14,6 +14,7 @@ #include "CArmedInstance.h" #include "IOwnableObject.h" +#include "../entities/hero/EHeroGender.h" #include "../CArtHandler.h" // For CArtifactSet VCMI_LIB_NAMESPACE_BEGIN @@ -24,7 +25,6 @@ class CGTownInstance; class CMap; struct TerrainTile; struct TurnInfo; -enum class EHeroGender : int8_t; class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance { diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 051349282..1cc466324 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -16,9 +16,9 @@ #include "../ArtifactUtils.h" #include "../CSoundBase.h" #include "../texts/CGeneralTextHandler.h" -#include "../CHeroHandler.h" #include "CGCreature.h" #include "../IGameCallback.h" +#include "../entities/hero/CHeroHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../serializer/JsonSerializeFormat.h" #include "../GameConstants.h" diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 57e3a4c24..373bb3fc4 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -13,11 +13,11 @@ #include "../CArtHandler.h" #include "../VCMI_Lib.h" #include "../CCreatureHandler.h" -#include "../CHeroHandler.h" #include "../GameSettings.h" #include "../RiverHandler.h" #include "../RoadHandler.h" #include "../TerrainHandler.h" +#include "../entities/hero/CHeroHandler.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CQuest.h" diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index ec9c74df8..66ed48dbd 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -12,9 +12,9 @@ #include "MapFormat.h" -#include "../CHeroHandler.h" #include "../VCMI_Lib.h" #include "../entities/faction/CTownHandler.h" +#include "../entities/hero/CHeroHandler.h" #include "../json/JsonUtils.h" #include "../modding/CModHandler.h" #include "../texts/CGeneralTextHandler.h" diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index 74ee2cd44..a13084ac9 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -25,7 +25,6 @@ #include "../texts/TextOperations.h" #include "../CCreatureHandler.h" #include "../IGameSettings.h" -#include "../CHeroHandler.h" #include "../CConfigHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 1fe7da29f..16e02db92 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -18,7 +18,6 @@ #include "../ArtifactUtils.h" #include "../CCreatureHandler.h" #include "../texts/CGeneralTextHandler.h" -#include "../CHeroHandler.h" #include "../CSkillHandler.h" #include "../CStopWatch.h" #include "../IGameSettings.h" @@ -27,6 +26,7 @@ #include "../TerrainHandler.h" #include "../VCMI_Lib.h" #include "../constants/StringConstants.h" +#include "../entities/hero/CHeroHandler.h" #include "../filesystem/CBinaryReader.h" #include "../filesystem/Filesystem.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 0777defed..efbce4560 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -17,12 +17,12 @@ #include "CMap.h" #include "MapFormat.h" #include "../ArtifactUtils.h" -#include "../CHeroHandler.h" #include "../VCMI_Lib.h" #include "../RiverHandler.h" #include "../RoadHandler.h" #include "../TerrainHandler.h" #include "../entities/faction/CTownHandler.h" +#include "../entities/hero/CHeroHandler.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../mapObjects/ObjectTemplate.h" diff --git a/lib/mapping/MapIdentifiersH3M.cpp b/lib/mapping/MapIdentifiersH3M.cpp index 3e093227b..e75e58235 100644 --- a/lib/mapping/MapIdentifiersH3M.cpp +++ b/lib/mapping/MapIdentifiersH3M.cpp @@ -12,7 +12,6 @@ #include "MapIdentifiersH3M.h" #include "../VCMI_Lib.h" -#include "../CHeroHandler.h" #include "../entities/faction/CFaction.h" #include "../entities/faction/CTownHandler.h" #include "../filesystem/Filesystem.h" diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 0a9acb0f4..18f2605ff 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -19,8 +19,9 @@ #include "../CCreatureHandler.h" #include "../CConfigHandler.h" #include "../entities/faction/CTownHandler.h" +#include "../entities/hero/CHeroClassHandler.h" +#include "../entities/hero/CHeroHandler.h" #include "../texts/CGeneralTextHandler.h" -#include "../CHeroHandler.h" #include "../CSkillHandler.h" #include "../CStopWatch.h" #include "../IGameSettings.h" diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 6ea83ab8f..6ef830fdb 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -20,7 +20,6 @@ #include "NetPackVisitor.h" #include "texts/CGeneralTextHandler.h" #include "CArtHandler.h" -#include "CHeroHandler.h" #include "VCMI_Lib.h" #include "mapping/CMap.h" #include "spells/CSpellHandler.h" diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index f67250efe..7bef36108 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -11,10 +11,10 @@ #include "StdInc.h" #include "Interface.h" -#include "../CHeroHandler.h" #include "../TerrainHandler.h" #include "../CPlayerState.h" #include "../CSoundBase.h" +#include "../entities/hero/CHeroHandler.h" #include "../gameState/CGameState.h" #include "../spells/CSpellHandler.h" #include "../spells/ISpellMechanics.h" diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index 384ffea92..5e0d4bd80 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -17,7 +17,6 @@ #include "../networkPacks/Component.h" #include "../serializer/JsonSerializeFormat.h" #include "../constants/StringConstants.h" -#include "../CHeroHandler.h" #include "../CSkillHandler.h" #include "../ArtifactUtils.h" diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 0e02212b9..c33a10dd4 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -17,11 +17,11 @@ #include "../CRandomGenerator.h" #include "../entities/faction/CTownHandler.h" #include "../entities/faction/CFaction.h" +#include "../entities/hero/CHero.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../mapping/CMapEditManager.h" #include "../CArtHandler.h" -#include "../CHeroHandler.h" #include "../constants/StringConstants.h" #include "../filesystem/Filesystem.h" #include "CZonePlacer.h" @@ -35,6 +35,7 @@ #include "modificators/RoadPlacer.h" #include +#include VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/serializer/CSerializer.cpp b/lib/serializer/CSerializer.cpp index f8311a95b..3e7e84a6f 100644 --- a/lib/serializer/CSerializer.cpp +++ b/lib/serializer/CSerializer.cpp @@ -10,9 +10,9 @@ #include "StdInc.h" #include "CSerializer.h" +#include "../entities/hero/CHero.h" #include "../gameState/CGameState.h" #include "../mapping/CMap.h" -#include "../CHeroHandler.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CQuest.h" diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 492d0e371..204d99432 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -9,7 +9,6 @@ */ #pragma once -#include "../CHeroHandler.h" #include "../CPlayerState.h" #include "../CStack.h" #include "../battle/BattleInfo.h" diff --git a/lib/serializer/SerializerReflection.cpp b/lib/serializer/SerializerReflection.cpp index 4862c62b7..ecb3427b8 100644 --- a/lib/serializer/SerializerReflection.cpp +++ b/lib/serializer/SerializerReflection.cpp @@ -19,6 +19,7 @@ #include "../RiverHandler.h" #include "../RoadHandler.h" #include "../TerrainHandler.h" +#include "../entities/hero/CHero.h" #include "../mapObjects/ObjectTemplate.h" #include "../mapping/CMapInfo.h" #include "../rmg/CMapGenOptions.h" diff --git a/lib/spells/BonusCaster.cpp b/lib/spells/BonusCaster.cpp index deb119a89..a2d8523b1 100644 --- a/lib/spells/BonusCaster.cpp +++ b/lib/spells/BonusCaster.cpp @@ -12,12 +12,12 @@ #include "BonusCaster.h" #include +#include #include "../battle/Unit.h" #include "../bonuses/Bonus.h" #include "../VCMI_Lib.h" #include "../CSkillHandler.h" -#include "../CHeroHandler.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index a1488e1e8..24f40b067 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -36,7 +36,6 @@ #include "CSpellHandler.h" -#include "../CHeroHandler.h"//todo: remove #include "../IGameCallback.h"//todo: remove #include "../BattleFieldHandler.h" diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index e46cdc6da..e4d51bd5b 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -18,7 +18,6 @@ #include "../../battle/Unit.h" #include "../../serializer/JsonSerializeFormat.h" #include "../../CCreatureHandler.h" -#include "../../CHeroHandler.h" #include "../../mapObjects/CGHeroInstance.h" #include "../../networkPacks/PacksForClientBattle.h" diff --git a/lib/texts/TextLocalizationContainer.cpp b/lib/texts/TextLocalizationContainer.cpp index f45898cf7..0a5c8aa36 100644 --- a/lib/texts/TextLocalizationContainer.cpp +++ b/lib/texts/TextLocalizationContainer.cpp @@ -107,7 +107,7 @@ void TextLocalizationContainer::registerString(const std::string & identifierMod assert(!identifierModContext.empty()); assert(!localizedStringModContext.empty()); assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string - assert(stringsLocalizations.count(UID.get()) == 0 || identifierModContext == "map"); // registering already registered string? + assert(stringsLocalizations.count(UID.get()) == 0 || boost::algorithm::starts_with(UID.get(), "map")); // registering already registered string? if(stringsLocalizations.count(UID.get()) > 0) { diff --git a/mapeditor/graphics.cpp b/mapeditor/graphics.cpp index 3ca8bd4a9..33dfb8b4d 100644 --- a/mapeditor/graphics.cpp +++ b/mapeditor/graphics.cpp @@ -29,11 +29,11 @@ #include "../lib/texts/CGeneralTextHandler.h" #include "BitmapHandler.h" #include "../lib/CStopWatch.h" +#include "../lib/entities/hero/CHeroClassHandler.h" #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/mapObjects/CGObjectInstance.h" #include "../lib/mapObjects/ObjectTemplate.h" -#include "../lib/CHeroHandler.h" Graphics * graphics = nullptr; diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 4ad9c5eaf..f8a651325 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -11,8 +11,9 @@ #include "inspector.h" #include "../lib/ArtifactUtils.h" #include "../lib/CArtHandler.h" +#include "../lib/entities/hero/CHeroClass.h" +#include "../lib/entities/hero/CHeroHandler.h" #include "../lib/spells/CSpellHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/CRandomGenerator.h" #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" diff --git a/mapeditor/inspector/portraitwidget.cpp b/mapeditor/inspector/portraitwidget.cpp index 669bb880c..af113be2e 100644 --- a/mapeditor/inspector/portraitwidget.cpp +++ b/mapeditor/inspector/portraitwidget.cpp @@ -10,8 +10,8 @@ #include "StdInc.h" #include "portraitwidget.h" #include "ui_portraitwidget.h" -#include "../../lib/CHeroHandler.h" #include "../Animation.h" +#include "../lib/entities/hero/CHeroHandler.h" PortraitWidget::PortraitWidget(CGHeroInstance & h, QWidget *parent): QDialog(parent), diff --git a/mapeditor/inspector/questwidget.cpp b/mapeditor/inspector/questwidget.cpp index df706c19d..8edd018de 100644 --- a/mapeditor/inspector/questwidget.cpp +++ b/mapeditor/inspector/questwidget.cpp @@ -16,12 +16,16 @@ #include "../lib/spells/CSpellHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CCreatureHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/constants/StringConstants.h" #include "../lib/mapping/CMap.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGCreature.h" +#include +#include +#include +#include + QuestWidget::QuestWidget(MapController & _controller, CQuest & _sh, QWidget *parent) : QDialog(parent), controller(_controller), diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index 4a8d7ab4c..b4710ff88 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -13,7 +13,6 @@ #include "../lib/VCMI_Lib.h" #include "../lib/CSkillHandler.h" #include "../lib/spells/CSpellHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/constants/StringConstants.h" @@ -24,6 +23,11 @@ #include "../lib/mapObjects/CGPandoraBox.h" #include "../lib/mapObjects/CQuest.h" +#include +#include +#include +#include + RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) : QDialog(parent), map(m), diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index a30a90aa8..0afe214d1 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -13,6 +13,8 @@ #include "../lib/ArtifactUtils.h" #include "../lib/GameConstants.h" +#include "../lib/entities/hero/CHeroClass.h" +#include "../lib/entities/hero/CHeroHandler.h" #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/mapObjects/ObjectTemplate.h" @@ -25,7 +27,6 @@ #include "../lib/TerrainHandler.h" #include "../lib/CSkillHandler.h" #include "../lib/spells/CSpellHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/CRandomGenerator.h" #include "../lib/serializer/CMemorySerializer.h" #include "mapview.h" diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index c9b18ac32..70aac50b0 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -19,7 +19,6 @@ #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/ObjectTemplate.h" #include "../lib/mapObjects/MiscObjects.h" -#include "../lib/CHeroHandler.h" #include "../lib/GameConstants.h" const int tileSize = 32; diff --git a/mapeditor/mapsettings/abstractsettings.cpp b/mapeditor/mapsettings/abstractsettings.cpp index f75a77685..a21d2f24b 100644 --- a/mapeditor/mapsettings/abstractsettings.cpp +++ b/mapeditor/mapsettings/abstractsettings.cpp @@ -13,7 +13,6 @@ #include "../mapcontroller.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGCreature.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/mapObjects/CGCreature.h" //parses date for lose condition (1m 1w 1d) diff --git a/mapeditor/mapsettings/mapsettings.cpp b/mapeditor/mapsettings/mapsettings.cpp index 7e579cb81..f94783020 100644 --- a/mapeditor/mapsettings/mapsettings.cpp +++ b/mapeditor/mapsettings/mapsettings.cpp @@ -13,10 +13,10 @@ #include "ui_mapsettings.h" #include "mainwindow.h" -#include "../../lib/CSkillHandler.h" -#include "../../lib/spells/CSpellHandler.h" #include "../../lib/CArtHandler.h" -#include "../../lib/CHeroHandler.h" +#include "../../lib/CSkillHandler.h" +#include "../../lib/entities/hero/CHeroHandler.h" +#include "../../lib/spells/CSpellHandler.h" MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index b4a98df30..17c1d2dcc 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -12,12 +12,12 @@ #include "validator.h" #include "mapcontroller.h" #include "ui_validator.h" +#include "../lib/entities/hero/CHero.h" #include "../lib/mapping/CMap.h" #include "../lib/mapObjects/MapObjects.h" #include "../lib/modding/CModHandler.h" #include "../lib/modding/CModInfo.h" #include "../lib/spells/CSpellHandler.h" -#include "../lib/CHeroHandler.h" Validator::Validator(const CMap * map, QWidget *parent) : QDialog(parent), diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1f9549fa0..08d311cbd 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -29,7 +29,6 @@ #include "../lib/CCreatureHandler.h" #include "../lib/CCreatureSet.h" #include "../lib/texts/CGeneralTextHandler.h" -#include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" #include "../lib/CRandomGenerator.h" #include "../lib/CSoundBase.h" @@ -48,6 +47,7 @@ #include "../lib/entities/building/CBuilding.h" #include "../lib/entities/faction/CTownHandler.h" +#include "../lib/entities/hero/CHeroHandler.h" #include "../lib/filesystem/FileInfo.h" #include "../lib/filesystem/Filesystem.h" diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index c0753d2a1..e69813542 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -15,9 +15,10 @@ #include "LobbyNetPackVisitors.h" #include "processors/PlayerMessageProcessor.h" -#include "../lib/CHeroHandler.h" #include "../lib/CPlayerState.h" #include "../lib/campaign/CampaignState.h" +#include "../lib/entities/hero/CHeroHandler.h" +#include "../lib/entities/hero/CHeroClass.h" #include "../lib/gameState/CGameState.h" #include "../lib/mapping/CMapDefines.h" #include "../lib/mapping/CMapInfo.h" diff --git a/server/processors/HeroPoolProcessor.cpp b/server/processors/HeroPoolProcessor.cpp index 44a50884a..976c74b6f 100644 --- a/server/processors/HeroPoolProcessor.cpp +++ b/server/processors/HeroPoolProcessor.cpp @@ -14,10 +14,11 @@ #include "../CGameHandler.h" #include "../../lib/CRandomGenerator.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/CPlayerState.h" #include "../../lib/IGameSettings.h" #include "../../lib/StartInfo.h" +#include "../../lib/entities/hero/CHeroClass.h" +#include "../../lib/entities/hero/CHero.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/networkPacks/PacksForClient.h" diff --git a/server/processors/PlayerMessageProcessor.cpp b/server/processors/PlayerMessageProcessor.cpp index 6ebc78375..38b4409d0 100644 --- a/server/processors/PlayerMessageProcessor.cpp +++ b/server/processors/PlayerMessageProcessor.cpp @@ -16,10 +16,10 @@ #include "../CVCMIServer.h" #include "../TurnTimerHandler.h" -#include "../../lib/CHeroHandler.h" #include "../../lib/CPlayerState.h" #include "../../lib/StartInfo.h" #include "../../lib/entities/building/CBuilding.h" +#include "../../lib/entities/hero/CHeroHandler.h" #include "../../lib/gameState/CGameState.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h" diff --git a/test/entity/CHeroClassTest.cpp b/test/entity/CHeroClassTest.cpp index db109ae94..23eb7266b 100644 --- a/test/entity/CHeroClassTest.cpp +++ b/test/entity/CHeroClassTest.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "../../lib/CHeroHandler.h" +#include "../../lib/entities/hero/CHeroClass.h" namespace test { diff --git a/test/entity/CHeroTest.cpp b/test/entity/CHeroTest.cpp index edea854a5..92f0e190d 100644 --- a/test/entity/CHeroTest.cpp +++ b/test/entity/CHeroTest.cpp @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "../../lib/CHeroHandler.h" +#include "../../lib/entities/hero/CHero.h" namespace test { From d43997f5ba68552b1adfd27f616ca395bde7bca6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 13 Oct 2024 21:08:43 +0200 Subject: [PATCH 332/726] use sdl mixer to get audio duration --- client/media/CSoundHandler.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/media/CSoundHandler.cpp b/client/media/CSoundHandler.cpp index 79562f15c..a0b5e6301 100644 --- a/client/media/CSoundHandler.cpp +++ b/client/media/CSoundHandler.cpp @@ -147,18 +147,23 @@ uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound) auto data = CResourceHandler::get()->load(resourcePath)->readAll(); - SDL_AudioSpec spec; - uint32_t audioLen; - uint8_t * audioBuf; uint32_t milliseconds = 0; - if(SDL_LoadWAV_RW(SDL_RWFromMem(data.first.get(), data.second), 1, &spec, &audioBuf, &audioLen) != nullptr) + Mix_Chunk * chunk = Mix_LoadWAV_RW(SDL_RWFromMem(data.first.get(), data.second), 1); + + int freq = 0; + Uint16 fmt = 0; + int chans = 0; + if(!Mix_QuerySpec(&freq, &fmt, &chans)) + return 0; + + if(chunk != nullptr) { - SDL_FreeWAV(audioBuf); - uint32_t sampleSize = SDL_AUDIO_BITSIZE(spec.format) / 8; - uint32_t sampleCount = audioLen / sampleSize; - uint32_t sampleLen = sampleCount / spec.channels; - milliseconds = 1000 * sampleLen / spec.freq; + Uint32 points = (chunk->alen / ((fmt & 0xFF) / 8)); + Uint32 frames = (points / chans); + milliseconds = ((frames * 1000) / freq); + + Mix_FreeChunk(chunk); } return milliseconds; From 024778dfe81cd3614a9eca907275fdf5b3377afa Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:18:32 +0200 Subject: [PATCH 333/726] Update swedish.json --- Mods/vcmi/config/vcmi/swedish.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index e9145d40f..b5ec96341 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -536,7 +536,7 @@ "core.bonus.ADDITIONAL_ATTACK.name" : "Dubbelslag", "core.bonus.ADDITIONAL_ATTACK.description" : "Attackerar två gånger.", "core.bonus.ADDITIONAL_RETALIATION.name" : "Ytterligare motattacker", - "core.bonus.ADDITIONAL_RETALIATION.description" : "Kan slå tillbaka ${val} extra gånger.", + "core.bonus.ADDITIONAL_RETALIATION.description" : "Kan slå tillbaka ${val} extra gång(er).", "core.bonus.AIR_IMMUNITY.name" : "Luft-immunitet", "core.bonus.AIR_IMMUNITY.description" : "Immun mot alla luftmagi-trollformler.", "core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring", @@ -625,7 +625,7 @@ "core.bonus.NO_DISTANCE_PENALTY.description" : "Gör full skada på alla avstånd i strid.", "core.bonus.NO_MELEE_PENALTY.name" : "Närstridsspecialist", "core.bonus.NO_MELEE_PENALTY.description" : "Ingen närstridsbestraffning.", - "core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan", + "core.bonus.NO_MORALE.name" : "Ingen moralpåverkan", "core.bonus.NO_MORALE.description" : "Immun mot moral-effekter (neutral moral).", "core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning", "core.bonus.NO_WALL_PENALTY.description" : "Gör full skada mot fiender bakom en mur.", @@ -641,14 +641,14 @@ "core.bonus.REBIRTH.description" : "${val}% återuppväcks efter döden.", "core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid", "core.bonus.RETURN_AFTER_STRIKE.description" : "Återvänder till sin ruta efter attack.", - "core.bonus.REVENGE.name" : "Hämnd", - "core.bonus.REVENGE.description" : "Extra skada om den själv blivit skadad.", + "core.bonus.REVENGE.name" : "Hämndlysten", + "core.bonus.REVENGE.description" : "Vållar mer skada om den själv blivit skadad.", "core.bonus.SHOOTER.name" : "Distans-attack", "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd.", "core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten", "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Distans-attack drabbar alla inom räckhåll.", "core.bonus.SOUL_STEAL.name" : "Själtjuv", - "core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna/dödad fiende.", + "core.bonus.SOUL_STEAL.description" : "För varje dödad fiende återuppväcks: ${val}.", "core.bonus.SPELLCASTER.name" : "Besvärjare", "core.bonus.SPELLCASTER.description" : "Kan kasta: ${subtype.spell}.", "core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack", From 22d9ad2538617bdf875d9afb2263e2f9081425a7 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:27:59 +0200 Subject: [PATCH 334/726] Update swedish.json --- Mods/vcmi/config/vcmi/swedish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index b5ec96341..be905ed40 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -662,7 +662,7 @@ "core.bonus.SPELL_LIKE_ATTACK.name" : "Magisk attack", "core.bonus.SPELL_LIKE_ATTACK.description" : "Attackerar med '${subtype.spell}'.", "core.bonus.SPELL_RESISTANCE_AURA.name" : "Motståndsaura", - "core.bonus.SPELL_RESISTANCE_AURA.description" : "Närbelägna förband får ${val}% magi-resistens.", + "core.bonus.SPELL_RESISTANCE_AURA.description" : "Angränsande förband får ${val}% magi-resistens.", "core.bonus.SUMMON_GUARDIANS.name" : "Åkalla väktare", "core.bonus.SUMMON_GUARDIANS.description" : "Vid strid åkallas: ${subtype.creature} ${val}%.", "core.bonus.SYNERGY_TARGET.name" : "Synergibar", From 1e4956d3ea2b5d251e1481e9f4dbaa58bcbc2e24 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 14 Oct 2024 01:03:18 +0200 Subject: [PATCH 335/726] Update swedish.json --- Mods/vcmi/config/vcmi/swedish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index be905ed40..af3c27b82 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -243,7 +243,7 @@ "vcmi.adventureOptions.showGrid.hover" : "Visa rutnät", "vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.", "vcmi.adventureOptions.borderScroll.hover" : "Kantrullning", - "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", + "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantera armén i nedre högra hörnet", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.", "vcmi.adventureOptions.leftButtonDrag.hover" : "V.klicksdragning", @@ -670,7 +670,7 @@ "core.bonus.TWO_HEX_ATTACK_BREATH.name" : "Dödlig andedräkt", "core.bonus.TWO_HEX_ATTACK_BREATH.description" : "Andningsattack (2 rutors räckvidd).", "core.bonus.THREE_HEADED_ATTACK.name" : "Trehövdad attack", - "core.bonus.THREE_HEADED_ATTACK.description" : "Attackerar tre angränsande enheter.", + "core.bonus.THREE_HEADED_ATTACK.description" : "Attackerar upp till tre enheter framför sig.", "core.bonus.TRANSMUTATION.name" : "Transmutation", "core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet.", "core.bonus.UNDEAD.name" : "Odöd", From bbe216c51a11422e79940dce65f716dfacb847d6 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 14 Oct 2024 01:20:56 +0200 Subject: [PATCH 336/726] Update swedish.json --- Mods/vcmi/config/vcmi/swedish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index af3c27b82..b04096f46 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -243,7 +243,7 @@ "vcmi.adventureOptions.showGrid.hover" : "Visa rutnät", "vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.", "vcmi.adventureOptions.borderScroll.hover" : "Kantrullning", - "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", + "vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantera armén i nedre högra hörnet", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.", "vcmi.adventureOptions.leftButtonDrag.hover" : "V.klicksdragning", From 7183314413b201a831728e2b099f27d5e0699731 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Mon, 14 Oct 2024 01:30:27 +0200 Subject: [PATCH 337/726] Update swedish.json --- Mods/vcmi/config/vcmi/swedish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index b04096f46..75958bbf5 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -378,7 +378,7 @@ "vcmi.randomMapTab.widgets.roadTypesLabel" : "Vägtyper", "vcmi.optionsTab.turnOptions.hover" : "Turomgångsalternativ", - "vcmi.optionsTab.turnOptions.help" : "Ställ in turomgångs-timern och samtidiga turomgångar", + "vcmi.optionsTab.turnOptions.help" : "Turomgångs-timer och samtidiga turomgångar (förinställningar)", "vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern", "vcmi.optionsTab.chessFieldTurn.hover" : "Tur-timern", From b3c5e0680d5b03bddd1fc842a4812226042086fd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Aug 2024 19:02:21 +0000 Subject: [PATCH 338/726] Install ninja for ios CI builds --- CI/ios/before_install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CI/ios/before_install.sh b/CI/ios/before_install.sh index a8326e1fd..a90bf89b4 100755 --- a/CI/ios/before_install.sh +++ b/CI/ios/before_install.sh @@ -3,3 +3,5 @@ echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV . CI/install_conan_dependencies.sh "dependencies-ios" + +brew install ninja From 1623afd35d23f03c4fa1e11e9ba6c265b8017573 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Aug 2024 19:03:51 +0000 Subject: [PATCH 339/726] Use Ubuntu 24.04 for mingw builds --- .github/workflows/github.yml | 5 +++-- CI/conan/base/cross-macro.j2 | 2 +- CI/mingw-32/before_install.sh | 9 --------- CI/mingw/before_install.sh | 9 --------- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index fc40fd23a..92ac0352b 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -69,7 +69,7 @@ jobs: extension: exe preset: windows-msvc-release - platform: mingw - os: ubuntu-22.04 + os: ubuntu-24.04 test: 0 pack: 1 pack_type: Release @@ -78,8 +78,9 @@ jobs: cmake_args: -G Ninja preset: windows-mingw-conan-linux conan_profile: mingw64-linux.jinja + conan_prebuilts: dependencies-mingw - platform: mingw-32 - os: ubuntu-22.04 + os: ubuntu-24.04 test: 0 pack: 1 pack_type: Release diff --git a/CI/conan/base/cross-macro.j2 b/CI/conan/base/cross-macro.j2 index 7f4edf0ee..ba3c53212 100644 --- a/CI/conan/base/cross-macro.j2 +++ b/CI/conan/base/cross-macro.j2 @@ -10,7 +10,7 @@ STRIP={{ target_host }}-strip {%- endmacro -%} {% macro generate_env_win32(target_host) -%} -CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/10-posix/ +CONAN_SYSTEM_LIBRARY_LOCATION=/usr/lib/gcc/{{ target_host }}/13-posix/ RC={{ target_host }}-windres {%- endmacro -%} diff --git a/CI/mingw-32/before_install.sh b/CI/mingw-32/before_install.sh index 857f4a716..9305f8b7b 100644 --- a/CI/mingw-32/before_install.sh +++ b/CI/mingw-32/before_install.sh @@ -3,12 +3,3 @@ sudo apt-get update sudo apt-get install ninja-build mingw-w64 nsis sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix - -# Workaround for getting new MinGW headers on Ubuntu 22.04. -# Remove it once MinGW headers version in repository will be 10.0 at least -curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-3_all.deb \ - && sudo dpkg -i mingw-w64-common_10.0.0-3_all.deb; -curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-i686-dev_10.0.0-3_all.deb \ - && sudo dpkg -i mingw-w64-i686-dev_10.0.0-3_all.deb; - -. CI/install_conan_dependencies.sh "dependencies-mingw-32" diff --git a/CI/mingw/before_install.sh b/CI/mingw/before_install.sh index 70fbaf0d5..4ebe8607a 100755 --- a/CI/mingw/before_install.sh +++ b/CI/mingw/before_install.sh @@ -3,12 +3,3 @@ sudo apt-get update sudo apt-get install ninja-build mingw-w64 nsis sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - -# Workaround for getting new MinGW headers on Ubuntu 22.04. -# Remove it once MinGW headers version in repository will be 10.0 at least -curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-3_all.deb \ - && sudo dpkg -i mingw-w64-common_10.0.0-3_all.deb; -curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-x86-64-dev_10.0.0-3_all.deb \ - && sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-3_all.deb; - -. CI/install_conan_dependencies.sh "dependencies-mingw" From 4dc521743cee9ec4804c5ca9fdaa6a86513077ac Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Aug 2024 19:04:41 +0000 Subject: [PATCH 340/726] Install dependencies as separate CI step --- .github/workflows/github.yml | 15 +++++++++++++-- CI/android/before_install.sh | 4 +--- CI/ios/before_install.sh | 2 -- CI/mac-arm/before_install.sh | 1 - CI/mac-intel/before_install.sh | 1 - CI/mac/before_install.sh | 2 -- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 92ac0352b..6a6d3d993 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -40,6 +40,7 @@ jobs: extension: dmg preset: macos-conan-ninja-release conan_profile: macos-intel + conan_prebuilts: dependencies-mac-intel conan_options: --options with_apple_system_libs=True artifact_platform: intel - platform: mac-arm @@ -50,6 +51,7 @@ jobs: extension: dmg preset: macos-arm-conan-ninja-release conan_profile: macos-arm + conan_prebuilts: dependencies-mac-arm conan_options: --options with_apple_system_libs=True artifact_platform: arm - platform: ios @@ -60,6 +62,7 @@ jobs: extension: ipa preset: ios-release-conan-ccache conan_profile: ios-arm64 + conan_prebuilts: dependencies-ios conan_options: --options with_apple_system_libs=True - platform: msvc os: windows-latest @@ -89,11 +92,13 @@ jobs: cmake_args: -G Ninja preset: windows-mingw-conan-linux conan_profile: mingw32-linux.jinja + conan_prebuilts: dependencies-mingw-32 - platform: android-32 os: macos-14 extension: apk preset: android-conan-ninja-release conan_profile: android-32 + conan_prebuilts: dependencies-android-32 conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT artifact_platform: armeabi-v7a - platform: android-64 @@ -101,6 +106,7 @@ jobs: extension: apk preset: android-conan-ninja-release conan_profile: android-64 + conan_prebuilts: dependencies-android-64 conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT artifact_platform: arm64-v8a runs-on: ${{ matrix.os }} @@ -109,15 +115,20 @@ jobs: shell: bash steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 with: submodules: recursive - - name: Dependencies + - name: Prepare CI run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh' env: VCMI_BUILD_PLATFORM: x64 + - name: Install Conan Dependencies + if: "${{ matrix.conan_prebuilts != '' }}" + run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}' + # ensure the ccache for each PR is separate so they don't interfere with each other # fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found - name: ccache for PRs diff --git a/CI/android/before_install.sh b/CI/android/before_install.sh index 146d52110..fe416b26f 100755 --- a/CI/android/before_install.sh +++ b/CI/android/before_install.sh @@ -2,6 +2,4 @@ echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/25.2.9519653" >> $GITHUB_ENV -brew install ninja - -. CI/install_conan_dependencies.sh "$DEPS_FILENAME" \ No newline at end of file +brew install ninja \ No newline at end of file diff --git a/CI/ios/before_install.sh b/CI/ios/before_install.sh index a90bf89b4..0664cc910 100755 --- a/CI/ios/before_install.sh +++ b/CI/ios/before_install.sh @@ -2,6 +2,4 @@ echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV -. CI/install_conan_dependencies.sh "dependencies-ios" - brew install ninja diff --git a/CI/mac-arm/before_install.sh b/CI/mac-arm/before_install.sh index 41701758b..be9945c20 100755 --- a/CI/mac-arm/before_install.sh +++ b/CI/mac-arm/before_install.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash -DEPS_FILENAME=dependencies-mac-arm . CI/mac/before_install.sh diff --git a/CI/mac-intel/before_install.sh b/CI/mac-intel/before_install.sh index a96955b20..be9945c20 100755 --- a/CI/mac-intel/before_install.sh +++ b/CI/mac-intel/before_install.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash -DEPS_FILENAME=dependencies-mac-intel . CI/mac/before_install.sh diff --git a/CI/mac/before_install.sh b/CI/mac/before_install.sh index ed11e87df..0664cc910 100755 --- a/CI/mac/before_install.sh +++ b/CI/mac/before_install.sh @@ -3,5 +3,3 @@ echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV brew install ninja - -. CI/install_conan_dependencies.sh "$DEPS_FILENAME" From 0e1d5274babcd46ea89cbbe1a97ed5889759205c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Aug 2024 19:04:58 +0000 Subject: [PATCH 341/726] Generate aab's only for builds on master branch --- .github/workflows/github.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 6a6d3d993..e689e0dc2 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -282,7 +282,7 @@ jobs: echo "ANDROID_AAB_PATH=$ANDROID_AAB_PATH" >> $GITHUB_ENV - name: Android apk artifacts - if: ${{ startsWith(matrix.platform, 'android') }} + if: ${{ startsWith(matrix.platform, 'android') }} uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} @@ -290,7 +290,7 @@ jobs: ${{ env.ANDROID_APK_PATH }} - name: Android aab artifacts - if: ${{ startsWith(matrix.platform, 'android') }} + if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }} uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - aab From a64f1f1232bceae9e89b106eaf97c79e9665e68b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Aug 2024 19:05:15 +0000 Subject: [PATCH 342/726] Use newer SDL libraries --- conanfile.py | 53 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/conanfile.py b/conanfile.py index c9c5df23d..891eb2a89 100644 --- a/conanfile.py +++ b/conanfile.py @@ -15,10 +15,11 @@ class VCMI(ConanFile): "minizip/[~1.2.12]", ] _clientRequires = [ - "sdl/[~2.26.1 || >=2.0.20 <=2.22.0]", # versions in between have broken sound - "sdl_image/[~2.0.5]", - "sdl_mixer/[~2.0.4]", - "sdl_ttf/[~2.0.18]", + # Versions between 2.5-2.8 have broken loading of palette sdl images which a lot of mods use + # there is workaround that require disabling cmake flag which is not available in conan recipes. + # Bug is fixed in version 2.8, however it is not available in conan at the moment + "sdl_image/2.0.5", + "sdl_ttf/[>=2.0.18]", "onetbb/[^2021.7 <2021.10]", # 2021.10+ breaks mobile builds due to added hwloc dependency "xz_utils/[>=5.2.5]", # Required for innoextract ] @@ -52,15 +53,36 @@ class VCMI(ConanFile): # static Qt for iOS is the only viable option at the moment self.options["qt"].shared = self.settings.os != "iOS" - if self.settings.os == "Android": - self.options["qt"].android_sdk = tools.get_env("ANDROID_HOME", default="") - # TODO: enable for all platforms if self.settings.os == "Android": self.options["bzip2"].shared = True self.options["libiconv"].shared = True self.options["zlib"].shared = True + # TODO: enable for all platforms? + if self.settings.os == "Windows": + self.options["sdl"].shared = True + self.options["sdl_image"].shared = True + self.options["sdl_mixer"].shared = True + self.options["sdl_ttf"].shared = True + + if self.settings.os == "iOS": + # TODO: ios - newer sdl fails to link + self.requires("sdl/2.26.1") + self.requires("sdl_mixer/2.0.4") + elif self.settings.os == "Android": + # On Android SDL version must be same as version of Java wrapper for SDL in VCMI source code + self.requires("sdl/2.26.5") + self.requires("sdl_mixer/2.0.4") + else: + # upcoming SDL version 3.0+ is not supported at the moment due to API breakage + # SDL versions between 2.22-2.26.1 have broken sound + self.requires("sdl/[^2.26 || >=2.0.20 <=2.22.0]") + self.requires("sdl_mixer/[>=2.0.4]") + + if self.settings.os == "Android": + self.options["qt"].android_sdk = tools.get_env("ANDROID_HOME", default="") + if self.options.default_options_of_requirements: return @@ -149,8 +171,13 @@ class VCMI(ConanFile): self.options["sdl"].sdl2main = self.settings.os != "iOS" self.options["sdl"].vulkan = False + # bmp, png are the only ones that needs to be supported + # pcx is also enabled since some people might use it due to H3 using format named 'pcx' (but unrelated to sdl_image pcx) + # dds support may be useful for HD edition, but not supported by sdl_image at the moment + self.options["sdl_image"].gif = False self.options["sdl_image"].lbm = False self.options["sdl_image"].pnm = False + self.options["sdl_image"].qoi = False self.options["sdl_image"].svg = False self.options["sdl_image"].tga = False self.options["sdl_image"].with_libjpeg = False @@ -162,13 +189,17 @@ class VCMI(ConanFile): if is_apple_os(self): self.options["sdl_image"].imageio = True + # mp3, ogg and wav are the only ones that needs to be supported + # opus is nice to have, but fails to build in CI + # flac can be considered, but generally unnecessary self.options["sdl_mixer"].flac = False - self.options["sdl_mixer"].mad = False - self.options["sdl_mixer"].mikmod = False self.options["sdl_mixer"].modplug = False - self.options["sdl_mixer"].nativemidi = False self.options["sdl_mixer"].opus = False - self.options["sdl_mixer"].wav = False + if self.settings.os == "iOS" or self.settings.os == "Android": + # only available in older sdl_mixer version, removed in newer version + self.options["sdl_mixer"].mad = False + self.options["sdl_mixer"].mikmod = False + self.options["sdl_mixer"].nativemidi = False def _disableQtOptions(disableFlag, options): return " ".join([f"-{disableFlag}-{tool}" for tool in options]) From 289ed742d090fdcd0adeb03137d0b4853237ecc6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 29 Aug 2024 21:28:55 +0000 Subject: [PATCH 343/726] Disable unused boost_url --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index 891eb2a89..ab824158e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -108,6 +108,7 @@ class VCMI(ConanFile): self.options["boost"].without_timer = True self.options["boost"].without_type_erasure = True self.options["boost"].without_wave = True + self.options["boost"].without_url = True self.options["ffmpeg"].disable_all_bitstream_filters = True self.options["ffmpeg"].disable_all_decoders = True From ecf063cd1c86a7a34b2a28d803de96e2cb83c74b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Sep 2024 16:54:49 +0000 Subject: [PATCH 344/726] Reorganized layout of CI directory, move all before_install files into common directory, remove copy-pasted files --- .github/workflows/github.yml | 12 +++++++++++- CI/android-32/before_install.sh | 4 ---- CI/android-64/before_install.sh | 4 ---- CI/android/before_install.sh | 5 ----- CI/before_install/android.sh | 4 ++++ .../linux_qt5.sh} | 5 ++--- .../linux_qt6.sh} | 4 +++- .../before_install.sh => before_install/macos.sh} | 0 .../mingw_x86.sh} | 0 .../mingw_x86_64.sh} | 0 .../before_install.sh => before_install/msvc.sh} | 0 CI/linux-qt6/upload_package.sh | 1 - CI/linux/upload_package.sh | 1 - CI/mac-arm/before_install.sh | 3 --- CI/mac-intel/before_install.sh | 3 --- CI/mac/before_install.sh | 5 ----- CI/{linux-qt6 => }/validate_json.py | 0 17 files changed, 20 insertions(+), 31 deletions(-) delete mode 100755 CI/android-32/before_install.sh delete mode 100755 CI/android-64/before_install.sh delete mode 100755 CI/android/before_install.sh create mode 100644 CI/before_install/android.sh rename CI/{linux/before_install.sh => before_install/linux_qt5.sh} (80%) rename CI/{linux-qt6/before_install.sh => before_install/linux_qt6.sh} (76%) rename CI/{ios/before_install.sh => before_install/macos.sh} (100%) mode change 100755 => 100644 rename CI/{mingw-32/before_install.sh => before_install/mingw_x86.sh} (100%) rename CI/{mingw/before_install.sh => before_install/mingw_x86_64.sh} (100%) rename CI/{msvc/before_install.sh => before_install/msvc.sh} (100%) delete mode 100644 CI/linux-qt6/upload_package.sh delete mode 100644 CI/linux/upload_package.sh delete mode 100755 CI/mac-arm/before_install.sh delete mode 100755 CI/mac-intel/before_install.sh delete mode 100755 CI/mac/before_install.sh rename CI/{linux-qt6 => }/validate_json.py (100%) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index e689e0dc2..4a4acb2ad 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -23,14 +23,17 @@ jobs: - platform: linux-qt6 os: ubuntu-24.04 test: 0 + before_install: linux_qt6.sh preset: linux-clang-test - platform: linux os: ubuntu-24.04 test: 1 + before_install: linux_qt5.sh preset: linux-gcc-test - platform: linux os: ubuntu-20.04 test: 0 + before_install: linux_qt5.sh preset: linux-gcc-debug - platform: mac-intel os: macos-13 @@ -38,6 +41,7 @@ jobs: pack: 1 pack_type: Release extension: dmg + before_install: macos.sh preset: macos-conan-ninja-release conan_profile: macos-intel conan_prebuilts: dependencies-mac-intel @@ -49,6 +53,7 @@ jobs: pack: 1 pack_type: Release extension: dmg + before_install: macos.sh preset: macos-arm-conan-ninja-release conan_profile: macos-arm conan_prebuilts: dependencies-mac-arm @@ -60,6 +65,7 @@ jobs: pack: 1 pack_type: Release extension: ipa + before_install: macos.sh preset: ios-release-conan-ccache conan_profile: ios-arm64 conan_prebuilts: dependencies-ios @@ -70,6 +76,7 @@ jobs: pack: 1 pack_type: RelWithDebInfo extension: exe + before_install: msvc.sh preset: windows-msvc-release - platform: mingw os: ubuntu-24.04 @@ -79,6 +86,7 @@ jobs: extension: exe cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis` cmake_args: -G Ninja + before_install: mingw_x86_64.sh preset: windows-mingw-conan-linux conan_profile: mingw64-linux.jinja conan_prebuilts: dependencies-mingw @@ -90,6 +98,7 @@ jobs: extension: exe cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis` cmake_args: -G Ninja + before_install: mingw_x86.sh preset: windows-mingw-conan-linux conan_profile: mingw32-linux.jinja conan_prebuilts: dependencies-mingw-32 @@ -121,7 +130,8 @@ jobs: submodules: recursive - name: Prepare CI - run: source '${{github.workspace}}/CI/${{matrix.platform}}/before_install.sh' + if: "${{ matrix.before_install != '' }}" + run: source '${{github.workspace}}/CI/before_install/${{matrix.before_install}}' env: VCMI_BUILD_PLATFORM: x64 diff --git a/CI/android-32/before_install.sh b/CI/android-32/before_install.sh deleted file mode 100755 index 67cacaddf..000000000 --- a/CI/android-32/before_install.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -DEPS_FILENAME=dependencies-android-32 -. CI/android/before_install.sh diff --git a/CI/android-64/before_install.sh b/CI/android-64/before_install.sh deleted file mode 100755 index af0a36874..000000000 --- a/CI/android-64/before_install.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -DEPS_FILENAME=dependencies-android-64 -. CI/android/before_install.sh diff --git a/CI/android/before_install.sh b/CI/android/before_install.sh deleted file mode 100755 index fe416b26f..000000000 --- a/CI/android/before_install.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/25.2.9519653" >> $GITHUB_ENV - -brew install ninja \ No newline at end of file diff --git a/CI/before_install/android.sh b/CI/before_install/android.sh new file mode 100644 index 000000000..9fba7de9f --- /dev/null +++ b/CI/before_install/android.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +sudo apt-get update +sudo apt-get install ninja-build diff --git a/CI/linux/before_install.sh b/CI/before_install/linux_qt5.sh similarity index 80% rename from CI/linux/before_install.sh rename to CI/before_install/linux_qt5.sh index 5df49a521..ebf9faeb1 100644 --- a/CI/linux/before_install.sh +++ b/CI/before_install/linux_qt5.sh @@ -1,6 +1,5 @@ #!/bin/sh -sudo apt remove needrestart sudo apt-get update # Dependencies @@ -9,6 +8,6 @@ sudo apt-get update # - debian build settings at debian/control sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev libboost-iostreams-dev \ libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ -qtbase5-dev \ +qtbase5-dev qttools5-dev \ ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ -libminizip-dev libfuzzylite-dev qttools5-dev libsqlite3-dev # Optional dependencies +libminizip-dev libfuzzylite-dev libsqlite3-dev # Optional dependencies diff --git a/CI/linux-qt6/before_install.sh b/CI/before_install/linux_qt6.sh similarity index 76% rename from CI/linux-qt6/before_install.sh rename to CI/before_install/linux_qt6.sh index b88d42704..422b50e98 100644 --- a/CI/linux-qt6/before_install.sh +++ b/CI/before_install/linux_qt6.sh @@ -1,9 +1,11 @@ #!/bin/sh -sudo apt remove needrestart sudo apt-get update # Dependencies +# In case of change in dependencies list please also update: +# - developer docs at docs/developer/Building_Linux.md +# - debian build settings at debian/control sudo apt-get install libboost-dev libboost-filesystem-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-locale-dev libboost-iostreams-dev \ libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \ diff --git a/CI/ios/before_install.sh b/CI/before_install/macos.sh old mode 100755 new mode 100644 similarity index 100% rename from CI/ios/before_install.sh rename to CI/before_install/macos.sh diff --git a/CI/mingw-32/before_install.sh b/CI/before_install/mingw_x86.sh similarity index 100% rename from CI/mingw-32/before_install.sh rename to CI/before_install/mingw_x86.sh diff --git a/CI/mingw/before_install.sh b/CI/before_install/mingw_x86_64.sh similarity index 100% rename from CI/mingw/before_install.sh rename to CI/before_install/mingw_x86_64.sh diff --git a/CI/msvc/before_install.sh b/CI/before_install/msvc.sh similarity index 100% rename from CI/msvc/before_install.sh rename to CI/before_install/msvc.sh diff --git a/CI/linux-qt6/upload_package.sh b/CI/linux-qt6/upload_package.sh deleted file mode 100644 index 1a2485251..000000000 --- a/CI/linux-qt6/upload_package.sh +++ /dev/null @@ -1 +0,0 @@ -#!/bin/sh diff --git a/CI/linux/upload_package.sh b/CI/linux/upload_package.sh deleted file mode 100644 index 1a2485251..000000000 --- a/CI/linux/upload_package.sh +++ /dev/null @@ -1 +0,0 @@ -#!/bin/sh diff --git a/CI/mac-arm/before_install.sh b/CI/mac-arm/before_install.sh deleted file mode 100755 index be9945c20..000000000 --- a/CI/mac-arm/before_install.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -. CI/mac/before_install.sh diff --git a/CI/mac-intel/before_install.sh b/CI/mac-intel/before_install.sh deleted file mode 100755 index be9945c20..000000000 --- a/CI/mac-intel/before_install.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -. CI/mac/before_install.sh diff --git a/CI/mac/before_install.sh b/CI/mac/before_install.sh deleted file mode 100755 index 0664cc910..000000000 --- a/CI/mac/before_install.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV - -brew install ninja diff --git a/CI/linux-qt6/validate_json.py b/CI/validate_json.py similarity index 100% rename from CI/linux-qt6/validate_json.py rename to CI/validate_json.py From 2d2bc8293f53f77f013f6f6bdf2ef6d259f71920 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Sep 2024 16:55:35 +0000 Subject: [PATCH 345/726] Delete old coverity files --- CI/msvc/build_script.bat | 6 ------ CI/msvc/coverity_build_script.bat | 5 ----- CI/msvc/coverity_upload_script.ps | 17 ----------------- 3 files changed, 28 deletions(-) delete mode 100644 CI/msvc/build_script.bat delete mode 100644 CI/msvc/coverity_build_script.bat delete mode 100644 CI/msvc/coverity_upload_script.ps diff --git a/CI/msvc/build_script.bat b/CI/msvc/build_script.bat deleted file mode 100644 index 5bd1d8485..000000000 --- a/CI/msvc/build_script.bat +++ /dev/null @@ -1,6 +0,0 @@ -cd %APPVEYOR_BUILD_FOLDER% -cd build_%VCMI_BUILD_PLATFORM% - -cmake --build . --config %VCMI_BUILD_CONFIGURATION% -- /maxcpucount:2 - -cpack diff --git a/CI/msvc/coverity_build_script.bat b/CI/msvc/coverity_build_script.bat deleted file mode 100644 index 9fe2cbf2a..000000000 --- a/CI/msvc/coverity_build_script.bat +++ /dev/null @@ -1,5 +0,0 @@ -cd %APPVEYOR_BUILD_FOLDER% -cd build_%VCMI_BUILD_PLATFORM% - -echo Building with coverity... -cov-build.exe --dir cov-int cmake --build . --config %VCMI_BUILD_CONFIGURATION% -- /maxcpucount:2 diff --git a/CI/msvc/coverity_upload_script.ps b/CI/msvc/coverity_upload_script.ps deleted file mode 100644 index e830ae970..000000000 --- a/CI/msvc/coverity_upload_script.ps +++ /dev/null @@ -1,17 +0,0 @@ -7z a "$Env:APPVEYOR_BUILD_FOLDER\$Env:APPVEYOR_PROJECT_NAME.zip" "$Env:APPVEYOR_BUILD_FOLDER\build_$Env:VCMI_BUILD_PLATFORM\cov-int\" - -# cf. http://stackoverflow.com/a/25045154/335418 -Remove-item alias:curl - -Write-Host "Uploading Coverity analysis result..." -ForegroundColor "Green" - -curl --silent --show-error ` - --output curl-out.txt ` - --form token="$Env:coverity_token" ` - --form email="$Env:coverity_email" ` - --form "file=@$Env:APPVEYOR_BUILD_FOLDER\$Env:APPVEYOR_PROJECT_NAME.zip" ` - --form version="$Env:APPVEYOR_REPO_COMMIT" ` - --form description="CI server scheduled build." ` - https://scan.coverity.com/builds?project=vcmi%2Fvcmi - -cat .\curl-out.txt From 501622229df8ab18f1e9ab2ccaf07d0aebfd8201 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Sep 2024 16:57:37 +0000 Subject: [PATCH 346/726] Use new prebuilts package --- .github/workflows/github.yml | 26 ++++++++++++-------------- CI/install_conan_dependencies.sh | 2 +- CMakeLists.txt | 20 ++++++++++++-------- conanfile.py | 8 +++----- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 4a4acb2ad..361e650c0 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -78,45 +78,43 @@ jobs: extension: exe before_install: msvc.sh preset: windows-msvc-release - - platform: mingw + - platform: mingw_x86_64 os: ubuntu-24.04 test: 0 pack: 1 pack_type: Release extension: exe - cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis` cmake_args: -G Ninja before_install: mingw_x86_64.sh preset: windows-mingw-conan-linux conan_profile: mingw64-linux.jinja - conan_prebuilts: dependencies-mingw - - platform: mingw-32 + conan_prebuilts: dependencies-mingw-x86-64 + - platform: mingw_x86 os: ubuntu-24.04 test: 0 pack: 1 pack_type: Release extension: exe - cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis` cmake_args: -G Ninja before_install: mingw_x86.sh preset: windows-mingw-conan-linux conan_profile: mingw32-linux.jinja - conan_prebuilts: dependencies-mingw-32 + conan_prebuilts: dependencies-mingw-x86 - platform: android-32 - os: macos-14 + os: ubuntu-24.04 extension: apk preset: android-conan-ninja-release - conan_profile: android-32 - conan_prebuilts: dependencies-android-32 - conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT + before_install: android.sh + conan_profile: android-32-ndk + conan_prebuilts: dependencies-android-armeabi-v7a artifact_platform: armeabi-v7a - platform: android-64 - os: macos-14 + os: ubuntu-24.04 extension: apk preset: android-conan-ninja-release - conan_profile: android-64 - conan_prebuilts: dependencies-android-64 - conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT + before_install: android.sh + conan_profile: android-64-ndk + conan_prebuilts: dependencies-android-arm64-v8a artifact_platform: arm64-v8a runs-on: ${{ matrix.os }} defaults: diff --git a/CI/install_conan_dependencies.sh b/CI/install_conan_dependencies.sh index 2b14811d0..593f96e30 100644 --- a/CI/install_conan_dependencies.sh +++ b/CI/install_conan_dependencies.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -RELEASE_TAG="1.2" +RELEASE_TAG="1.3" FILENAME="$1" DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME.txz" diff --git a/CMakeLists.txt b/CMakeLists.txt index 10c0ca817..e7e02f550 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,11 +180,6 @@ else() add_definitions(-DVCMI_NO_EXTRA_VERSION) endif(ENABLE_GITVERSION) -# Precompiled header configuration -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0 ) - set(ENABLE_PCH OFF) # broken -endif() - if(ENABLE_PCH) macro(enable_pch name) target_precompile_headers(${name} PRIVATE $<$:>) @@ -328,7 +323,6 @@ if(MINGW OR MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244") # 4244: conversion from 'xxx' to 'yyy', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") # 4267: conversion from 'xxx' to 'yyy', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4275") # 4275: non dll-interface class 'xxx' used as base for dll-interface class - #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss if(ENABLE_STRICT_COMPILATION) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") # Treats all compiler warnings as errors @@ -493,14 +487,23 @@ if (ENABLE_CLIENT) if(TARGET SDL2_image::SDL2_image) add_library(SDL2::Image ALIAS SDL2_image::SDL2_image) endif() + if(TARGET SDL2_image::SDL2_image-static) + add_library(SDL2::Image ALIAS SDL2_image::SDL2_image-static) + endif() find_package(SDL2_mixer REQUIRED) if(TARGET SDL2_mixer::SDL2_mixer) add_library(SDL2::Mixer ALIAS SDL2_mixer::SDL2_mixer) endif() + if(TARGET SDL2_mixer::SDL2_mixer-static) + add_library(SDL2::Mixer ALIAS SDL2_mixer::SDL2_mixer-static) + endif() find_package(SDL2_ttf REQUIRED) if(TARGET SDL2_ttf::SDL2_ttf) add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf) endif() + if(TARGET SDL2_ttf::SDL2_ttf-static) + add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf-static) + endif() endif() if(ENABLE_LOBBY) @@ -727,7 +730,7 @@ endif() if(WIN32) if(TBB_FOUND AND MSVC) - install_vcpkg_imported_tgt(TBB::tbb) + install_vcpkg_imported_tgt(TBB::tbb) endif() if(USING_CONAN) @@ -737,7 +740,8 @@ if(WIN32) ${dep_files} "${CMAKE_SYSROOT}/bin/*.dll" "${CMAKE_SYSROOT}/lib/*.dll" - "${CONAN_SYSTEM_LIBRARY_LOCATION}/*.dll") + "${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_seh-1.dll" + "${CONAN_SYSTEM_LIBRARY_LOCATION}/libstdc++-6.dll") else() file(GLOB dep_files ${dep_files} diff --git a/conanfile.py b/conanfile.py index ab824158e..08621c6ef 100644 --- a/conanfile.py +++ b/conanfile.py @@ -47,9 +47,7 @@ class VCMI(ConanFile): self.options["freetype"].shared = self.settings.os == "Android" # SDL_image and Qt depend on it, in iOS both are static - # Enable static libpng due to https://github.com/conan-io/conan-center-index/issues/15440, - # which leads to VCMI crashes of MinGW - self.options["libpng"].shared = not (self.settings.os == "Windows" and cross_building(self)) and self.settings.os != "iOS" + self.options["libpng"].shared = not self.settings.os != "iOS" # static Qt for iOS is the only viable option at the moment self.options["qt"].shared = self.settings.os != "iOS" @@ -173,12 +171,12 @@ class VCMI(ConanFile): self.options["sdl"].vulkan = False # bmp, png are the only ones that needs to be supported - # pcx is also enabled since some people might use it due to H3 using format named 'pcx' (but unrelated to sdl_image pcx) # dds support may be useful for HD edition, but not supported by sdl_image at the moment self.options["sdl_image"].gif = False self.options["sdl_image"].lbm = False self.options["sdl_image"].pnm = False - self.options["sdl_image"].qoi = False + self.options["sdl_image"].pcx = False + #self.options["sdl_image"].qoi = False # sdl_image >=2.6 self.options["sdl_image"].svg = False self.options["sdl_image"].tga = False self.options["sdl_image"].with_libjpeg = False From 06cd74dde6f00f76bc9912ec2441cf6b8a29b5fc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Sep 2024 16:59:54 +0000 Subject: [PATCH 347/726] Use new path to the script --- .github/workflows/github.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 361e650c0..c7a7f1116 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -379,4 +379,4 @@ jobs: - name: Validate JSON run: | sudo apt install python3-jstyleson - python3 CI/linux-qt6/validate_json.py + python3 CI/validate_json.py From 9a1f26883de33ac2e217b00cbff660bd0e59e554 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Sep 2024 17:00:04 +0000 Subject: [PATCH 348/726] Update documentation --- docs/modders/File_Formats.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/modders/File_Formats.md b/docs/modders/File_Formats.md index d1466e4cf..b80188625 100644 --- a/docs/modders/File_Formats.md +++ b/docs/modders/File_Formats.md @@ -25,13 +25,21 @@ For animations VCMI supports .def format from Heroes III as well as alternative ### Sounds -For sounds VCMI currently requires .wav format. Generally, VCMI will support any .wav parameters, however you might want to use high-bitrate versions, such as 44100 Hz or 48000 Hz, 32 bit, 1 or 2 channels +For sounds VCMI currently supports: +- .ogg/vorbis format - preferred for mods. Unlike wav, vorbis uses compression which may cause some data loss, however even 128kbit is generally undistinguishable from lossless formats +- .wav format. This is format used by H3. It is supported by vcmi, but it may result in large file sizes (and as result - large mods) -Support for additional formats, such as ogg/vorbis and ogg/opus is likely to be added in future +Generally, VCMI will support any audio parameters, however you might want to use high-bitrate versions, such as 44100 Hz or 48000 Hz, 32 bit, 1 or 2 channels + +Support for additional formats, such as ogg/opus or flac may be added in future ### Music -For sounds VCMI currently requires .mp3 format. Support for additional formats, such as ogg/vorbis and ogg/opus is likely to be added in future +For music VCMI currently supports: +- .ogg/vorbis format - preferred for mods. Generally offers better quality and lower sizes compared to mp3 +- .mp3 format. This is format used by H3 + +Support for additional formats, such as ogg/opus may be added in future ### Video From 2506490db4b1f4d210118988ce1026ce4fcc25e2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Sep 2024 20:19:18 +0000 Subject: [PATCH 349/726] Use python provided in CI runner image instead of installing via action --- .github/workflows/github.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index c7a7f1116..5c8785a00 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -178,15 +178,13 @@ jobs: mkdir -p ~/.local/share/vcmi/ mv h3_assets/* ~/.local/share/vcmi/ - - uses: actions/setup-python@v5 + - name: Install Conan if: "${{ matrix.conan_profile != '' }}" - with: - python-version: '3.10' + run: pipx install 'conan<2.0' - - name: Conan setup + - name: Install Conan profile if: "${{ matrix.conan_profile != '' }}" run: | - pip3 install 'conan<2.0' conan profile new default --detect conan install . \ --install-folder=conan-generated \ @@ -364,11 +362,6 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - if: "${{ matrix.conan_profile != '' }}" - with: - python-version: '3.10' - - name: Ensure LF line endings run: | find . -path ./.git -prune -o -path ./AI/FuzzyLite -prune -o -path ./test/googletest \ From a63d3a11c48e5dd680067b02ec0d6e2bd71da2ce Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 6 Sep 2024 16:59:31 +0000 Subject: [PATCH 350/726] Simplify cpack logic --- .github/workflows/github.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 5c8785a00..e737f47fb 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -261,11 +261,13 @@ jobs: if: ${{ matrix.pack == 1 }} run: | cd '${{github.workspace}}/out/build/${{matrix.preset}}' - CPACK_PATH=`which -a cpack | grep -m1 -v -i chocolatey` - counter=0; until "$CPACK_PATH" -C ${{matrix.pack_type}} ${{ matrix.cpack_args }} || ((counter > 20)); do sleep 3; ((counter++)); done - test -f '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' \ - && '${{github.workspace}}/CI/${{matrix.platform}}/post_pack.sh' '${{github.workspace}}' "$(ls '${{ env.VCMI_PACKAGE_FILE_NAME }}'.*)" - rm -rf _CPack_Packages + + # Workaround for CPack bug on macOS 13 + counter=0 + until cpack -C ${{matrix.pack_type}} || ((counter > 20)) + do sleep 3 + ((counter++)) + done - name: Artifacts if: ${{ matrix.pack == 1 }} From eb06eb15cd31e354e05680f9f1cbeba894b5e929 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 9 Sep 2024 20:32:44 +0000 Subject: [PATCH 351/726] Clean up workflow --- .github/workflows/github.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index e737f47fb..768885299 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -261,7 +261,7 @@ jobs: if: ${{ matrix.pack == 1 }} run: | cd '${{github.workspace}}/out/build/${{matrix.preset}}' - + # Workaround for CPack bug on macOS 13 counter=0 until cpack -C ${{matrix.pack_type}} || ((counter > 20)) @@ -289,15 +289,15 @@ jobs: echo "ANDROID_APK_PATH=$ANDROID_APK_PATH" >> $GITHUB_ENV echo "ANDROID_AAB_PATH=$ANDROID_AAB_PATH" >> $GITHUB_ENV - - name: Android apk artifacts - if: ${{ startsWith(matrix.platform, 'android') }} + - name: Upload android apk artifacts + if: ${{ startsWith(matrix.platform, 'android') }} uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} path: | ${{ env.ANDROID_APK_PATH }} - - name: Android aab artifacts + - name: Upload Android aab artifacts if: ${{ startsWith(matrix.platform, 'android') && github.ref == 'refs/heads/master' }} uses: actions/upload-artifact@v4 with: @@ -305,7 +305,7 @@ jobs: path: | ${{ env.ANDROID_AAB_PATH }} - - name: Symbols + - name: Upload debug symbols if: ${{ matrix.platform == 'msvc' }} uses: actions/upload-artifact@v4 with: From 6b1fd428bd016f221ca22f2500134a6fbe4a983f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Sep 2024 17:04:40 +0000 Subject: [PATCH 352/726] Use shared setup file for 32 and 64 bit bingw --- .github/workflows/github.yml | 4 ++-- CI/before_install/{mingw_x86.sh => mingw.sh} | 2 ++ CI/before_install/mingw_x86_64.sh | 5 ----- 3 files changed, 4 insertions(+), 7 deletions(-) rename CI/before_install/{mingw_x86.sh => mingw.sh} (65%) delete mode 100755 CI/before_install/mingw_x86_64.sh diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 768885299..fdbc9b159 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -85,7 +85,7 @@ jobs: pack_type: Release extension: exe cmake_args: -G Ninja - before_install: mingw_x86_64.sh + before_install: mingw.sh preset: windows-mingw-conan-linux conan_profile: mingw64-linux.jinja conan_prebuilts: dependencies-mingw-x86-64 @@ -96,7 +96,7 @@ jobs: pack_type: Release extension: exe cmake_args: -G Ninja - before_install: mingw_x86.sh + before_install: mingw.sh preset: windows-mingw-conan-linux conan_profile: mingw32-linux.jinja conan_prebuilts: dependencies-mingw-x86 diff --git a/CI/before_install/mingw_x86.sh b/CI/before_install/mingw.sh similarity index 65% rename from CI/before_install/mingw_x86.sh rename to CI/before_install/mingw.sh index 9305f8b7b..2d01d179e 100644 --- a/CI/before_install/mingw_x86.sh +++ b/CI/before_install/mingw.sh @@ -2,4 +2,6 @@ sudo apt-get update sudo apt-get install ninja-build mingw-w64 nsis + sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix +sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix \ No newline at end of file diff --git a/CI/before_install/mingw_x86_64.sh b/CI/before_install/mingw_x86_64.sh deleted file mode 100755 index 4ebe8607a..000000000 --- a/CI/before_install/mingw_x86_64.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -sudo apt-get update -sudo apt-get install ninja-build mingw-w64 nsis -sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix From ab2c5ba64fc521b31049e5c739a13778a74339aa Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Sep 2024 17:56:12 +0000 Subject: [PATCH 353/726] Add workaround for Android NDK installed via conan - required for correct stripping of resulting binaries --- .github/workflows/github.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index fdbc9b159..e5ce1fd74 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -196,7 +196,13 @@ jobs: env: GENERATE_ONLY_BUILT_CONFIG: 1 - - uses: actions/setup-java@v4 + # Workaround for gradle not discovering SDK that was installed via conan + - name: Find Android NDK + if: ${{ startsWith(matrix.platform, 'android') }} + run: sudo ln -s -T /home/runner/.conan/data/android-ndk/r25c/_/_/package/4db1be536558d833e52e862fd84d64d75c2b3656/bin /usr/local/lib/android/sdk/ndk/25.2.9519653 + + - name: Install Java + uses: actions/setup-java@v4 if: ${{ startsWith(matrix.platform, 'android') }} with: distribution: 'temurin' From 55916fad0a675ff7f92407da81db1266bd4692ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Sep 2024 17:57:27 +0000 Subject: [PATCH 354/726] Fix libpng linkage - shared on all platforms other than iOS --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 08621c6ef..6e3ac8a35 100644 --- a/conanfile.py +++ b/conanfile.py @@ -47,7 +47,7 @@ class VCMI(ConanFile): self.options["freetype"].shared = self.settings.os == "Android" # SDL_image and Qt depend on it, in iOS both are static - self.options["libpng"].shared = not self.settings.os != "iOS" + self.options["libpng"].shared = self.settings.os != "iOS" # static Qt for iOS is the only viable option at the moment self.options["qt"].shared = self.settings.os != "iOS" From 34447fd844b558b3c6f7876fa959f2038fafbc85 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Sep 2024 17:59:37 +0000 Subject: [PATCH 355/726] Fix missing standard libraries from package on mingw --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7e02f550..78a4a7027 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -740,7 +740,8 @@ if(WIN32) ${dep_files} "${CMAKE_SYSROOT}/bin/*.dll" "${CMAKE_SYSROOT}/lib/*.dll" - "${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_seh-1.dll" + "${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_dw2-1.dll" # for 32-bit only? + "${CONAN_SYSTEM_LIBRARY_LOCATION}/libgcc_s_seh-1.dll" # for 64-bit only? "${CONAN_SYSTEM_LIBRARY_LOCATION}/libstdc++-6.dll") else() file(GLOB dep_files From 55e79b1b966f81a03cae1f97aa42648675ec8a6d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 14 Oct 2024 13:23:19 +0000 Subject: [PATCH 356/726] Remove Android CI workaround --- .github/workflows/github.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index e5ce1fd74..6d93ca4ad 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -17,7 +17,6 @@ env: jobs: build: strategy: - fail-fast: false matrix: include: - platform: linux-qt6 From ccd0edf13d5e9ff0cb0227eaa0c467d93f617bea Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 14 Oct 2024 13:23:48 +0000 Subject: [PATCH 357/726] Changes according to review --- .github/workflows/github.yml | 4 ++-- CI/before_install/mingw.sh | 2 +- conanfile.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 6d93ca4ad..1fec92b84 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -269,8 +269,8 @@ jobs: # Workaround for CPack bug on macOS 13 counter=0 - until cpack -C ${{matrix.pack_type}} || ((counter > 20)) - do sleep 3 + until cpack -C ${{matrix.pack_type}} || ((counter > 20)); do + sleep 3 ((counter++)) done diff --git a/CI/before_install/mingw.sh b/CI/before_install/mingw.sh index 2d01d179e..30a865d49 100644 --- a/CI/before_install/mingw.sh +++ b/CI/before_install/mingw.sh @@ -4,4 +4,4 @@ sudo apt-get update sudo apt-get install ninja-build mingw-w64 nsis sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix -sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix \ No newline at end of file +sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix diff --git a/conanfile.py b/conanfile.py index 6e3ac8a35..302aba724 100644 --- a/conanfile.py +++ b/conanfile.py @@ -70,6 +70,7 @@ class VCMI(ConanFile): self.requires("sdl_mixer/2.0.4") elif self.settings.os == "Android": # On Android SDL version must be same as version of Java wrapper for SDL in VCMI source code + # Wrapper can be found in following directory: android/vcmi-app/src/main/java/org/libsdl/app self.requires("sdl/2.26.5") self.requires("sdl_mixer/2.0.4") else: From 69b6e9c167c1f8fa008be50dcbee42c91e341141 Mon Sep 17 00:00:00 2001 From: Dydzio Date: Mon, 14 Oct 2024 17:19:34 +0200 Subject: [PATCH 358/726] Count days from 1 instead of 0 in map editor timed event UI --- mapeditor/mapsettings/timedevent.cpp | 4 ++-- mapeditor/mapsettings/timedevent.ui | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mapeditor/mapsettings/timedevent.cpp b/mapeditor/mapsettings/timedevent.cpp index 083bd1d72..772eea5c3 100644 --- a/mapeditor/mapsettings/timedevent.cpp +++ b/mapeditor/mapsettings/timedevent.cpp @@ -28,7 +28,7 @@ TimedEvent::TimedEvent(QListWidgetItem * t, QWidget *parent) : ui->eventMessageText->setPlainText(params.value("message").toString()); ui->eventAffectsCpu->setChecked(params.value("computerAffected").toBool()); ui->eventAffectsHuman->setChecked(params.value("humanAffected").toBool()); - ui->eventFirstOccurrence->setValue(params.value("firstOccurrence").toInt()); + ui->eventFirstOccurrence->setValue(params.value("firstOccurrence").toInt() + 1); ui->eventRepeatAfter->setValue(params.value("nextOccurrence").toInt()); auto playerList = params.value("players").toList(); @@ -68,7 +68,7 @@ void TimedEvent::on_TimedEvent_finished(int result) descriptor["message"] = ui->eventMessageText->toPlainText(); descriptor["humanAffected"] = QVariant::fromValue(ui->eventAffectsHuman->isChecked()); descriptor["computerAffected"] = QVariant::fromValue(ui->eventAffectsCpu->isChecked()); - descriptor["firstOccurrence"] = QVariant::fromValue(ui->eventFirstOccurrence->value()); + descriptor["firstOccurrence"] = QVariant::fromValue(ui->eventFirstOccurrence->value() - 1); descriptor["nextOccurrence"] = QVariant::fromValue(ui->eventRepeatAfter->value()); QVariantList players; diff --git a/mapeditor/mapsettings/timedevent.ui b/mapeditor/mapsettings/timedevent.ui index 4fef09168..104dd16b5 100644 --- a/mapeditor/mapsettings/timedevent.ui +++ b/mapeditor/mapsettings/timedevent.ui @@ -72,7 +72,11 @@ - + + + 1 + + From 3f59942b04ffe8d85fd03c27663bbde564fb2fa3 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:28:38 +0200 Subject: [PATCH 359/726] code review --- client/media/CSoundHandler.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/media/CSoundHandler.cpp b/client/media/CSoundHandler.cpp index a0b5e6301..adfb1a2f7 100644 --- a/client/media/CSoundHandler.cpp +++ b/client/media/CSoundHandler.cpp @@ -153,14 +153,15 @@ uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound) int freq = 0; Uint16 fmt = 0; - int chans = 0; - if(!Mix_QuerySpec(&freq, &fmt, &chans)) + int channels = 0; + if(!Mix_QuerySpec(&freq, &fmt, &channels)) return 0; if(chunk != nullptr) { - Uint32 points = (chunk->alen / ((fmt & 0xFF) / 8)); - Uint32 frames = (points / chans); + Uint32 sampleSizeBytes = (fmt & 0xFF) / 8; + Uint32 samples = (chunk->alen / sampleSizeBytes); + Uint32 frames = (samples / channels); milliseconds = ((frames * 1000) / freq); Mix_FreeChunk(chunk); From 129f8e6f34eb5e52c413fe568bfac2f4a69c0f5b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:30:03 +0200 Subject: [PATCH 360/726] code review Co-authored-by: Ivan Savenko --- lib/CGameInfoCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 9837848db..cc8572f57 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -484,7 +484,7 @@ std::vector> CGameInfoCallback::getAllVisit { std::vector> ret; for(auto & obj : gs->map->objects) - if(obj->isVisitable() && obj->ID != Obj::EVENT && getTile(obj->pos, false)) + if(obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj)) ret.push_back(obj); return ret; From b2da3179067b320e1fd4334006f39f03bdb5c904 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:26:31 +0200 Subject: [PATCH 361/726] fixes audio playback after using Back button --- client/lobby/CBonusSelection.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index 32ceab982..e04b19f8c 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -38,6 +38,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "../gui/WindowHandler.h" +#include "../adventureMap/AdventureMapInterface.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CCreatureHandler.h" @@ -389,10 +390,13 @@ void CBonusSelection::goBack() if(CSH->getState() != EClientState::GAMEPLAY) { GH.windows().popWindows(2); + CMM->playMusic(); } else { close(); + if(adventureInt) + adventureInt->onAudioResumed(); } // TODO: we can actually only pop bonus selection interface for custom campaigns // Though this would require clearing CLobbyScreen::bonusSel pointer when poping this interface @@ -403,7 +407,6 @@ void CBonusSelection::goBack() CSH->state = EClientState::LOBBY; } */ - CMM->playMusic(); } void CBonusSelection::startMap() From 6e65eaafbccafb01d5c0c0c05cd7bb7daeb8bc74 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:59:09 +0200 Subject: [PATCH 362/726] workaround for assert --- lib/texts/TextLocalizationContainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/texts/TextLocalizationContainer.cpp b/lib/texts/TextLocalizationContainer.cpp index 34259bfb4..eff0a692e 100644 --- a/lib/texts/TextLocalizationContainer.cpp +++ b/lib/texts/TextLocalizationContainer.cpp @@ -107,7 +107,7 @@ void TextLocalizationContainer::registerString(const std::string & identifierMod assert(!identifierModContext.empty()); assert(!localizedStringModContext.empty()); assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string - assert(stringsLocalizations.count(UID.get()) == 0 || boost::algorithm::starts_with(UID.get(), "map")); // registering already registered string? + assert(stringsLocalizations.count(UID.get()) == 0 || boost::algorithm::starts_with(UID.get(), "map") || boost::algorithm::starts_with(UID.get(), "header")); // registering already registered string? FIXME: "header" is a workaround. VMAP needs proper integration in translation system if(stringsLocalizations.count(UID.get()) > 0) { From cb6fa74cc41890ccce3a6dc115d313af79e57ddd Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:47:06 +0200 Subject: [PATCH 363/726] lower background music while campaign audio plays --- client/mainmenu/CPrologEpilogVideo.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index 3b68e5536..54992cf54 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -48,6 +48,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f if (videoPlayer->pos.h == 400) videoPlayer->moveBy(Point(0, 100)); + CCS->musich->setVolume(CCS->musich->getVolume() / 2); // background volume is too loud by default CCS->musich->playMusic(spe.prologMusic, true, true); voiceDurationMilliseconds = CCS->soundh->getSoundDurationMilliseconds(spe.prologVoice); voiceSoundHandle = CCS->soundh->playSound(spe.prologVoice); @@ -88,6 +89,7 @@ void CPrologEpilogVideo::show(Canvas & to) void CPrologEpilogVideo::clickPressed(const Point & cursorPosition) { + CCS->musich->setVolume(CCS->musich->getVolume() * 2); // restore background volume close(); CCS->soundh->resetCallback(voiceSoundHandle); // reset callback to avoid memory corruption since 'this' will be destroyed CCS->soundh->stopSound(voiceSoundHandle); From b379491b966606e257862eca88a29567cbf65ecc Mon Sep 17 00:00:00 2001 From: altiereslima Date: Mon, 14 Oct 2024 18:24:45 -0300 Subject: [PATCH 364/726] Update Portuguese Translation (map object search) --- Mods/vcmi/config/vcmi/portuguese.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index d8388fe1f..bf7359615 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -15,6 +15,8 @@ "vcmi.adventureMap.monsterLevel" : "\n\nNível %LEVEL, unidade %TOWN de ataque %ATTACK_TYPE", "vcmi.adventureMap.monsterMeleeType" : "corpo a corpo", "vcmi.adventureMap.monsterRangedType" : "à distância", + "vcmi.adventureMap.search.hover" : "Procurar objeto no mapa", + "vcmi.adventureMap.search.help" : "Selecione o objeto para procurar no mapa.", "vcmi.adventureMap.confirmRestartGame" : "Tem certeza de que deseja reiniciar o jogo?", "vcmi.adventureMap.noTownWithMarket" : "Não há mercados disponíveis!", From d65938fa17b6b5f205e76473694ca91d5d7ec7bb Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:26:24 +0200 Subject: [PATCH 365/726] fix campaign video scrolling for short texts --- client/mainmenu/CPrologEpilogVideo.cpp | 5 ++++- client/widgets/TextControls.cpp | 5 +++++ client/widgets/TextControls.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index 3b68e5536..7e6f47e45 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -59,7 +59,10 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f CCS->soundh->setCallback(voiceSoundHandle, onVoiceStop); text = std::make_shared(Rect(100, 500, 600, 100), EFonts::FONT_BIG, ETextAlignment::CENTER, Colors::METALLIC_GOLD, spe.prologText.toString()); - text->scrollTextTo(-50); // beginning of text in the vertical middle of black area + if(text->getLines().size() == 3) + text->scrollTextTo(-25); // beginning of text in the vertical middle of black area + else if(text->getLines().size() > 3) + text->scrollTextTo(-50); // beginning of text in the vertical middle of black area } void CPrologEpilogVideo::tick(uint32_t msPassed) diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 2fa440955..16745214f 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -176,6 +176,11 @@ void CMultiLineLabel::setText(const std::string & Txt) CLabel::setText(Txt); } +std::vector CMultiLineLabel::getLines() +{ + return lines; +} + void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what) { const auto f = GH.renderHandler().loadFont(font); diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 6880e6b44..0c99c193b 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -96,6 +96,7 @@ public: CMultiLineLabel(Rect position, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const ColorRGBA & Color = Colors::WHITE, const std::string & Text = ""); void setText(const std::string & Txt) override; + std::vector getLines(); void showAll(Canvas & to) override; void setVisibleSize(Rect visibleSize, bool redrawElement = true); From 6c4e7f8058c13d2fae423ce4dc3d1fff91d22f6a Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 15 Oct 2024 11:09:14 +0300 Subject: [PATCH 366/726] Update obstacles.json --- config/obstacles.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/obstacles.json b/config/obstacles.json index 52c8de672..b9d5bc57d 100644 --- a/config/obstacles.json +++ b/config/obstacles.json @@ -538,7 +538,7 @@ }, "55": { - "allowedTerrains" : ["water"], + "specialBattlefields" : ["ship"], "width" : 3, "height" : 3, "blockedTiles" : [-15, -16, -33], From 5f34d3b06775d7948c841f05587de383fe3155e1 Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 15 Oct 2024 14:15:43 +0300 Subject: [PATCH 367/726] Update obstacles.json --- config/obstacles.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config/obstacles.json b/config/obstacles.json index b9d5bc57d..95cfdfd20 100644 --- a/config/obstacles.json +++ b/config/obstacles.json @@ -538,6 +538,7 @@ }, "55": { + "allowedTerrains" : ["water"], "specialBattlefields" : ["ship"], "width" : 3, "height" : 3, From 86d5c05ffa3535444783cc6de36435bba56e5736 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:20:29 +0300 Subject: [PATCH 368/726] Fused identical artifacts --- Mods/vcmi/config/vcmi/english.json | 1 + client/ArtifactsUIController.cpp | 5 ++- client/widgets/CArtPlace.cpp | 14 ++++-- client/widgets/CArtifactsOfHeroBase.cpp | 10 ++++- config/schemas/artifact.json | 4 ++ lib/ArtifactUtils.cpp | 14 +++--- lib/CArtHandler.cpp | 59 ++++++++++++++----------- lib/CArtHandler.h | 6 +-- lib/networkPacks/NetPacksLib.cpp | 12 ++--- 9 files changed, 72 insertions(+), 53 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index ce9bc9e45..f6cff35ca 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -356,6 +356,7 @@ "vcmi.heroWindow.sortBackpackBySlot.help" : "Sort artifacts in backpack by equipped slot.", "vcmi.heroWindow.sortBackpackByClass.hover" : "Sort by class", "vcmi.heroWindow.sortBackpackByClass.help" : "Sort artifacts in backpack by artifact class. Treasure, Minor, Major, Relic", + "vcmi.heroWindow.fusingArtifact.fusing" : "You possess all of the components needed for the fusion of the %s. Do you wish to perform the fusion? {All components will be consumed upon fusion.}", "vcmi.tavernWindow.inviteHero" : "Invite hero", diff --git a/client/ArtifactsUIController.cpp b/client/ArtifactsUIController.cpp index 30374ebc2..649cd7304 100644 --- a/client/ArtifactsUIController.cpp +++ b/client/ArtifactsUIController.cpp @@ -74,7 +74,10 @@ bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const Art MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID()); message.appendEOL(); message.appendEOL(); - message.appendRawString(CGI->generaltexth->allTexts[732]); // You possess all of the components needed to assemble the + if(combinedArt->isFused()) + message.appendRawString(CGI->generaltexth->translate("vcmi.heroWindow.fusingArtifact.fusing")); + else + message.appendRawString(CGI->generaltexth->allTexts[732]); // You possess all of the components needed to assemble the message.replaceName(ArtifactID(combinedArt->getId())); LOCPLINT->showYesNoDialog(message.toString(), [&assembleConfirmed, hero, slot, combinedArt]() { diff --git a/client/widgets/CArtPlace.cpp b/client/widgets/CArtPlace.cpp index dbae173a2..954366315 100644 --- a/client/widgets/CArtPlace.cpp +++ b/client/widgets/CArtPlace.cpp @@ -217,9 +217,9 @@ void CArtPlace::setGestureCallback(const ClickFunctor & callback) void CArtPlace::addCombinedArtInfo(const std::map> & arts) { - for(const auto & availableArts : arts) + for(auto [combinedId, availableArts] : arts) { - const auto combinedArt = availableArts.first.toArtifact(); + const auto combinedArt = combinedId.toArtifact(); MetaString info; info.appendEOL(); info.appendEOL(); @@ -227,14 +227,20 @@ void CArtPlace::addCombinedArtInfo(const std::mapgetId()); info.appendRawString("}"); info.appendRawString(" (%d/%d)"); - info.replaceNumber(availableArts.second.size()); + info.replaceNumber(availableArts.size()); info.replaceNumber(combinedArt->getConstituents().size()); for(const auto part : combinedArt->getConstituents()) { + const auto found = std::find_if(availableArts.begin(), availableArts.end(), [part](const auto & availablePart) -> bool + { + return availablePart == part->getId() ? true : false; + }); + info.appendEOL(); - if(vstd::contains(availableArts.second, part->getId())) + if(found < availableArts.end()) { info.appendName(part->getId()); + availableArts.erase(found); } else { diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 8318fdf89..6d857b010 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -268,11 +268,17 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit std::map> arts; for(const auto combinedArt : slotInfo->artifact->artType->getPartOf()) { - arts.try_emplace(combinedArt->getId(), std::vector{}); + assert(combinedArt->isCombined()); + arts.try_emplace(combinedArt->getId()); + CArtifactFittingSet fittingSet(*curHero); for(const auto part : combinedArt->getConstituents()) { - if(curHero->hasArt(part->getId(), false, false)) + const auto partSlot = fittingSet.getArtPos(part->getId(), false, false); + if(partSlot != ArtifactPosition::PRE_FIRST) + { arts.at(combinedArt->getId()).emplace_back(part->getId()); + fittingSet.lockSlot(partSlot); + } } } artPlace->addCombinedArtInfo(arts); diff --git a/config/schemas/artifact.json b/config/schemas/artifact.json index ab335a5ca..7143ddecc 100644 --- a/config/schemas/artifact.json +++ b/config/schemas/artifact.json @@ -61,6 +61,10 @@ "description" : "Optional, list of components for combinational artifacts", "items" : { "type" : "string" } }, + "fusedComponents" : { + "type" : "boolean", + "description" : "Used together with components fild. Marks the artifact as fused. Cannot be disassembled." + }, "bonuses" : { "type" : "array", "description" : "Bonuses provided by this artifact using bonus system", diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index 7c95e435a..bfdb48468 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -202,21 +202,23 @@ DLL_LINKAGE std::vector ArtifactUtils::assemblyPossibilities( if(art->isCombined()) return arts; - for(const auto artifact : art->getPartOf()) + for(const auto combinedArt : art->getPartOf()) { - assert(artifact->isCombined()); + assert(combinedArt->isCombined()); bool possible = true; - - for(const auto constituent : artifact->getConstituents()) //check if all constituents are available + CArtifactFittingSet fittingSet(*artSet); + for(const auto part : combinedArt->getConstituents()) // check if all constituents are available { - if(!artSet->hasArt(constituent->getId(), onlyEquiped, false)) + const auto slot = fittingSet.getArtPos(part->getId(), onlyEquiped, false); + if(slot == ArtifactPosition::PRE_FIRST) { possible = false; break; } + fittingSet.lockSlot(slot); } if(possible) - arts.push_back(artifact); + arts.push_back(combinedArt); } return arts; } diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index f96e17f83..1cd5afdda 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -56,7 +56,7 @@ const std::vector & CCombinedArtifact::getConstituents() const return constituents; } -const std::vector & CCombinedArtifact::getPartOf() const +const std::set & CCombinedArtifact::getPartOf() const { return partOf; } @@ -630,7 +630,7 @@ void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node) // when this code is called both combinational art as well as component are loaded // so it is safe to access any of them art->constituents.push_back(ArtifactID(id).toArtifact()); - objects[id]->partOf.push_back(art); + objects[id]->partOf.insert(art); }); } } @@ -784,8 +784,27 @@ bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchComb CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & slot, CArtifactInstance * art) { ArtPlacementMap resArtPlacement; + const auto putToSlot = [this](const ArtifactPosition & targetSlot, CArtifactInstance * targetArt, bool locked) + { + ArtSlotInfo * slotInfo; + if(targetSlot == ArtifactPosition::TRANSITION_POS) + { + slotInfo = &artifactsTransitionPos; + } + else if(ArtifactUtils::isSlotEquipment(targetSlot)) + { + slotInfo = &artifactsWorn[targetSlot]; + } + else + { + auto position = artifactsInBackpack.begin() + targetSlot - ArtifactPosition::BACKPACK_START; + slotInfo = &(*artifactsInBackpack.emplace(position)); + } + slotInfo->artifact = targetArt; + slotInfo->locked = locked; + }; - setNewArtSlot(slot, art, false); + putToSlot(slot, art, false); if(art->artType->isCombined() && ArtifactUtils::isSlotEquipment(slot)) { const CArtifactInstance * mainPart = nullptr; @@ -806,7 +825,7 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & partSlot = ArtifactUtils::getArtAnyPosition(this, part.art->getTypeId()); assert(ArtifactUtils::isSlotEquipment(partSlot)); - setNewArtSlot(partSlot, part.art, true); + putToSlot(partSlot, part.art, true); resArtPlacement.emplace(part.art, partSlot); } else @@ -894,7 +913,15 @@ const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const void CArtifactSet::lockSlot(const ArtifactPosition & pos) { - setNewArtSlot(pos, nullptr, true); + if(pos == ArtifactPosition::TRANSITION_POS) + artifactsTransitionPos.locked = true; + else if(ArtifactUtils::isSlotEquipment(pos)) + artifactsWorn[pos].locked = true; + else + { + assert(artifactsInBackpack.size() > pos - ArtifactPosition::BACKPACK_START); + (artifactsInBackpack.begin() + pos - ArtifactPosition::BACKPACK_START)->locked = true; + } } bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck) const @@ -908,28 +935,6 @@ bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockChe return true; //no slot means not used } -void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked) -{ - assert(!vstd::contains(artifactsWorn, slot)); - - ArtSlotInfo * slotInfo; - if(slot == ArtifactPosition::TRANSITION_POS) - { - slotInfo = &artifactsTransitionPos; - } - else if(ArtifactUtils::isSlotEquipment(slot)) - { - slotInfo = &artifactsWorn[slot]; - } - else - { - auto position = artifactsInBackpack.begin() + slot - ArtifactPosition::BACKPACK_START; - slotInfo = &(*artifactsInBackpack.emplace(position)); - } - slotInfo->artifact = art; - slotInfo->locked = locked; -} - void CArtifactSet::artDeserializationFix(CBonusSystemNode *node) { for(auto & elem : artifactsWorn) diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index f0bf7d8af..a902f22c8 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -49,13 +49,13 @@ protected: CCombinedArtifact() : fused(false) {}; std::vector constituents; // Artifacts IDs a combined artifact consists of, or nullptr. - std::vector partOf; // Reverse map of constituents - combined arts that include this art + std::set partOf; // Reverse map of constituents - combined arts that include this art bool fused; public: bool isCombined() const; const std::vector & getConstituents() const; - const std::vector & getPartOf() const; + const std::set & getPartOf() const; void setFused(bool isFused); bool isFused() const; bool hasParts() const; @@ -229,8 +229,6 @@ public: const CArtifactInstance * getCombinedArtWithPart(const ArtifactID & partId) const; private: - void setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked); - void serializeJsonHero(JsonSerializeFormat & handler); void serializeJsonCreature(JsonSerializeFormat & handler); void serializeJsonCommander(JsonSerializeFormat & handler); diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index f44e6411e..0e0331d82 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1818,14 +1818,11 @@ void AssembledArtifact::applyGs(CGameState *gs) // Find slots for all involved artifacts std::vector slotsInvolved; + CArtifactFittingSet artSet(*hero); for(const auto constituent : builtArt->getConstituents()) { - ArtifactPosition slot; - if(transformedArt->getTypeId() == constituent->getId()) - slot = transformedArtSlot; - else - slot = hero->getArtPos(constituent->getId(), false, false); - + const auto slot = artSet.getArtPos(constituent->getId(), false, false); + artSet.lockSlot(slot); assert(slot != ArtifactPosition::PRE_FIRST); slotsInvolved.emplace_back(slot); } @@ -2487,10 +2484,7 @@ void SetBankConfiguration::applyGs(CGameState *gs) const CArtifactInstance * ArtSlotInfo::getArt() const { if(locked) - { - logNetwork->warn("ArtifactLocation::getArt: This location is locked!"); return nullptr; - } return artifact; } From 049c6c539f097b77bd47d7b189be0516c99cf12d Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 15 Oct 2024 23:01:53 +0300 Subject: [PATCH 369/726] Revert changes --- config/obstacles.json | 1 - 1 file changed, 1 deletion(-) diff --git a/config/obstacles.json b/config/obstacles.json index 95cfdfd20..b9d5bc57d 100644 --- a/config/obstacles.json +++ b/config/obstacles.json @@ -538,7 +538,6 @@ }, "55": { - "allowedTerrains" : ["water"], "specialBattlefields" : ["ship"], "width" : 3, "height" : 3, From 5e093d82b7b1e14575d62b748319c1fa5fa8d05b Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 15 Oct 2024 23:02:27 +0300 Subject: [PATCH 370/726] Update battlefields.json --- config/battlefields.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/battlefields.json b/config/battlefields.json index 9d9b12fa3..9f032e603 100644 --- a/config/battlefields.json +++ b/config/battlefields.json @@ -162,5 +162,5 @@ 159, 160, 161, 162, 163, 176, 177, 178, 179, 180] }, - "ship": { "graphics" : "CMBKDECK.BMP" } + "ship": { "graphics" : "CMBKDECK.BMP", "isSpecial" : true} } From 4d46a2084de450dfe0399477bcff932d238c5a27 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 16 Oct 2024 02:36:26 +0200 Subject: [PATCH 371/726] basic subtitle rendering --- client/media/CVideoHandler.cpp | 6 ++++++ client/media/CVideoHandler.h | 1 + client/media/IVideoPlayer.h | 3 +++ client/widgets/VideoWidget.cpp | 10 ++++++++++ client/widgets/VideoWidget.h | 2 ++ 5 files changed, 22 insertions(+) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index b028e8c57..3932aa1d6 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -316,6 +316,12 @@ bool CVideoInstance::loadNextFrame() return true; } + +double CVideoInstance::timeStamp() +{ + return getCurrentFrameEndTime(); +} + bool CVideoInstance::videoEnded() { return getCurrentFrame() == nullptr; diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index d40582b62..42e48eaff 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -88,6 +88,7 @@ public: void openVideo(); bool loadNextFrame(); + double timeStamp() final; bool videoEnded() final; Point size() final; diff --git a/client/media/IVideoPlayer.h b/client/media/IVideoPlayer.h index 2c979a088..6f7380166 100644 --- a/client/media/IVideoPlayer.h +++ b/client/media/IVideoPlayer.h @@ -20,6 +20,9 @@ VCMI_LIB_NAMESPACE_END class IVideoInstance { public: + /// Returns current video timestamp + virtual double timeStamp() = 0; + /// Returns true if video playback is over virtual bool videoEnded() = 0; diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 0fa6570cf..4d35e788d 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "VideoWidget.h" +#include "TextControls.h" #include "../CGameInfo.h" #include "../gui/CGuiHandler.h" @@ -33,11 +34,14 @@ VideoWidgetBase::~VideoWidgetBase() = default; void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { + OBJECT_CONSTRUCTION; + videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { pos.w = videoInstance->size().x; pos.h = videoInstance->size().y; + subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE, ""); } if (playAudio) @@ -52,6 +56,8 @@ void VideoWidgetBase::show(Canvas & to) { if(videoInstance) videoInstance->show(pos.topLeft(), to); + if(subTitle) + subTitle->showAll(to); } void VideoWidgetBase::loadAudio(const VideoPath & fileToPlay) @@ -107,6 +113,8 @@ void VideoWidgetBase::showAll(Canvas & to) { if(videoInstance) videoInstance->show(pos.topLeft(), to); + if(subTitle) + subTitle->showAll(to); } void VideoWidgetBase::tick(uint32_t msPassed) @@ -122,6 +130,8 @@ void VideoWidgetBase::tick(uint32_t msPassed) onPlaybackFinished(); } } + if(subTitle && videoInstance) + subTitle->setText(std::to_string(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + "\n" + std::to_string(msPassed) + "\n" + std::to_string(videoInstance->timeStamp())); } VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio) diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index b0264d58d..48d6fc42f 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -14,10 +14,12 @@ #include "../lib/filesystem/ResourcePath.h" class IVideoInstance; +class CMultiLineLabel; class VideoWidgetBase : public CIntObject { std::unique_ptr videoInstance; + std::unique_ptr subTitle; std::pair, si64> audioData = {nullptr, 0}; int audioHandle = -1; From 54542c54b3d104cb5afd2083ea10f5c3bf431280 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 16 Oct 2024 03:48:14 +0200 Subject: [PATCH 372/726] working subtitles --- client/widgets/VideoWidget.cpp | 48 +++++++++++++++++++++++++++++++-- client/widgets/VideoWidget.h | 2 ++ lib/filesystem/ResourcePath.cpp | 1 + lib/filesystem/ResourcePath.h | 2 ++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 4d35e788d..0db38a1d8 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -17,6 +17,8 @@ #include "../media/IVideoPlayer.h" #include "../render/Canvas.h" +#include "../../lib/filesystem/Filesystem.h" + VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio) : VideoWidgetBase(position, video, playAudio, 1.0) { @@ -36,6 +38,20 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { OBJECT_CONSTRUCTION; + using SubTitlePath = ResourcePathTempl; + SubTitlePath subTitlePath = fileToPlay.toType(); + SubTitlePath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/"); + if(CResourceHandler::get()->existsResource(subTitlePath)) + { + auto rawData = CResourceHandler::get()->load(subTitlePath)->readAll(); + srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); + } + if(CResourceHandler::get()->existsResource(subTitlePathVideoDir)) + { + auto rawData = CResourceHandler::get()->load(subTitlePathVideoDir)->readAll(); + srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); + } + videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { @@ -83,7 +99,7 @@ void VideoWidgetBase::startAudio() { this->audioHandle = -1; } - ); + ); } } @@ -97,6 +113,34 @@ void VideoWidgetBase::stopAudio() } } +std::string VideoWidgetBase::getSubTitleLine(double timestamp) +{ + if(srtContent.empty()) + return ""; + + std::regex exp("^\\s*(\\d+:\\d+:\\d+,\\d+)[^\\S\\n]+-->[^\\S\\n]+(\\d+:\\d+:\\d+,\\d+)((?:\\n(?!\\d+:\\d+:\\d+,\\d+\\b|\\n+\\d+$).*)*)", std::regex::multiline); + std::smatch res; + + std::string::const_iterator searchStart(srtContent.cbegin()); + while (std::regex_search(searchStart, srtContent.cend(), res, exp)) + { + std::vector timestamp1Str; + boost::split(timestamp1Str, static_cast(res[1]), boost::is_any_of(":,")); + std::vector timestamp2Str; + boost::split(timestamp2Str, static_cast(res[2]), boost::is_any_of(":,")); + double timestamp1 = std::stoi(timestamp1Str[0]) * 3600 + std::stoi(timestamp1Str[1]) * 60 + std::stoi(timestamp1Str[2]) + (std::stoi(timestamp1Str[3]) / 1000.0); + double timestamp2 = std::stoi(timestamp2Str[0]) * 3600 + std::stoi(timestamp2Str[1]) * 60 + std::stoi(timestamp2Str[2]) + (std::stoi(timestamp2Str[3]) / 1000.0); + std::string text = res[3]; + text.erase(0, text.find_first_not_of("\r\n\t ")); + + if(timestamp > timestamp1 && timestamp < timestamp2) + return text; + + searchStart = res.suffix().first; + } + return ""; +} + void VideoWidgetBase::activate() { CIntObject::activate(); @@ -131,7 +175,7 @@ void VideoWidgetBase::tick(uint32_t msPassed) } } if(subTitle && videoInstance) - subTitle->setText(std::to_string(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + "\n" + std::to_string(msPassed) + "\n" + std::to_string(videoInstance->timeStamp())); + subTitle->setText(getSubTitleLine(videoInstance->timeStamp())); } VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio) diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index 48d6fc42f..465e92f1b 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -25,10 +25,12 @@ class VideoWidgetBase : public CIntObject int audioHandle = -1; bool playAudio = false; float scaleFactor = 1.0; + std::string srtContent = ""; void loadAudio(const VideoPath & file); void startAudio(); void stopAudio(); + std::string getSubTitleLine(double timestamp); protected: VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio); diff --git a/lib/filesystem/ResourcePath.cpp b/lib/filesystem/ResourcePath.cpp index 347c3e9fd..b29d24954 100644 --- a/lib/filesystem/ResourcePath.cpp +++ b/lib/filesystem/ResourcePath.cpp @@ -117,6 +117,7 @@ EResType EResTypeHelper::getTypeFromExtension(std::string extension) {".BIK", EResType::VIDEO}, {".OGV", EResType::VIDEO}, {".WEBM", EResType::VIDEO}, + {".SRT", EResType::SUBTITLE}, {".ZIP", EResType::ARCHIVE_ZIP}, {".LOD", EResType::ARCHIVE_LOD}, {".PAC", EResType::ARCHIVE_LOD}, diff --git a/lib/filesystem/ResourcePath.h b/lib/filesystem/ResourcePath.h index 4f4b4e9a1..ba594d6ff 100644 --- a/lib/filesystem/ResourcePath.h +++ b/lib/filesystem/ResourcePath.h @@ -30,6 +30,7 @@ class JsonSerializeFormat; * Sound: .wav .82m * Video: .smk, .bik .ogv .webm * Music: .mp3, .ogg + * Subtitle: .srt * Archive: .lod, .snd, .vid .pac .zip * Palette: .pal * Savegame: .v*gm1 @@ -48,6 +49,7 @@ enum class EResType VIDEO, VIDEO_LOW_QUALITY, SOUND, + SUBTITLE, ARCHIVE_VID, ARCHIVE_ZIP, ARCHIVE_SND, From 349f27fa52933579012606e7cd6bf798973ae0f6 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:17:41 +0200 Subject: [PATCH 373/726] Updated Czech translation --- Mods/vcmi/config/vcmi/czech.json | 406 +++++++++++++++++++++---------- 1 file changed, 274 insertions(+), 132 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 57625bd43..378961136 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -12,14 +12,21 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Převažující", "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtelná", "vcmi.adventureMap.monsterThreat.levels.11" : "Nemožná", + "vcmi.adventureMap.monsterLevel": "\n\nÚroveň %LEVEL %TOWN %ATTACK_TYPE jednotka", + "vcmi.adventureMap.monsterMeleeType": "boj zblízka", + "vcmi.adventureMap.monsterRangedType": "dálkový útok", + "vcmi.adventureMap.search.hover": "Prohledat mapový objekt", + "vcmi.adventureMap.search.help": "Vyberte objekt na mapě pro prohledání.", "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", - "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupná jakákoliv tržiště!", - "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná jakákoliv města s krčmou!", + "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", + "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", "vcmi.capitalColors.0" : "Červený", "vcmi.capitalColors.1" : "Modrý", @@ -29,7 +36,7 @@ "vcmi.capitalColors.5" : "Fialový", "vcmi.capitalColors.6" : "Tyrkysový", "vcmi.capitalColors.7" : "Růžový", - + "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", "vcmi.heroOverview.warMachine" : "Bojové stroje", "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", @@ -41,11 +48,11 @@ "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", - + "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", - "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiního hrdiny", + "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", @@ -56,6 +63,13 @@ "vcmi.spellBook.search" : "hledat...", + "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", + "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", + "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", + "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", + "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", + "vcmi.spellResearch.abort" : "Přerušit", + "vcmi.mainMenu.serverConnecting" : "Připojování...", "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", @@ -70,9 +84,14 @@ "vcmi.lobby.noPreview" : "bez náhledu", "vcmi.lobby.noUnderground" : "bez podzemí", "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", - "vcmi.lobby.backToLobby" : "Vrátit se do předsíně", - - "vcmi.lobby.login.title" : "Online předsíň VCMI", + "vcmi.lobby.backToLobby" : "Vrátit se do lobby", + "vcmi.lobby.author" : "Autor", + "vcmi.lobby.handicap" : "Handicap", + "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", + "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", + "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", + + "vcmi.lobby.login.title" : "Online lobby VCMI", "vcmi.lobby.login.username" : "Uživatelské jméno:", "vcmi.lobby.login.connecting" : "Připojování...", "vcmi.lobby.login.error" : "Chyba při připojování: %s", @@ -135,13 +154,14 @@ "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", + "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", - + "vcmi.dimensionDoor.seaToLandError" : "It's not possible to teleport from sea to land or vice versa with a Dimension Door.", //TODO "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", @@ -156,6 +176,38 @@ "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now "vcmi.systemOptions.townsGroup" : "Obrazovka města", + "vcmi.statisticWindow.statistics" : "Statistiky", + "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", + "vcmi.statisticWindow.selectView" : "Vybrat pohled", + "vcmi.statisticWindow.value" : "Hodnota", + "vcmi.statisticWindow.title.overview" : "Přehled", + "vcmi.statisticWindow.title.resources" : "Zdroje", + "vcmi.statisticWindow.title.income" : "Příjem", + "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", + "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", + "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", + "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", + "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", + "vcmi.statisticWindow.title.armyStrength" : "Síla armády", + "vcmi.statisticWindow.title.experience" : "Zkušenosti", + "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", + "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", + "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", + "vcmi.statisticWindow.param.playerName" : "Jméno hráče", + "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", + "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", + "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", + "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.battlesHero" : "Bojů (proti hrdinům)", + "vcmi.statisticWindow.param.battlesNeutral" : "Bojů (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", + "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", + "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", + "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", + "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", + "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", + "vcmi.statisticWindow.icon.defeated" : "Porážka", + "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", @@ -169,18 +221,18 @@ "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky budou zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", + "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", - "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počitadla snímků za sekundu v rohu obrazovky hry.", + "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", - "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se jich více vleze na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", + "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", @@ -194,18 +246,24 @@ "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okraji", "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okraji}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management", //TODO - "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components.", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", + "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", "vcmi.adventureOptions.leftButtonDrag.hover" : "Posouvání mapy levým kliknutím", "vcmi.adventureOptions.leftButtonDrag.help" : "{Posouvání mapy levým kliknutím}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Přetahování pravým tlačítkem", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Přetahování pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nWhen enabled, map dragging has a modern run out effect.", // TODO + "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "", "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", + "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", + "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", @@ -230,9 +288,13 @@ "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", + "vcmi.battleOptions.endWithAutocombat.hover": "Ukončit bitvu", + "vcmi.battleOptions.endWithAutocombat.help": "{Ukončit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", + "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", + "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", - "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit místo", - "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit místo}\n\nPokud se hrdina nachází na nějakém místě mapy, může jej znovu navštívit.", + "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", + "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", @@ -245,18 +307,18 @@ "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", - - "vcmi.battleWindow.damageRetaliation.will" : "Zahyne ", - "vcmi.battleWindow.damageRetaliation.may" : "Možná zahyne ", - "vcmi.battleWindow.damageRetaliation.never" : "Nezahyne.", + + "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu", + "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", + "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", "vcmi.battleWindow.killed" : "Zabito", //TODO - "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s were killed by accurate shots!", - "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s was killed with an accurate shot!", - "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s were killed by accurate shots!", - "vcmi.battleWindow.endWithAutocombat" : "Are you sure you wish to end the battle with auto combat?", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete ukončit bitvu s automatickým bojem?", "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", @@ -276,26 +338,37 @@ "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", - "vcmi.townHall.missingBase" : "Základní budova %s musí být postavena jako první", - "vcmi.townHall.noCreaturesToRecruit" : "Žádné jednotky k vycvičení!", + "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", + "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", + + "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", + "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", "vcmi.logicalExpressions.anyOf" : "Něco z následujících:", "vcmi.logicalExpressions.allOf" : "Všechny následující:", "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", - "vcmi.heroWindow.openCommander.hover" : "Open commander info window", - "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero.", - "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", - "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.", + "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", + "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", + "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", + "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", + "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", + "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", - "vcmi.commanderWindow.artifactMessage" : "Chcete navrátit tento artefakt hrdinovi?", + "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", + + "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", - "vcmi.creatureWindow.showBonuses.help" : "Display all active bonuses of the commander.", - "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení schoostí", - "vcmi.creatureWindow.showSkills.help" : "Display all learned skills of the commander.", + "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", + "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", + "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", - "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro navrácení artefaktů do hrdinova batohu.", + "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", @@ -371,9 +444,9 @@ "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", - "vcmi.optionsTab.unlimitedReplay.hover" : "Unlimited battle replay", + "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", - "vcmi.optionsTab.unlimitedReplay.help" : "{Unlimited battle replay}\nNo limit of replaying battles.", + "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", @@ -383,9 +456,10 @@ "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", // few strings from WoG used by vcmi - "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh bojovníka ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet bojovníků v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", + "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh bojovníka ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", "vcmi.stackExperience.rank.0" : "Začátečník", "vcmi.stackExperience.rank.1" : "Učeň", "vcmi.stackExperience.rank.2" : "Trénovaný", @@ -398,151 +472,219 @@ "vcmi.stackExperience.rank.9" : "Mistr", "vcmi.stackExperience.rank.10" : "Eso", - "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý úder", + // Strings for HotA Seer Hut / Quest Guards + "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", + + "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", + + "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", - "core.bonus.ADDITIONAL_RETALIATION.name": "Další odveta", - "core.bonus.ADDITIONAL_RETALIATION.description": "Může zaútočit zpět navíc ${val}x", - "core.bonus.AIR_IMMUNITY.name": "Vzdušná odolnost", - "core.bonus.AIR_IMMUNITY.description": "Imunní všem kouzlům školy vzdušné magie", - "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok okolo", - "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední jednotky", + "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", + "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", + "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", + "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", + "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", + "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", - "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže zaútočit zpět", - "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná odveta na dálku", - "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže zaútočit zpět útokem na dálku", + "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", + "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", + "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", "core.bonus.CATAPULT.name": "Katapult", "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje cenu energie hrdiny o ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje cenu energie kouzlení nepřítele o ${val}", - "core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge", // TODO - "core.bonus.CHARGE_IMMUNITY.description": "Immune to Cavalier's and Champion's Charge", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", + "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", + "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", "core.bonus.DARKNESS.name": "Závoj temnoty", - "core.bonus.DARKNESS.description": "Vytvoří clonu temnoty v oblasti ${val} polí", + "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", - "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu creature", + "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", - "core.bonus.DEFENSIVE_STANCE.description": "+${val} obranné síly při obraně", + "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", "core.bonus.DESTRUCTION.name": "Zničení", "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", - "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtící rána", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci na udělení dvojnásobného základního poškození při útoku", - "core.bonus.DRAGON_NATURE.name": "Drak", - "core.bonus.DRAGON_NATURE.description": "Jednotka má povahu draka", - "core.bonus.EARTH_IMMUNITY.name": "Zemní odolnost", - "core.bonus.EARTH_IMMUNITY.description": "Imunní všem kouzlům školy zemské magie", + "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", + "core.bonus.DRAGON_NATURE.name": "Dračí povaha", + "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", + "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", + "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", "core.bonus.ENCHANTER.name": "Zaklínač", - "core.bonus.ENCHANTER.description": "Může masově seslat ${subtype.spell} každý tah", + "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", "core.bonus.ENCHANTED.name": "Očarovaný", - "core.bonus.ENCHANTED.description": "Trvale ovlivněm kouzlem ${subtype.spell}", - "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Nevšímá si ${val} % bodů obrany", - "core.bonus.ENEMY_ATTACK_REDUCTION.description": "When being attacked, ${val}% of the attacker's attack is ignored", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude brát v potaz ${val}% bodů obrany obránce", - "core.bonus.FIRE_IMMUNITY.name": "Ohnivá odolnost", - "core.bonus.FIRE_IMMUNITY.description": "Imunní všem kouzlům školy ohnivé magie", + "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", + "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", + "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", + "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", + "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", - "core.bonus.FIRE_SHIELD.description": "Odrazí část zranení útoku zblízka", + "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", "core.bonus.FIRST_STRIKE.name": "První úder", - "core.bonus.FIRST_STRIKE.description": "Tato jednotka provede odvetu ještě než je na ni zaútočeno", + "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", "core.bonus.FEAR.name": "Strach", - "core.bonus.FEAR.description": "Způsobí strach nepřátelskému oddílu", + "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", "core.bonus.FEARLESS.name": "Nebojácnost", - "core.bonus.FEARLESS.description": "Odolnost proti strachu", - "core.bonus.FEROCITY.name": "Ferocity", //TODO - "core.bonus.FEROCITY.description": "Attacks ${val} additional times if killed anybody", - "core.bonus.FLYING.name": "Letec", - "core.bonus.FLYING.description": "Při pohybu létá (přes překážky)", - "core.bonus.FREE_SHOOTING.name": "Blízké výstřely", - "core.bonus.FREE_SHOOTING.description": "Může použít výstřely při útoku zblízka", + "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", + "core.bonus.FEROCITY.name": "Zuřivost", + "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", + "core.bonus.FLYING.name": "Létání", + "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", + "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", + "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", "core.bonus.GARGOYLE.name": "Chrlič", - "core.bonus.GARGOYLE.description": "Cannot be raised or healed", // TODO + "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", - "core.bonus.HATE.name": "Nesnáší ${subtype.creature}", - "core.bonus.HATE.description": "Dává o ${val} % větší zranění jednotce ${subtype.creature}", + "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", + "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", "core.bonus.HEALER.name": "Léčitel", "core.bonus.HEALER.description": "Léčí spojenecké jednotky", "core.bonus.HP_REGENERATION.name": "Regenerace", - "core.bonus.HP_REGENERATION.description": "Každé kolo léčí ${val} životů", - "core.bonus.JOUSTING.name": "Nabití šampiona", + "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", + "core.bonus.JOUSTING.name": "Nájezd šampionů", "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", "core.bonus.KING.name": "Král", - "core.bonus.KING.description": "Zranitelný zabijákovi úrovně ${val} a vyšší", + "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", - "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Omezený dosah střelby", - "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nevystřelí na jednotky dále než ${val} polí", - "core.bonus.LIFE_DRAIN.name": "Vysátí životů (${val}%)", - "core.bonus.LIFE_DRAIN.description": "Vysaje ${val}% uděleného poškození", - "core.bonus.MANA_CHANNELING.name": "${val}% kouzelný kanál", - "core.bonus.MANA_CHANNELING.description": "Dá vašemu hrdinovi ${val} % many využité nepřítelem", - "core.bonus.MANA_DRAIN.name": "Vysátí many", - "core.bonus.MANA_DRAIN.description": "Každé kolo vysaje ${val} many", - "core.bonus.MAGIC_MIRROR.name": "Kouzelné zrcadlo (${val}%)", + "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", + "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", + "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", + "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", + "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", + "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", + "core.bonus.MANA_DRAIN.name": "Vysávání many", + "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", + "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", - "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci ustát nepřátelské kouzlo", - "core.bonus.MIND_IMMUNITY.name": "Imunita kouzel mysli", - "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům cílícím na mysl", - "core.bonus.NO_DISTANCE_PENALTY.name": "Bez penalizace vzdálenosti", - "core.bonus.NO_DISTANCE_PENALTY.description": "Plné poškození na jakoukoliv vzdálenost", + "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", + "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", + "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", + "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", + "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", "core.bonus.NO_MORALE.name": "Neutrální morálka", - "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektu morálky", + "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", - "core.bonus.NO_WALL_PENALTY.description": "Plné poškození při obléhání", - "core.bonus.NON_LIVING.name": "Neživoucí", + "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", + "core.bonus.NON_LIVING.name": "Neživý", "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", - "core.bonus.RANGED_RETALIATION.name": "Vzdálená odveta", + "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", - "core.bonus.RECEPTIVE.name": "Přijímavý", - "core.bonus.RECEPTIVE.description": "Není imunní vůči přátelským kouzlům", + "core.bonus.RECEPTIVE.name": "Vnímavý", + "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", - "core.bonus.REBIRTH.description": "${val}% oddílu se po smrti znovu narodí", + "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", - "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na blízko", - "core.bonus.REVENGE.name": "Msta", - "core.bonus.REVENGE.description": "Deals extra damage based on attacker's lost health in battle", //TODO + "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", + "core.bonus.REVENGE.name": "Pomsta", + "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", "core.bonus.SHOOTER.name": "Střelec", "core.bonus.SHOOTER.description": "Jednotka může střílet", - "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí okolo", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "Vzdálené útoky této jednotky zasáhnou všechny cíle v malé oblasti", + "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", "core.bonus.SOUL_STEAL.name": "Zloděj duší", - "core.bonus.SOUL_STEAL.description": "Získá ${val} nových jednotek za každého zabitého nepřítele", + "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", "core.bonus.SPELLCASTER.name": "Kouzelník", - "core.bonus.SPELLCASTER.description": "Může seslat ${subtype.spell}", - "core.bonus.SPELL_AFTER_ATTACK.name": "Kouzlení po útoku", - "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po zaútočení", - "core.bonus.SPELL_BEFORE_ATTACK.name": "Kouzlení před útokem", - "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před zaútočením", + "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", + "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", + "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", + "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", + "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Zranění od kouzel sníženo o ${val}%.", - "core.bonus.SPELL_IMMUNITY.name": "Odolnost vůči kouzlům", - "core.bonus.SPELL_IMMUNITY.description": "Odolnost proti ${subtype.spell}", + "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", + "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", + "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", - "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odolnosti", - "core.bonus.SPELL_RESISTANCE_AURA.description": "Oddíly poblíž získají ${val}% magickou odolnost", - "core.bonus.SUMMON_GUARDIANS.name": "Povolat strážce", - "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy povolá ${subtype.creature} (${val}%)", - "core.bonus.SYNERGY_TARGET.name": "Synergizable", // TODO - "core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect", //TODO + "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", + "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", + "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", + "core.bonus.SUMMON_GUARDIANS.description": "Na začátku boje přivolá ${subtype.creature} (${val}%)", + "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", + "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", - "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Dechový útok (dosah do dvou polí)", + "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", "core.bonus.TRANSMUTATION.name": "Transmutace", - "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu útočené jednotky na jiný druh", + "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", "core.bonus.UNDEAD.name": "Nemrtvý", "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", - "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvety", - "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést odvetu za neomezený počet útoků", - "core.bonus.WATER_IMMUNITY.name": "Vodní odolnost", - "core.bonus.WATER_IMMUNITY.description": "Imunní všem kouzlům školy vodní magie", + "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", + "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", + "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", + "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", "core.bonus.WIDE_BREATH.name": "Široký dech", - "core.bonus.WIDE_BREATH.description": "Útočí širokým dechem (více polí)" + "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", + "core.bonus.DISINTEGRATE.name": "Rozpad", + "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", + "core.bonus.INVINCIBLE.name": "Neporazitelný", + "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem" } From 6b537319be8f8c86951aee43a67693b2edb2e14f Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:24:09 +0200 Subject: [PATCH 374/726] Updated Czech translation --- Mods/vcmi/config/vcmi/czech.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 378961136..90a0733a8 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -12,11 +12,11 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Převažující", "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtelná", "vcmi.adventureMap.monsterThreat.levels.11" : "Nemožná", - "vcmi.adventureMap.monsterLevel": "\n\nÚroveň %LEVEL %TOWN %ATTACK_TYPE jednotka", - "vcmi.adventureMap.monsterMeleeType": "boj zblízka", - "vcmi.adventureMap.monsterRangedType": "dálkový útok", - "vcmi.adventureMap.search.hover": "Prohledat mapový objekt", - "vcmi.adventureMap.search.help": "Vyberte objekt na mapě pro prohledání.", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL %TOWN %ATTACK_TYPE jednotka", + "vcmi.adventureMap.monsterMeleeType" : "útok zblízka", + "vcmi.adventureMap.monsterRangedType" : "útok na dálku", + "vcmi.adventureMap.search.hover" : "Prohledat mapový objekt", + "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě pro prohledání.", "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", @@ -198,8 +198,8 @@ "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.battlesHero" : "Bojů (proti hrdinům)", - "vcmi.statisticWindow.param.battlesNeutral" : "Bojů (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", + "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", @@ -326,7 +326,7 @@ "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", - "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/bojovnínků a příkazy měst.", + "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", @@ -451,7 +451,7 @@ // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", - "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny bojovníky zamořující tuto zemi a nárokuje si vítězství!", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", @@ -459,7 +459,7 @@ "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", // few strings from WoG used by vcmi - "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh bojovníka ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", + "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", "vcmi.stackExperience.rank.0" : "Začátečník", "vcmi.stackExperience.rank.1" : "Učeň", "vcmi.stackExperience.rank.2" : "Trénovaný", @@ -666,7 +666,7 @@ "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", - "core.bonus.SUMMON_GUARDIANS.description": "Na začátku boje přivolá ${subtype.creature} (${val}%)", + "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", From 8904701e2e9caee7d5d6be61fbfd2f8459ed9bc5 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:55:13 +0200 Subject: [PATCH 375/726] Updated Czech translation Added newly added "vcmi.heroWindow.fusingArtifact.fusing" translation --- Mods/vcmi/config/vcmi/czech.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 90a0733a8..9e72b3add 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -358,6 +358,7 @@ "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", + "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", From 21c83bd3dc26995b9002c9e5bfdf26661056e616 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:58:26 +0200 Subject: [PATCH 376/726] Updated Czech translation Updated Launcher translation --- launcher/translation/czech.ts | 121 +++++++++++++++++----------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index 1a47abdaf..2ccefa301 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -54,7 +54,7 @@ Log files directory - Složka záznamů hry + Adresář souborů s logy @@ -176,7 +176,7 @@ Creatures - Bojovníci + Jednotky @@ -209,7 +209,7 @@ Downloadable - Stahovatelné + Ke stažení @@ -219,7 +219,7 @@ Updatable - Aktualizovatelné + K aktualizaci @@ -387,12 +387,12 @@ This mod can not be installed or enabled because the following dependencies are not present - Tato modifikace nemůže být nainstalována nebo povolena, protože následující závislosti nejsou přítomny + Tato modifikace nelze nainstalovat ani povolit, protože nejsou přítomny následující závislosti This mod can not be enabled because the following mods are incompatible with it - Tato modifikace nemůže být povolena, protože následující modifikace s ní nejsou kompatibilní + Tato modifikace nemůže být povolena, protože není kompatibilní s následujícími modifikacemi @@ -407,7 +407,7 @@ This is a submod and it cannot be installed or uninstalled separately from its parent mod - Toto je podmodifikace, která nemůže být nainstalována nebo odinstalována bez její rodičovské modifikace + Toto je podmodifikace a nelze ji nainstalovat ani odinstalovat samostatně bez hlavní modifikace @@ -442,17 +442,17 @@ Gog files - + Soubory GOG All files (*.*) - + Všechny soubory (*.*) Select files (configs, mods, maps, campaigns, gog files) to install... - + Vyberte soubory (konfigurace, modifikace, mapy, kampaně, soubory GOG) k instalaci... @@ -467,7 +467,7 @@ Downloading %1. %p% (%v MB out of %m MB) finished - + Stahování %1. %p% (%v MB z %m MB) dokončeno @@ -499,7 +499,7 @@ Nainstalovat úspěšně stažené? Installing chronicles - + Instalování kronik @@ -667,7 +667,7 @@ Nainstalovat úspěšně stažené? Downscaling Filter - + Filtr pro zmenšování @@ -677,7 +677,7 @@ Nainstalovat úspěšně stažené? Online Lobby port - Port online předsíně + Port online lobby @@ -692,7 +692,7 @@ Nainstalovat úspěšně stažené? Automatic (Linear) - + Automaticky (Lineárně) @@ -709,42 +709,42 @@ Nainstalovat úspěšně stažené? Automatic - + Automaticky Mods Validation - + Validace modifikací None - + Nic xBRZ x2 - + xBRZ x2 xBRZ x3 - + xBRZ x3 xBRZ x4 - + xBRZ x4 Full - + Plné Use scalable fonts - + Použít škálovatelná písma @@ -754,27 +754,27 @@ Nainstalovat úspěšně stažené? Cursor Scaling - + Škálování kurzoru Scalable - + Škálovatelné Miscellaneous - + Ostatní Font Scaling (experimental) - + Škálování písma (experimentální) Original - + Původní @@ -784,7 +784,7 @@ Nainstalovat úspěšně stažené? Basic - + Základní @@ -814,12 +814,12 @@ Nainstalovat úspěšně stažené? Show Tutorial again - + Znovu zobrazi Tutoriál Reset - + Restart @@ -1011,7 +1011,7 @@ Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybra Display index - + Monitor @@ -1059,35 +1059,35 @@ Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybra File cannot opened - + Soubor nelze otevřít Invalid file selected - Vybrán neplatný soubor + Vybrán neplatný soubor You have to select an gog installer file! - + Musíte vybrat instalační soubor GOG! You have to select an chronicle installer file! - + Musíte vybrat instalační soubor kronik! Extracting error! - + Chyb při rozbalování! Heroes Chronicles - + Heroes Chronicles @@ -1154,13 +1154,13 @@ Before you can start playing, there are a few more steps that need to be complet Please keep in mind that in order to use VCMI you must own the original data files for Heroes® of Might and Magic® III: Complete or The Shadow of Death. Heroes® of Might and Magic® III HD is currently not supported! - Děkujeme za instalaci VCMI! + Děkujeme, že jste si nainstalovali VCMI! -Před začátkem hraní musíte ještě dokončit pár kroků. +Než začnete hrát, je třeba dokončit několik kroků. -Prosíme, mějte na paměti, že abyste mohli hrát VCMI, musíte vlastnit originální datové soubory Heroes® of Might and Magic® III: Complete nebo The Shadow of Death. +Pamatujte, že pro používání VCMI musíte vlastnit originální herní soubory pro Heroes® of Might and Magic® III: Complete nebo The Shadow of Death. -Heroes® of Might and Magic® III HD není v současnosti podporovaný! +Heroes® of Might and Magic® III HD momentálně není podporováno! @@ -1175,7 +1175,7 @@ Heroes® of Might and Magic® III HD není v současnosti podporovaný! You can manually copy directories Maps, Data and Mp3 from the original game directory to VCMI data directory that you can see on top of this page - Můžete ručně zkopírovat existující mapy, data a MP3 z originální složky hry do složky dat VCMI, kterou můžete vidět nahoře na této stránce. + Můžete ručně zkopírovat existující mapy, data a MP3 z originální složky hry do složky dat VCMI, kterou můžete vidět nahoře na této stránce @@ -1195,7 +1195,7 @@ Heroes® of Might and Magic® III HD není v současnosti podporovaný! Install a translation of Heroes III in your preferred language - Instalovat překlad Heroes III vašeho upřednostněného jazyka + Nainstalujte si překlad Heroes III dle preferovaného jazyku @@ -1222,7 +1222,7 @@ Offline instalátor obsahuje dvě části, .exe a .bin. Ujistěte se, že stahuj Optionally, you can install additional mods either now, or at any point later, using the VCMI Launcher - Nyní můžete volitelně nainstalovat další modifikace, nebo též kdykoliv potom pomocí spouštěče VCMI + Můžete si nyní, nebo kdykoliv později, nainstalovat další mody pomocí VCMI Launcheru, podle svých preferencí @@ -1232,12 +1232,12 @@ Offline instalátor obsahuje dvě části, .exe a .bin. Ujistěte se, že stahuj Install compatible version of "Horn of the Abyss", a fan-made Heroes III expansion ported by the VCMI team - + Instalovat kompatibilní verzi 'Horn of the Abyss', fanouškovského rozšíření Heroes III portovaného týmem VCMI Install compatible version of "In The Wake of Gods", a fan-made Heroes III expansion - + "Instalovat kompatibilní verzi In The Wake of Gods', fanouškovského rozšíření Heroes III portovaného týmem VCMI" @@ -1294,7 +1294,7 @@ Offline instalátor obsahuje dvě části, .exe a .bin. Ujistěte se, že stahuj Horn of the Abyss - + Horn of the Abyss @@ -1304,7 +1304,7 @@ Offline instalátor obsahuje dvě části, .exe a .bin. Ujistěte se, že stahuj In The Wake of Gods - + In The Wake of Gods @@ -1341,7 +1341,7 @@ Offline instalátor obsahuje dvě části, .exe a .bin. Ujistěte se, že stahuj File cannot be opened - + Soubor nelze otevřít @@ -1361,17 +1361,17 @@ Offline instalátor obsahuje dvě části, .exe a .bin. Ujistěte se, že stahuj You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer! - + Poskytli jste instalátor GOG Galaxy! Tento soubor neobsahuje hru. Prosím, stáhněte si záložní offline instalátor hry! Extracting error! - + Chyba při rozbalování! No Heroes III data! - Žádná data Heroes III! + Chybí data Heroes III! @@ -1397,15 +1397,15 @@ Prosíme vyberte složku s nainstalovanými daty Heroes III. Heroes III: HD Edition files are not supported by VCMI. Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death. - Soubory Heroes III HD Edice nejsou podporována ve VCMI. -Prosíme vyberte složku s Heroes III: Complete Edition nebo Heroes III: Shadow of Death. + Soubory Heroes III HD Edice nejsou podporovány ve VCMI. +Prosím vyberte složku s Heroes III: Complete Edition nebo Heroes III: Shadow of Death. Unknown or unsupported Heroes III version found. Please select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death. Nalezena neznámá nebo nepodporovaná verze Heroes III. -Prosíme vyberte složku s Heroes III: Complete Edition nebo Heroes III: Shadow of Death. +Prosím vyberte složku s Heroes III: Complete Edition nebo Heroes III: Shadow of Death. @@ -1422,17 +1422,18 @@ Prosíme vyberte složku s Heroes III: Complete Edition nebo Heroes III: Shadow Stream error while extracting files! error reason: - + Chyba při extrahování souborů! +Důvod chyby: Not a supported Inno Setup installer! - + Nepodporovaný Inno Setup instalátor! VCMI was compiled without innoextract support, which is needed to extract exe files! - + VCMI bylo zkompilováno bez podpory innoextract, která je potřebná pro extrahování EXE souborů! @@ -1533,7 +1534,7 @@ error reason: VCMI Launcher - Spouštěč VCMI + VCMI Launcher From 7c0dec1b5ee3d1943215482ca9f61b5372c9d809 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:00:13 +0200 Subject: [PATCH 377/726] Small update Updated Launcher translation --- launcher/translation/czech.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index 2ccefa301..5434843d7 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -1195,7 +1195,7 @@ Heroes® of Might and Magic® III HD momentálně není podporováno! Install a translation of Heroes III in your preferred language - Nainstalujte si překlad Heroes III dle preferovaného jazyku + Nainstalujte si překlad Heroes III dle preferovaného jazyka From 8b427c3989f6cc228155c5ce76b8a1fad99ac8b6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:37:18 +0200 Subject: [PATCH 378/726] use json format --- client/widgets/VideoWidget.cpp | 49 +++++++++----------------------- client/widgets/VideoWidget.h | 3 +- docs/translators/Translations.md | 13 +++++++++ lib/filesystem/ResourcePath.cpp | 1 - lib/filesystem/ResourcePath.h | 2 -- 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 0db38a1d8..348548935 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -38,26 +38,19 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { OBJECT_CONSTRUCTION; - using SubTitlePath = ResourcePathTempl; - SubTitlePath subTitlePath = fileToPlay.toType(); - SubTitlePath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/"); + JsonPath subTitlePath = fileToPlay.toType(); + JsonPath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/"); if(CResourceHandler::get()->existsResource(subTitlePath)) - { - auto rawData = CResourceHandler::get()->load(subTitlePath)->readAll(); - srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); - } - if(CResourceHandler::get()->existsResource(subTitlePathVideoDir)) - { - auto rawData = CResourceHandler::get()->load(subTitlePathVideoDir)->readAll(); - srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); - } + subTitleData = JsonNode(subTitlePath); + else if(CResourceHandler::get()->existsResource(subTitlePathVideoDir)) + subTitleData = JsonNode(subTitlePathVideoDir); videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { pos.w = videoInstance->size().x; pos.h = videoInstance->size().y; - subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE, ""); + subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE); } if (playAudio) @@ -115,30 +108,14 @@ void VideoWidgetBase::stopAudio() std::string VideoWidgetBase::getSubTitleLine(double timestamp) { - if(srtContent.empty()) - return ""; + if(subTitleData.isNull()) + return {}; + + for(auto & segment : subTitleData.Vector()) + if(timestamp > segment["timeStart"].Float() && timestamp < segment["timeEnd"].Float()) + return segment["text"].String(); - std::regex exp("^\\s*(\\d+:\\d+:\\d+,\\d+)[^\\S\\n]+-->[^\\S\\n]+(\\d+:\\d+:\\d+,\\d+)((?:\\n(?!\\d+:\\d+:\\d+,\\d+\\b|\\n+\\d+$).*)*)", std::regex::multiline); - std::smatch res; - - std::string::const_iterator searchStart(srtContent.cbegin()); - while (std::regex_search(searchStart, srtContent.cend(), res, exp)) - { - std::vector timestamp1Str; - boost::split(timestamp1Str, static_cast(res[1]), boost::is_any_of(":,")); - std::vector timestamp2Str; - boost::split(timestamp2Str, static_cast(res[2]), boost::is_any_of(":,")); - double timestamp1 = std::stoi(timestamp1Str[0]) * 3600 + std::stoi(timestamp1Str[1]) * 60 + std::stoi(timestamp1Str[2]) + (std::stoi(timestamp1Str[3]) / 1000.0); - double timestamp2 = std::stoi(timestamp2Str[0]) * 3600 + std::stoi(timestamp2Str[1]) * 60 + std::stoi(timestamp2Str[2]) + (std::stoi(timestamp2Str[3]) / 1000.0); - std::string text = res[3]; - text.erase(0, text.find_first_not_of("\r\n\t ")); - - if(timestamp > timestamp1 && timestamp < timestamp2) - return text; - - searchStart = res.suffix().first; - } - return ""; + return {}; } void VideoWidgetBase::activate() diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index 465e92f1b..4e2672d68 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -12,6 +12,7 @@ #include "../gui/CIntObject.h" #include "../lib/filesystem/ResourcePath.h" +#include "../lib/json/JsonNode.h" class IVideoInstance; class CMultiLineLabel; @@ -25,7 +26,7 @@ class VideoWidgetBase : public CIntObject int audioHandle = -1; bool playAudio = false; float scaleFactor = 1.0; - std::string srtContent = ""; + JsonNode subTitleData; void loadAudio(const VideoPath & file); void startAudio(); diff --git a/docs/translators/Translations.md b/docs/translators/Translations.md index 89e6cbba8..fed671632 100644 --- a/docs/translators/Translations.md +++ b/docs/translators/Translations.md @@ -56,6 +56,19 @@ This will export all strings from game into `Documents/My Games/VCMI/extracted/t To export maps and campaigns, use '/translate maps' command instead. +### Video subtitles +It's possible to add video subtitles. Create a JSON file in `video` folder of translation mod with the name of the video (e.g. `H3Intro.json`): +``` +[ + { + "timeStart" : 5.640, // start time, seconds + "timeEnd" : 8.120, // end time, seconds + "text" : " ... " // text to show during this period + }, + ... +] +``` + ## Translating VCMI data VCMI contains several new strings, to cover functionality not existing in Heroes III. It can be roughly split into following parts: diff --git a/lib/filesystem/ResourcePath.cpp b/lib/filesystem/ResourcePath.cpp index b29d24954..347c3e9fd 100644 --- a/lib/filesystem/ResourcePath.cpp +++ b/lib/filesystem/ResourcePath.cpp @@ -117,7 +117,6 @@ EResType EResTypeHelper::getTypeFromExtension(std::string extension) {".BIK", EResType::VIDEO}, {".OGV", EResType::VIDEO}, {".WEBM", EResType::VIDEO}, - {".SRT", EResType::SUBTITLE}, {".ZIP", EResType::ARCHIVE_ZIP}, {".LOD", EResType::ARCHIVE_LOD}, {".PAC", EResType::ARCHIVE_LOD}, diff --git a/lib/filesystem/ResourcePath.h b/lib/filesystem/ResourcePath.h index ba594d6ff..4f4b4e9a1 100644 --- a/lib/filesystem/ResourcePath.h +++ b/lib/filesystem/ResourcePath.h @@ -30,7 +30,6 @@ class JsonSerializeFormat; * Sound: .wav .82m * Video: .smk, .bik .ogv .webm * Music: .mp3, .ogg - * Subtitle: .srt * Archive: .lod, .snd, .vid .pac .zip * Palette: .pal * Savegame: .v*gm1 @@ -49,7 +48,6 @@ enum class EResType VIDEO, VIDEO_LOW_QUALITY, SOUND, - SUBTITLE, ARCHIVE_VID, ARCHIVE_ZIP, ARCHIVE_SND, From 9ef6696e785c68639aa9f389d1b78d5b3677593f Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:54:41 +0200 Subject: [PATCH 379/726] Updated Czech translation Translated 2 overlooked lines --- Mods/vcmi/config/vcmi/czech.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 9e72b3add..f0e0e0410 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -162,7 +162,7 @@ "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", - "vcmi.dimensionDoor.seaToLandError" : "It's not possible to teleport from sea to land or vice versa with a Dimension Door.", //TODO + "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", @@ -253,7 +253,7 @@ "vcmi.adventureOptions.rightButtonDrag.hover" : "Přetahování pravým tlačítkem", "vcmi.adventureOptions.rightButtonDrag.help" : "{Přetahování pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", - "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nWhen enabled, map dragging has a modern run out effect.", // TODO + "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", @@ -314,7 +314,7 @@ "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", - "vcmi.battleWindow.killed" : "Zabito", //TODO + "vcmi.battleWindow.killed" : "Zabito", "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", From 59dae43a74715451263eccdc87ae58872ee43b5e Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 17 Oct 2024 02:12:41 +0200 Subject: [PATCH 380/726] fix remembering last map --- client/lobby/SelectionTab.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 2cf811129..8029dac48 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -693,7 +693,7 @@ void SelectionTab::selectFileName(std::string fname) for(int i = (int)allItems.size() - 1; i >= 0; i--) { - if(allItems[i]->fileURI == fname) + if(boost::to_upper_copy(allItems[i]->fileURI) == fname) { auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(allItems[i]->originalFileURI); curFolder = baseFolder != "" ? baseFolder + "/" : ""; @@ -704,7 +704,7 @@ void SelectionTab::selectFileName(std::string fname) for(int i = (int)curItems.size() - 1; i >= 0; i--) { - if(curItems[i]->fileURI == fname) + if(boost::to_upper_copy(curItems[i]->fileURI) == fname) { slider->scrollTo(i); selectAbs(i); From 10332547ac59f76594e3a3dc943a76215528a07e Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:42:46 +0200 Subject: [PATCH 381/726] Updated Czech translation --- mapeditor/translation/czech.ts | 138 ++++++++++++++++----------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/mapeditor/translation/czech.ts b/mapeditor/translation/czech.ts index cfdc136c7..a7456cf42 100644 --- a/mapeditor/translation/czech.ts +++ b/mapeditor/translation/czech.ts @@ -67,22 +67,22 @@ Author - + Autor Author contact (e.g. email) - + Kontakt na autora (např. email) Map Creation Time - + Čas vytvoření mapy Map Version - + Verze mapy @@ -788,12 +788,12 @@ Set all mods having a game content as mandatory - + Nastavte všechny modifikace obsahující herní obsah jako povinné Full content mods - + Modifikace s kompletním herním obsahem @@ -836,7 +836,7 @@ Generate hero at main - + Vytvořit hrdinu v hlavním městě @@ -877,7 +877,7 @@ Portrait - + Portrét @@ -947,17 +947,17 @@ Can't place object - Nelze umístit objekt + Nelze umístit objekt There can only be one grail object on the map. - + Na mapě může být pouze jeden grál. Hero %1 cannot be created as NEUTRAL. - + Hrdina %1 nemůže být vytvořen jako NEUTRÁLNÍ. @@ -1129,7 +1129,7 @@ On select text - + Text při výběru @@ -1210,7 +1210,7 @@ Overflow - + Přetečení @@ -1473,37 +1473,37 @@ Build all - + Postavit vše Demolish all - + Zbořit vše Enable all - + Povolit vše Disable all - + Zakázat vše Type - Druh + Typ Enabled - + Povoleno Built - + Postaveno @@ -1511,77 +1511,77 @@ Town event - + Událost ve městě General - + Hlavní Event name - Název události + Název události Type event message text - Zadejte text zprávy události + Zadejte text události Day of first occurrence - Den prvního výskytu + Den prvního výskytu Repeat after (0 = no repeat) - Opakovat po (0 = bez opak.) + Opakovat po (0 = bez opakováí) Affected players - Ovlivnění hráči + Ovlivnění hráči affects human - ovlivňuje lidi + ovlivňuje lidi affects AI - ovlivňuje AI + ovlivňuje AI Resources - Zdroje + Zdroje Buildings - Budovy + Budovy Creatures - Jednotky + Jednotky OK - + OK Creature level %1 / Creature level %1 Upgrade - + Úroveň jednotky %1 / Úroveň jednotky%1 vylepšení Day %1 - %2 - + Den %1 - %2 @@ -1589,32 +1589,32 @@ Town events - + Události ve městě Timed events - Načasované události + Načasované události Add - Přidat + Přidat Remove - Odebrat + Odebrat Day %1 - %2 - + Den %1 - %2 New event - Nová událost + Nová událost @@ -1622,17 +1622,17 @@ Spells - Kouzla + Kouzla Customize spells - Přizpůsobit kouzla + Přizpůsobit kouzla Level 1 - Úroveň 1 + 1. stupeň @@ -1641,7 +1641,7 @@ Spell that may appear in mage guild - + Kouzlo, které se může objevit ve věži kouzel @@ -1650,27 +1650,27 @@ Spell that must appear in mage guild - + Kouzlo, které se musí objevit ve věži kouzel Level 2 - Úroveň 2 + 2. stupeň Level 3 - Úroveň 3 + 3. stupeň Level 4 - Úroveň 4 + 4. stupeň Level 5 - Úroveň 5 + 5. stupeň @@ -1722,7 +1722,7 @@ Map validation results - Výsledky posudku mapy + Výsledky ověření mapy @@ -1732,27 +1732,27 @@ No factions allowed for player %1 - + Pro hráče %1 nejsou povoleny žádné frakce No players allowed to play this map - Žádní hráči nejsou dovoleni hrát tuto mapu + Tato mapa neumožňuje hru žádnému hráči Map is allowed for one player and cannot be started - Mapa je pouze pro jednoho hráče na nemůže být spuštěna + Tato mapa je určena pouze pro jednoho hráče a nelze ji spustit No human players allowed to play this map - Žádní lidští hráči nejsou dovoleni hrát tuto mapu + Na této mapě není povolen žádný lidský hráč Armored instance %1 is UNFLAGGABLE but must have NEUTRAL or player owner - + Obrněná instance %1 nemůže být označena vlajkou, ale musí mít vlastníka nebo neutrálního nebo hráče @@ -1762,17 +1762,17 @@ Spell scroll %1 doesn't have instance assigned and must be removed - + Kouzelný svitek %1 nemá přiřazenou instanci a musí být odstraněn Artifact %1 is prohibited by map settings - + Artefakt %1 je zakázán nastavením mapy Player %1 has no towns and heroes assigned - + Hráč %1 nemá přiřazena žádná města ani hrdiny @@ -1797,7 +1797,7 @@ Hero %1 has an empty type and must be removed - + Hrdina %1 nemá přiřazený typ a musí být odstraněn @@ -1923,7 +1923,7 @@ Two level map - Dvě úrovně + Dvouvrstvá mapa @@ -2016,7 +2016,7 @@ Monster strength - Síla příšer + Síla jednotek @@ -2037,7 +2037,7 @@ Water content - Obsah vody + Vodní obsah @@ -2052,22 +2052,22 @@ Roads - Cesty + Cesty Dirt - + Hlína Gravel - + Štěrk Cobblestone - + Dlažba @@ -2115,7 +2115,7 @@ Filepath of the map to open. - Cesta k souboru mapy pro otevření. + Cesta k souboru mapy, kterou chcete otevřít. @@ -2135,7 +2135,7 @@ Delete original files, for the ones split / converted. - + Odstranit původní soubory pro ty, které byly rozděleny nebo převedeny. From c576438a958b7787f0f768130bd6f9d1f6879c1d Mon Sep 17 00:00:00 2001 From: kodobi Date: Wed, 16 Oct 2024 14:14:47 +0200 Subject: [PATCH 382/726] Fix battle setup for sieges without forts - Update the if statement in BattleProcessor::SetupBattle to allow obstacles in battlefields during sieges without forts. - Ensure towns without forts use the native battlegrounds. - Refactor variable name from curB to currentBattle for clarity. --- lib/battle/BattleInfo.cpp | 97 ++++++++++++++---------------- server/battles/BattleProcessor.cpp | 20 ++++-- 2 files changed, 59 insertions(+), 58 deletions(-) diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 8a8eb633c..10a4988f5 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -161,54 +161,45 @@ struct RangeGenerator BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, BattleSideArray armies, BattleSideArray heroes, const BattleLayout & layout, const CGTownInstance * town) { CMP_stack cmpst; - auto * curB = new BattleInfo(layout); + auto * currentBattle = new BattleInfo(layout); for(auto i : { BattleSide::LEFT_SIDE, BattleSide::RIGHT_SIDE}) - curB->sides[i].init(heroes[i], armies[i]); + currentBattle->sides[i].init(heroes[i], armies[i]); - std::vector & stacks = (curB->stacks); + std::vector & stacks = (currentBattle->stacks); - curB->tile = tile; - curB->battlefieldType = battlefieldType; - curB->round = -2; - curB->activeStack = -1; - curB->replayAllowed = false; - - if(town) - { - curB->town = town; - curB->terrainType = town->getNativeTerrain(); - } - else - { - curB->town = nullptr; - curB->terrainType = terrain; - } + currentBattle->tile = tile; + currentBattle->terrainType = terrain; + currentBattle->battlefieldType = battlefieldType; + currentBattle->round = -2; + currentBattle->activeStack = -1; + currentBattle->replayAllowed = false; + currentBattle->town = town; //setting up siege obstacles if (town && town->fortificationsLevel().wallsHealth != 0) { auto fortification = town->fortificationsLevel(); - curB->si.gateState = EGateState::CLOSED; + currentBattle->si.gateState = EGateState::CLOSED; - curB->si.wallState[EWallPart::GATE] = EWallState::INTACT; + currentBattle->si.wallState[EWallPart::GATE] = EWallState::INTACT; for(const auto wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL}) - curB->si.wallState[wall] = static_cast(fortification.wallsHealth); + currentBattle->si.wallState[wall] = static_cast(fortification.wallsHealth); if (fortification.citadelHealth != 0) - curB->si.wallState[EWallPart::KEEP] = static_cast(fortification.citadelHealth); + currentBattle->si.wallState[EWallPart::KEEP] = static_cast(fortification.citadelHealth); if (fortification.upperTowerHealth != 0) - curB->si.wallState[EWallPart::UPPER_TOWER] = static_cast(fortification.upperTowerHealth); + currentBattle->si.wallState[EWallPart::UPPER_TOWER] = static_cast(fortification.upperTowerHealth); if (fortification.lowerTowerHealth != 0) - curB->si.wallState[EWallPart::BOTTOM_TOWER] = static_cast(fortification.lowerTowerHealth); + currentBattle->si.wallState[EWallPart::BOTTOM_TOWER] = static_cast(fortification.lowerTowerHealth); } //randomize obstacles - if (layout.obstaclesAllowed && !town) + if (layout.obstaclesAllowed && (!town || !town->hasFort())) { RandGen r{}; auto ourRand = [&](){ return r.rand(); }; @@ -221,12 +212,12 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const auto appropriateAbsoluteObstacle = [&](int id) { const auto * info = Obstacle(id).getInfo(); - return info && info->isAbsoluteObstacle && info->isAppropriate(curB->terrainType, battlefieldType); + return info && info->isAbsoluteObstacle && info->isAppropriate(currentBattle->terrainType, battlefieldType); }; auto appropriateUsualObstacle = [&](int id) { const auto * info = Obstacle(id).getInfo(); - return info && !info->isAbsoluteObstacle && info->isAppropriate(curB->terrainType, battlefieldType); + return info && !info->isAbsoluteObstacle && info->isAppropriate(currentBattle->terrainType, battlefieldType); }; if(r.rand(1,100) <= 40) //put cliff-like obstacle @@ -237,8 +228,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const auto obstPtr = std::make_shared(); obstPtr->obstacleType = CObstacleInstance::ABSOLUTE_OBSTACLE; obstPtr->ID = obidgen.getSuchNumber(appropriateAbsoluteObstacle); - obstPtr->uniqueID = static_cast(curB->obstacles.size()); - curB->obstacles.push_back(obstPtr); + obstPtr->uniqueID = static_cast(currentBattle->obstacles.size()); + currentBattle->obstacles.push_back(obstPtr); for(BattleHex blocked : obstPtr->getBlockedTiles()) blockedTiles.push_back(blocked); @@ -256,7 +247,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const while(tilesToBlock > 0) { RangeGenerator obidgen(0, VLC->obstacleHandler->size() - 1, ourRand); - auto tileAccessibility = curB->getAccessibility(); + auto tileAccessibility = currentBattle->getAccessibility(); const int obid = obidgen.getSuchNumber(appropriateUsualObstacle); const ObstacleInfo &obi = *Obstacle(obid).getInfo(); @@ -290,8 +281,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const auto obstPtr = std::make_shared(); obstPtr->ID = obid; obstPtr->pos = posgenerator.getSuchNumber(validPosition); - obstPtr->uniqueID = static_cast(curB->obstacles.size()); - curB->obstacles.push_back(obstPtr); + obstPtr->uniqueID = static_cast(currentBattle->obstacles.size()); + currentBattle->obstacles.push_back(obstPtr); for(BattleHex blocked : obstPtr->getBlockedTiles()) blockedTiles.push_back(blocked); @@ -315,7 +306,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const CreatureID cre = warMachineArt->artType->getWarMachine(); if(cre != CreatureID::NONE) - curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex); + currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex); } }; @@ -353,7 +344,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const const BattleHex & pos = layout.units.at(side).at(k); if (pos.isValid()) - curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos); + currentBattle->generateNewStack(currentBattle->nextUnitId(), *i->second, side, i->first, pos); } } @@ -362,20 +353,20 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const { if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive) { - curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, layout.commanders.at(i)); + currentBattle->generateNewStack(currentBattle->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, layout.commanders.at(i)); } } - if (curB->town) + if (currentBattle->town) { - if (curB->town->fortificationsLevel().citadelHealth != 0) - curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER); + if (currentBattle->town->fortificationsLevel().citadelHealth != 0) + currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER); - if (curB->town->fortificationsLevel().upperTowerHealth != 0) - curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER); + if (currentBattle->town->fortificationsLevel().upperTowerHealth != 0) + currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER); - if (curB->town->fortificationsLevel().lowerTowerHealth != 0) - curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER); + if (currentBattle->town->fortificationsLevel().lowerTowerHealth != 0) + currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER); //Moat generating is done on server } @@ -390,15 +381,15 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const for(const std::shared_ptr & bonus : bgInfo->bonuses) { - curB->addNewBonus(bonus); + currentBattle->addNewBonus(bonus); } //native terrain bonuses auto nativeTerrain = std::make_shared(); - curB->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID())->addLimiter(nativeTerrain)); - curB->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::ATTACK))->addLimiter(nativeTerrain)); - curB->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::DEFENSE))->addLimiter(nativeTerrain)); + currentBattle->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID())->addLimiter(nativeTerrain)); + currentBattle->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::ATTACK))->addLimiter(nativeTerrain)); + currentBattle->addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::DEFENSE))->addLimiter(nativeTerrain)); ////////////////////////////////////////////////////////////////////////// //tactics @@ -428,21 +419,21 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const logGlobal->warn("Double tactics is not implemented, only attacker will have tactics!"); if(tacticsSkillDiffAttacker > 0) { - curB->tacticsSide = BattleSide::ATTACKER; + currentBattle->tacticsSide = BattleSide::ATTACKER; //bonus specifies distance you can move beyond base row; this allows 100% compatibility with HMM3 mechanics - curB->tacticDistance = 1 + tacticsSkillDiffAttacker; + currentBattle->tacticDistance = 1 + tacticsSkillDiffAttacker; } else if(tacticsSkillDiffDefender > 0) { - curB->tacticsSide = BattleSide::DEFENDER; + currentBattle->tacticsSide = BattleSide::DEFENDER; //bonus specifies distance you can move beyond base row; this allows 100% compatibility with HMM3 mechanics - curB->tacticDistance = 1 + tacticsSkillDiffDefender; + currentBattle->tacticDistance = 1 + tacticsSkillDiffDefender; } else - curB->tacticDistance = 0; + currentBattle->tacticDistance = 0; } - return curB; + return currentBattle; } const CGHeroInstance * BattleInfo::getHero(const PlayerColor & player) const diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index 87b9deb74..597933013 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -28,10 +28,12 @@ #include "../../lib/gameState/CGameState.h" #include "../../lib/mapping/CMap.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/modding/IdentifierStorage.h" #include "../../lib/networkPacks/PacksForClient.h" #include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/CPlayerState.h" +#include "vstd/RNG.h" BattleProcessor::BattleProcessor(CGameHandler * gameHandler) : gameHandler(gameHandler) @@ -156,16 +158,24 @@ BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArraygetTile(tile); TerrainId terrain = t.terType->getId(); - if (gameHandler->gameState()->map->isCoastalTile(tile)) //coastal tile is always ground + if (town) + terrain = town->getNativeTerrain(); + else if (gameHandler->gameState()->map->isCoastalTile(tile)) //coastal tile is always ground terrain = ETerrainId::SAND; - BattleField terType = gameHandler->gameState()->battleGetBattlefieldType(tile, gameHandler->getRandomGenerator()); - if (heroes[BattleSide::ATTACKER] && heroes[BattleSide::ATTACKER]->boat && heroes[BattleSide::DEFENDER] && heroes[BattleSide::DEFENDER]->boat) - terType = BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.ship_to_ship")); + BattleField battlefieldType = gameHandler->gameState()->battleGetBattlefieldType(tile, gameHandler->getRandomGenerator()); + + if (town) + { + const TerrainType* terrainData = VLC->terrainTypeHandler->getById(terrain); + battlefieldType = BattleField(*RandomGeneratorUtil::nextItem(terrainData->battleFields, gameHandler->getRandomGenerator())); + } + else if (heroes[BattleSide::ATTACKER] && heroes[BattleSide::ATTACKER]->boat && heroes[BattleSide::DEFENDER] && heroes[BattleSide::DEFENDER]->boat) + battlefieldType = BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.ship_to_ship")); //send info about battles BattleStart bs; - bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, layout, town); + bs.info = BattleInfo::setupBattle(tile, terrain, battlefieldType, armies, heroes, layout, town); bs.battleID = gameHandler->gameState()->nextBattleID; engageIntoBattle(bs.info->getSide(BattleSide::ATTACKER).color); From 3c56769b2a50ad0746e71951ad475b6a62c6acc5 Mon Sep 17 00:00:00 2001 From: kodobi <37045566+kodobi@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:21:06 +0200 Subject: [PATCH 383/726] Update server/battles/BattleProcessor.cpp Co-authored-by: Dydzio --- server/battles/BattleProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index 597933013..d16b77a9b 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -33,7 +33,7 @@ #include "../../lib/networkPacks/PacksForClient.h" #include "../../lib/networkPacks/PacksForClientBattle.h" #include "../../lib/CPlayerState.h" -#include "vstd/RNG.h" +#include BattleProcessor::BattleProcessor(CGameHandler * gameHandler) : gameHandler(gameHandler) From d42e77b5a7ebc78de58c1ac726926b68fabf96c4 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:34:27 +0200 Subject: [PATCH 384/726] Updated Czech translation Small fixes --- Mods/vcmi/config/vcmi/czech.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index f0e0e0410..6c31b1424 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -86,7 +86,7 @@ "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", "vcmi.lobby.backToLobby" : "Vrátit se do lobby", "vcmi.lobby.author" : "Autor", - "vcmi.lobby.handicap" : "Handicap", + "vcmi.lobby.handicap" : "Postih", "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", @@ -381,7 +381,7 @@ "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", - "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo tahů a nastavení souběžných tahů", + "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", @@ -404,7 +404,7 @@ "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", - "vcmi.optionsTab.turnTime.select" : "Vyberte šablonu nastavení časovače", + "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", @@ -419,7 +419,7 @@ "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", - "vcmi.optionsTab.simturns.select" : "Vyberte šablonu souběžných tahů", + "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", From 3ab31320080c88cd6f92f344f6c40e6317892d69 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Thu, 17 Oct 2024 23:51:57 -0300 Subject: [PATCH 385/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index bf7359615..5ebff83cf 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -358,6 +358,7 @@ "vcmi.heroWindow.sortBackpackBySlot.help" : "Ordenar artefatos na mochila por espaço equipado.", "vcmi.heroWindow.sortBackpackByClass.hover" : "Ordenar por classe", "vcmi.heroWindow.sortBackpackByClass.help" : "Ordenar artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia", + "vcmi.heroWindow.fusingArtifact.fusing" : "Você possui todos os componentes necessários para a fusão de %s. Deseja realizar a fusão? {Todos os componentes serão consumidos após a fusão.}", "vcmi.tavernWindow.inviteHero" : "Convidar herói", From f0b7d007a0de060a45632ebd11ea2254b469c04a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:30:22 +0200 Subject: [PATCH 386/726] video: use global timer; implement frameskip --- client/media/CVideoHandler.cpp | 12 ++++++++++-- client/media/CVideoHandler.h | 6 ++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 3932aa1d6..41e2eb6a6 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -173,6 +173,7 @@ void CVideoInstance::openVideo() { openContext(); openCodec(findVideoStream()); + startTime = std::chrono::high_resolution_clock::now(); } void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput) @@ -391,9 +392,16 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); - frameTime += msPassed / 1000.0; + auto nowTime = std::chrono::high_resolution_clock::now(); + double difference = std::chrono::duration_cast(nowTime - startTime).count() / 1000.0; - if(frameTime >= getCurrentFrameEndTime()) + int frameskipCounter = 0; + while(!videoEnded() && difference >= getCurrentFrameEndTime() + getCurrentFrameDuration() && frameskipCounter < MAX_FRAMESKIP) // Frameskip + { + decodeNextFrame(); + frameskipCounter++; + } + if(!videoEnded() && difference >= getCurrentFrameEndTime()) loadNextFrame(); } diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index 42e48eaff..b4a3c7c81 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -77,10 +77,12 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream SDL_Surface * surface = nullptr; Point dimensions; - /// video playback current progress, in seconds - double frameTime = 0.0; + /// video playback start time point + std::chrono::high_resolution_clock::time_point startTime; void prepareOutput(float scaleFactor, bool useTextureOutput); + + const int MAX_FRAMESKIP = 5; public: ~CVideoInstance(); From 538c7ce7b10d0ca738230441b17efe3b445f9390 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Fri, 18 Oct 2024 07:24:38 -0300 Subject: [PATCH 387/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 60 +++++++++++++-------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 5ebff83cf..3ac8dac68 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -213,11 +213,11 @@ "vcmi.systemOptions.fullscreenExclusive.hover" : "Tela Cheia (exclusiva)", "vcmi.systemOptions.fullscreenExclusive.help" : "{Tela Cheia}\n\nSe selecionado, o VCMI será executado em modo de tela cheia exclusiva. Neste modo, o jogo mudará a resolução do monitor para a resolução selecionada.", "vcmi.systemOptions.resolutionButton.hover" : "Resolução: %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Selecionar Resolução}\n\nMuda a resolução da tela do jogo.", + "vcmi.systemOptions.resolutionButton.help" : "{Seleciona a Resolução}\n\nMuda a resolução da tela do jogo.", "vcmi.systemOptions.resolutionMenu.hover" : "Selecionar Resolução", "vcmi.systemOptions.resolutionMenu.help" : "Muda a resolução da tela do jogo.", "vcmi.systemOptions.scalingButton.hover" : "Escala da Interface: %p%", - "vcmi.systemOptions.scalingButton.help" : "{Escala da Interface}\n\nAlterar escala da interface do jogo.", + "vcmi.systemOptions.scalingButton.help" : "{Escala da Interface}\n\nAltera a escala da interface do jogo.", "vcmi.systemOptions.scalingMenu.hover" : "Selecionar Escala da Interface", "vcmi.systemOptions.scalingMenu.help" : "Altera a escala da interface do jogo.", "vcmi.systemOptions.longTouchButton.hover" : "Intervalo de Toque Longo: %d ms", // Translation note: "ms" = "milliseconds" @@ -226,15 +226,15 @@ "vcmi.systemOptions.longTouchMenu.help" : "Muda a duração do intervalo de toque longo.", "vcmi.systemOptions.longTouchMenu.entry" : "%d milissegundos", "vcmi.systemOptions.framerateButton.hover" : "Mostrar FPS", - "vcmi.systemOptions.framerateButton.help" : "{Mostrar FPS}\n\nAtiva ou desativa a visibilidade do contador de Quadros Por Segundo no canto da janela do jogo.", - "vcmi.systemOptions.hapticFeedbackButton.hover" : "Resposta tátil", - "vcmi.systemOptions.hapticFeedbackButton.help" : "{Resposta tátil}\n\nAtiva ou desativa a resposta tátil nos toques na tela.", + "vcmi.systemOptions.framerateButton.help" : "{Mostra os Quadros Por Segundo}\n\nAtiva ou desativa a visibilidade do contador de Quadros Por Segundo no canto da janela do jogo.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Resposta Tátil", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Resposta Tátil}\n\nAtiva ou desativa a resposta tátil nos toques na tela.", "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Aprimoramentos da Interface", - "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Aprimoramentos da Interface}\n\nAtiva ou desativa várias melhorias de interface. Como um botão de mochila etc. Desative para ter uma experiência mais clássica.", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Aprimoramentos da Interface}\n\nAtiva ou desativa várias melhorias de interface, como um botão de mochila etc. Desative para ter uma experiência mais clássica.", "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Grimório Grande", "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Grimório Grande}\n\nAtiva um grimório maior que comporta mais feitiços por página. A animação de mudança de página do grimório não funciona com esta configuração ativada.", - "vcmi.systemOptions.audioMuteFocus.hover" : "Silenciar na inatividade", - "vcmi.systemOptions.audioMuteFocus.help" : "{Silenciar na inatividade}\n\nSilencia o áudio quando a janela está inativa. As exceções são mensagens no jogo e som de novo turno.", + "vcmi.systemOptions.audioMuteFocus.hover" : "Silenciar na Inatividade", + "vcmi.systemOptions.audioMuteFocus.help" : "{Silencia o Áudio na Inatividade}\n\nSilencia o áudio quando a janela está inativa. As exceções são mensagens no jogo e som de novo turno.", "vcmi.adventureOptions.infoBarPick.hover" : "Mensagens no Painel de Informações", "vcmi.adventureOptions.infoBarPick.help" : "{Mostra as Mensagens no Painel de Informações}\n\nSempre que possível, as mensagens do jogo provenientes de objetos no mapa serão mostradas no painel de informações, em vez de aparecerem em uma janela separada.", @@ -243,7 +243,7 @@ "vcmi.adventureOptions.forceMovementInfo.hover" : "Sempre Mostrar o Custo de Movimento", "vcmi.adventureOptions.forceMovementInfo.help" : "{Sempre Mostrar o Custo de Movimento}\n\nSempre mostra os dados de pontos de movimento na barra de status (em vez de apenas visualizá-los enquanto você mantém pressionada a tecla ALT).", "vcmi.adventureOptions.showGrid.hover" : "Mostrar Grade", - "vcmi.adventureOptions.showGrid.help" : "{Mostrar Grade}\n\nMostra a sobreposição da grade, destacando as fronteiras entre as telhas do mapa de aventura.", + "vcmi.adventureOptions.showGrid.help" : "{Mostra a Grade}\n\nMostra a sobreposição da grade, destacando as fronteiras entre os hexágonos do mapa de aventura.", "vcmi.adventureOptions.borderScroll.hover" : "Rolagem de Borda", "vcmi.adventureOptions.borderScroll.help" : "{Rolagem de Borda}\n\nFaz o mapa de aventura rolar quando o cursor está adjacente à borda da janela. Pode ser desativado mantendo pressionada a tecla CTRL.", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Gerenciar Criaturas no Painel de Info.", @@ -255,7 +255,7 @@ "vcmi.adventureOptions.smoothDragging.hover" : "Arrastar Suavemente o Mapa", "vcmi.adventureOptions.smoothDragging.help" : "{Arrasta o Mapa Suavemente}\n\nQuando ativado, o arrasto do mapa tem um efeito de movimento moderno.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Omitir Efeitos de Desvanecimento", - "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Omitir Efeitos de Desvanecimento}\n\nQuando ativado, omite o desvanecimento de objetos e efeitos semelhantes (coleta de recursos, embarque em navios etc). Torna a interface do usuário mais reativa em alguns casos em detrimento da estética. Especialmente útil em jogos PvP. Para obter velocidade de movimento máxima, o pulo está ativo independentemente desta configuração.", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Omite os Efeitos de Desvanecimento}\n\nQuando ativado, omite o desvanecimento de objetos e efeitos semelhantes (coleta de recursos, embarque em navios etc.). Torna a interface do usuário mais reativa em alguns casos, em detrimento da estética. Especialmente útil em jogos PvP. Para obter velocidade de movimento máxima, o pulo está ativo independentemente desta configuração.", "vcmi.adventureOptions.mapScrollSpeed1.hover": "", "vcmi.adventureOptions.mapScrollSpeed5.hover": "", "vcmi.adventureOptions.mapScrollSpeed6.hover": "", @@ -263,7 +263,7 @@ "vcmi.adventureOptions.mapScrollSpeed5.help" : "Define a velocidade de rolagem do mapa como muito rápida.", "vcmi.adventureOptions.mapScrollSpeed6.help" : "Define a velocidade de rolagem do mapa como instantânea.", "vcmi.adventureOptions.hideBackground.hover" : "Ocultar Fundo", - "vcmi.adventureOptions.hideBackground.help" : "{Ocultar Fundo}\n\nOculta o mapa de aventura no fundo e mostra uma textura em vez disso.", + "vcmi.adventureOptions.hideBackground.help" : "{Oculta o Fundo}\n\nOculta o mapa de aventura no fundo e mostra uma textura em vez disso.", "vcmi.battleOptions.queueSizeLabel.hover": "Mostrar Fila de Ordem de Turno", "vcmi.battleOptions.queueSizeNoneButton.hover": "DESL.", @@ -271,7 +271,7 @@ "vcmi.battleOptions.queueSizeSmallButton.hover": "PEQU.", "vcmi.battleOptions.queueSizeBigButton.hover": "GRAN.", "vcmi.battleOptions.queueSizeNoneButton.help": "Não exibir Fila de Ordem de Turno.", - "vcmi.battleOptions.queueSizeAutoButton.help": "Ajusta automaticamente o tamanho da fila de ordem de turno com base na resolução do jogo (o tamanho PEQUENO é usado ao jogar o jogo em uma resolução com altura inferior a 700 pixels, o tamanho GRANDE é usado caso contrário).", + "vcmi.battleOptions.queueSizeAutoButton.help": "Ajusta automaticamente o tamanho da fila de ordem de turno com base na resolução do jogo (o tamanho PEQUENO é usado ao jogar em uma resolução com altura inferior a 700 pixels; o tamanho GRANDE é usado caso contrário).", "vcmi.battleOptions.queueSizeSmallButton.help": "Define o tamanho da fila de ordem de turno como PEQUENO.", "vcmi.battleOptions.queueSizeBigButton.help": "Define o tamanho da fila de ordem de turno como GRANDE (não suportado se a altura da resolução do jogo for inferior a 700 pixels).", "vcmi.battleOptions.animationsSpeed1.hover": "", @@ -283,7 +283,7 @@ "vcmi.battleOptions.movementHighlightOnHover.hover": "Destacar Movimento ao Passar o Mouse", "vcmi.battleOptions.movementHighlightOnHover.help": "{Destaca o Movimento ao Passar o Mouse}\n\nDestaca o alcance de movimento da unidade quando você passa o mouse sobre ela.", "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Mostrar Limites de Alcance de Atiradores", - "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Mostra o Limites de Alcance dos Atiradores ao Passar o Mouse}\n\nMostra os limites de alcance do atirador quando você passa o mouse sobre ele.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Mostra os Limites de Alcance dos Atiradores ao Passar o Mouse}\n\nMostra os limites de alcance do atirador quando você passa o mouse sobre ele.", "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Mostrar Janelas de Estatísticas de Heróis", "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostra as Janelas de Estatísticas de Heróis}\n\nAlterna permanentemente as janelas de estatísticas dos heróis que mostram estatísticas primárias e pontos de feitiço.", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Pular Música de Introdução", @@ -291,7 +291,7 @@ "vcmi.battleOptions.endWithAutocombat.hover": "Terminar a batalha", "vcmi.battleOptions.endWithAutocombat.help": "{Termina a batalha}\n\nO Combate Automático reproduz a batalha até o final instantâneo.", "vcmi.battleOptions.showQuickSpell.hover": "Mostrar Painel de Feitiço Rápido", - "vcmi.battleOptions.showQuickSpell.help": "{Mostrar Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços", + "vcmi.battleOptions.showQuickSpell.help": "{Mostra o Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços.", "vcmi.adventureMap.revisitObject.hover" : "Revisitar Objeto", "vcmi.adventureMap.revisitObject.help" : "{Revisitar Objeto}\n\nSe um herói estiver atualmente em um Objeto do Mapa, ele pode revisitar o local.", @@ -336,9 +336,9 @@ "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Mostrar Produção Semanal de Criaturas", "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Mostrar Produção Semanal de Criaturas}\n\nMostra a produção semanal das criaturas em vez da quantidade disponível no resumo da cidade (canto inferior esquerdo da tela da cidade).", "vcmi.otherOptions.compactTownCreatureInfo.hover" : "Informações Compactas de Criaturas", - "vcmi.otherOptions.compactTownCreatureInfo.help" : "{Informações Compactas de Criaturas}\n\nMostra informações menores para criaturas da cidade no resumo da cidade (canto inferior esquerdo da tela da cidade).", + "vcmi.otherOptions.compactTownCreatureInfo.help" : "{Informações Compactas de Criaturas}\n\nMostra informações reduzidas para criaturas da cidade no resumo da cidade (canto inferior esquerdo da tela da cidade).", - "vcmi.townHall.missingBase" : "A construção base %s deve ser construída primeiro", + "vcmi.townHall.missingBase" : "A construção base %s deve ser feita primeiro.", "vcmi.townHall.noCreaturesToRecruit" : "Não há criaturas para recrutar!", "vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode pegar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"", @@ -353,11 +353,11 @@ "vcmi.heroWindow.openBackpack.hover" : "Abrir janela da mochila de artefatos", "vcmi.heroWindow.openBackpack.help" : "Abre a janela que facilita o gerenciamento da mochila de artefatos.", "vcmi.heroWindow.sortBackpackByCost.hover" : "Ordenar por custo", - "vcmi.heroWindow.sortBackpackByCost.help" : "Ordenar artefatos na mochila por custo.", + "vcmi.heroWindow.sortBackpackByCost.help" : "Ordena artefatos na mochila por custo.", "vcmi.heroWindow.sortBackpackBySlot.hover" : "Ordenar por espaço", - "vcmi.heroWindow.sortBackpackBySlot.help" : "Ordenar artefatos na mochila por espaço equipado.", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Ordena artefatos na mochila por espaço equipado.", "vcmi.heroWindow.sortBackpackByClass.hover" : "Ordenar por classe", - "vcmi.heroWindow.sortBackpackByClass.help" : "Ordenar artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia", + "vcmi.heroWindow.sortBackpackByClass.help" : "Ordena artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia.", "vcmi.heroWindow.fusingArtifact.fusing" : "Você possui todos os componentes necessários para a fusão de %s. Deseja realizar a fusão? {Todos os componentes serão consumidos após a fusão.}", "vcmi.tavernWindow.inviteHero" : "Convidar herói", @@ -369,7 +369,7 @@ "vcmi.creatureWindow.showSkills.hover" : "Alternar para visualização de habilidades", "vcmi.creatureWindow.showSkills.help" : "Exibe todas as habilidades aprendidas do comandante.", "vcmi.creatureWindow.returnArtifact.hover" : "Devolver artefato", - "vcmi.creatureWindow.returnArtifact.help" : "Clique neste botão para devolver o artefato para a mochila do herói.", + "vcmi.creatureWindow.returnArtifact.help" : "Clique neste botão para devolver o artefato à mochila do herói.", "vcmi.questLog.hideComplete.hover" : "Ocultar missões completas", "vcmi.questLog.hideComplete.help" : "Oculta todas as missões completas.", @@ -381,7 +381,7 @@ "vcmi.randomMapTab.widgets.roadTypesLabel" : "Tipos de Estrada", "vcmi.optionsTab.turnOptions.hover" : "Opções de Turno", - "vcmi.optionsTab.turnOptions.help" : "Selecione as opções de cronômetro do turno e turnos simultâneos", + "vcmi.optionsTab.turnOptions.help" : "Selecione as opções de cronômetro do turno e turnos simultâneos.", "vcmi.optionsTab.chessFieldBase.hover" : "Cronômetro Base", "vcmi.optionsTab.chessFieldTurn.hover" : "Cronôm. Turno", @@ -391,8 +391,8 @@ "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Usado fora de combate ou quando o {Cronômetro da Batalha} se esgota. Restaurado a cada turno. O tempo restante é adicionado ao {Tempo Base} no final do turno.", "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Usado fora de combate ou quando o {Cronômetro da Batalha} se esgota. Restaurado a cada turno. Qualquer tempo não utilizado é perdido.", "vcmi.optionsTab.chessFieldBattle.help" : "Usado em batalhas com a IA ou em combates PvP quando o {Cronômetro da Unidade} se esgota. Restaurado no início de cada combate.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Usado ao selecionar ação da unidade em combates PvP. O tempo restante é adicionado ao {Cronômetro da Batalha} no final do turno da unidade.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Usado ao selecionar ação da unidade em combates PvP. Restaurado no início do turno de cada unidade. Qualquer tempo não utilizado é perdido.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Usado ao selecionar a ação da unidade em combates PvP. O tempo restante é adicionado ao {Cronômetro da Batalha} no final do turno da unidade.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Usado ao selecionar a ação da unidade em combates PvP. Restaurado no início do turno de cada unidade. Qualquer tempo não utilizado é perdido.", "vcmi.optionsTab.accumulate" : "Acumular", @@ -523,9 +523,9 @@ "core.seerhut.quest.reachDate.hover.3" : "(Não retorne antes de %s)", "core.seerhut.quest.reachDate.hover.4" : "(Não retorne antes de %s)", "core.seerhut.quest.reachDate.hover.5" : "(Não retorne antes de %s)", - "core.seerhut.quest.reachDate.receive.0" : "Estou ocupado. Não volte antes de %s", - "core.seerhut.quest.reachDate.receive.1" : "Estou ocupado. Não volte antes de %s", - "core.seerhut.quest.reachDate.receive.2" : "Estou ocupado. Não volte antes de %s", + "core.seerhut.quest.reachDate.receive.0" : "Estou ocupado. Não volte antes de %s.", + "core.seerhut.quest.reachDate.receive.1" : "Estou ocupado. Não volte antes de %s.", + "core.seerhut.quest.reachDate.receive.2" : "Estou ocupado. Não volte antes de %s.", "core.seerhut.quest.reachDate.receive.3" : "Fechado até %s.", "core.seerhut.quest.reachDate.receive.4" : "Fechado até %s.", "core.seerhut.quest.reachDate.receive.5" : "Fechado até %s.", @@ -573,7 +573,7 @@ "core.bonus.ENCHANTER.name" : "Encantador", "core.bonus.ENCHANTER.description" : "Pode lançar ${subtype.spell} em massa a cada turno", "core.bonus.ENCHANTED.name" : "Encantado", - "core.bonus.ENCHANTED.description" : "Afetado por ${subtype.spell} permanente", + "core.bonus.ENCHANTED.description" : "Afetado por ${subtype.spell} permanentemente", "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Ignorar Ataque (${val}%)", "core.bonus.ENEMY_ATTACK_REDUCTION.description" : "Ao ser atacado, ${val}% do ataque do agressor é ignorado", "core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Ignorar Defesa (${val}%)", @@ -665,7 +665,7 @@ "core.bonus.SPELL_LIKE_ATTACK.name" : "Ataque Similar a Feitiço", "core.bonus.SPELL_LIKE_ATTACK.description" : "Ataques com ${subtype.spell}", "core.bonus.SPELL_RESISTANCE_AURA.name" : "Aura de Resistência", - "core.bonus.SPELL_RESISTANCE_AURA.description" : "Pilhas próximas ganham ${val}% de resistência a magia", + "core.bonus.SPELL_RESISTANCE_AURA.description" : "Pilhas próximas ganham ${val}% de resistência à magia", "core.bonus.SUMMON_GUARDIANS.name" : "Invocar Guardas", "core.bonus.SUMMON_GUARDIANS.description" : "No início da batalha, invoca ${subtype.creature} (${val}%)", "core.bonus.SYNERGY_TARGET.name" : "Alvo Sinergizável", @@ -678,8 +678,8 @@ "core.bonus.TRANSMUTATION.description" : "${val}% de chance de transformar a unidade atacada em um tipo diferente", "core.bonus.UNDEAD.name" : "Morto-vivo", "core.bonus.UNDEAD.description" : "A criatura é um Morto-vivo", - "core.bonus.UNLIMITED_RETALIATIONS.name" : "Contra-ataques Ilimitadas", - "core.bonus.UNLIMITED_RETALIATIONS.description" : "Pode contra-atacar contra um número ilimitado de ataques", + "core.bonus.UNLIMITED_RETALIATIONS.name" : "Contra-ataques Ilimitados", + "core.bonus.UNLIMITED_RETALIATIONS.description" : "Pode contra-atacar um número ilimitado de vezes", "core.bonus.WATER_IMMUNITY.name" : "Imunidade à Água", "core.bonus.WATER_IMMUNITY.description" : "Imune a todos os feitiços da escola de magia da Água", "core.bonus.WIDE_BREATH.name" : "Sopro Amplo", From 7e4b268a7837c389e3ee8ddd99e7c6829fb29cb3 Mon Sep 17 00:00:00 2001 From: nick huang Date: Fri, 18 Oct 2024 20:04:00 +0800 Subject: [PATCH 388/726] Building dependence of Unicorn Glide in Rampart should include Dendroid Arches --- config/factions/rampart.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 2b4c12f85..42bd462f9 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -199,7 +199,7 @@ "dwellingLvl3": { "id" : 32, "requires" : [ "dwellingLvl1" ] }, "dwellingLvl4": { "id" : 33, "requires" : [ "dwellingLvl3" ] }, "dwellingLvl5": { "id" : 34, "requires" : [ "dwellingLvl3" ] }, - "dwellingLvl6": { "id" : 35, "requires" : [ "allOf", [ "dwellingLvl3" ], [ "dwellingLvl4" ] ] }, + "dwellingLvl6": { "id" : 35, "requires" : [ "allOf", [ "dwellingLvl4" ], [ "dwellingLvl5" ] ] }, "dwellingLvl7": { "id" : 36, "requires" : [ "allOf", [ "dwellingLvl6" ], [ "mageGuild2" ] ] }, "dwellingUpLvl1": { "id" : 37, "upgrades" : "dwellingLvl1" }, From 0f41361873ce1ebbf57623e2d7ae88aa1bdaab05 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:33:29 +0200 Subject: [PATCH 389/726] fix edge case --- client/media/CVideoHandler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 41e2eb6a6..48aa18a04 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -173,7 +173,6 @@ void CVideoInstance::openVideo() { openContext(); openCodec(findVideoStream()); - startTime = std::chrono::high_resolution_clock::now(); } void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput) @@ -392,6 +391,9 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); + if(startTime == std::chrono::high_resolution_clock()) + startTime = std::chrono::high_resolution_clock::now(); + auto nowTime = std::chrono::high_resolution_clock::now(); double difference = std::chrono::duration_cast(nowTime - startTime).count() / 1000.0; From a68522b370d5d6daba037806abdebb0a9ba71cec Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:37:18 +0200 Subject: [PATCH 390/726] fix --- client/media/CVideoHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 48aa18a04..809af5b26 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -391,7 +391,7 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); - if(startTime == std::chrono::high_resolution_clock()) + if(startTime == std::chrono::high_resolution_clock::time_point()) startTime = std::chrono::high_resolution_clock::now(); auto nowTime = std::chrono::high_resolution_clock::now(); From 19ad4ee0da4a54067261aad675d2a265b4c61fa2 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:56:07 +0200 Subject: [PATCH 391/726] Small fixes Fixed for new v1.6 strings --- Mods/vcmi/config/vcmi/czech.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 6c31b1424..5652b6169 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -12,10 +12,10 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Převažující", "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtelná", "vcmi.adventureMap.monsterThreat.levels.11" : "Nemožná", - "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL %TOWN %ATTACK_TYPE jednotka", - "vcmi.adventureMap.monsterMeleeType" : "útok zblízka", - "vcmi.adventureMap.monsterRangedType" : "útok na dálku", - "vcmi.adventureMap.search.hover" : "Prohledat mapový objekt", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL %TOWN jednotka %ATTACK_TYPE", + "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", + "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", + "vcmi.adventureMap.search.hover" : "Prohledat objekt", "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě pro prohledání.", "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", From 7dc74f558502bff99649a2307413ec15006c2676 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Fri, 18 Oct 2024 22:42:23 +0200 Subject: [PATCH 392/726] Small fixes Fixes for new v1.6 strings --- Mods/vcmi/config/vcmi/czech.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 5652b6169..27fc968dd 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -12,7 +12,7 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Převažující", "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtelná", "vcmi.adventureMap.monsterThreat.levels.11" : "Nemožná", - "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL %TOWN jednotka %ATTACK_TYPE", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN, jednotka %ATTACK_TYPE", "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", "vcmi.adventureMap.search.hover" : "Prohledat objekt", From 1df1177506d8c54c05948724573ff297c46ec79f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:15:11 +0200 Subject: [PATCH 393/726] fix for defect mp3 --- client/media/CMusicHandler.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/media/CMusicHandler.cpp b/client/media/CMusicHandler.cpp index 2fbf48118..0a04b0633 100644 --- a/client/media/CMusicHandler.cpp +++ b/client/media/CMusicHandler.cpp @@ -265,7 +265,12 @@ void MusicEntry::load(const AudioPath & musicURI) try { - auto * musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName)); + std::unique_ptr stream = CResourceHandler::get()->load(currentName); + + if(musicURI.getName() == "BLADEFWCAMPAIGN") // handle defect MP3 file - ffprobe says: Skipping 52 bytes of junk at 0. + stream->seek(52); + + auto * musicFile = MakeSDLRWops(std::move(stream)); music = Mix_LoadMUS_RW(musicFile, SDL_TRUE); } catch(std::exception & e) From 7a190e4929c1c06cd2e774d1ad7b2a6678b3bd1d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:25:26 +0200 Subject: [PATCH 394/726] fix crash --- lib/CGameInfoCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 112f37fcd..e24d7fd1e 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -484,7 +484,7 @@ std::vector> CGameInfoCallback::getAllVisit { std::vector> ret; for(auto & obj : gs->map->objects) - if(obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj)) + if(obj && obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj)) ret.push_back(obj); return ret; From 2ea2a3150ec902ca61f012a0f4a3d790f506c4a5 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 19 Oct 2024 15:59:02 +0200 Subject: [PATCH 395/726] fix shortcuts with Modifier Key --- client/eventsSDL/InputSourceKeyboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/eventsSDL/InputSourceKeyboard.cpp b/client/eventsSDL/InputSourceKeyboard.cpp index ee43e5c53..d97c45b10 100644 --- a/client/eventsSDL/InputSourceKeyboard.cpp +++ b/client/eventsSDL/InputSourceKeyboard.cpp @@ -111,7 +111,7 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key) if(key.repeat != 0) return; // ignore periodic event resends - std::string keyName = SDL_GetKeyName(key.keysym.sym); + std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym)); logGlobal->trace("keyboard: key '%s' released", keyName); if (SDL_IsTextInputActive() == SDL_TRUE) From de0463318c8b56e536ca0861645190974e8fe5ad Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Sat, 19 Oct 2024 16:16:58 +0200 Subject: [PATCH 396/726] Update swedish.json Added new code strings and trimmed down some descriptions so that they will fit in the small description box (hopefully). --- Mods/vcmi/config/vcmi/swedish.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/vcmi/swedish.json index 75958bbf5..e981350dc 100644 --- a/Mods/vcmi/config/vcmi/swedish.json +++ b/Mods/vcmi/config/vcmi/swedish.json @@ -15,6 +15,8 @@ "vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN", "vcmi.adventureMap.monsterMeleeType" : "närstrid", "vcmi.adventureMap.monsterRangedType" : "fjärrstrid", + "vcmi.adventureMap.search.hover" : "Sök kartobjekt", + "vcmi.adventureMap.search.help" : "Välj objekt för att söka på kartan.", "vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?", "vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!", @@ -356,8 +358,9 @@ "vcmi.heroWindow.sortBackpackBySlot.help" : "Sorterar artefakter i ryggsäcken efter utrustad plats.", "vcmi.heroWindow.sortBackpackByClass.hover" : "Sortera efter klass", "vcmi.heroWindow.sortBackpackByClass.help" : "Sorterar artefakter i ryggsäcken efter artefaktklass (skatt, mindre, större, relik)", + "vcmi.heroWindow.fusingArtifact.fusing" : "Du har alla komponenterna som behövs för en sammanslagning av %s. Vill du utföra sammanslagningen? {Alla komponenter kommer att förbrukas vid sammanslagningen.}", - "vcmi.tavernWindow.inviteHero" : "Bjud in hjälte", + "vcmi.tavernWindow.inviteHero" : "Bjud in hjälte", "vcmi.commanderWindow.artifactMessage" : "Vill du återlämna denna artefakt till hjälten?", @@ -574,7 +577,7 @@ "core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)", "core.bonus.ENEMY_ATTACK_REDUCTION.description" : "Ignorerar ${val}% av angriparens attack.", "core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Förbigå försvar (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "Din attack ignorerar ${val}% av fiendens försvar.", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "Attacker ignorerar ${val}% av fiendens försvar.", "core.bonus.FIRE_IMMUNITY.name" : "Eld-immunitet", "core.bonus.FIRE_IMMUNITY.description" : "Immun mot alla eldmagi-trollformler.", "core.bonus.FIRE_SHIELD.name" : "Eldsköld (${val}%)", @@ -594,7 +597,7 @@ "core.bonus.GARGOYLE.name" : "Stenfigur", "core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas.", "core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från inkommande attacker.", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från fiendens attacker.", "core.bonus.HATE.name" : "Hatar: ${subtype.creature}", "core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtype.creature}.", "core.bonus.HEALER.name" : "Helare", @@ -644,7 +647,7 @@ "core.bonus.REVENGE.name" : "Hämndlysten", "core.bonus.REVENGE.description" : "Vållar mer skada om den själv blivit skadad.", "core.bonus.SHOOTER.name" : "Distans-attack", - "core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd.", + "core.bonus.SHOOTER.description" : "Skjuter/attackerar på avstånd.", "core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten", "core.bonus.SHOOTS_ALL_ADJACENT.description" : "Distans-attack drabbar alla inom räckhåll.", "core.bonus.SOUL_STEAL.name" : "Själtjuv", From 681542bd2a271199474772b25913ecd016b77beb Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:42:18 +0200 Subject: [PATCH 397/726] Quality fixes --- Mods/vcmi/config/vcmi/czech.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 27fc968dd..f7aed222c 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -61,7 +61,7 @@ "vcmi.radialWheel.moveDown" : "Posunout níže", "vcmi.radialWheel.moveBottom" : "Přesunout dolů", - "vcmi.spellBook.search" : "hledat...", + "vcmi.spellBook.search" : "Hledat kouzlo", "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", @@ -215,7 +215,7 @@ "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", - "vcmi.systemOptions.resolutionMenu.help" : "Změnit rozlišení herní obrazovky.", + "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", @@ -244,13 +244,13 @@ "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", - "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okraji", - "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okraji}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", + "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", + "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Posouvání mapy levým kliknutím", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", "vcmi.adventureOptions.leftButtonDrag.help" : "{Posouvání mapy levým kliknutím}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", - "vcmi.adventureOptions.rightButtonDrag.hover" : "Přetahování pravým tlačítkem", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", "vcmi.adventureOptions.rightButtonDrag.help" : "{Přetahování pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", @@ -288,8 +288,8 @@ "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", - "vcmi.battleOptions.endWithAutocombat.hover": "Ukončit bitvu", - "vcmi.battleOptions.endWithAutocombat.help": "{Ukončit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", + "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", + "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", @@ -318,7 +318,7 @@ "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete ukončit bitvu s automatickým bojem?", + "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", From 28a2fa679030c4e263f9889bfdbe470acda314de Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:43:43 +0200 Subject: [PATCH 398/726] Quality fixes --- Mods/vcmi/config/vcmi/czech.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index f7aed222c..64c41b403 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -249,9 +249,9 @@ "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", - "vcmi.adventureOptions.leftButtonDrag.help" : "{Posouvání mapy levým kliknutím}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", + "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", - "vcmi.adventureOptions.rightButtonDrag.help" : "{Přetahování pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", From 94606ba5a3d6c898e106c1e0e47e266ec8cb4112 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 20 Oct 2024 02:13:50 +0200 Subject: [PATCH 399/726] campaign map description size --- client/lobby/CBonusSelection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index e04b19f8c..ced7705fd 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -99,7 +99,7 @@ CBonusSelection::CBonusSelection() int availableSpace = videoButtonActive ? 225 : 285; mapName = std::make_shared(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), availableSpace ); labelMapDescription = std::make_shared(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]); - mapDescription = std::make_shared("", Rect(480, 278, 292, 108), 1); + mapDescription = std::make_shared("", Rect(480, 278, 286, 108), 1); labelChooseBonus = std::make_shared(475, 432, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]); groupBonuses = std::make_shared(std::bind(&IServerAPI::setCampaignBonus, CSH, _1)); From 98b912dab865bc4a42e2142ce450ec740d438b2f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 20 Oct 2024 02:26:40 +0200 Subject: [PATCH 400/726] optimize layout --- client/lobby/CLobbyScreen.cpp | 4 ++-- client/lobby/CSelectionBase.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index fbf0dfce7..ead6f3ef3 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -188,7 +188,7 @@ void CLobbyScreen::toggleMode(bool host) return; auto buttonColor = host ? Colors::WHITE : Colors::ORANGE; - buttonSelect->setTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); + buttonSelect->setTextOverlay(" " + CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); buttonOptions->setTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor); if (buttonTurnOptions) @@ -199,7 +199,7 @@ void CLobbyScreen::toggleMode(bool host) if(buttonRMG) { - buttonRMG->setTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); + buttonRMG->setTextOverlay(" " + CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); buttonRMG->block(!host); } buttonSelect->block(!host); diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 13d79d672..7b49da41c 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -187,8 +187,8 @@ InfoCard::InfoCard() iconsVictoryCondition = std::make_shared(AnimationPath::builtin("SCNRVICT"), 0, 0, 24, 302); iconsLossCondition = std::make_shared(AnimationPath::builtin("SCNRLOSS"), 0, 0, 24, 359); - labelVictoryConditionText = std::make_shared(60, 307, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); - labelLossConditionText = std::make_shared(60, 366, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); + labelVictoryConditionText = std::make_shared(60, 307, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, "", 290); + labelLossConditionText = std::make_shared(60, 366, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, "", 290); labelDifficulty = std::make_shared(62, 472, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); labelDifficultyPercent = std::make_shared(311, 472, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); From 185d036d78b067e6d426f48e8cd136d69ae34faa Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 20 Oct 2024 03:16:57 +0200 Subject: [PATCH 401/726] no scrollbar for hotseat title (with ttf fonts) --- client/mainmenu/CMainMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index d19275185..b28b5f138 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -540,7 +540,7 @@ CMultiPlayers::CMultiPlayers(const std::vector & playerNames, ESele std::string text = CGI->generaltexth->allTexts[446]; boost::replace_all(text, "\t", "\n"); - textTitle = std::make_shared(text, Rect(25, 20, 315, 50), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE); //HOTSEAT Please enter names + textTitle = std::make_shared(text, Rect(25, 10, 315, 60), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE); //HOTSEAT Please enter names for(int i = 0; i < inputNames.size(); i++) { From 573bb6abc68e51c185aaab1c90f1f474602d27a2 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 19 Oct 2024 16:25:26 +0300 Subject: [PATCH 402/726] CArtPlace now works with artifact ID --- client/widgets/CArtPlace.cpp | 120 +++++++++----------- client/widgets/CArtPlace.h | 18 +-- client/widgets/CArtifactsOfHeroBackpack.cpp | 2 +- client/widgets/CArtifactsOfHeroBase.cpp | 8 +- client/widgets/CArtifactsOfHeroKingdom.cpp | 4 +- client/windows/CCreatureWindow.cpp | 17 +-- client/windows/CCreatureWindow.h | 4 +- lib/CArtifactInstance.cpp | 8 -- lib/CArtifactInstance.h | 1 - 9 files changed, 83 insertions(+), 99 deletions(-) diff --git a/client/widgets/CArtPlace.cpp b/client/widgets/CArtPlace.cpp index 954366315..0444ef77d 100644 --- a/client/widgets/CArtPlace.cpp +++ b/client/widgets/CArtPlace.cpp @@ -29,26 +29,44 @@ #include "../../lib/networkPacks/ArtifactLocation.h" #include "../../lib/CConfigHandler.h" -void CArtPlace::setInternals(const CArtifactInstance * artInst) +CArtPlace::CArtPlace(Point position, const ArtifactID & artId, const SpellID & spellId) + : SelectableSlot(Rect(position, Point(44, 44)), Point(1, 1)) + , locked(false) + , imageIndex(0) { - ourArt = artInst; - if(!artInst) + OBJECT_CONSTRUCTION; + + image = std::make_shared(AnimationPath::builtin("artifact"), 0); + setArtifact(artId, spellId); + moveSelectionForeground(); +} + +void CArtPlace::setArtifact(const SpellID & spellId) +{ + setArtifact(ArtifactID::SPELL_SCROLL, spellId); +} + +void CArtPlace::setArtifact(const ArtifactID & artId, const SpellID & spellId) +{ + this->artId = artId; + if(artId == ArtifactID::NONE) { image->disable(); text.clear(); - hoverText = CGI->generaltexth->allTexts[507]; + lockSlot(false); return; } - imageIndex = artInst->artType->getIconIndex(); - if(artInst->getTypeId() == ArtifactID::SPELL_SCROLL) + const auto artType = artId.toArtifact(); + imageIndex = artType->getIconIndex(); + if(artId == ArtifactID::SPELL_SCROLL) { - auto spellID = artInst->getScrollSpellID(); - assert(spellID.num >= 0); + this->spellId = spellId; + assert(spellId.num > 0); if(settings["general"]["enableUiEnhancements"].Bool()) { - imageIndex = spellID.num; + imageIndex = spellId.num; if(component.type != ComponentType::SPELL_SCROLL) { image->setScale(Point(pos.w, 34)); @@ -58,7 +76,7 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst) } // Add spell component info (used to provide a pic in r-click popup) component.type = ComponentType::SPELL_SCROLL; - component.subType = spellID; + component.subType = spellId; } else { @@ -69,47 +87,33 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst) image->moveTo(Point(pos.x, pos.y)); } component.type = ComponentType::ARTIFACT; - component.subType = artInst->getTypeId(); + component.subType = artId; } image->enable(); - text = artInst->getDescription(); + lockSlot(locked); + + text = artType->getDescriptionTranslated(); + if(artType->isScroll()) + ArtifactUtils::insertScrrollSpellName(text, spellId); } -CArtPlace::CArtPlace(Point position, const CArtifactInstance * art) - : SelectableSlot(Rect(position, Point(44, 44)), Point(1, 1)) - , ourArt(art) - , locked(false) +ArtifactID CArtPlace::getArtifactId() const { - OBJECT_CONSTRUCTION; - - imageIndex = 0; - if(locked) - imageIndex = ArtifactID::ART_LOCK; - else if(ourArt) - imageIndex = ourArt->artType->getIconIndex(); - - image = std::make_shared(AnimationPath::builtin("artifact"), imageIndex); - image->disable(); - moveSelectionForeground(); + return artId; } -const CArtifactInstance * CArtPlace::getArt() const -{ - return ourArt; -} - -CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art) - : CArtPlace(position, art), +CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, + const ArtifactID & artId, const SpellID & spellId) + : CArtPlace(position, artId, spellId), commanderOwner(commanderOwner), commanderSlotID(artSlot.num) { - setArtifact(art); } void CCommanderArtPlace::returnArtToHeroCallback() { ArtifactPosition artifactPos = commanderSlotID; - ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, getArt()->getTypeId()); + ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, getArtifactId()); if(freeSlot == ArtifactPosition::PRE_FIRST) { LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152")); @@ -120,10 +124,10 @@ void CCommanderArtPlace::returnArtToHeroCallback() src.creature = SlotID::COMMANDER_SLOT_PLACEHOLDER; ArtifactLocation dst(commanderOwner->id, freeSlot); - if(getArt()->canBePutAt(commanderOwner, freeSlot, true)) + if(getArtifactId().toArtifact()->canBePutAt(commanderOwner, freeSlot, true)) { LOCPLINT->cb->swapArtifacts(src, dst); - setArtifact(nullptr); + setArtifact(ArtifactID(ArtifactID::NONE)); parent->redraw(); } } @@ -131,29 +135,35 @@ void CCommanderArtPlace::returnArtToHeroCallback() void CCommanderArtPlace::clickPressed(const Point & cursorPosition) { - if(getArt() && text.size()) + if(getArtifactId() != ArtifactID::NONE && text.size()) LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {}); } void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition) { - if(getArt() && text.size()) + if(getArtifactId() != ArtifactID::NONE && text.size()) CArtPlace::showPopupWindow(cursorPosition); } void CArtPlace::lockSlot(bool on) { - if(locked == on) - return; - locked = on; - if(on) + { image->setFrame(ArtifactID::ART_LOCK); - else if(ourArt) + hoverText = CGI->generaltexth->allTexts[507]; + } + else if(artId != ArtifactID::NONE) + { image->setFrame(imageIndex); + auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[1]); + hoverText.replaceName(artId); + this->hoverText = hoverText.toString(); + } else - image->setFrame(0); + { + hoverText = CGI->generaltexth->allTexts[507]; + } } bool CArtPlace::isLocked() const @@ -182,24 +192,6 @@ void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & fi gestureCallback(*this, initialPosition); } -void CArtPlace::setArtifact(const CArtifactInstance * art) -{ - setInternals(art); - if(art) - { - image->setFrame(locked ? static_cast(ArtifactID::ART_LOCK) : imageIndex); - - if(locked) // Locks should appear as empty. - hoverText = CGI->generaltexth->allTexts[507]; - else - hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->getNameTranslated()); - } - else - { - lockSlot(false); - } -} - void CArtPlace::setClickPressedCallback(const ClickFunctor & callback) { clickPressedCallback = callback; diff --git a/client/widgets/CArtPlace.h b/client/widgets/CArtPlace.h index 95ff02b7e..645a57e5a 100644 --- a/client/widgets/CArtPlace.h +++ b/client/widgets/CArtPlace.h @@ -20,11 +20,12 @@ public: ArtifactPosition slot; - CArtPlace(Point position, const CArtifactInstance * art = nullptr); - const CArtifactInstance * getArt() const; + CArtPlace(Point position, const ArtifactID & artId = ArtifactID::NONE, const SpellID & spellId = SpellID::NONE); + void setArtifact(const SpellID & spellId); + void setArtifact(const ArtifactID & artId, const SpellID & spellId = SpellID::NONE); + ArtifactID getArtifactId() const; void lockSlot(bool on); bool isLocked() const; - void setArtifact(const CArtifactInstance * art); void setClickPressedCallback(const ClickFunctor & callback); void setShowPopupCallback(const ClickFunctor & callback); void setGestureCallback(const ClickFunctor & callback); @@ -34,16 +35,14 @@ public: void addCombinedArtInfo(const std::map> & arts); private: - const CArtifactInstance * ourArt; + ArtifactID artId; + SpellID spellId; bool locked; - int imageIndex; + int32_t imageIndex; std::shared_ptr image; ClickFunctor clickPressedCallback; ClickFunctor showPopupCallback; ClickFunctor gestureCallback; - -protected: - void setInternals(const CArtifactInstance * artInst); }; class CCommanderArtPlace : public CArtPlace @@ -55,7 +54,8 @@ private: void returnArtToHeroCallback(); public: - CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art = nullptr); + CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, + const ArtifactID & artId = ArtifactID::NONE, const SpellID & spellId = SpellID::NONE); void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; }; diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index 6e77636a3..8ecc5b9f3 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -83,7 +83,7 @@ void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider) slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax)); backpackSlotsBackgrounds.emplace_back(std::make_shared(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos)); artPlace = std::make_shared(pos); - artPlace->setArtifact(nullptr); + artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); artPlaceIdx++; diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 6d857b010..9ccd9248e 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -66,13 +66,13 @@ void CArtifactsOfHeroBase::init( for(auto artPlace : artWorn) { artPlace.second->slot = artPlace.first; - artPlace.second->setArtifact(nullptr); + artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE)); artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } for(auto artPlace : backpack) { - artPlace->setArtifact(nullptr); + artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } @@ -260,7 +260,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit if(auto slotInfo = curHero->getSlot(slot)) { artPlace->lockSlot(slotInfo->locked); - artPlace->setArtifact(slotInfo->artifact); + artPlace->setArtifact(slotInfo->artifact->getTypeId(), slotInfo->artifact->getScrollSpellID()); if(slotInfo->locked || slotInfo->artifact->isCombined()) return; @@ -285,7 +285,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit } else { - artPlace->setArtifact(nullptr); + artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); } } diff --git a/client/widgets/CArtifactsOfHeroKingdom.cpp b/client/widgets/CArtifactsOfHeroKingdom.cpp index e65bd8bfb..7bc0853ce 100644 --- a/client/widgets/CArtifactsOfHeroKingdom.cpp +++ b/client/widgets/CArtifactsOfHeroKingdom.cpp @@ -29,14 +29,14 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto for(auto artPlace : artWorn) { artPlace.second->slot = artPlace.first; - artPlace.second->setArtifact(nullptr); + artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE)); artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } enableGesture(); for(auto artPlace : backpack) { - artPlace->setArtifact(nullptr); + artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 293324c3e..28f0b7b92 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -432,7 +432,9 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i for(auto equippedArtifact : parent->info->commander->artifactsWorn) { Point artPos = getArtifactPos(equippedArtifact.first); - auto artPlace = std::make_shared(artPos, parent->info->owner, equippedArtifact.first, equippedArtifact.second.artifact); + const auto commanderArt = equippedArtifact.second.artifact; + assert(commanderArt); + auto artPlace = std::make_shared(artPos, parent->info->owner, equippedArtifact.first, commanderArt->getTypeId()); artifacts.push_back(artPlace); } @@ -616,11 +618,11 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s auto art = parent->info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT); if(art) { - parent->stackArtifactIcon = std::make_shared(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y); - parent->stackArtifactHelp = std::make_shared(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT); - parent->stackArtifactHelp->component.subType = art->artType->getId(); - parent->stackArtifactHelp->text = art->getDescription(); - + parent->stackArtifact = std::make_shared(pos, art->getTypeId()); + parent->stackArtifact->setShowPopupCallback([](CArtPlace & artPlace, const Point & cursorPosition) + { + artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition); + }); if(parent->info->owner) { parent->stackArtifactButton = std::make_shared( @@ -987,8 +989,7 @@ void CStackWindow::removeStackArtifact(ArtifactPosition pos) artLoc.creature = info->stackNode->armyObj->findStack(info->stackNode); LOCPLINT->cb->swapArtifacts(artLoc, ArtifactLocation(info->owner->id, slot)); stackArtifactButton.reset(); - stackArtifactHelp.reset(); - stackArtifactIcon.reset(); + stackArtifact.reset(); redraw(); } } diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h index 4728b3b1f..df1391be4 100644 --- a/client/windows/CCreatureWindow.h +++ b/client/windows/CCreatureWindow.h @@ -28,6 +28,7 @@ class CTabbedInt; class CButton; class CMultiLineLabel; class CListBox; +class CArtPlace; class CCommanderArtPlace; class LRClickableArea; @@ -156,8 +157,7 @@ class CStackWindow : public CWindowObject MainSection(CStackWindow * owner, int yOffset, bool showExp, bool showArt); }; - std::shared_ptr stackArtifactIcon; - std::shared_ptr stackArtifactHelp; + std::shared_ptr stackArtifact; std::shared_ptr stackArtifactButton; diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index f65e6d95b..cf01339db 100644 --- a/lib/CArtifactInstance.cpp +++ b/lib/CArtifactInstance.cpp @@ -134,14 +134,6 @@ std::string CArtifactInstance::nodeName() const return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type"; } -std::string CArtifactInstance::getDescription() const -{ - std::string text = artType->getDescriptionTranslated(); - if(artType->isScroll()) - ArtifactUtils::insertScrrollSpellName(text, getScrollSpellID()); - return text; -} - ArtifactID CArtifactInstance::getTypeId() const { return artType->getId(); diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index 6ff0bdbe0..e1c621c0e 100644 --- a/lib/CArtifactInstance.h +++ b/lib/CArtifactInstance.h @@ -80,7 +80,6 @@ public: CArtifactInstance(); void setType(const CArtifact * art); std::string nodeName() const override; - std::string getDescription() const; ArtifactID getTypeId() const; ArtifactInstanceID getId() const; void setId(ArtifactInstanceID id); From f04e110e2cef83e1c642d51fb33c37a7038010af Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 20 Oct 2024 12:59:08 +0200 Subject: [PATCH 403/726] Update RecruitHeroBehavior.cpp Removed inclusion of no longer existing and also not needed header. --- AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index aa7d4b23f..7cfab3731 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -13,7 +13,6 @@ #include "../AIUtility.h" #include "../Goals/RecruitHero.h" #include "../Goals/ExecuteHeroChain.h" -#include "../lib/CHeroHandler.h" namespace NKAI { From 11980e0f97392860617e153e818ba1eb8a2103c9 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 20 Oct 2024 15:49:23 +0200 Subject: [PATCH 404/726] Garrisoning behavior improvement AI will now also garrison a hero as defender if the town to be defended has troops as long as the hero can merge their own troops with the town. AI will no longer just dismiss existing troops in a town if a hero trying to garrison there can merge with it. --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 1 + AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 158fd8989..95f1d6980 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -270,6 +270,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta // dismiss creatures we are not able to pick to be able to hide in garrison if(town->garrisonHero || town->getUpperArmy()->stacksCount() == 0 + || path.targetHero->canBeMergedWith(*town) || (town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL)) { tasks.push_back( diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index e03901910..5e7f8df63 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -90,9 +90,12 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai) if(!town->garrisonHero) { - while(upperArmy->stacksCount() != 0) + if (!garrisonHero->canBeMergedWith(*town)) { - cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first); + while (upperArmy->stacksCount() != 0) + { + cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first); + } } } From 3a8a67407b9e348b29f221ec15cdf6f863edcc2f Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 20 Oct 2024 16:37:03 +0200 Subject: [PATCH 405/726] Update RecruitHeroBehavior.cpp AI's willingness to hire hero now depends more on the availability of treasure again. --- .../Behaviors/RecruitHeroBehavior.cpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 7cfab3731..16c19ed62 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -57,6 +57,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const bool haveCapitol = false; ai->dangerHitMap->updateHitMap(); + int treasureSourcesCount = 0; for(auto town : towns) { @@ -77,6 +78,22 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const if(ai->heroManager->canRecruitHero(town)) { auto availableHeroes = ai->cb->getAvailableHeroes(town); + + for (auto obj : ai->objectClusterizer->getNearbyObjects()) + { + if ((obj->ID == Obj::RESOURCE) + || obj->ID == Obj::TREASURE_CHEST + || obj->ID == Obj::CAMPFIRE + || isWeeklyRevisitable(ai, obj) + || obj->ID == Obj::ARTIFACT) + { + auto tile = obj->visitablePos(); + auto closestTown = ai->dangerHitMap->getClosestTown(tile); + + if (town == closestTown) + treasureSourcesCount++; + } + } for(auto hero : availableHeroes) { @@ -105,7 +122,8 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const } if (bestHeroToHire && bestTownToHireFrom) { - if (ai->cb->getHeroesInfo().size() < ai->cb->getTownsInfo().size() + 1 + if (ai->cb->getHeroesInfo().size() == 0 + || treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5 || (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol) || (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh())) { From 0991f022826777ece4b6a01425418e8758e230d2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:02:56 +0200 Subject: [PATCH 406/726] Bonus: prism breath --- Mods/vcmi/config/vcmi/english.json | 4 ++- config/bonuses.json | 8 ++++++ docs/modders/Bonus/Bonus_Types.md | 4 +++ lib/battle/CBattleInfoCallback.cpp | 46 +++++++++++++++++++----------- lib/bonuses/BonusEnum.h | 1 + 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 2cb7b5772..a090251a9 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -687,5 +687,7 @@ "core.bonus.DISINTEGRATE.name": "Disintegrate", "core.bonus.DISINTEGRATE.description": "No corpse remains after death", "core.bonus.INVINCIBLE.name": "Invincible", - "core.bonus.INVINCIBLE.description": "Cannot be affected by anything" + "core.bonus.INVINCIBLE.description": "Cannot be affected by anything", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Prism Breath", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Prism Breath Attack (three directions)" } diff --git a/config/bonuses.json b/config/bonuses.json index 897e0a9b9..f11faaec8 100644 --- a/config/bonuses.json +++ b/config/bonuses.json @@ -548,6 +548,14 @@ } }, + "PRISM_HEX_ATTACK_BREATH": + { + "graphics": + { + "icon": "zvs/Lib1.res/PrismBreath" + } + }, + "THREE_HEADED_ATTACK": { "graphics": diff --git a/docs/modders/Bonus/Bonus_Types.md b/docs/modders/Bonus/Bonus_Types.md index 34e4d0b57..ec025eb3c 100644 --- a/docs/modders/Bonus/Bonus_Types.md +++ b/docs/modders/Bonus/Bonus_Types.md @@ -502,6 +502,10 @@ Affected unit attacks all adjacent creatures (Hydra). Only directly targeted cre Affected unit attacks creature located directly behind targeted tile (Dragons). Only directly targeted creature will attempt to retaliate +### PRISM_HEX_ATTACK_BREATH + +Like `TWO_HEX_ATTACK_BREATH` but affects also two additional cratures (in triangle form from target tile) + ### RETURN_AFTER_STRIKE Affected unit can return to his starting location after attack (Harpies) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 05afc364c..d4ec621c6 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1392,7 +1392,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( at.friendlyCreaturePositions.insert(tile); } } - else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH)) + else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH) || attacker->hasBonusOfType(BonusType::PRISM_HEX_ATTACK_BREATH)) { auto direction = BattleHex::mutualPosition(attackOriginHex, destinationTile); @@ -1404,27 +1404,39 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( direction = BattleHex::mutualPosition(attackOriginHex, defender->occupiedHex(defenderPos)); } - if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation + for(int i = 0; i < 3; i++) { - BattleHex nextHex = destinationTile.cloneInDirection(direction, false); - - if ( defender->doubleWide() ) + if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation { - auto secondHex = destinationTile == defenderPos ? defender->occupiedHex(defenderPos) : defenderPos; + BattleHex nextHex = destinationTile.cloneInDirection(direction, false); - // if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin) - // then dragon breath should target tile on the opposite side of targeted creature - if(BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE) - nextHex = secondHex.cloneInDirection(direction, false); + if ( defender->doubleWide() ) + { + auto secondHex = destinationTile == defenderPos ? defender->occupiedHex(defenderPos) : defenderPos; + + // if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin) + // then dragon breath should target tile on the opposite side of targeted creature + if(BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE) + nextHex = secondHex.cloneInDirection(direction, false); + } + + if (nextHex.isValid()) + { + //friendly stacks can also be damaged by Dragon Breath + const auto * st = battleGetUnitByPos(nextHex, true); + if(st != nullptr) + at.friendlyCreaturePositions.insert(nextHex); + } } - if (nextHex.isValid()) - { - //friendly stacks can also be damaged by Dragon Breath - const auto * st = battleGetUnitByPos(nextHex, true); - if(st != nullptr) - at.friendlyCreaturePositions.insert(nextHex); - } + if(!attacker->hasBonusOfType(BonusType::PRISM_HEX_ATTACK_BREATH)) + break; + + // only needed for prism + int tmpDirection = static_cast(direction) + 2; + if(tmpDirection > static_cast(BattleHex::EDir::LEFT)) + tmpDirection -= static_cast(BattleHex::EDir::TOP); + direction = static_cast(tmpDirection); } } return at; diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index e68450c08..ec5f586f3 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -180,6 +180,7 @@ class JsonNode; BONUS_NAME(RESOURCES_TOWN_MULTIPLYING_BOOST) /*Bonus that does not account for propagation and gives extra resources per day with amount multiplied by number of owned towns. val - base resource amount to be multiplied times number of owned towns, subtype - resource type*/ \ BONUS_NAME(DISINTEGRATE) /* after death no corpse remains */ \ BONUS_NAME(INVINCIBLE) /* cannot be target of attacks or spells */ \ + BONUS_NAME(PRISM_HEX_ATTACK_BREATH) /*eg. dragons*/ \ /* end of list */ From d93c6211da20a88e9d38edd2edf963fe64480ab3 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sun, 20 Oct 2024 23:32:39 +0200 Subject: [PATCH 407/726] Road exploration The non-cheating-AI on 100% and below is now smarter about exploration and will explore alongside roads with higher priority. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 18 +++++++++++++++++- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 1e616b875..420686f46 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -15,6 +15,8 @@ #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h" #include "../../../lib/mapObjects/MapObjects.h" +#include "../../../lib/mapping/CMapDefines.h" +#include "../../../lib/RoadHandler.h" #include "../../../lib/CCreatureHandler.h" #include "../../../lib/VCMI_Lib.h" #include "../../../lib/StartInfo.h" @@ -901,6 +903,8 @@ public: break; } } + if(evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType && evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType->getId() != RoadId::NO_ROAD) + evaluationContext.explorePriority = 1; if (evaluationContext.explorePriority == 0) evaluationContext.explorePriority = 3; } @@ -1408,7 +1412,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (arriveNextWeek && evaluationContext.isEnemy) return 0; - if (evaluationContext.conquestValue > 0 || evaluationContext.explorePriority == 1) + if (evaluationContext.conquestValue > 0) score = 1000; if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; @@ -1419,6 +1423,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } + case PriorityTier::HIGH_PRIO_EXPLORE: + { + if (evaluationContext.enemyHeroDangerRatio > 1) + return 0; + if (evaluationContext.explorePriority != 1) + return 0; + score = 1000; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + break; + } case PriorityTier::HUNTER_GATHER: //Collect guarded stuff { if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index d912130b7..2e757c819 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -114,6 +114,7 @@ public: INSTAKILL, INSTADEFEND, KILL, + HIGH_PRIO_EXPLORE, HUNTER_GATHER, LOW_PRIO_EXPLORE, DEFEND From 76f5d925e61310629d0a56af17a8854c8cce0f58 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 21 Oct 2024 08:59:18 +0200 Subject: [PATCH 408/726] Update PriorityEvaluator.cpp The army loss will no longer affect the score for defensive decisions. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 420686f46..919138d7f 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1401,8 +1401,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) score = evaluationContext.armyInvolvement; - if (evaluationContext.isEnemy) - score *= (maxWillingToLose - evaluationContext.armyLossPersentage); + if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; score *= evaluationContext.closestWayRatio; break; } From 60084243af0fab78509d3febe70ee93c683dab53 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 21 Oct 2024 09:21:00 +0200 Subject: [PATCH 409/726] Update PriorityEvaluator.cpp Fixed losing heroes while exploring. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 919138d7f..ce2f94570 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1429,6 +1429,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.explorePriority != 1) return 0; + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; score = 1000; score *= evaluationContext.closestWayRatio; if (evaluationContext.movementCost > 0) @@ -1473,6 +1475,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.explorePriority != 3) return 0; + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; score = 1000; score *= evaluationContext.closestWayRatio; if (evaluationContext.movementCost > 0) From 2b994147935cc23dafab1c34d0306ead837e70da Mon Sep 17 00:00:00 2001 From: Xilmi Date: Mon, 21 Oct 2024 09:37:44 +0200 Subject: [PATCH 410/726] Using hero's stats instead of level to determine their role. Since there are custom maps/campaigns in which heroes have pretty high base-stats even at level 1. --- AI/Nullkiller/Analyzers/HeroManager.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 6 ++++++ lib/mapObjects/CGHeroInstance.h | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index e18961663..2d5166bc3 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -95,7 +95,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const { - return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f; + return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->getBasePrimarySkillValue(PrimarySkill::ATTACK) + hero->getBasePrimarySkillValue(PrimarySkill::DEFENSE) + hero->getBasePrimarySkillValue(PrimarySkill::SPELL_POWER) + hero->getBasePrimarySkillValue(PrimarySkill::KNOWLEDGE); } void HeroManager::update() diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index b14e727de..47cc2ce36 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1898,5 +1898,11 @@ const IOwnableObject * CGHeroInstance::asOwnable() const return this; } +int CGHeroInstance::getBasePrimarySkillValue(PrimarySkill which) const +{ + std::string cachingStr = "type_PRIMARY_SKILL_base_" + std::to_string(static_cast(which)); + auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)); + return valOfBonuses(selector, cachingStr); +} VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 960bc89fa..04ca63ac8 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -229,6 +229,7 @@ public: double getHeroStrengthForCampaign() const; // includes fighting and the for-campaign-version of magic strength ui64 getTotalStrength() const; // includes fighting strength and army strength TExpType calculateXp(TExpType exp) const; //apply learning skill + int getBasePrimarySkillValue(PrimarySkill which) const; //the value of a base-skill without items or temporary bonuses CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; void showNecromancyDialog(const CStackBasicDescriptor &raisedStack, vstd::RNG & rand) const; From ff1053b0a025117b763cca018c132d8f79ad85df Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Mon, 21 Oct 2024 23:45:10 +0200 Subject: [PATCH 411/726] Quality fixes --- Mods/vcmi/config/vcmi/czech.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 64c41b403..311abe3ad 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -3,20 +3,20 @@ "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", - "vcmi.adventureMap.monsterThreat.levels.3" : "Trochu slabší", - "vcmi.adventureMap.monsterThreat.levels.4" : "Podobná", - "vcmi.adventureMap.monsterThreat.levels.5" : "Trochu silnější", + "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", + "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", + "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", - "vcmi.adventureMap.monsterThreat.levels.9" : "Převažující", - "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtelná", - "vcmi.adventureMap.monsterThreat.levels.11" : "Nemožná", - "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN, jednotka %ATTACK_TYPE", + "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", + "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", + "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", "vcmi.adventureMap.search.hover" : "Prohledat objekt", - "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě pro prohledání.", + "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", @@ -308,7 +308,7 @@ "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", - "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu", + "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", @@ -344,7 +344,7 @@ "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", - "vcmi.logicalExpressions.anyOf" : "Něco z následujících:", + "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", "vcmi.logicalExpressions.allOf" : "Všechny následující:", "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", From 7955960901d18692c56230ac1c0fcb08e1d891f6 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sat, 19 Oct 2024 22:48:06 +0300 Subject: [PATCH 412/726] CTradeableItem refactoring --- client/widgets/CArtifactsOfHeroBackpack.cpp | 2 - client/widgets/markets/CAltarArtifacts.cpp | 2 +- client/widgets/markets/CAltarCreatures.cpp | 50 +++++++++++-------- client/widgets/markets/CAltarCreatures.h | 1 + client/widgets/markets/CArtifactsBuying.cpp | 20 ++++---- client/widgets/markets/CArtifactsSelling.cpp | 8 +-- client/widgets/markets/CFreelancerGuild.cpp | 16 +++--- client/widgets/markets/CMarketBase.cpp | 6 +-- client/widgets/markets/CMarketResources.cpp | 22 ++++---- client/widgets/markets/CTransferResources.cpp | 14 +++--- client/widgets/markets/TradePanels.cpp | 38 ++++++-------- client/widgets/markets/TradePanels.h | 12 ++--- 12 files changed, 95 insertions(+), 96 deletions(-) diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index 8ecc5b9f3..ea05279c4 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -126,8 +126,6 @@ size_t CArtifactsOfHeroBackpack::calcRows(size_t slots) CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot) : CArtifactsOfHeroBackpack(0, 0) { - assert(ArtifactUtils::checkIfSlotValid(*getHero(), filterBySlot)); - if(!ArtifactUtils::isSlotEquipment(filterBySlot)) return; diff --git a/client/widgets/markets/CAltarArtifacts.cpp b/client/widgets/markets/CAltarArtifacts.cpp index 4f57e543e..4d88e1d8e 100644 --- a/client/widgets/markets/CAltarArtifacts.cpp +++ b/client/widgets/markets/CAltarArtifacts.cpp @@ -58,7 +58,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * CAltarArtifacts::onSlotClickPressed(altarSlot, offerTradePanel); }); offerTradePanel->updateSlotsCallback = std::bind(&CAltarArtifacts::updateAltarSlots, this); - offerTradePanel->moveTo(pos.topLeft() + Point(315, 52)); + offerTradePanel->moveTo(pos.topLeft() + Point(315, 53)); CMarketBase::updateShowcases(); CAltarArtifacts::deselect(); diff --git a/client/widgets/markets/CAltarCreatures.cpp b/client/widgets/markets/CAltarCreatures.cpp index 896e0644b..2464ceedd 100644 --- a/client/widgets/markets/CAltarCreatures.cpp +++ b/client/widgets/markets/CAltarCreatures.cpp @@ -162,9 +162,9 @@ void CAltarCreatures::makeDeal() for(int & units : unitsOnAltar) units = 0; - for(auto heroSlot : offerTradePanel->slots) + for(auto & heroSlot : offerTradePanel->slots) { - heroSlot->setType(EType::CREATURE_PLACEHOLDER); + heroSlot->setID(CreatureID::NONE); heroSlot->subtitle->clear(); } deselect(); @@ -175,16 +175,16 @@ CMarketBase::MarketShowcasesParams CAltarCreatures::getShowcasesParams() const std::optional bidSelected = std::nullopt; std::optional offerSelected = std::nullopt; if(bidTradePanel->isHighlighted()) - bidSelected = ShowcaseParams {std::to_string(offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getSelectedItemId())->getIconIndex()}; + bidSelected = ShowcaseParams {std::to_string(offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getHighlightedItemId())->getIconIndex()}; if(offerTradePanel->isHighlighted() && offerSlider->getValue() > 0) - offerSelected = ShowcaseParams {offerTradePanel->highlightedSlot->subtitle->getText(), CGI->creatures()->getByIndex(offerTradePanel->getSelectedItemId())->getIconIndex()}; + offerSelected = ShowcaseParams {offerTradePanel->highlightedSlot->subtitle->getText(), CGI->creatures()->getByIndex(offerTradePanel->getHighlightedItemId())->getIconIndex()}; return MarketShowcasesParams {bidSelected, offerSelected}; } void CAltarCreatures::sacrificeAll() { std::optional lastSlot; - for(auto heroSlot : bidTradePanel->slots) + for(const auto & heroSlot : bidTradePanel->slots) { auto stackCount = hero->getStackCount(SlotID(heroSlot->serial)); if(stackCount > unitsOnAltar[heroSlot->serial]) @@ -211,7 +211,8 @@ void CAltarCreatures::sacrificeAll() void CAltarCreatures::updateAltarSlot(const std::shared_ptr & slot) { auto units = unitsOnAltar[slot->serial]; - slot->setType(units > 0 ? EType::CREATURE : EType::CREATURE_PLACEHOLDER); + const auto [oppositeSlot, oppositePanel] = getOpposite(slot); + slot->setID(units > 0 ? oppositeSlot->id : CreatureID::NONE); slot->subtitle->setText(units > 0 ? boost::str(boost::format(CGI->generaltexth->allTexts[122]) % std::to_string(hero->calculateXp(units * expPerUnit[slot->serial]))) : ""); } @@ -234,21 +235,9 @@ void CAltarCreatures::onSlotClickPressed(const std::shared_ptr & if(newSlot == curPanel->highlightedSlot) return; - auto oppositePanel = bidTradePanel; curPanel->onSlotClickPressed(newSlot); - if(curPanel->highlightedSlot == bidTradePanel->highlightedSlot) - { - oppositePanel = offerTradePanel; - } - std::shared_ptr oppositeNewSlot; - for(const auto & slot : oppositePanel->slots) - if(slot->serial == newSlot->serial) - { - oppositeNewSlot = slot; - break; - } - assert(oppositeNewSlot); - oppositePanel->onSlotClickPressed(oppositeNewSlot); + auto [oppositeSlot, oppositePanel] = getOpposite(newSlot); + oppositePanel->onSlotClickPressed(oppositeSlot); highlightingChanged(); redraw(); } @@ -258,7 +247,7 @@ std::string CAltarCreatures::getTraderText() if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { MetaString message = MetaString::createFromTextID("core.genrltxt.484"); - message.replaceNamePlural(CreatureID(bidTradePanel->getSelectedItemId())); + message.replaceNamePlural(CreatureID(bidTradePanel->getHighlightedItemId())); return message.toString(); } else @@ -266,3 +255,22 @@ std::string CAltarCreatures::getTraderText() return ""; } } + +std::tuple, std::shared_ptr> CAltarCreatures::getOpposite( + const std::shared_ptr & curSlot) +{ + assert(curSlot); + + auto oppositePanel = bidTradePanel; + if(vstd::contains(bidTradePanel->slots, curSlot)) + oppositePanel = offerTradePanel; + + std::shared_ptr oppositeSlot; + for(const auto & slot : oppositePanel->slots) + if (slot->serial == curSlot->serial) + { + oppositeSlot = slot; + break; + } + return std::make_tuple(oppositeSlot, oppositePanel); +} diff --git a/client/widgets/markets/CAltarCreatures.h b/client/widgets/markets/CAltarCreatures.h index bf5654919..a5407d74d 100644 --- a/client/widgets/markets/CAltarCreatures.h +++ b/client/widgets/markets/CAltarCreatures.h @@ -33,4 +33,5 @@ private: void onOfferSliderMoved(int newVal) override; void onSlotClickPressed(const std::shared_ptr & newSlot, std::shared_ptr & curPanel) override; std::string getTraderText() override; + std::tuple, std::shared_ptr> getOpposite(const std::shared_ptr & curSlot); }; diff --git a/client/widgets/markets/CArtifactsBuying.cpp b/client/widgets/markets/CArtifactsBuying.cpp index 853bc848b..0ae8a8312 100644 --- a/client/widgets/markets/CArtifactsBuying.cpp +++ b/client/widgets/markets/CArtifactsBuying.cpp @@ -46,7 +46,7 @@ CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance CArtifactsBuying::onSlotClickPressed(newSlot, offerTradePanel); }, [this]() { - CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_ARTIFACT, bidTradePanel->getSelectedItemId()); + CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_ARTIFACT, bidTradePanel->getHighlightedItemId()); }, market->availableItemsIds(EMarketMode::RESOURCE_ARTIFACT)); offerTradePanel->deleteSlotsCheck = [this](const std::shared_ptr & slot) { @@ -66,10 +66,10 @@ void CArtifactsBuying::deselect() void CArtifactsBuying::makeDeal() { - if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero)) + if(ArtifactID(offerTradePanel->getHighlightedItemId()).toArtifact()->canBePutAt(hero)) { - LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()), - ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getHighlightedItemId()), + ArtifactID(offerTradePanel->getHighlightedItemId()), offerQty, hero); CMarketTraderText::makeDeal(); deselect(); } @@ -84,8 +84,8 @@ CMarketBase::MarketShowcasesParams CArtifactsBuying::getShowcasesParams() const if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) return MarketShowcasesParams { - ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : bidQty), bidTradePanel->getSelectedItemId()}, - ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : offerQty), CGI->artifacts()->getByIndex(offerTradePanel->getSelectedItemId())->getIconIndex()} + ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : bidQty), bidTradePanel->getHighlightedItemId()}, + ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : offerQty), CGI->artifacts()->getByIndex(offerTradePanel->getHighlightedItemId())->getIconIndex()} }; else return MarketShowcasesParams {std::nullopt, std::nullopt}; @@ -95,8 +95,8 @@ void CArtifactsBuying::highlightingChanged() { if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { - market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT); - deal->block(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())) < bidQty || !LOCPLINT->makingTurn); + market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT); + deal->block(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())) < bidQty || !LOCPLINT->makingTurn); } CMarketBase::highlightingChanged(); CMarketTraderText::highlightingChanged(); @@ -107,10 +107,10 @@ std::string CArtifactsBuying::getTraderText() if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { MetaString message = MetaString::createFromTextID("core.genrltxt.267"); - message.replaceName(ArtifactID(offerTradePanel->getSelectedItemId())); + message.replaceName(ArtifactID(offerTradePanel->getHighlightedItemId())); message.replaceNumber(bidQty); message.replaceTextID(bidQty == 1 ? "core.genrltxt.161" : "core.genrltxt.160"); - message.replaceName(GameResID(bidTradePanel->getSelectedItemId())); + message.replaceName(GameResID(bidTradePanel->getHighlightedItemId())); return message.toString(); } else diff --git a/client/widgets/markets/CArtifactsSelling.cpp b/client/widgets/markets/CArtifactsSelling.cpp index dbbe41278..b31de19e7 100644 --- a/client/widgets/markets/CArtifactsSelling.cpp +++ b/client/widgets/markets/CArtifactsSelling.cpp @@ -79,7 +79,7 @@ void CArtifactsSelling::makeDeal() const auto art = hero->getArt(selectedHeroSlot); assert(art); LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(), - GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero); + GameResID(offerTradePanel->getHighlightedItemId()), offerQty, hero); CMarketTraderText::makeDeal(); } @@ -129,7 +129,7 @@ CMarketBase::MarketShowcasesParams CArtifactsSelling::getShowcasesParams() const return MarketShowcasesParams { std::nullopt, - ShowcaseParams {std::to_string(offerQty), offerTradePanel->getSelectedItemId()} + ShowcaseParams {std::to_string(offerQty), offerTradePanel->getHighlightedItemId()} }; else return MarketShowcasesParams {std::nullopt, std::nullopt}; @@ -147,7 +147,7 @@ void CArtifactsSelling::highlightingChanged() const auto art = hero->getArt(selectedHeroSlot); if(art && offerTradePanel->isHighlighted()) { - market->getOffer(art->getTypeId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE); + market->getOffer(art->getTypeId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE); deal->block(!LOCPLINT->makingTurn); } CMarketBase::highlightingChanged(); @@ -162,7 +162,7 @@ std::string CArtifactsSelling::getTraderText() MetaString message = MetaString::createFromTextID("core.genrltxt.268"); message.replaceNumber(offerQty); message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]); - message.replaceName(GameResID(offerTradePanel->getSelectedItemId())); + message.replaceName(GameResID(offerTradePanel->getHighlightedItemId())); message.replaceName(art->getTypeId()); return message.toString(); } diff --git a/client/widgets/markets/CFreelancerGuild.cpp b/client/widgets/markets/CFreelancerGuild.cpp index 19c138d1b..cf2081ea2 100644 --- a/client/widgets/markets/CFreelancerGuild.cpp +++ b/client/widgets/markets/CFreelancerGuild.cpp @@ -29,7 +29,7 @@ CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance : CMarketBase(market, hero) , CResourcesBuying( [this](const std::shared_ptr & heroSlot){CFreelancerGuild::onSlotClickPressed(heroSlot, offerTradePanel);}, - [this](){CMarketBase::updateSubtitlesForBid(EMarketMode::CREATURE_RESOURCE, bidTradePanel->getSelectedItemId());}) + [this](){CMarketBase::updateSubtitlesForBid(EMarketMode::CREATURE_RESOURCE, bidTradePanel->getHighlightedItemId());}) , CMarketSlider([this](int newVal){CMarketSlider::onOfferSliderMoved(newVal);}) { OBJECT_CONSTRUCTION; @@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal() { if(auto toTrade = offerSlider->getValue(); toTrade != 0) { - LOCPLINT->cb->trade(market->getObjInstanceID(), 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->getHighlightedItemId()), bidQty * toTrade, hero); CMarketTraderText::makeDeal(); deselect(); } @@ -80,8 +80,8 @@ CMarketBase::MarketShowcasesParams CFreelancerGuild::getShowcasesParams() const if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) return MarketShowcasesParams { - ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getSelectedItemId())->getIconIndex()}, - ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getSelectedItemId()} + ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getHighlightedItemId())->getIconIndex()}, + ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getHighlightedItemId()} }; else return MarketShowcasesParams {std::nullopt, std::nullopt}; @@ -91,7 +91,7 @@ void CFreelancerGuild::highlightingChanged() { if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { - market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::CREATURE_RESOURCE); + market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::CREATURE_RESOURCE); offerSlider->setAmount((hero->getStackCount(SlotID(bidTradePanel->highlightedSlot->serial)) - (hero->stacksCount() == 1 && hero->needsLastStack() ? 1 : 0)) / bidQty); offerSlider->scrollTo(0); offerSlider->block(false); @@ -109,12 +109,12 @@ std::string CFreelancerGuild::getTraderText() MetaString message = MetaString::createFromTextID("core.genrltxt.269"); message.replaceNumber(offerQty); message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]); - message.replaceName(GameResID(offerTradePanel->getSelectedItemId())); + message.replaceName(GameResID(offerTradePanel->getHighlightedItemId())); message.replaceNumber(bidQty); if(bidQty == 1) - message.replaceNameSingular(bidTradePanel->getSelectedItemId()); + message.replaceNameSingular(bidTradePanel->getHighlightedItemId()); else - message.replaceNamePlural(bidTradePanel->getSelectedItemId()); + message.replaceNamePlural(bidTradePanel->getHighlightedItemId()); return message.toString(); } else diff --git a/client/widgets/markets/CMarketBase.cpp b/client/widgets/markets/CMarketBase.cpp index 8c6d78ec8..d7b75f1a6 100644 --- a/client/widgets/markets/CMarketBase.cpp +++ b/client/widgets/markets/CMarketBase.cpp @@ -93,7 +93,7 @@ void CMarketBase::updateSubtitlesForBid(EMarketMode marketMode, int bidId) void CMarketBase::updateShowcases() { - const auto updateSelectedBody = [](const std::shared_ptr & tradePanel, const std::optional & params) + const auto updateShowcase = [](const std::shared_ptr & tradePanel, const std::optional & params) { if(params.has_value()) { @@ -109,9 +109,9 @@ void CMarketBase::updateShowcases() const auto params = getShowcasesParams(); if(bidTradePanel) - updateSelectedBody(bidTradePanel, params.bidParams); + updateShowcase(bidTradePanel, params.bidParams); if(offerTradePanel) - updateSelectedBody(offerTradePanel, params.offerParams); + updateShowcase(offerTradePanel, params.offerParams); } void CMarketBase::highlightingChanged() diff --git a/client/widgets/markets/CMarketResources.cpp b/client/widgets/markets/CMarketResources.cpp index 4233d2f2a..0ddca24ab 100644 --- a/client/widgets/markets/CMarketResources.cpp +++ b/client/widgets/markets/CMarketResources.cpp @@ -60,7 +60,7 @@ void CMarketResources::makeDeal() { if(auto toTrade = offerSlider->getValue(); toTrade != 0) { - LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()), + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getHighlightedItemId()), GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero); CMarketTraderText::makeDeal(); deselect(); @@ -69,11 +69,11 @@ void CMarketResources::makeDeal() CMarketBase::MarketShowcasesParams CMarketResources::getShowcasesParams() const { - if(bidTradePanel->highlightedSlot && offerTradePanel->highlightedSlot && bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId()) + if(bidTradePanel->highlightedSlot && offerTradePanel->highlightedSlot && bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId()) return MarketShowcasesParams { - ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), bidTradePanel->getSelectedItemId()}, - ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getSelectedItemId()} + ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), bidTradePanel->getHighlightedItemId()}, + ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getHighlightedItemId()} }; else return MarketShowcasesParams {std::nullopt, std::nullopt}; @@ -83,10 +83,10 @@ void CMarketResources::highlightingChanged() { if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { - market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_RESOURCE); - offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())) / bidQty); + market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_RESOURCE); + offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())) / bidQty); offerSlider->scrollTo(0); - const bool isControlsBlocked = bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId() ? false : true; + const bool isControlsBlocked = bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId() ? false : true; offerSlider->block(isControlsBlocked); maxAmount->block(isControlsBlocked); deal->block(isControlsBlocked || !LOCPLINT->makingTurn); @@ -97,7 +97,7 @@ void CMarketResources::highlightingChanged() void CMarketResources::updateSubtitles() { - CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_RESOURCE, bidTradePanel->getSelectedItemId()); + CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_RESOURCE, bidTradePanel->getHighlightedItemId()); if(bidTradePanel->highlightedSlot) offerTradePanel->slots[bidTradePanel->highlightedSlot->serial]->subtitle->setText(CGI->generaltexth->allTexts[164]); // n/a } @@ -105,15 +105,15 @@ void CMarketResources::updateSubtitles() std::string CMarketResources::getTraderText() { if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted() && - bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId()) + bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId()) { MetaString message = MetaString::createFromTextID("core.genrltxt.157"); message.replaceNumber(offerQty); message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]); - message.replaceName(GameResID(bidTradePanel->getSelectedItemId())); + message.replaceName(GameResID(bidTradePanel->getHighlightedItemId())); message.replaceNumber(bidQty); message.replaceRawString(bidQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]); - message.replaceName(GameResID(offerTradePanel->getSelectedItemId())); + message.replaceName(GameResID(offerTradePanel->getHighlightedItemId())); return message.toString(); } else diff --git a/client/widgets/markets/CTransferResources.cpp b/client/widgets/markets/CTransferResources.cpp index f2871c869..3e58c816c 100644 --- a/client/widgets/markets/CTransferResources.cpp +++ b/client/widgets/markets/CTransferResources.cpp @@ -64,8 +64,8 @@ void CTransferResources::makeDeal() { if(auto toTrade = offerSlider->getValue(); toTrade != 0) { - LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()), - PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero); + LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getHighlightedItemId()), + PlayerColor(offerTradePanel->getHighlightedItemId()), toTrade, hero); CMarketTraderText::makeDeal(); deselect(); } @@ -76,8 +76,8 @@ CMarketBase::MarketShowcasesParams CTransferResources::getShowcasesParams() cons if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) return MarketShowcasesParams { - ShowcaseParams {std::to_string(offerSlider->getValue()), bidTradePanel->getSelectedItemId()}, - ShowcaseParams {CGI->generaltexth->capColors[offerTradePanel->getSelectedItemId()], offerTradePanel->getSelectedItemId()} + ShowcaseParams {std::to_string(offerSlider->getValue()), bidTradePanel->getHighlightedItemId()}, + ShowcaseParams {CGI->generaltexth->capColors[offerTradePanel->getHighlightedItemId()], offerTradePanel->getHighlightedItemId()} }; else return MarketShowcasesParams {std::nullopt, std::nullopt}; @@ -87,7 +87,7 @@ void CTransferResources::highlightingChanged() { if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { - offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId()))); + offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId()))); offerSlider->scrollTo(0); offerSlider->block(false); maxAmount->block(false); @@ -102,8 +102,8 @@ std::string CTransferResources::getTraderText() if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted()) { MetaString message = MetaString::createFromTextID("core.genrltxt.165"); - message.replaceName(GameResID(bidTradePanel->getSelectedItemId())); - message.replaceName(PlayerColor(offerTradePanel->getSelectedItemId())); + message.replaceName(GameResID(bidTradePanel->getHighlightedItemId())); + message.replaceName(PlayerColor(offerTradePanel->getHighlightedItemId())); return message.toString(); } else diff --git a/client/widgets/markets/TradePanels.cpp b/client/widgets/markets/TradePanels.cpp index cfea07353..119f6995a 100644 --- a/client/widgets/markets/TradePanels.cpp +++ b/client/widgets/markets/TradePanels.cpp @@ -23,11 +23,11 @@ #include "../../../lib/texts/CGeneralTextHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h" -CTradeableItem::CTradeableItem(const Rect & area, EType Type, int ID, int Serial) +CTradeableItem::CTradeableItem(const Rect & area, EType Type, int32_t ID, int32_t serial) : SelectableSlot(area, Point(1, 1)) , type(EType(-1)) // set to invalid, will be corrected in setType , id(ID) - , serial(Serial) + , serial(serial) { OBJECT_CONSTRUCTION; @@ -65,17 +65,14 @@ void CTradeableItem::setType(EType newType) subtitle->moveTo(pos.topLeft() + Point(35, 55)); image->moveTo(pos.topLeft() + Point(19, 8)); break; - case EType::CREATURE_PLACEHOLDER: case EType::CREATURE: subtitle->moveTo(pos.topLeft() + Point(30, 77)); break; case EType::PLAYER: subtitle->moveTo(pos.topLeft() + Point(31, 76)); break; - case EType::ARTIFACT_PLACEHOLDER: - case EType::ARTIFACT_INSTANCE: - image->moveTo(pos.topLeft() + Point(0, 1)); - subtitle->moveTo(pos.topLeft() + Point(21, 56)); + case EType::ARTIFACT: + subtitle->moveTo(pos.topLeft() + Point(21, 55)); break; case EType::ARTIFACT_TYPE: subtitle->moveTo(pos.topLeft() + Point(35, 57)); @@ -85,14 +82,14 @@ void CTradeableItem::setType(EType newType) } } -void CTradeableItem::setID(int newID) +void CTradeableItem::setID(int32_t newID) { if(id != newID) { id = newID; if(image) { - int index = getIndex(); + const auto index = getIndex(); if(index < 0) image->disable(); else @@ -121,8 +118,7 @@ AnimationPath CTradeableItem::getFilename() case EType::PLAYER: return AnimationPath::builtin("CREST58"); case EType::ARTIFACT_TYPE: - case EType::ARTIFACT_PLACEHOLDER: - case EType::ARTIFACT_INSTANCE: + case EType::ARTIFACT: return AnimationPath::builtin("artifact"); case EType::CREATURE: return AnimationPath::builtin("TWCRPORT"); @@ -142,8 +138,7 @@ int CTradeableItem::getIndex() case EType::PLAYER: return id; case EType::ARTIFACT_TYPE: - case EType::ARTIFACT_INSTANCE: - case EType::ARTIFACT_PLACEHOLDER: + case EType::ARTIFACT: return CGI->artifacts()->getByIndex(id)->getIconIndex(); case EType::CREATURE: return CGI->creatures()->getByIndex(id)->getIconIndex(); @@ -169,11 +164,10 @@ void CTradeableItem::hover(bool on) switch(type) { case EType::CREATURE: - case EType::CREATURE_PLACEHOLDER: GH.statusbar()->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->getNamePluralTranslated())); break; case EType::ARTIFACT_TYPE: - case EType::ARTIFACT_PLACEHOLDER: + case EType::ARTIFACT: if(id < 0) GH.statusbar()->write(CGI->generaltexth->zelp[582].first); else @@ -193,11 +187,9 @@ void CTradeableItem::showPopupWindow(const Point & cursorPosition) switch(type) { case EType::CREATURE: - case EType::CREATURE_PLACEHOLDER: break; case EType::ARTIFACT_TYPE: - case EType::ARTIFACT_PLACEHOLDER: - //TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription. + case EType::ARTIFACT: if (id >= 0) CRClickPopup::createAndPush(CGI->artifacts()->getByIndex(id)->getDescriptionTranslated()); break; @@ -241,7 +233,7 @@ void TradePanelBase::setShowcaseSubtitle(const std::string & text) showcaseSlot->subtitle->setText(text); } -int TradePanelBase::getSelectedItemId() const +int32_t TradePanelBase::getHighlightedItemId() const { if(highlightedSlot) return highlightedSlot->id; @@ -263,7 +255,7 @@ void TradePanelBase::onSlotClickPressed(const std::shared_ptr & bool TradePanelBase::isHighlighted() const { - return getSelectedItemId() != -1; + return highlightedSlot != nullptr; } ResourcesPanel::ResourcesPanel(const CTradeableItem::ClickPressedFunctor & clickPressedCallback, @@ -339,7 +331,7 @@ CreaturesPanel::CreaturesPanel(const CTradeableItem::ClickPressedFunctor & click for(const auto & [creatureId, slotId, creaturesNum] : initialSlots) { auto slot = slots.emplace_back(std::make_shared(Rect(slotsPos[slotId.num], slotDimension), - creaturesNum == 0 ? EType::CREATURE_PLACEHOLDER : EType::CREATURE, creatureId.num, slotId)); + EType::CREATURE, creaturesNum == 0 ? -1 : creatureId.num, slotId)); slot->clickPressedCallback = clickPressedCallback; if(creaturesNum != 0) slot->subtitle->setText(std::to_string(creaturesNum)); @@ -357,7 +349,7 @@ CreaturesPanel::CreaturesPanel(const CTradeableItem::ClickPressedFunctor & click for(const auto & srcSlot : srcSlots) { auto slot = slots.emplace_back(std::make_shared(Rect(slotsPos[srcSlot->serial], srcSlot->pos.dimensions()), - emptySlots ? EType::CREATURE_PLACEHOLDER : EType::CREATURE, srcSlot->id, srcSlot->serial)); + EType::CREATURE, emptySlots ? -1 : srcSlot->id, srcSlot->serial)); slot->clickPressedCallback = clickPressedCallback; slot->subtitle->setText(emptySlots ? "" : srcSlot->subtitle->getText()); slot->setSelectionWidth(selectionWidth); @@ -372,7 +364,7 @@ ArtifactsAltarPanel::ArtifactsAltarPanel(const CTradeableItem::ClickPressedFunct int slotNum = 0; for(auto & altarSlotPos : slotsPos) { - auto slot = slots.emplace_back(std::make_shared(Rect(altarSlotPos, Point(44, 44)), EType::ARTIFACT_PLACEHOLDER, -1, slotNum)); + auto slot = slots.emplace_back(std::make_shared(Rect(altarSlotPos, Point(44, 44)), EType::ARTIFACT, -1, slotNum)); slot->clickPressedCallback = clickPressedCallback; slot->subtitle->clear(); slot->subtitle->moveBy(Point(0, -1)); diff --git a/client/widgets/markets/TradePanels.h b/client/widgets/markets/TradePanels.h index a1927380a..3e2610106 100644 --- a/client/widgets/markets/TradePanels.h +++ b/client/widgets/markets/TradePanels.h @@ -16,7 +16,7 @@ enum class EType { - RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE + RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, ARTIFACT }; class CTradeableItem : public SelectableSlot, public std::enable_shared_from_this @@ -28,19 +28,19 @@ public: using ClickPressedFunctor = std::function&)>; EType type; - int id; - const int serial; + int32_t id; + const int32_t serial; std::shared_ptr subtitle; ClickPressedFunctor clickPressedCallback; void setType(EType newType); - void setID(int newID); + void setID(int32_t newID); void clear(); void showPopupWindow(const Point & cursorPosition) override; void hover(bool on) override; void clickPressed(const Point & cursorPosition) override; - CTradeableItem(const Rect & area, EType Type, int ID, int Serial); + CTradeableItem(const Rect & area, EType Type, int32_t ID, int32_t serial); }; class TradePanelBase : public CIntObject @@ -61,7 +61,7 @@ public: virtual void clearSubtitles(); void updateOffer(CTradeableItem & slot, int, int); void setShowcaseSubtitle(const std::string & text); - int getSelectedItemId() const; + int32_t getHighlightedItemId() const; void onSlotClickPressed(const std::shared_ptr & newSlot); bool isHighlighted() const; }; From 03b4733c64d452a6a90e60c2e7b64095a57574cd Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:27:21 +0300 Subject: [PATCH 413/726] CSecSkillPlace --- client/CMakeLists.txt | 4 +- client/widgets/CArtifactsOfHeroBase.cpp | 47 ++++---- client/widgets/CArtifactsOfHeroBase.h | 8 +- client/widgets/CArtifactsOfHeroMarket.cpp | 11 +- client/widgets/CArtifactsOfHeroMarket.h | 2 +- .../{CArtPlace.cpp => CComponentHolder.cpp} | 100 +++++++++++------- .../{CArtPlace.h => CComponentHolder.h} | 44 +++++--- client/windows/CCreatureWindow.cpp | 4 +- 8 files changed, 131 insertions(+), 89 deletions(-) rename client/widgets/{CArtPlace.cpp => CComponentHolder.cpp} (83%) rename client/widgets/{CArtPlace.h => CComponentHolder.h} (75%) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 991aba090..5de831a28 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -116,8 +116,8 @@ set(vcmiclientcommon_SRCS globalLobby/GlobalLobbyWindow.cpp widgets/Buttons.cpp - widgets/CArtPlace.cpp widgets/CComponent.cpp + widgets/CComponentHolder.cpp widgets/CExchangeController.cpp widgets/CGarrisonInt.cpp widgets/CreatureCostBox.cpp @@ -327,8 +327,8 @@ set(vcmiclientcommon_HEADERS globalLobby/GlobalLobbyWindow.h widgets/Buttons.h - widgets/CArtPlace.h widgets/CComponent.h + widgets/CComponentHolder.h widgets/CExchangeController.h widgets/CGarrisonInt.h widgets/CreatureCostBox.h diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 9ccd9248e..5941e90dd 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -89,31 +89,40 @@ void CArtifactsOfHeroBase::init( setRedrawParent(true); } -void CArtifactsOfHeroBase::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition) +void CArtifactsOfHeroBase::clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - if(artPlace.isLocked()) + auto ownedPlace = getArtPlace(cursorPosition); + assert(ownedPlace != nullptr); + + if(ownedPlace->isLocked()) return; if(clickPressedCallback) - clickPressedCallback(artPlace, cursorPosition); + clickPressedCallback(*ownedPlace, cursorPosition); } -void CArtifactsOfHeroBase::showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition) +void CArtifactsOfHeroBase::showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - if(artPlace.isLocked()) + auto ownedPlace = getArtPlace(cursorPosition); + assert(ownedPlace != nullptr); + + if(ownedPlace->isLocked()) return; if(showPopupCallback) - showPopupCallback(artPlace, cursorPosition); + showPopupCallback(*ownedPlace, cursorPosition); } -void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition) +void CArtifactsOfHeroBase::gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - if(artPlace.isLocked()) + auto ownedPlace = getArtPlace(cursorPosition); + assert(ownedPlace != nullptr); + + if(ownedPlace->isLocked()) return; if(gestureCallback) - gestureCallback(artPlace, cursorPosition); + gestureCallback(*ownedPlace, cursorPosition); } void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero) @@ -156,24 +165,16 @@ CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const Artifa { if(ArtifactUtils::isSlotEquipment(slot)) { - if(artWorn.find(slot) == artWorn.end()) - { - logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot); - return nullptr; - } - return artWorn[slot]; + if(artWorn.find(slot) != artWorn.end()) + return artWorn[slot]; } if(ArtifactUtils::isSlotBackpack(slot)) { - for(ArtPlacePtr artPlace : backpack) - if(artPlace->slot == slot) - return artPlace; - return nullptr; - } - else - { - return nullptr; + if(slot - ArtifactPosition::BACKPACK_START < backpack.size()) + return(backpack[slot - ArtifactPosition::BACKPACK_START]); } + logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot); + return nullptr; } CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const Point & cursorPosition) diff --git a/client/widgets/CArtifactsOfHeroBase.h b/client/widgets/CArtifactsOfHeroBase.h index 7aaaf5ce3..df4b02a0d 100644 --- a/client/widgets/CArtifactsOfHeroBase.h +++ b/client/widgets/CArtifactsOfHeroBase.h @@ -9,7 +9,7 @@ */ #pragma once -#include "CArtPlace.h" +#include "CComponentHolder.h" #include "Scrollable.h" #include "../gui/Shortcut.h" @@ -33,9 +33,9 @@ public: CArtifactsOfHeroBase(); virtual void putBackPickedArtifact(); - virtual void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition); - virtual void showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition); - virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition); + virtual void clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); + virtual void showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); + virtual void gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); virtual void setHero(const CGHeroInstance * hero); virtual const CGHeroInstance * getHero() const; virtual void scrollBackpack(bool left); diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index 7158d0371..0b7dacc3c 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -22,18 +22,21 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int artPlace->setSelectionWidth(selectionWidth); }; -void CArtifactsOfHeroMarket::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition) +void CArtifactsOfHeroMarket::clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - if(artPlace.isLocked()) + auto ownedPlace = getArtPlace(cursorPosition); + assert(ownedPlace != nullptr); + + if(ownedPlace->isLocked()) return; - if(const auto art = getArt(artPlace.slot)) + if(const auto art = getArt(ownedPlace->slot)) { if(onSelectArtCallback && art->artType->isTradable()) { unmarkSlots(); artPlace.selectSlot(true); - onSelectArtCallback(&artPlace); + onSelectArtCallback(ownedPlace.get()); } else { diff --git a/client/widgets/CArtifactsOfHeroMarket.h b/client/widgets/CArtifactsOfHeroMarket.h index 87334c0d7..5fd88b9ca 100644 --- a/client/widgets/CArtifactsOfHeroMarket.h +++ b/client/widgets/CArtifactsOfHeroMarket.h @@ -18,5 +18,5 @@ public: std::function onClickNotTradableCallback; CArtifactsOfHeroMarket(const Point & position, const int selectionWidth); - void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition) override; + void clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) override; }; diff --git a/client/widgets/CArtPlace.cpp b/client/widgets/CComponentHolder.cpp similarity index 83% rename from client/widgets/CArtPlace.cpp rename to client/widgets/CComponentHolder.cpp index 0444ef77d..81a9cdfd3 100644 --- a/client/widgets/CArtPlace.cpp +++ b/client/widgets/CComponentHolder.cpp @@ -1,5 +1,5 @@ /* - * CArtPlace.cpp, part of VCMI engine + * CComponentHolder.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -8,14 +8,14 @@ * */ #include "StdInc.h" -#include "CArtPlace.h" +#include "CComponentHolder.h" #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "CComponent.h" +#include "Images.h" -#include "../windows/GUIClasses.h" #include "../render/Canvas.h" #include "../render/Colors.h" #include "../render/IRenderHandler.h" @@ -28,9 +28,51 @@ #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/networkPacks/ArtifactLocation.h" #include "../../lib/CConfigHandler.h" +#include "../../lib/CSkillHandler.h" + +CComponentHolder::CComponentHolder(const Rect & area, const Point & selectionOversize) + : SelectableSlot(area, selectionOversize) +{ +} + +void CComponentHolder::setClickPressedCallback(const ClickFunctor & callback) +{ + clickPressedCallback = callback; +} + +void CComponentHolder::setShowPopupCallback(const ClickFunctor & callback) +{ + showPopupCallback = callback; +} + +void CComponentHolder::setGestureCallback(const ClickFunctor & callback) +{ + gestureCallback = callback; +} + +void CComponentHolder::clickPressed(const Point & cursorPosition) +{ + if(clickPressedCallback) + clickPressedCallback(*this, cursorPosition); +} + +void CComponentHolder::showPopupWindow(const Point & cursorPosition) +{ + if(showPopupCallback) + showPopupCallback(*this, cursorPosition); +} + +void CComponentHolder::gesture(bool on, const Point & initialPosition, const Point & finalPosition) +{ + if(!on) + return; + + if(gestureCallback) + gestureCallback(*this, initialPosition); +} CArtPlace::CArtPlace(Point position, const ArtifactID & artId, const SpellID & spellId) - : SelectableSlot(Rect(position, Point(44, 44)), Point(1, 1)) + : CComponentHolder(Rect(position, Point(44, 44)), Point(1, 1)) , locked(false) , imageIndex(0) { @@ -171,42 +213,6 @@ bool CArtPlace::isLocked() const return locked; } -void CArtPlace::clickPressed(const Point & cursorPosition) -{ - if(clickPressedCallback) - clickPressedCallback(*this, cursorPosition); -} - -void CArtPlace::showPopupWindow(const Point & cursorPosition) -{ - if(showPopupCallback) - showPopupCallback(*this, cursorPosition); -} - -void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & finalPosition) -{ - if(!on) - return; - - if(gestureCallback) - gestureCallback(*this, initialPosition); -} - -void CArtPlace::setClickPressedCallback(const ClickFunctor & callback) -{ - clickPressedCallback = callback; -} - -void CArtPlace::setShowPopupCallback(const ClickFunctor & callback) -{ - showPopupCallback = callback; -} - -void CArtPlace::setGestureCallback(const ClickFunctor & callback) -{ - gestureCallback = callback; -} - void CArtPlace::addCombinedArtInfo(const std::map> & arts) { for(auto [combinedId, availableArts] : arts) @@ -244,3 +250,17 @@ void CArtPlace::addCombinedArtInfo(const std::map(AnimationPath::builtin("SECSKILL"), 0); + setSkill(skillId); +} + +void CSecSkillPlace::setSkill(const SecondarySkill & skillId) +{ + //skillId.toSkill()->getIconIndex(); +} diff --git a/client/widgets/CArtPlace.h b/client/widgets/CComponentHolder.h similarity index 75% rename from client/widgets/CArtPlace.h rename to client/widgets/CComponentHolder.h index 645a57e5a..dd49e4bae 100644 --- a/client/widgets/CArtPlace.h +++ b/client/widgets/CComponentHolder.h @@ -1,5 +1,5 @@ /* - * CArtPlace.h, part of VCMI engine + * CComponentHolder.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -13,11 +13,29 @@ class CAnimImage; -class CArtPlace : public SelectableSlot +class CComponentHolder : public SelectableSlot { public: - using ClickFunctor = std::function; + using ClickFunctor = std::function; + ClickFunctor clickPressedCallback; + ClickFunctor showPopupCallback; + ClickFunctor gestureCallback; + std::shared_ptr image; + + CComponentHolder(const Rect & area, const Point & selectionOversize); + void setClickPressedCallback(const ClickFunctor & callback); + void setShowPopupCallback(const ClickFunctor & callback); + void setGestureCallback(const ClickFunctor & callback); + void clickPressed(const Point & cursorPosition) override; + void showPopupWindow(const Point & cursorPosition) override; + void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; + virtual ~CComponentHolder() = default; +}; + +class CArtPlace : public CComponentHolder +{ +public: ArtifactPosition slot; CArtPlace(Point position, const ArtifactID & artId = ArtifactID::NONE, const SpellID & spellId = SpellID::NONE); @@ -26,12 +44,6 @@ public: ArtifactID getArtifactId() const; void lockSlot(bool on); bool isLocked() const; - void setClickPressedCallback(const ClickFunctor & callback); - void setShowPopupCallback(const ClickFunctor & callback); - void setGestureCallback(const ClickFunctor & callback); - void clickPressed(const Point & cursorPosition) override; - void showPopupWindow(const Point & cursorPosition) override; - void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; void addCombinedArtInfo(const std::map> & arts); private: @@ -39,10 +51,6 @@ private: SpellID spellId; bool locked; int32_t imageIndex; - std::shared_ptr image; - ClickFunctor clickPressedCallback; - ClickFunctor showPopupCallback; - ClickFunctor gestureCallback; }; class CCommanderArtPlace : public CArtPlace @@ -59,3 +67,13 @@ public: void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; }; + +class CSecSkillPlace : public CComponentHolder +{ +public: + CSecSkillPlace(const Point & position, const SecondarySkill & skillId = SecondarySkill::NONE); + void setSkill(const SecondarySkill & skillId); + +private: + SecondarySkill skillId; +}; diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 28f0b7b92..79726ea26 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -17,8 +17,8 @@ #include "../CPlayerInterface.h" #include "../render/Canvas.h" #include "../widgets/Buttons.h" -#include "../widgets/CArtPlace.h" #include "../widgets/CComponent.h" +#include "../widgets/CComponentHolder.h" #include "../widgets/Images.h" #include "../widgets/TextControls.h" #include "../widgets/ObjectLists.h" @@ -619,7 +619,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s if(art) { parent->stackArtifact = std::make_shared(pos, art->getTypeId()); - parent->stackArtifact->setShowPopupCallback([](CArtPlace & artPlace, const Point & cursorPosition) + parent->stackArtifact->setShowPopupCallback([](CComponentHolder & artPlace, const Point & cursorPosition) { artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition); }); From 83279211e6903559e36a2384ff1fd6fc7854bbcd Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:49:22 +0300 Subject: [PATCH 414/726] CSecSkillPlace done --- client/widgets/CArtifactsOfHeroAltar.cpp | 2 + client/widgets/CArtifactsOfHeroBackpack.cpp | 6 +- client/widgets/CArtifactsOfHeroBase.cpp | 38 +++++++----- client/widgets/CArtifactsOfHeroBase.h | 2 + client/widgets/CArtifactsOfHeroMain.cpp | 2 + client/widgets/CArtifactsOfHeroMarket.cpp | 2 +- client/widgets/CComponentHolder.cpp | 69 +++++++++++++++++---- client/widgets/CComponentHolder.h | 19 ++++-- client/widgets/markets/CAltarCreatures.cpp | 2 +- client/windows/CExchangeWindow.cpp | 20 +----- client/windows/CExchangeWindow.h | 3 +- client/windows/CHeroBackpackWindow.cpp | 4 -- client/windows/CHeroWindow.cpp | 11 +--- client/windows/CHeroWindow.h | 3 +- client/windows/CMarketWindow.cpp | 1 - client/windows/CWindowWithArtifacts.cpp | 6 -- client/windows/CWindowWithArtifacts.h | 1 - client/windows/GUIClasses.cpp | 34 +++------- client/windows/GUIClasses.h | 6 +- lib/CSkillHandler.cpp | 2 +- 20 files changed, 124 insertions(+), 109 deletions(-) diff --git a/client/widgets/CArtifactsOfHeroAltar.cpp b/client/widgets/CArtifactsOfHeroAltar.cpp index bcef56cd1..436cbc1c4 100644 --- a/client/widgets/CArtifactsOfHeroAltar.cpp +++ b/client/widgets/CArtifactsOfHeroAltar.cpp @@ -22,6 +22,8 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position) { init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); + setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); enableGesture(); // The backpack is in the altar window above and to the right for(auto & slot : backpack) diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index ea05279c4..04f6734a0 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -40,6 +40,8 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack() visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax; initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap); + setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } void CArtifactsOfHeroBackpack::onSliderMoved(int newVal) @@ -84,8 +86,6 @@ void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider) backpackSlotsBackgrounds.emplace_back(std::make_shared(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos)); artPlace = std::make_shared(pos); artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); - artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); - artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); artPlaceIdx++; } @@ -130,6 +130,7 @@ CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosit return; this->filterBySlot = filterBySlot; + setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero) @@ -172,6 +173,7 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero) slotsColumnsMax = ceilf(sqrtf(requiredSlots)); slotsRowsMax = calcRows(requiredSlots); initAOHbackpack(requiredSlots, false); + setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); auto artPlace = backpack.begin(); for(auto & art : filteredArts) setSlotData(*artPlace++, curHero->getArtPos(art.second)); diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 5941e90dd..686cbafd5 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -63,18 +63,14 @@ void CArtifactsOfHeroBase::init( auto artPlace = std::make_shared(Point(403 + 46 * s, 365)); backpack.push_back(artPlace); } - for(auto artPlace : artWorn) + for(auto & artPlace : artWorn) { artPlace.second->slot = artPlace.first; artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE)); - artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); - artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } - for(auto artPlace : backpack) + for(const auto & artPlace : backpack) { artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); - artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); - artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } leftBackpackRoll = std::make_shared(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollCallback](){scrollCallback(true);}, EShortcut::MOVE_LEFT); @@ -89,6 +85,22 @@ void CArtifactsOfHeroBase::init( setRedrawParent(true); } +void CArtifactsOfHeroBase::setClickPrassedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const +{ + for(const auto & [slot, artPlace] : artWorn) + artPlace->setClickPressedCallback(callback); + for(const auto & artPlace : backpack) + artPlace->setClickPressedCallback(callback); +} + +void CArtifactsOfHeroBase::setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const +{ + for(const auto & [slot, artPlace] : artWorn) + artPlace->setShowPopupCallback(callback); + for(const auto & artPlace : backpack) + artPlace->setShowPopupCallback(callback); +} + void CArtifactsOfHeroBase::clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { auto ownedPlace = getArtPlace(cursorPosition); @@ -163,16 +175,10 @@ void CArtifactsOfHeroBase::unmarkSlots() CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const ArtifactPosition & slot) { - if(ArtifactUtils::isSlotEquipment(slot)) - { - if(artWorn.find(slot) != artWorn.end()) - return artWorn[slot]; - } - if(ArtifactUtils::isSlotBackpack(slot)) - { - if(slot - ArtifactPosition::BACKPACK_START < backpack.size()) - return(backpack[slot - ArtifactPosition::BACKPACK_START]); - } + if(ArtifactUtils::isSlotEquipment(slot) && artWorn.find(slot) != artWorn.end()) + return artWorn[slot]; + if(ArtifactUtils::isSlotBackpack(slot) && slot - ArtifactPosition::BACKPACK_START < backpack.size()) + return(backpack[slot - ArtifactPosition::BACKPACK_START]); logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot); return nullptr; } diff --git a/client/widgets/CArtifactsOfHeroBase.h b/client/widgets/CArtifactsOfHeroBase.h index df4b02a0d..a22ec78dd 100644 --- a/client/widgets/CArtifactsOfHeroBase.h +++ b/client/widgets/CArtifactsOfHeroBase.h @@ -50,6 +50,8 @@ public: void enableGesture(); const CArtifactInstance * getArt(const ArtifactPosition & slot) const; void enableKeyboardShortcuts(); + void setClickPrassedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const; + void setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const; const CGHeroInstance * curHero; ArtPlaceMap artWorn; diff --git a/client/widgets/CArtifactsOfHeroMain.cpp b/client/widgets/CArtifactsOfHeroMain.cpp index e10e0e37a..da5c20c31 100644 --- a/client/widgets/CArtifactsOfHeroMain.cpp +++ b/client/widgets/CArtifactsOfHeroMain.cpp @@ -21,6 +21,8 @@ CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position) { init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); + setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); enableGesture(); } diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index 0b7dacc3c..df66702a8 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -15,7 +15,7 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int selectionWidth) { init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); - + setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); for(const auto & [slot, artPlace] : artWorn) artPlace->setSelectionWidth(selectionWidth); for(auto artPlace : backpack) diff --git a/client/widgets/CComponentHolder.cpp b/client/widgets/CComponentHolder.cpp index 81a9cdfd3..8ce49fe06 100644 --- a/client/widgets/CComponentHolder.cpp +++ b/client/widgets/CComponentHolder.cpp @@ -33,6 +33,16 @@ CComponentHolder::CComponentHolder(const Rect & area, const Point & selectionOversize) : SelectableSlot(area, selectionOversize) { + setClickPressedCallback([this](const CComponentHolder &, const Point & cursorPosition) + { + if(text.size()) + LRClickableAreaWTextComp::clickPressed(cursorPosition); + }); + setShowPopupCallback([this](const CComponentHolder &, const Point & cursorPosition) + { + if(text.size()) + LRClickableAreaWTextComp::showPopupWindow(cursorPosition); + }); } void CComponentHolder::setClickPressedCallback(const ClickFunctor & callback) @@ -83,14 +93,14 @@ CArtPlace::CArtPlace(Point position, const ArtifactID & artId, const SpellID & s moveSelectionForeground(); } -void CArtPlace::setArtifact(const SpellID & spellId) +void CArtPlace::setArtifact(const SpellID & newSpellId) { - setArtifact(ArtifactID::SPELL_SCROLL, spellId); + setArtifact(ArtifactID::SPELL_SCROLL, newSpellId); } -void CArtPlace::setArtifact(const ArtifactID & artId, const SpellID & spellId) +void CArtPlace::setArtifact(const ArtifactID & newArtId, const SpellID & newSpellId) { - this->artId = artId; + artId = newArtId; if(artId == ArtifactID::NONE) { image->disable(); @@ -103,7 +113,7 @@ void CArtPlace::setArtifact(const ArtifactID & artId, const SpellID & spellId) imageIndex = artType->getIconIndex(); if(artId == ArtifactID::SPELL_SCROLL) { - this->spellId = spellId; + spellId = newSpellId; assert(spellId.num > 0); if(settings["general"]["enableUiEnhancements"].Bool()) @@ -251,16 +261,53 @@ void CArtPlace::addCombinedArtInfo(const std::map(AnimationPath::builtin("SECSKILL"), 0); - setSkill(skillId); + auto imagePath = AnimationPath::builtin("SECSKILL"); + if(imageSize == ImageSize::MEDIUM) + imagePath = AnimationPath::builtin("SECSK32"); + if(imageSize == ImageSize::SMALL) + imagePath = AnimationPath::builtin("SECSK82"); + + image = std::make_shared(imagePath, 0); + component.type = ComponentType::SEC_SKILL; + pos.w = image->pos.w; + pos.h = image->pos.h; + setSkill(newSkillId, level); } -void CSecSkillPlace::setSkill(const SecondarySkill & skillId) +void CSecSkillPlace::setSkill(const SecondarySkill & newSkillId, const uint8_t level) { - //skillId.toSkill()->getIconIndex(); + skillId = newSkillId; + component.subType = newSkillId; + setLevel(level); +} + +void CSecSkillPlace::setLevel(const uint8_t level) +{ + // 0 - none + // 1 - base + // 2 - advanced + // 3 - expert + assert(level <= 3); + if(skillId != SecondarySkill::NONE && level > 0) + { + image->setFrame(skillId.toSkill()->getIconIndex() + level - 1); + image->enable(); + auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[21]); + hoverText.replaceRawString(CGI->generaltexth->levels[level - 1]); + hoverText.replaceTextID(SecondarySkill(skillId).toSkill()->getNameTextID()); + this->hoverText = hoverText.toString(); + component.value = level; + text = CGI->skillh->getByIndex(skillId)->getDescriptionTranslated(level); + } + else + { + image->disable(); + hoverText.clear(); + text.clear(); + } } diff --git a/client/widgets/CComponentHolder.h b/client/widgets/CComponentHolder.h index dd49e4bae..9d364bd1d 100644 --- a/client/widgets/CComponentHolder.h +++ b/client/widgets/CComponentHolder.h @@ -30,7 +30,6 @@ public: void clickPressed(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; - virtual ~CComponentHolder() = default; }; class CArtPlace : public CComponentHolder @@ -38,9 +37,9 @@ class CArtPlace : public CComponentHolder public: ArtifactPosition slot; - CArtPlace(Point position, const ArtifactID & artId = ArtifactID::NONE, const SpellID & spellId = SpellID::NONE); - void setArtifact(const SpellID & spellId); - void setArtifact(const ArtifactID & artId, const SpellID & spellId = SpellID::NONE); + CArtPlace(Point position, const ArtifactID & newArtId = ArtifactID::NONE, const SpellID & newSpellId = SpellID::NONE); + void setArtifact(const SpellID & newSpellId); + void setArtifact(const ArtifactID & newArtId, const SpellID & newSpellId = SpellID::NONE); ArtifactID getArtifactId() const; void lockSlot(bool on); bool isLocked() const; @@ -71,8 +70,16 @@ public: class CSecSkillPlace : public CComponentHolder { public: - CSecSkillPlace(const Point & position, const SecondarySkill & skillId = SecondarySkill::NONE); - void setSkill(const SecondarySkill & skillId); + enum class ImageSize + { + LARGE, + MEDIUM, + SMALL + }; + + CSecSkillPlace(const Point & position, const ImageSize & imageSize, const SecondarySkill & skillId = SecondarySkill::NONE, const uint8_t level = 0); + void setSkill(const SecondarySkill & newSkillId, const uint8_t level = 0); + void setLevel(const uint8_t level); private: SecondarySkill skillId; diff --git a/client/widgets/markets/CAltarCreatures.cpp b/client/widgets/markets/CAltarCreatures.cpp index 2464ceedd..c61906f18 100644 --- a/client/widgets/markets/CAltarCreatures.cpp +++ b/client/widgets/markets/CAltarCreatures.cpp @@ -162,7 +162,7 @@ void CAltarCreatures::makeDeal() for(int & units : unitsOnAltar) units = 0; - for(auto & heroSlot : offerTradePanel->slots) + for(const auto & heroSlot : offerTradePanel->slots) { heroSlot->setID(CreatureID::NONE); heroSlot->subtitle->clear(); diff --git a/client/windows/CExchangeWindow.cpp b/client/windows/CExchangeWindow.cpp index 197ef9795..dde4bb635 100644 --- a/client/windows/CExchangeWindow.cpp +++ b/client/windows/CExchangeWindow.cpp @@ -82,7 +82,8 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, for(int m=0; m < hero->secSkills.size(); ++m) - secSkillIcons[leftRight].push_back(std::make_shared(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88)); + secSkills[leftRight].push_back(std::make_shared(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::MEDIUM, + hero->secSkills[m].first, hero->secSkills[m].second)); specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); @@ -126,21 +127,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, { const CGHeroInstance * hero = heroInst.at(b); - //secondary skill's clickable areas - for(int g=0; gsecSkills.size(); ++g) - { - SecondarySkill skill = hero->secSkills[g].first; - int level = hero->secSkills[g].second; // <1, 3> - secSkillAreas[b].push_back(std::make_shared()); - secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) ); - secSkillAreas[b][g]->component = Component(ComponentType::SEC_SKILL, skill, level); - secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level); - - secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21]; - boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]); - boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->getByIndex(skill)->getNameTranslated()); - } - heroAreas[b] = std::make_shared(257 + 228 * b, 13, hero); heroAreas[b]->addClickCallback([this, hero]() -> void { @@ -362,7 +348,7 @@ void CExchangeWindow::update() int id = hero->secSkills[m].first; int level = hero->secSkills[m].second; - secSkillIcons[leftRight][m]->setFrame(2 + id * 3 + level); + secSkills[leftRight][m]->setSkill(id, level); } expValues[leftRight]->setText(TextOperations::formatMetric(hero->exp, 3)); diff --git a/client/windows/CExchangeWindow.h b/client/windows/CExchangeWindow.h index 97f40fffe..d19b27279 100644 --- a/client/windows/CExchangeWindow.h +++ b/client/windows/CExchangeWindow.h @@ -19,7 +19,6 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public std::array, 2> titles; std::vector> primSkillImages;//shared for both heroes std::array>, 2> primSkillValues; - std::array>, 2> secSkillIcons; std::array, 2> specImages; std::array, 2> expImages; std::array, 2> expValues; @@ -27,7 +26,7 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public std::array, 2> manaValues; std::vector> primSkillAreas; - std::array>, 2> secSkillAreas; + std::array>, 2> secSkills; std::array, 2> heroAreas; std::array, 2> specialtyAreas; diff --git a/client/windows/CHeroBackpackWindow.cpp b/client/windows/CHeroBackpackWindow.cpp index c184b7ed8..ce8b90e46 100644 --- a/client/windows/CHeroBackpackWindow.cpp +++ b/client/windows/CHeroBackpackWindow.cpp @@ -93,10 +93,6 @@ CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero, if(const auto curHero = arts->getHero()) swapArtifactAndClose(*arts, artPlace.slot, ArtifactLocation(curHero->id, arts->getFilterSlot())); }; - arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition) - { - showArifactInfo(*arts, artPlace, cursorPosition); - }; addSet(arts); arts->setHero(hero); addUsedEvents(GESTURE); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 9599fb567..26a9e35f9 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -152,8 +152,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) for(int i = 0; i < std::min(hero->secSkills.size(), 8u); ++i) { Rect r = Rect(i%2 == 0 ? 18 : 162, 276 + 48 * (i/2), 136, 42); - secSkillAreas.push_back(std::make_shared(r, ComponentType::SEC_SKILL)); - secSkillImages.push_back(std::make_shared(AnimationPath::builtin("SECSKILL"), 0, 0, r.x, r.y)); + secSkills.emplace_back(std::make_shared(r.topLeft(), CSecSkillPlace::ImageSize::LARGE)); int x = (i % 2) ? 212 : 68; int y = 280 + 48 * (i/2); @@ -234,20 +233,16 @@ void CHeroWindow::update() } //secondary skills support - for(size_t g=0; g< secSkillAreas.size(); ++g) + for(size_t g=0; g< secSkills.size(); ++g) { SecondarySkill skill = curHero->secSkills[g].first; int level = curHero->getSecSkillLevel(skill); std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated(); std::string skillValue = CGI->generaltexth->levels[level-1]; - secSkillAreas[g]->component.subType = skill; - secSkillAreas[g]->component.value = level; - secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level); - secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName); - secSkillImages[g]->setFrame(skill*3 + level + 2); secSkillNames[g]->setText(skillName); secSkillValues[g]->setText(skillValue); + secSkills[g]->setSkill(skill, level); } std::ostringstream expstr; diff --git a/client/windows/CHeroWindow.h b/client/windows/CHeroWindow.h index 75a3cff5a..fd33df05d 100644 --- a/client/windows/CHeroWindow.h +++ b/client/windows/CHeroWindow.h @@ -74,8 +74,7 @@ class CHeroWindow : public CStatusbarWindow, public IGarrisonHolder, public CWin std::shared_ptr specName; std::shared_ptr morale; std::shared_ptr luck; - std::vector> secSkillAreas; - std::vector> secSkillImages; + std::vector< std::shared_ptr> secSkills; std::vector> secSkillNames; std::vector> secSkillValues; diff --git a/client/windows/CMarketWindow.cpp b/client/windows/CMarketWindow.cpp index 02b167575..059e1f9f0 100644 --- a/client/windows/CMarketWindow.cpp +++ b/client/windows/CMarketWindow.cpp @@ -219,7 +219,6 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI auto artsSellingMarket = std::make_shared(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]); diff --git a/client/windows/CWindowWithArtifacts.cpp b/client/windows/CWindowWithArtifacts.cpp index ca8c5a809..b8d20b8f4 100644 --- a/client/windows/CWindowWithArtifacts.cpp +++ b/client/windows/CWindowWithArtifacts.cpp @@ -128,12 +128,6 @@ void CWindowWithArtifacts::showArtifactAssembling(const CArtifactsOfHeroBase & a } } -void CWindowWithArtifacts::showArifactInfo(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const -{ - if(artsInst.getArt(artPlace.slot) && artPlace.text.size()) - artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition); -} - void CWindowWithArtifacts::showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot, const Point & cursorPosition) const { diff --git a/client/windows/CWindowWithArtifacts.h b/client/windows/CWindowWithArtifacts.h index c6af89aba..9b83ec991 100644 --- a/client/windows/CWindowWithArtifacts.h +++ b/client/windows/CWindowWithArtifacts.h @@ -31,7 +31,6 @@ public: bool allowExchange, bool altarTrading, bool closeWindow); void swapArtifactAndClose(const CArtifactsOfHeroBase & artsInst, const ArtifactPosition & slot, const ArtifactLocation & dstLoc); void showArtifactAssembling(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const; - void showArifactInfo(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const; void showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot, const Point & cursorPosition) const; void activate() override; void deactivate() override; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index ad630a77d..1aff6baa0 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -896,28 +896,18 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int pos.x += X; pos.y += Y; - icon = std::make_shared(AnimationPath::builtin("SECSKILL"), _ID * 3 + 3, 0); - - pos.h = icon->pos.h; - pos.w = icon->pos.w; + skill = std::make_shared(Point(), CSecSkillPlace::ImageSize::LARGE, _ID, 1); + skill->setClickPressedCallback([this](const CComponentHolder&, const Point& cursorPosition) + { + bool skillKnown = parent->hero->getSecSkillLevel(ID); + bool canLearn = parent->hero->canLearnSkill(ID); + if(!skillKnown && canLearn) + GH.windows().createAndPushWindow(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000); + }); update(); } -void CUniversityWindow::CItem::clickPressed(const Point & cursorPosition) -{ - bool skillKnown = parent->hero->getSecSkillLevel(ID); - bool canLearn = parent->hero->canLearnSkill(ID); - - if (!skillKnown && canLearn) - GH.windows().createAndPushWindow(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000); -} - -void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition) -{ - CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared(ComponentType::SEC_SKILL, ID, 1)); -} - void CUniversityWindow::CItem::update() { bool skillKnown = parent->hero->getSecSkillLevel(ID); @@ -941,14 +931,6 @@ void CUniversityWindow::CItem::update() level = std::make_shared(22, 57, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[0]); } -void CUniversityWindow::CItem::hover(bool on) -{ - if(on) - GH.statusbar()->write(ID.toEntity(VLC)->getNameTranslated()); - else - GH.statusbar()->clear(); -} - CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID building, const IMarket * _market, const std::function & onWindowClosed) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("UNIVERS1")), hero(_hero), diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index fb3740a02..45979b6b7 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -46,6 +46,7 @@ class VideoWidget; class VideoWidgetOnce; class GraphicalPrimitiveCanvas; class TransparentFilledRectangle; +class CSecSkillPlace; enum class EUserEvent; @@ -370,7 +371,7 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder { class CItem final : public CIntObject { - std::shared_ptr icon; + std::shared_ptr skill; std::shared_ptr topBar; std::shared_ptr bottomBar; std::shared_ptr name; @@ -379,9 +380,6 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder SecondarySkill ID;//id of selected skill CUniversityWindow * parent; - void clickPressed(const Point & cursorPosition) override; - void showPopupWindow(const Point & cursorPosition) override; - void hover(bool on) override; void update(); CItem(CUniversityWindow * _parent, int _ID, int X, int Y); }; diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 87cad05bc..2c43585e8 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -45,7 +45,7 @@ int32_t CSkill::getIndex() const int32_t CSkill::getIconIndex() const { - return getIndex(); //TODO: actual value with skill level + return getIndex() * 3 + 3; //TODO: actual value with skill level } std::string CSkill::getNameTextID() const From 53b7c5da6f4e435da7310c0cd08870619a36e875 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:30:51 +0200 Subject: [PATCH 415/726] added MECHANICAL bonus --- Mods/vcmi/config/vcmi/english.json | 4 +++- client/widgets/MiscWidgets.cpp | 3 ++- config/artifacts.json | 8 ++++++++ config/bonuses.json | 8 ++++++++ config/spells/ability.json | 5 +++++ config/spells/other.json | 2 ++ config/spells/timed.json | 9 ++++++++- config/spells/vcmiAbility.json | 1 + docs/modders/Bonus/Bonus_Types.md | 4 ++++ lib/BasicTypes.cpp | 3 ++- lib/bonuses/BonusEnum.h | 1 + 11 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 2cb7b5772..e5dbbe817 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -687,5 +687,7 @@ "core.bonus.DISINTEGRATE.name": "Disintegrate", "core.bonus.DISINTEGRATE.description": "No corpse remains after death", "core.bonus.INVINCIBLE.name": "Invincible", - "core.bonus.INVINCIBLE.description": "Cannot be affected by anything" + "core.bonus.INVINCIBLE.description": "Cannot be affected by anything", + "core.bonus.MECHANICAL.name": "Mechanical", + "core.bonus.MECHANICAL.description": "Immunity to many effects, repairable" } diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index 14cc7b14d..299c125a9 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -573,7 +573,8 @@ void MoraleLuckBox::set(const AFactionMember * node) boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]); if (morale && node && (node->getBonusBearer()->hasBonusOfType(BonusType::UNDEAD) - || node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING))) + || node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING) + || node->getBonusBearer()->hasBonusOfType(BonusType::MECHANICAL))) { text += CGI->generaltexth->arraytxt[113]; //unaffected by morale component.value = 0; diff --git a/config/artifacts.json b/config/artifacts.json index ae5501f30..8dc237ff6 100644 --- a/config/artifacts.json +++ b/config/artifacts.json @@ -1995,6 +1995,10 @@ "type" : "HAS_ANOTHER_BONUS_LIMITER", "parameters" : [ "NON_LIVING" ] }, + { + "type" : "HAS_ANOTHER_BONUS_LIMITER", + "parameters" : [ "MECHANICAL" ] + }, { "type" : "HAS_ANOTHER_BONUS_LIMITER", "parameters" : [ "GARGOYLE" ] @@ -2012,6 +2016,10 @@ "type" : "HAS_ANOTHER_BONUS_LIMITER", "parameters" : [ "NON_LIVING" ] }, + { + "type" : "HAS_ANOTHER_BONUS_LIMITER", + "parameters" : [ "MECHANICAL" ] + }, { "type" : "HAS_ANOTHER_BONUS_LIMITER", "parameters" : [ "GARGOYLE" ] diff --git a/config/bonuses.json b/config/bonuses.json index 897e0a9b9..21c20f22d 100644 --- a/config/bonuses.json +++ b/config/bonuses.json @@ -399,6 +399,14 @@ } }, + "MECHANICAL": + { + "graphics": + { + "icon": "zvs/Lib1.res/Mechanical" + } + }, + "RANDOM_SPELLCASTER": { "graphics": diff --git a/config/spells/ability.json b/config/spells/ability.json index d9e945722..92afcec15 100644 --- a/config/spells/ability.json +++ b/config/spells/ability.json @@ -88,6 +88,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" @@ -159,6 +160,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "normal", + "bonus.MECHANICAL" : "normal", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "normal" } @@ -238,6 +240,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" @@ -270,6 +273,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" @@ -357,6 +361,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" diff --git a/config/spells/other.json b/config/spells/other.json index 633332d8d..b0552133b 100644 --- a/config/spells/other.json +++ b/config/spells/other.json @@ -531,6 +531,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" @@ -602,6 +603,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" diff --git a/config/spells/timed.json b/config/spells/timed.json index 77edbabcd..e6b7f6697 100644 --- a/config/spells/timed.json +++ b/config/spells/timed.json @@ -806,6 +806,7 @@ "noneOf" : { "bonus.MIND_IMMUNITY" : "normal", "bonus.NON_LIVING" : "normal", + "bonus.MECHANICAL" : "normal", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute" } @@ -859,6 +860,7 @@ "noneOf" : { "bonus.MIND_IMMUNITY" : "normal", "bonus.NON_LIVING" : "normal", + "bonus.MECHANICAL" : "normal", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute" } @@ -1155,6 +1157,7 @@ "noneOf" : { "bonus.MIND_IMMUNITY" : "absolute", "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute" } @@ -1245,6 +1248,7 @@ "noneOf" : { "bonus.MIND_IMMUNITY" : "normal", "bonus.NON_LIVING" : "normal", + "bonus.MECHANICAL" : "normal", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "normal" } @@ -1311,7 +1315,8 @@ "bonus.SIEGE_WEAPON":"absolute", "bonus.MIND_IMMUNITY":"normal", "bonus.UNDEAD":"normal", - "bonus.NON_LIVING":"normal" + "bonus.NON_LIVING":"normal", + "bonus.MECHANICAL":"normal" } }, "flags" : { @@ -1379,6 +1384,7 @@ "noneOf" : { "bonus.MIND_IMMUNITY" : "normal", "bonus.NON_LIVING" : "normal", + "bonus.MECHANICAL" : "normal", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "normal" } @@ -1437,6 +1443,7 @@ "noneOf" : { "bonus.MIND_IMMUNITY" : "normal", "bonus.NON_LIVING" : "normal", + "bonus.MECHANICAL" : "normal", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute" } diff --git a/config/spells/vcmiAbility.json b/config/spells/vcmiAbility.json index db1212b7e..257c75b64 100644 --- a/config/spells/vcmiAbility.json +++ b/config/spells/vcmiAbility.json @@ -41,6 +41,7 @@ "targetCondition" : { "noneOf" : { "bonus.NON_LIVING" : "absolute", + "bonus.MECHANICAL" : "absolute", "bonus.SIEGE_WEAPON" : "absolute", "bonus.UNDEAD" : "absolute", "bonus.GARGOYLE" : "absolute" diff --git a/docs/modders/Bonus/Bonus_Types.md b/docs/modders/Bonus/Bonus_Types.md index 34e4d0b57..5fef78ee8 100644 --- a/docs/modders/Bonus/Bonus_Types.md +++ b/docs/modders/Bonus/Bonus_Types.md @@ -402,6 +402,10 @@ Increases starting amount of shots that unit has in battle Affected unit is considered to not be alive and not affected by morale and certain spells +### MECHANICAL + +Affected unit is considered to not be alive and not affected by morale and certain spells but should be repairable from engineers (factory). + ### GARGOYLE Affected unit is considered to be a gargoyle and not affected by certain spells diff --git a/lib/BasicTypes.cpp b/lib/BasicTypes.cpp index fecc59c7d..dec7cbaba 100644 --- a/lib/BasicTypes.cpp +++ b/lib/BasicTypes.cpp @@ -102,7 +102,7 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const return maxGoodMorale; } - static const auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::UNDEAD)) + static const auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::MECHANICAL)).Or(Selector::type()(BonusType::UNDEAD)) .Or(Selector::type()(BonusType::SIEGE_WEAPON)).Or(Selector::type()(BonusType::NO_MORALE)); static const std::string cachingStrUn = "AFactionMember::unaffectedByMoraleSelector"; @@ -187,6 +187,7 @@ bool ACreature::isLiving() const //TODO: theoreticaly there exists "LIVING" bonu static const std::string cachingStr = "ACreature::isLiving"; static const CSelector selector = Selector::type()(BonusType::UNDEAD) .Or(Selector::type()(BonusType::NON_LIVING)) + .Or(Selector::type()(BonusType::MECHANICAL)) .Or(Selector::type()(BonusType::GARGOYLE)) .Or(Selector::type()(BonusType::SIEGE_WEAPON)); diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index e68450c08..0992b4520 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -180,6 +180,7 @@ class JsonNode; BONUS_NAME(RESOURCES_TOWN_MULTIPLYING_BOOST) /*Bonus that does not account for propagation and gives extra resources per day with amount multiplied by number of owned towns. val - base resource amount to be multiplied times number of owned towns, subtype - resource type*/ \ BONUS_NAME(DISINTEGRATE) /* after death no corpse remains */ \ BONUS_NAME(INVINCIBLE) /* cannot be target of attacks or spells */ \ + BONUS_NAME(MECHANICAL) /*eg. factory creatures, cannot be rised or healed, only neutral morale, repairable by engineer */ \ /* end of list */ From 3690e05e40028159f4c7e5771e68c52f8ef2bf37 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 24 Oct 2024 18:54:39 -0700 Subject: [PATCH 416/726] Draft to hide scaling dropdown as needed --- CMakeLists.txt | 2 +- launcher/settingsView/csettingsview_moc.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78a4a7027..6305e3b26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ endif() option(ENABLE_CLIENT "Enable compilation of game client" ON) option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) -option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) +option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" OFF) option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON) option(ENABLE_MINIMAL_LIB "Build only core parts of vcmi library that are required for game lobby" OFF) option(ENABLE_GOLDMASTER "Build in public release mode in which some debug routines are disabled" OFF) diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 94ca8fc6c..214879cec 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -838,6 +838,11 @@ void CSettingsView::on_sliderScalingCursor_valueChanged(int value) void CSettingsView::on_buttonScalingAuto_toggled(bool checked) { + if (checked) + ui->spinBoxInterfaceScaling->hide(); + else + ui->spinBoxInterfaceScaling->show(); + ui->spinBoxInterfaceScaling->setDisabled(checked); ui->spinBoxInterfaceScaling->setValue(100); From 9bc84b606eb387a8d31c22ce532e0ebf4658b264 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 24 Oct 2024 19:28:21 -0700 Subject: [PATCH 417/726] Cleanup --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6305e3b26..78a4a7027 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ endif() option(ENABLE_CLIENT "Enable compilation of game client" ON) option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) -option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" OFF) +option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON) option(ENABLE_MINIMAL_LIB "Build only core parts of vcmi library that are required for game lobby" OFF) option(ENABLE_GOLDMASTER "Build in public release mode in which some debug routines are disabled" OFF) From 8a5ac73438c8351fc637ed9321706b4aee5ea3f2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 16:48:10 +0000 Subject: [PATCH 418/726] Basic support for configurable flaggable objects Converted CGLighthouse class into FlaggableMapObject Bonus provided by Lighthouse/Flaggable is now defined in config and is accessible to mods. --- config/gameConfig.json | 1 + config/objects/generic.json | 17 --- config/objects/lighthouse.json | 30 +++++ lib/CMakeLists.txt | 4 + .../CObjectClassesHandler.cpp | 16 ++- .../FlaggableInstanceConstructor.cpp | 53 +++++++++ .../FlaggableInstanceConstructor.h | 34 ++++++ lib/mapObjects/FlaggableMapObject.cpp | 105 ++++++++++++++++++ lib/mapObjects/FlaggableMapObject.h | 41 +++++++ lib/mapObjects/MiscObjects.cpp | 69 ------------ lib/mapObjects/MiscObjects.h | 22 ---- lib/mapping/MapFormatH3M.cpp | 6 +- lib/mapping/MapFormatH3M.h | 2 +- lib/serializer/RegisterTypes.h | 5 +- mapeditor/inspector/inspector.cpp | 12 +- mapeditor/inspector/inspector.h | 5 +- mapeditor/mapcontroller.cpp | 2 +- 17 files changed, 296 insertions(+), 128 deletions(-) create mode 100644 config/objects/lighthouse.json create mode 100644 lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp create mode 100644 lib/mapObjectConstructors/FlaggableInstanceConstructor.h create mode 100644 lib/mapObjects/FlaggableMapObject.cpp create mode 100644 lib/mapObjects/FlaggableMapObject.h diff --git a/config/gameConfig.json b/config/gameConfig.json index e83046e30..0f51e2ea7 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -53,6 +53,7 @@ "config/objects/creatureBanks.json", "config/objects/dwellings.json", "config/objects/generic.json", + "config/objects/lighthouse.json", "config/objects/magicSpring.json", "config/objects/magicWell.json", "config/objects/moddables.json", diff --git a/config/objects/generic.json b/config/objects/generic.json index 23240bbd5..764fb9983 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -270,23 +270,6 @@ } } }, - "lighthouse" : { - "index" :42, - "handler" : "lighthouse", - "base" : { - "sounds" : { - "visit" : ["LIGHTHOUSE"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 500, - "rmg" : { - } - } - } - }, "obelisk" : { "index" :57, "handler" : "obelisk", diff --git a/config/objects/lighthouse.json b/config/objects/lighthouse.json new file mode 100644 index 000000000..cc20d80ef --- /dev/null +++ b/config/objects/lighthouse.json @@ -0,0 +1,30 @@ +{ + "lighthouse" : { + "index" :42, + "handler" : "flaggable", + "base" : { + "sounds" : { + "visit" : ["LIGHTHOUSE"] + } + }, + "types" : { + "lighthouse" : { + "compatibilityIdentifiers" : [ "object" ], + "index" : 0, + "aiValue" : 500, + "rmg" : { + } + + "message" : "@core.advevent.69", + + "bonuses" : { + "seaMovement" : { + "type" : "MOVEMENT", + "subtype" : "heroMovementSea", + "val" : 500 + } + } + } + } + } +} \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0db2f9e50..ad817733a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -116,6 +116,7 @@ set(lib_MAIN_SRCS mapObjectConstructors/CommonConstructors.cpp mapObjectConstructors/CRewardableConstructor.cpp mapObjectConstructors/DwellingInstanceConstructor.cpp + mapObjectConstructors/FlaggableInstanceConstructor.cpp mapObjectConstructors/HillFortInstanceConstructor.cpp mapObjectConstructors/ShipyardInstanceConstructor.cpp @@ -132,6 +133,7 @@ set(lib_MAIN_SRCS mapObjects/CObjectHandler.cpp mapObjects/CQuest.cpp mapObjects/CRewardableObject.cpp + mapObjects/FlaggableMapObject.cpp mapObjects/IMarket.cpp mapObjects/IObjectInterface.cpp mapObjects/MiscObjects.cpp @@ -497,6 +499,7 @@ set(lib_MAIN_HEADERS mapObjectConstructors/CRewardableConstructor.h mapObjectConstructors/DwellingInstanceConstructor.h mapObjectConstructors/HillFortInstanceConstructor.h + mapObjectConstructors/FlaggableInstanceConstructor.h mapObjectConstructors/IObjectInfo.h mapObjectConstructors/RandomMapInfo.h mapObjectConstructors/ShipyardInstanceConstructor.h @@ -515,6 +518,7 @@ set(lib_MAIN_HEADERS mapObjects/CObjectHandler.h mapObjects/CQuest.h mapObjects/CRewardableObject.h + mapObjects/FlaggableMapObject.h mapObjects/IMarket.h mapObjects/IObjectInterface.h mapObjects/IOwnableObject.h diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index 202372cf9..e8356d7ef 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -23,17 +23,21 @@ #include "../mapObjectConstructors/CRewardableConstructor.h" #include "../mapObjectConstructors/CommonConstructors.h" #include "../mapObjectConstructors/DwellingInstanceConstructor.h" +#include "../mapObjectConstructors/FlaggableInstanceConstructor.h" #include "../mapObjectConstructors/HillFortInstanceConstructor.h" #include "../mapObjectConstructors/ShipyardInstanceConstructor.h" + #include "../mapObjects/CGCreature.h" -#include "../mapObjects/CGPandoraBox.h" -#include "../mapObjects/CQuest.h" -#include "../mapObjects/ObjectTemplate.h" -#include "../mapObjects/CGMarket.h" -#include "../mapObjects/MiscObjects.h" #include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/CGMarket.h" +#include "../mapObjects/CGPandoraBox.h" #include "../mapObjects/CGTownInstance.h" +#include "../mapObjects/CQuest.h" +#include "../mapObjects/FlaggableMapObject.h" +#include "../mapObjects/MiscObjects.h" +#include "../mapObjects/ObjectTemplate.h" #include "../mapObjects/ObstacleSetHandler.h" + #include "../modding/IdentifierStorage.h" #include "../modding/CModHandler.h" #include "../modding/ModScope.h" @@ -57,6 +61,7 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER_CLASS("town", CTownInstanceConstructor); SET_HANDLER_CLASS("bank", CBankInstanceConstructor); SET_HANDLER_CLASS("boat", BoatInstanceConstructor); + SET_HANDLER_CLASS("flaggable", FlaggableInstanceConstructor); SET_HANDLER_CLASS("market", MarketInstanceConstructor); SET_HANDLER_CLASS("hillFort", HillFortInstanceConstructor); SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor); @@ -82,7 +87,6 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("garrison", CGGarrison); SET_HANDLER("heroPlaceholder", CGHeroPlaceholder); SET_HANDLER("keymaster", CGKeymasterTent); - SET_HANDLER("lighthouse", CGLighthouse); SET_HANDLER("magi", CGMagi); SET_HANDLER("mine", CGMine); SET_HANDLER("obelisk", CGObelisk); diff --git a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp new file mode 100644 index 000000000..efa68af0d --- /dev/null +++ b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp @@ -0,0 +1,53 @@ +/* +* FlaggableInstanceConstructor.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "FlaggableInstanceConstructor.h" + +#include "../json/JsonBonus.h" +#include "../texts/CGeneralTextHandler.h" +#include "../VCMI_Lib.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void FlaggableInstanceConstructor::initTypeData(const JsonNode & config) +{ + for (const auto & bonusJson : config["bonuses"].Struct()) + providedBonuses.push_back(JsonUtils::parseBonus(bonusJson.second)); + + if (!config["message"].isNull()) + { + std::string message = config["message"].String(); + if (!message.empty() && message.at(0) == '@') + { + visitMessageTextID = message.substr(1); + } + else + { + visitMessageTextID = TextIdentifier(getBaseTextID(), "onVisit").get(); + VLC->generaltexth->registerString( config.getModScope(), visitMessageTextID, config["message"]); + } + } +} + +void FlaggableInstanceConstructor::initializeObject(FlaggableMapObject * flaggable) const +{ +} + +const std::string & FlaggableInstanceConstructor::getVisitMessageTextID() const +{ + return visitMessageTextID; +} + +const std::vector> & FlaggableInstanceConstructor::getProvidedBonuses() const +{ + return providedBonuses; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/FlaggableInstanceConstructor.h b/lib/mapObjectConstructors/FlaggableInstanceConstructor.h new file mode 100644 index 000000000..fde90501f --- /dev/null +++ b/lib/mapObjectConstructors/FlaggableInstanceConstructor.h @@ -0,0 +1,34 @@ +/* +* FlaggableInstanceConstructor.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CDefaultObjectTypeHandler.h" +#include "../bonuses/Bonus.h" +#include "../mapObjects/FlaggableMapObject.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class FlaggableInstanceConstructor final : public CDefaultObjectTypeHandler +{ + std::vector> providedBonuses; + + /// ID of message to show on hero visit + std::string visitMessageTextID; + +protected: + void initTypeData(const JsonNode & config) override; + void initializeObject(FlaggableMapObject * object) const override; + +public: + const std::string & getVisitMessageTextID() const; + const std::vector> & getProvidedBonuses() const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/FlaggableMapObject.cpp b/lib/mapObjects/FlaggableMapObject.cpp new file mode 100644 index 000000000..40d1149b0 --- /dev/null +++ b/lib/mapObjects/FlaggableMapObject.cpp @@ -0,0 +1,105 @@ +/* + * FlaggableMapObject.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "FlaggableMapObject.h" + +#include "../IGameCallback.h" +#include "CGHeroInstance.h" +#include "../networkPacks/PacksForClient.h" +#include "../mapObjectConstructors/FlaggableInstanceConstructor.h" + +VCMI_LIB_NAMESPACE_BEGIN + +const IOwnableObject * FlaggableMapObject::asOwnable() const +{ + return this; +} + +ResourceSet FlaggableMapObject::dailyIncome() const +{ + return {}; +} + +std::vector FlaggableMapObject::providedCreatures() const +{ + return {}; +} + +void FlaggableMapObject::onHeroVisit( const CGHeroInstance * h ) const +{ + if (cb->getPlayerRelations(h->getOwner(), getOwner()) != PlayerRelations::ENEMIES) + return; // H3 behavior - revisiting owned Lighthouse is a no-op + + if (getOwner().isValidPlayer()) + takeBonusFrom(getOwner()); + + cb->setOwner(this, h->getOwner()); //not ours? flag it! + + InfoWindow iw; + iw.player = h->getOwner(); + iw.text.appendTextID(getFlaggableHandler()->getVisitMessageTextID()); + cb->showInfoDialog(&iw); + + giveBonusTo(h->getOwner()); +} + +void FlaggableMapObject::initObj(vstd::RNG & rand) +{ + if(getOwner().isValidPlayer()) + { + // FIXME: This is dirty hack + giveBonusTo(getOwner(), true); + } +} + +std::shared_ptr FlaggableMapObject::getFlaggableHandler() const +{ + return std::dynamic_pointer_cast(getObjectHandler()); +} + +void FlaggableMapObject::giveBonusTo(const PlayerColor & player, bool onInit) const +{ + for (auto const & bonus : getFlaggableHandler()->getProvidedBonuses()) + { + GiveBonus gb(GiveBonus::ETarget::PLAYER); + gb.id = player; + gb.bonus = *bonus; + + // FIXME: better place for this code? + gb.bonus.duration = BonusDuration::PERMANENT; + gb.bonus.source = BonusSource::OBJECT_INSTANCE; + gb.bonus.sid = BonusSourceID(id); + + // FIXME: This is really dirty hack + // Proper fix would be to make FlaggableMapObject into bonus system node + // Unfortunately this will cause saves breakage + if(onInit) + gb.applyGs(cb->gameState()); + else + cb->sendAndApply(gb); + } +} + +void FlaggableMapObject::takeBonusFrom(const PlayerColor & player) const +{ + RemoveBonus rb(GiveBonus::ETarget::PLAYER); + rb.whoID = player; + rb.source = BonusSource::OBJECT_INSTANCE; + rb.id = BonusSourceID(id); + cb->sendAndApply(rb); +} + +void FlaggableMapObject::serializeJsonOptions(JsonSerializeFormat& handler) +{ + serializeJsonOwner(handler); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/FlaggableMapObject.h b/lib/mapObjects/FlaggableMapObject.h new file mode 100644 index 000000000..f3c86e0d2 --- /dev/null +++ b/lib/mapObjects/FlaggableMapObject.h @@ -0,0 +1,41 @@ +/* + * FlaggableMapObject.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "CGObjectInstance.h" +#include "IOwnableObject.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Bonus; +class FlaggableInstanceConstructor; + +class DLL_LINKAGE FlaggableMapObject : public CGObjectInstance, public IOwnableObject +{ + std::shared_ptr getFlaggableHandler() const; + + void giveBonusTo(const PlayerColor & player, bool onInit = false) const; + void takeBonusFrom(const PlayerColor & player) const; + +public: + using CGObjectInstance::CGObjectInstance; + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj(vstd::RNG & rand) override; + + const IOwnableObject * asOwnable() const final; + ResourceSet dailyIncome() const override; + std::vector providedCreatures() const override; + +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index b8b95994d..0e2ce7e1d 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1311,75 +1311,6 @@ void CGObelisk::setPropertyDer(ObjProperty what, ObjPropertyID identifier) } } -const IOwnableObject * CGLighthouse::asOwnable() const -{ - return this; -} - -ResourceSet CGLighthouse::dailyIncome() const -{ - return {}; -} - -std::vector CGLighthouse::providedCreatures() const -{ - return {}; -} - -void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const -{ - if(h->tempOwner != tempOwner) - { - PlayerColor oldOwner = tempOwner; - cb->setOwner(this,h->tempOwner); //not ours? flag it! - h->showInfoDialog(69); - giveBonusTo(h->tempOwner); - - if(oldOwner.isValidPlayer()) //remove bonus from old owner - { - RemoveBonus rb(GiveBonus::ETarget::PLAYER); - rb.whoID = oldOwner; - rb.source = BonusSource::OBJECT_INSTANCE; - rb.id = BonusSourceID(id); - cb->sendAndApply(rb); - } - } -} - -void CGLighthouse::initObj(vstd::RNG & rand) -{ - if(tempOwner.isValidPlayer()) - { - // FIXME: This is dirty hack - giveBonusTo(tempOwner, true); - } -} - -void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const -{ - GiveBonus gb(GiveBonus::ETarget::PLAYER); - gb.bonus.type = BonusType::MOVEMENT; - gb.bonus.val = 500; - gb.id = player; - gb.bonus.duration = BonusDuration::PERMANENT; - gb.bonus.source = BonusSource::OBJECT_INSTANCE; - gb.bonus.sid = BonusSourceID(id); - gb.bonus.subtype = BonusCustomSubtype::heroMovementSea; - - // FIXME: This is really dirty hack - // Proper fix would be to make CGLighthouse into bonus system node - // Unfortunately this will cause saves breakage - if(onInit) - gb.applyGs(cb->gameState()); - else - cb->sendAndApply(gb); -} - -void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler) -{ - serializeJsonOwner(handler); -} - void HillFort::onHeroVisit(const CGHeroInstance * h) const { cb->showObjectWindow(this, EOpenWindowMode::HILL_FORT_WINDOW, h, false); diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 862d52596..44b6c5aa3 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -413,28 +413,6 @@ protected: void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override; }; -class DLL_LINKAGE CGLighthouse : public CGObjectInstance, public IOwnableObject -{ -public: - using CGObjectInstance::CGObjectInstance; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj(vstd::RNG & rand) override; - - const IOwnableObject * asOwnable() const final; - ResourceSet dailyIncome() const override; - std::vector providedCreatures() const override; - - template void serialize(Handler &h) - { - h & static_cast(*this); - } - void giveBonusTo(const PlayerColor & player, bool onInit = false) const; - -protected: - void serializeJsonOptions(JsonSerializeFormat & handler) override; -}; - class DLL_LINKAGE CGTerrainPatch : public CGObjectInstance { public: diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 16e02db92..f2662b3e2 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1478,9 +1478,9 @@ CGObjectInstance * CMapLoaderH3M::readShipyard(const int3 & mapPosition, std::sh return object; } -CGObjectInstance * CMapLoaderH3M::readLighthouse(const int3 & mapPosition) +CGObjectInstance * CMapLoaderH3M::readLighthouse(const int3 & mapPosition, std::shared_ptr objectTemplate) { - auto * object = new CGLighthouse(map->cb); + auto * object = readGeneric(mapPosition, objectTemplate); setOwnerAndValidate(mapPosition, object, reader->readPlayer32()); return object; } @@ -1618,7 +1618,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr objTempl); CGObjectInstance * readQuestGuard(const int3 & position); CGObjectInstance * readShipyard(const int3 & mapPosition, std::shared_ptr objectTemplate); - CGObjectInstance * readLighthouse(const int3 & mapPosition); + CGObjectInstance * readLighthouse(const int3 & mapPosition, std::shared_ptr objectTemplate); CGObjectInstance * readGeneric(const int3 & position, std::shared_ptr objectTemplate); CGObjectInstance * readBank(const int3 & position, std::shared_ptr objectTemplate); diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 204d99432..073b68ae9 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -20,14 +20,17 @@ #include "../gameState/CGameState.h" #include "../gameState/CGameStateCampaign.h" #include "../gameState/TavernHeroesPool.h" + #include "../mapObjects/CGCreature.h" #include "../mapObjects/CGDwelling.h" #include "../mapObjects/CGMarket.h" #include "../mapObjects/CGPandoraBox.h" #include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CQuest.h" +#include "../mapObjects/FlaggableMapObject.h" #include "../mapObjects/MiscObjects.h" #include "../mapObjects/TownBuildingInstance.h" + #include "../mapping/CMap.h" #include "../networkPacks/PacksForClient.h" #include "../networkPacks/PacksForClientBattle.h" @@ -73,7 +76,7 @@ void registerTypes(Serializer &s) s.template registerType(15); s.template registerType(16); s.template registerType(17); - s.template registerType(18); + s.template registerType(18); s.template registerType(19); s.template registerType(20); s.template registerType(21); diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index e4516fd9c..1e15d18d2 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -60,7 +60,7 @@ Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : default INIT_OBJ_TYPE(CGHeroPlaceholder); INIT_OBJ_TYPE(CGHeroInstance); INIT_OBJ_TYPE(CGSignBottle); - INIT_OBJ_TYPE(CGLighthouse); + INIT_OBJ_TYPE(FlaggableMapObject); //INIT_OBJ_TYPE(CRewardableObject); //INIT_OBJ_TYPE(CGPandoraBox); //INIT_OBJ_TYPE(CGEvent); @@ -108,7 +108,7 @@ void Initializer::initialize(CGShipyard * o) o->tempOwner = defaultPlayer; } -void Initializer::initialize(CGLighthouse * o) +void Initializer::initialize(FlaggableMapObject * o) { if(!o) return; @@ -244,7 +244,7 @@ void Inspector::updateProperties(CGDwelling * o) } } -void Inspector::updateProperties(CGLighthouse * o) +void Inspector::updateProperties(FlaggableMapObject * o) { if(!o) return; @@ -492,7 +492,7 @@ void Inspector::updateProperties() UPDATE_OBJ_PROPERTIES(CGHeroPlaceholder); UPDATE_OBJ_PROPERTIES(CGHeroInstance); UPDATE_OBJ_PROPERTIES(CGSignBottle); - UPDATE_OBJ_PROPERTIES(CGLighthouse); + UPDATE_OBJ_PROPERTIES(FlaggableMapObject); UPDATE_OBJ_PROPERTIES(CRewardableObject); UPDATE_OBJ_PROPERTIES(CGPandoraBox); UPDATE_OBJ_PROPERTIES(CGEvent); @@ -540,7 +540,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value) SET_PROPERTIES(CGHeroInstance); SET_PROPERTIES(CGShipyard); SET_PROPERTIES(CGSignBottle); - SET_PROPERTIES(CGLighthouse); + SET_PROPERTIES(FlaggableMapObject); SET_PROPERTIES(CRewardableObject); SET_PROPERTIES(CGPandoraBox); SET_PROPERTIES(CGEvent); @@ -553,7 +553,7 @@ void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVari if(!o) return; } -void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVariant & value) +void Inspector::setProperty(FlaggableMapObject * o, const QString & key, const QVariant & value) { if(!o) return; } diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 50d631185..1f03d5601 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -17,6 +17,7 @@ #include "../lib/GameConstants.h" #include "../lib/mapObjects/CGCreature.h" #include "../lib/mapObjects/MapObjects.h" +#include "../lib/mapObjects/FlaggableMapObject.h" #include "../lib/mapObjects/CRewardableObject.h" #include "../lib/texts/CGeneralTextHandler.h" #include "../lib/ResourceSet.h" @@ -48,7 +49,7 @@ public: DECLARE_OBJ_TYPE(CGHeroInstance); DECLARE_OBJ_TYPE(CGCreature); DECLARE_OBJ_TYPE(CGSignBottle); - DECLARE_OBJ_TYPE(CGLighthouse); + DECLARE_OBJ_TYPE(FlaggableMapObject); //DECLARE_OBJ_TYPE(CRewardableObject); //DECLARE_OBJ_TYPE(CGEvent); //DECLARE_OBJ_TYPE(CGPandoraBox); @@ -78,7 +79,7 @@ protected: DECLARE_OBJ_PROPERTY_METHODS(CGHeroInstance); DECLARE_OBJ_PROPERTY_METHODS(CGCreature); DECLARE_OBJ_PROPERTY_METHODS(CGSignBottle); - DECLARE_OBJ_PROPERTY_METHODS(CGLighthouse); + DECLARE_OBJ_PROPERTY_METHODS(FlaggableMapObject); DECLARE_OBJ_PROPERTY_METHODS(CRewardableObject); DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox); DECLARE_OBJ_PROPERTY_METHODS(CGEvent); diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 167ca42cc..7e5ba7ce2 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -121,7 +121,7 @@ void MapController::repairMap(CMap * map) const dynamic_cast(obj.get()) || dynamic_cast(obj.get()) || dynamic_cast(obj.get()) || - dynamic_cast(obj.get()) || + dynamic_cast(obj.get()) || dynamic_cast(obj.get())) obj->tempOwner = PlayerColor::NEUTRAL; } From 2480c4eae7c0a34abe848bd162df43bb487006b8 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 25 Oct 2024 09:58:57 -0700 Subject: [PATCH 419/726] Minor update --- launcher/settingsView/csettingsview_moc.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 214879cec..c7cc2e1e1 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -839,13 +839,16 @@ void CSettingsView::on_sliderScalingCursor_valueChanged(int value) void CSettingsView::on_buttonScalingAuto_toggled(bool checked) { if (checked) + { ui->spinBoxInterfaceScaling->hide(); + } else + { ui->spinBoxInterfaceScaling->show(); - - ui->spinBoxInterfaceScaling->setDisabled(checked); - ui->spinBoxInterfaceScaling->setValue(100); - + ui->spinBoxInterfaceScaling->setDisabled(false); + ui->spinBoxInterfaceScaling->setValue(100); + } + Settings node = settings.write["video"]["resolution"]["scaling"]; node->Integer() = checked ? 0 : 100; } From ee59bc4e71d94fd02bab68588fbc2111aba62607 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 18:36:02 +0000 Subject: [PATCH 420/726] Add bonus description generation for map objects --- client/widgets/MiscWidgets.cpp | 6 +++--- config/objects/lighthouse.json | 3 +-- lib/CSkillHandler.cpp | 2 +- lib/battle/BattleInfo.cpp | 4 ++-- lib/bonuses/Bonus.cpp | 9 ++++++++- lib/bonuses/Bonus.h | 3 ++- lib/bonuses/CBonusSystemNode.cpp | 6 +++--- lib/mapObjects/CGTownInstance.cpp | 4 ++-- 8 files changed, 22 insertions(+), 15 deletions(-) diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index 14cc7b14d..bff6a0407 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -581,13 +581,13 @@ void MoraleLuckBox::set(const AFactionMember * node) else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE)) { auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE)); - text += "\n" + noMorale->Description(); + text += "\n" + noMorale->Description(LOCPLINT->cb.get()); component.value = 0; } else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK)) { auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK)); - text += "\n" + noLuck->Description(); + text += "\n" + noLuck->Description(LOCPLINT->cb.get()); component.value = 0; } else @@ -596,7 +596,7 @@ void MoraleLuckBox::set(const AFactionMember * node) for(auto & bonus : * modifierList) { if(bonus->val) { - const std::string& description = bonus->Description(); + const std::string& description = bonus->Description(LOCPLINT->cb.get()); //arraytxt already contains \n if (description.size() && description[0] != '\n') addInfo += '\n'; diff --git a/config/objects/lighthouse.json b/config/objects/lighthouse.json index cc20d80ef..cecabfc0b 100644 --- a/config/objects/lighthouse.json +++ b/config/objects/lighthouse.json @@ -13,10 +13,9 @@ "index" : 0, "aiValue" : 500, "rmg" : { - } + }, "message" : "@core.advevent.69", - "bonuses" : { "seaMovement" : { "type" : "MOVEMENT", diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 87cad05bc..7c9e5fec6 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -122,7 +122,7 @@ CSkill::LevelInfo & CSkill::at(int level) DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info) { for(int i=0; i < info.effects.size(); i++) - out << (i ? "," : "") << info.effects[i]->Description(); + out << (i ? "," : "") << info.effects[i]->Description(nullptr); return out << "])"; } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 8a8eb633c..132358391 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -885,12 +885,12 @@ void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool fo if(forceAdd || !sta->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, value.sid).And(Selector::typeSubtypeValueType(value.type, value.subtype, value.valType)))) { //no such effect or cumulative - add new - logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description()); + logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description(nullptr)); sta->addNewBonus(std::make_shared(value)); } else { - logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description()); + logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description(nullptr)); for(const auto & stackBonus : sta->getExportedBonusList()) //TODO: optimize { diff --git a/lib/bonuses/Bonus.cpp b/lib/bonuses/Bonus.cpp index a11e794f4..95d665d76 100644 --- a/lib/bonuses/Bonus.cpp +++ b/lib/bonuses/Bonus.cpp @@ -18,8 +18,11 @@ #include "../CCreatureHandler.h" #include "../CCreatureSet.h" #include "../CSkillHandler.h" +#include "../IGameCallback.h" #include "../TerrainHandler.h" #include "../VCMI_Lib.h" +#include "../mapObjects/CGObjectInstance.h" +#include "../mapObjectConstructors/CObjectClassesHandler.h" #include "../battle/BattleInfo.h" #include "../constants/StringConstants.h" #include "../entities/hero/CHero.h" @@ -87,7 +90,7 @@ JsonNode CAddInfo::toJsonNode() const return node; } } -std::string Bonus::Description(std::optional customValue) const +std::string Bonus::Description(const IGameInfoCallback * cb, std::optional customValue) const { MetaString descriptionHelper = description; auto valueToShow = customValue.value_or(val); @@ -112,6 +115,10 @@ std::string Bonus::Description(std::optional customValue) const case BonusSource::HERO_SPECIAL: descriptionHelper.appendTextID(sid.as().toEntity(VLC)->getNameTextID()); break; + case BonusSource::OBJECT_INSTANCE: + const auto * object = cb->getObj(sid.as()); + if (object) + descriptionHelper.appendTextID(VLC->objtypeh->getObjectName(object->ID, object->subID)); } } diff --git a/lib/bonuses/Bonus.h b/lib/bonuses/Bonus.h index 63b4d6b1d..caefbc46a 100644 --- a/lib/bonuses/Bonus.h +++ b/lib/bonuses/Bonus.h @@ -26,6 +26,7 @@ class IPropagator; class IUpdater; class BonusList; class CSelector; +class IGameInfoCallback; using BonusSubtypeID = VariantIdentifier; using BonusSourceID = VariantIdentifier; @@ -177,7 +178,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this, public Se val += Val; } - std::string Description(std::optional customValue = {}) const; + std::string Description(const IGameInfoCallback * cb, std::optional customValue = {}) const; JsonNode toJsonNode() const; std::shared_ptr addLimiter(const TLimiterPtr & Limiter); //returns this for convenient chain-calls diff --git a/lib/bonuses/CBonusSystemNode.cpp b/lib/bonuses/CBonusSystemNode.cpp index 7b944eefb..b5a2d582d 100644 --- a/lib/bonuses/CBonusSystemNode.cpp +++ b/lib/bonuses/CBonusSystemNode.cpp @@ -378,7 +378,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr & b, const CB ? source.getUpdatedBonus(b, b->propagationUpdater) : b; bonuses.push_back(propagated); - logBonus->trace("#$# %s #propagated to# %s", propagated->Description(), nodeName()); + logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName()); } TNodes lchildren; @@ -392,9 +392,9 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr & b) if(b->propagator->shouldBeAttached(this)) { if (bonuses -= b) - logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(), nodeName()); + logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(nullptr), nodeName()); else - logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(), nodeName()); + logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName()); bonuses.remove_if([b](const auto & bonus) { diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 50d9b8b35..5b1a115ce 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -166,7 +166,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const const auto growth = b->val * (base + castleBonus) / 100; if (growth) { - ret.entries.emplace_back(growth, b->Description(growth)); + ret.entries.emplace_back(growth, b->Description(cb, growth)); } } @@ -174,7 +174,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const // Note: bonus uses 1-based levels (Pikeman is level 1), town list uses 0-based (Pikeman in 0-th creatures entry) TConstBonusListPtr bonuses = getBonuses(Selector::typeSubtype(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1))); for(const auto & b : *bonuses) - ret.entries.emplace_back(b->val, b->Description()); + ret.entries.emplace_back(b->val, b->Description(cb)); int dwellingBonus = 0; if(const PlayerState *p = cb->getPlayerState(tempOwner, false)) From 93096dc63c835dfc764a7ff013bd80198be1a01c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 18:47:19 +0000 Subject: [PATCH 421/726] Updated documentation --- docs/modders/Map_Object_Format.md | 2 +- docs/modders/Map_Objects/Flaggable.md | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 docs/modders/Map_Objects/Flaggable.md diff --git a/docs/modders/Map_Object_Format.md b/docs/modders/Map_Object_Format.md index 22785ac55..5f6472e6c 100644 --- a/docs/modders/Map_Object_Format.md +++ b/docs/modders/Map_Object_Format.md @@ -49,6 +49,7 @@ These are object types that are available for modding and have configurable prop - `dwelling` - see [Dwelling](Map_Objects/Dwelling.md). Object that allows recruitments of units outside of towns - `market` - see [Market](Map_Objects/Market.md). Trading resources, artifacts, creatures and such - `boat` - see [Boat](Map_Objects/Boat.md). Object to move across different terrains, such as water +- `flaggable` - see [Flaggable](Map_Objects/Flaggable.md). Object that can be flagged by a player to provide [Bonus](Bonus_Format.md) or resources - `hillFort` - TODO: documentation. See config files in vcmi installation for reference - `shipyard` - TODO: documentation. See config files in vcmi installation for reference - `terrain` - Defines terrain overlays such as magic grounds. TODO: documentation. See config files in vcmi installation for reference @@ -60,7 +61,6 @@ These are types that don't have configurable properties, however it is possible - `generic` - Defines empty object type that provides no functionality. Note that unlike `static`, objects of this type are never used by RMG - `borderGate` - `borderGuard` -- `lighthouse` - `magi` - `mine` - `obelisk` diff --git a/docs/modders/Map_Objects/Flaggable.md b/docs/modders/Map_Objects/Flaggable.md new file mode 100644 index 000000000..a833345a6 --- /dev/null +++ b/docs/modders/Map_Objects/Flaggable.md @@ -0,0 +1,26 @@ +# Flaggable objects + +Flaggable object are those that can be captured by a visiting hero. H3 examples are mines, dwellings, or lighthouse. + +```jsonc +{ + "baseObjectName" : { + "name" : "Object name", + "handler" : "flaggable", + "types" : { + "objectName" : { + + // Text for message that player will get on capturing this object with a hero + // Alternatively, it is possible to reuse existing string from H3 using form '@core.advevent.69' + "onVisit" : "{Object Name}\r\n\r\nText of messages that player will see on visit.", + + // List of bonuses that will be granted to player that owns this object + "bonuses" : { + "firstBonus" : { BONUS FORMAT }, + "secondBonus" : { BONUS FORMAT }, + } + } + } + } +} +``` From 7ae5e2b406900e1cdb89f61bc1ce2c9b111a8a57 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 19:01:00 +0000 Subject: [PATCH 422/726] Added support for daily income to flaggable objects --- docs/modders/Map_Objects/Flaggable.md | 13 +++++++++++++ .../FlaggableInstanceConstructor.cpp | 7 +++++++ .../FlaggableInstanceConstructor.h | 7 +++++++ lib/mapObjects/FlaggableMapObject.cpp | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/modders/Map_Objects/Flaggable.md b/docs/modders/Map_Objects/Flaggable.md index a833345a6..f11bfca79 100644 --- a/docs/modders/Map_Objects/Flaggable.md +++ b/docs/modders/Map_Objects/Flaggable.md @@ -2,6 +2,12 @@ Flaggable object are those that can be captured by a visiting hero. H3 examples are mines, dwellings, or lighthouse. +Currently, it is possible to make flaggable objects that provide player with: +- Any [Bonus](Bonus_Format.md) supported by bonus system +- Daily resources income (wood, ore, gold, etc) + +## Format description + ```jsonc { "baseObjectName" : { @@ -18,6 +24,13 @@ Flaggable object are those that can be captured by a visiting hero. H3 examples "bonuses" : { "firstBonus" : { BONUS FORMAT }, "secondBonus" : { BONUS FORMAT }, + }, + + // Resources that will be given to owner on every day + "dailyIncome" : { + "wood" : 2, + "ore" : 2, + "gold" : 1000 } } } diff --git a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp index efa68af0d..eeac5ee13 100644 --- a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp @@ -34,6 +34,8 @@ void FlaggableInstanceConstructor::initTypeData(const JsonNode & config) VLC->generaltexth->registerString( config.getModScope(), visitMessageTextID, config["message"]); } } + + dailyIncome = ResourceSet(config["dailyIncome"]); } void FlaggableInstanceConstructor::initializeObject(FlaggableMapObject * flaggable) const @@ -50,4 +52,9 @@ const std::vector> & FlaggableInstanceConstructor::getPro return providedBonuses; } +const ResourceSet & FlaggableInstanceConstructor::getDailyIncome() const +{ + return dailyIncome; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/FlaggableInstanceConstructor.h b/lib/mapObjectConstructors/FlaggableInstanceConstructor.h index fde90501f..c0ad722fe 100644 --- a/lib/mapObjectConstructors/FlaggableInstanceConstructor.h +++ b/lib/mapObjectConstructors/FlaggableInstanceConstructor.h @@ -10,6 +10,8 @@ #pragma once #include "CDefaultObjectTypeHandler.h" + +#include "../ResourceSet.h" #include "../bonuses/Bonus.h" #include "../mapObjects/FlaggableMapObject.h" @@ -17,11 +19,15 @@ VCMI_LIB_NAMESPACE_BEGIN class FlaggableInstanceConstructor final : public CDefaultObjectTypeHandler { + /// List of bonuses that are provided by every map object of this type std::vector> providedBonuses; /// ID of message to show on hero visit std::string visitMessageTextID; + /// Amount of resources granted by this object to owner every day + ResourceSet dailyIncome; + protected: void initTypeData(const JsonNode & config) override; void initializeObject(FlaggableMapObject * object) const override; @@ -29,6 +35,7 @@ protected: public: const std::string & getVisitMessageTextID() const; const std::vector> & getProvidedBonuses() const; + const ResourceSet & getDailyIncome() const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/FlaggableMapObject.cpp b/lib/mapObjects/FlaggableMapObject.cpp index 40d1149b0..5b22650ae 100644 --- a/lib/mapObjects/FlaggableMapObject.cpp +++ b/lib/mapObjects/FlaggableMapObject.cpp @@ -25,7 +25,7 @@ const IOwnableObject * FlaggableMapObject::asOwnable() const ResourceSet FlaggableMapObject::dailyIncome() const { - return {}; + return getFlaggableHandler()->getDailyIncome(); } std::vector FlaggableMapObject::providedCreatures() const From 26fecbf2cabdab4f81f1f53c48992f4a4b6d8c3c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 19:08:13 +0000 Subject: [PATCH 423/726] Simplify income calculation in kingdom overview, account for flaggables --- client/windows/CKingdomInterface.cpp | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 0d754741f..136f7317b 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -583,28 +583,16 @@ void CKingdomInterface::generateMinesList(const std::vectorID == Obj::MINE || object->ID == Obj::ABANDONED_MINE) { const CGMine * mine = dynamic_cast(object); - assert(mine); minesCount[mine->producedResource]++; - totalIncome += mine->dailyIncome()[EGameResID::GOLD]; } } - //Heroes can produce gold as well - skill, specialty or arts - std::vector heroes = LOCPLINT->cb->getHeroesInfo(true); - auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID); - for(auto & hero : heroes) - { - totalIncome += hero->dailyIncome()[EGameResID::GOLD]; - } - - //Add town income of all towns - std::vector towns = LOCPLINT->cb->getTownsInfo(true); - for(auto & town : towns) - { - totalIncome += town->dailyIncome()[EGameResID::GOLD]; - } + for(auto & mapObject : ownedObjects) + totalIncome += mapObject->asOwnable()->dailyIncome()[EGameResID::GOLD]; //if player has some modded boosts we want to show that as well + const auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID); + const auto & towns = LOCPLINT->cb->getTownsInfo(true); totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * playerSettings->handicap.percentIncome / 100; totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * towns.size() * playerSettings->handicap.percentIncome / 100; From c927324b191056ad99cf3557425716fa41cfd416 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:13:28 +0200 Subject: [PATCH 424/726] Fixed spacing between sentences --- config/objects/shrine.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/config/objects/shrine.json b/config/objects/shrine.json index 44bd2a952..725bc45cd 100644 --- a/config/objects/shrine.json +++ b/config/objects/shrine.json @@ -49,7 +49,7 @@ "message" : [ 127, "%s." ] // You learn new spell } ], - "onVisitedMessage" : [ 127, "%s.", 174 ], // You already known this spell + "onVisitedMessage" : [ 127, "%s. ", 174 ], // You already known this spell "onEmpty" : [ { "limiter" : { @@ -59,10 +59,10 @@ } ] }, - "message" : [ 127, "%s.", 130 ] // No Wisdom + "message" : [ 127, "%s. ", 130 ] // No Wisdom }, { - "message" : [ 127, "%s.", 131 ] // No spellbook + "message" : [ 127, "%s. ", 131 ] // No spellbook } ] } @@ -118,7 +118,7 @@ "message" : [ 128, "%s." ] // You learn new spell } ], - "onVisitedMessage" : [ 128, "%s.", 174 ], // You already known this spell + "onVisitedMessage" : [ 128, "%s. ", 174 ], // You already known this spell "onEmpty" : [ { "limiter" : { @@ -128,10 +128,10 @@ } ] }, - "message" : [ 128, "%s.", 130 ] // No Wisdom + "message" : [ 128, "%s. ", 130 ] // No Wisdom }, { - "message" : [ 128, "%s.", 131 ] // No spellbook + "message" : [ 128, "%s. ", 131 ] // No spellbook } ] } @@ -187,7 +187,7 @@ "message" : [ 129, "%s." ] // You learn new spell } ], - "onVisitedMessage" : [ 129, "%s.", 174 ], // You already known this spell + "onVisitedMessage" : [ 129, "%s. ", 174 ], // You already known this spell "onEmpty" : [ { "limiter" : { @@ -197,10 +197,10 @@ } ] }, - "message" : [ 129, "%s.", 130 ] // No Wisdom + "message" : [ 129, "%s. ", 130 ] // No Wisdom }, { - "message" : [ 129, "%s.", 131 ] // No spellbook + "message" : [ 129, "%s. ", 131 ] // No spellbook } ] } From c43844706e3713257b2315d36243eda5d0ce3353 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 20:41:19 +0000 Subject: [PATCH 425/726] Implemented translation support for random map descriptions --- Mods/vcmi/config/vcmi/english.json | 10 ++++++ lib/rmg/CMapGenerator.cpp | 52 +++++++++++++++++++----------- lib/rmg/CMapGenerator.h | 6 ++-- lib/texts/MetaString.cpp | 6 ++++ lib/texts/MetaString.h | 2 ++ 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 2cb7b5772..600c382c8 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -60,6 +60,16 @@ "vcmi.radialWheel.moveUp" : "Move up", "vcmi.radialWheel.moveDown" : "Move down", "vcmi.radialWheel.moveBottom" : "Move to bottom", + + "vcmi.randomMap.description" : "Map created by the Random Map Generator.\nTemplate was %s, size %dx%d, levels %d, players %d, computers %d, water %s, monster %s, VCMI map", + "vcmi.randomMap.description.isHuman" : ", %s is human", + "vcmi.randomMap.description.townChoice" : ", %s town choice is %s", + "vcmi.randomMap.description.water.none" : "none", + "vcmi.randomMap.description.water.normal" : "normal", + "vcmi.randomMap.description.water.islands" : "islands", + "vcmi.randomMap.description.monster.weak" : "weak", + "vcmi.randomMap.description.monster.normal" : "normal", + "vcmi.randomMap.description.monster.strong" : "strong", "vcmi.spellBook.search" : "search...", diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index c33a10dd4..f789b78c6 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -152,41 +152,55 @@ std::unique_ptr CMapGenerator::generate() return std::move(map->mapInstance); } -std::string CMapGenerator::getMapDescription() const +MetaString CMapGenerator::getMapDescription() const { - assert(map); + const TextIdentifier mainPattern("vcmi", "randomMap", "description"); + const TextIdentifier isHuman("vcmi", "randomMap", "description", "isHuman"); + const TextIdentifier townChoiceIs("vcmi", "randomMap", "description", "townChoice"); + const std::array waterContent = { + TextIdentifier("vcmi", "randomMap", "description", "water", "none"), + TextIdentifier("vcmi", "randomMap", "description", "water", "normal"), + TextIdentifier("vcmi", "randomMap", "description", "water", "islands") + }; + const std::array monsterStrength = { + TextIdentifier("vcmi", "randomMap", "description", "monster", "weak"), + TextIdentifier("vcmi", "randomMap", "description", "monster", "normal"), + TextIdentifier("vcmi", "randomMap", "description", "monster", "strong") + }; - const std::string waterContentStr[3] = { "none", "normal", "islands" }; - const std::string monsterStrengthStr[3] = { "weak", "normal", "strong" }; - - int monsterStrengthIndex = mapGenOptions.getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0 const auto * mapTemplate = mapGenOptions.getMapTemplate(); + int monsterStrengthIndex = mapGenOptions.getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0 - if(!mapTemplate) - throw rmgException("Map template for Random Map Generator is not found. Could not start the game."); + MetaString result = MetaString::createFromTextID(mainPattern.get()); - std::stringstream ss; - ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, size %dx%d") + - ", levels %d, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() % - map->width() % map->height() % static_cast(map->levels()) % static_cast(mapGenOptions.getHumanOrCpuPlayerCount()) % - static_cast(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] % - monsterStrengthStr[monsterStrengthIndex]); + result.replaceRawString(mapTemplate->getName()); + result.replaceNumber(map->width()); + result.replaceNumber(map->height()); + result.replaceNumber(map->levels()); + result.replaceNumber(mapGenOptions.getHumanOrCpuPlayerCount()); + result.replaceNumber(mapGenOptions.getCompOnlyPlayerCount()); + result.replaceTextID(waterContent.at(mapGenOptions.getWaterContent()).get()); + result.replaceTextID(monsterStrength.at(monsterStrengthIndex).get()); for(const auto & pair : mapGenOptions.getPlayersSettings()) { const auto & pSettings = pair.second; + if(pSettings.getPlayerType() == EPlayerType::HUMAN) { - ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor().getNum()] << " is human"; + result.appendTextID(isHuman.get()); + result.replaceName(pSettings.getColor()); } + if(pSettings.getStartingTown() != FactionID::RANDOM) { - ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor().getNum()] - << " town choice is " << (*VLC->townh)[pSettings.getStartingTown()]->getNameTranslated(); + result.appendTextID(townChoiceIs.get()); + result.replaceName(pSettings.getColor()); + result.replaceName(pSettings.getStartingTown()); } } - return ss.str(); + return result; } void CMapGenerator::addPlayerInfo() @@ -451,7 +465,7 @@ void CMapGenerator::addHeaderInfo() m.height = mapGenOptions.getHeight(); m.twoLevel = mapGenOptions.getHasTwoLevels(); m.name.appendLocalString(EMetaText::GENERAL_TXT, 740); - m.description.appendRawString(getMapDescription()); + m.description = getMapDescription(); m.difficulty = EMapDifficulty::NORMAL; addPlayerInfo(); m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE); diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index c1971bdba..a635534b6 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -10,14 +10,12 @@ #pragma once -#include "../GameConstants.h" #include "CMapGenOptions.h" -#include "../int3.h" -#include "CRmgTemplate.h" #include "../LoadProgress.h" VCMI_LIB_NAMESPACE_BEGIN +class MetaString; class CRmgTemplate; class CMapGenOptions; class JsonNode; @@ -93,7 +91,7 @@ private: /// Generation methods void loadConfig(); - std::string getMapDescription() const; + MetaString getMapDescription() const; void initPrisonsRemaining(); void initQuestArtsRemaining(); diff --git a/lib/texts/MetaString.cpp b/lib/texts/MetaString.cpp index 4ed57ce22..dfbf5f57a 100644 --- a/lib/texts/MetaString.cpp +++ b/lib/texts/MetaString.cpp @@ -13,6 +13,7 @@ #include "CArtHandler.h" #include "CCreatureHandler.h" #include "CCreatureSet.h" +#include "entities/faction/CFaction.h" #include "texts/CGeneralTextHandler.h" #include "CSkillHandler.h" #include "GameConstants.h" @@ -387,6 +388,11 @@ void MetaString::replaceName(const ArtifactID & id) replaceTextID(id.toEntity(VLC)->getNameTextID()); } +void MetaString::replaceName(const FactionID & id) +{ + replaceTextID(id.toEntity(VLC)->getNameTextID()); +} + void MetaString::replaceName(const MapObjectID& id) { replaceTextID(VLC->objtypeh->getObjectName(id, 0)); diff --git a/lib/texts/MetaString.h b/lib/texts/MetaString.h index e6ae5fd3f..d55cc279e 100644 --- a/lib/texts/MetaString.h +++ b/lib/texts/MetaString.h @@ -21,6 +21,7 @@ class MapObjectSubID; class PlayerColor; class SecondarySkill; class SpellID; +class FactionID; class GameResID; using TQuantity = si32; @@ -97,6 +98,7 @@ public: void replacePositiveNumber(int64_t txt); void replaceName(const ArtifactID & id); + void replaceName(const FactionID& id); void replaceName(const MapObjectID& id); void replaceName(const PlayerColor& id); void replaceName(const SecondarySkill& id); From 638bc174c3e7ee10d1efdb7ffd3fe92a99888408 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Oct 2024 21:23:11 +0000 Subject: [PATCH 426/726] Fix crash on exporting maps for translation --- client/ClientCommandManager.cpp | 13 ++++++++++--- lib/filesystem/CCompressedStream.cpp | 3 +++ lib/mapping/MapFormatH3M.cpp | 3 +++ lib/mapping/MapFormatJson.cpp | 2 -- lib/mapping/MapReaderH3M.cpp | 8 +++++--- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 203ea1c68..271c99903 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -254,9 +254,16 @@ void ClientCommandManager::handleTranslateMapsCommand() logGlobal->info("Loading campaigns for export"); for (auto const & campaignName : campaignList) { - loadedCampaigns.push_back(CampaignHandler::getCampaign(campaignName.getName())); - for (auto const & part : loadedCampaigns.back()->allScenarios()) - loadedCampaigns.back()->getMap(part, nullptr); + try + { + loadedCampaigns.push_back(CampaignHandler::getCampaign(campaignName.getName())); + for (auto const & part : loadedCampaigns.back()->allScenarios()) + loadedCampaigns.back()->getMap(part, nullptr); + } + catch(std::exception & e) + { + logGlobal->warn("Campaign %s is invalid. Message: %s", campaignName.getName(), e.what()); + } } std::map> textsByMod; diff --git a/lib/filesystem/CCompressedStream.cpp b/lib/filesystem/CCompressedStream.cpp index 9837c78bc..8c3970e1f 100644 --- a/lib/filesystem/CCompressedStream.cpp +++ b/lib/filesystem/CCompressedStream.cpp @@ -136,6 +136,9 @@ si64 CCompressedStream::readMore(ui8 *data, si64 size) { if (inflateState->avail_in == 0) { + if (gzipStream == nullptr) + throw std::runtime_error("Potentially truncated gzip file"); + //inflate ran out of available data or was not initialized yet // get new input data and update state accordingly si64 availSize = gzipStream->read(compressedBuffer.data(), compressedBuffer.size()); diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 16e02db92..4b040801e 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -208,6 +208,9 @@ void CMapLoaderH3M::readHeader() // optimization - load mappings only once to avoid slow parsing of map headers for map list static const std::map identifierMappers = generateMappings(); + if (!identifierMappers.count(mapHeader->version)) + throw std::runtime_error("Unsupported map format! Format ID " + std::to_string(static_cast(mapHeader->version))); + const MapIdentifiersH3M & identifierMapper = identifierMappers.at(mapHeader->version); reader->setIdentifierRemapper(identifierMapper); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index efbce4560..a5e33a325 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1014,8 +1014,6 @@ void CMapLoaderJson::readTerrain() const JsonNode underground = getFromArchive(TERRAIN_FILE_NAMES[1]); readTerrainLevel(underground, 1); } - - map->calculateWaterContent(); } CMapLoaderJson::MapObjectLoader::MapObjectLoader(CMapLoaderJson * _owner, JsonMap::value_type & json): diff --git a/lib/mapping/MapReaderH3M.cpp b/lib/mapping/MapReaderH3M.cpp index a2fec4e04..21c2a8771 100644 --- a/lib/mapping/MapReaderH3M.cpp +++ b/lib/mapping/MapReaderH3M.cpp @@ -410,9 +410,11 @@ bool MapReaderH3M::readBool() int8_t MapReaderH3M::readInt8Checked(int8_t lowerLimit, int8_t upperLimit) { int8_t result = readInt8(); - assert(result >= lowerLimit); - assert(result <= upperLimit); - return std::clamp(result, lowerLimit, upperLimit); + int8_t resultClamped = std::clamp(result, lowerLimit, upperLimit); + if (result != resultClamped) + logGlobal->warn("Map contains out of range value %d! Expected %d-%d", static_cast(result), static_cast(lowerLimit), static_cast(upperLimit)); + + return resultClamped; } uint8_t MapReaderH3M::readUInt8() From 12463333506f137241ec524244d68ef8c7df829d Mon Sep 17 00:00:00 2001 From: godric3 Date: Sat, 26 Oct 2024 12:15:40 +0200 Subject: [PATCH 427/726] Initialize town's `possibleSpells` only if hasn't been initialized already(for example when copying existing town) --- mapeditor/inspector/inspector.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index e4516fd9c..d68b8693f 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -172,10 +172,12 @@ void Initializer::initialize(CGTownInstance * o) if(lvl > 2) o->addBuilding(BuildingID::CASTLE); if(lvl > 3) o->addBuilding(BuildingID::CAPITOL); - for(auto const & spell : VLC->spellh->objects) //add all regular spells to town + if(o->possibleSpells.empty()) { - if(!spell->isSpecial() && !spell->isCreatureAbility()) - o->possibleSpells.push_back(spell->id); + for(auto const & spellId : VLC->spellh->getDefaultAllowed()) //add all regular spells to town + { + o->possibleSpells.push_back(spellId); + } } } From 152962354dab070b6a180544c83c513949c089ed Mon Sep 17 00:00:00 2001 From: godric3 Date: Sat, 26 Oct 2024 12:20:13 +0200 Subject: [PATCH 428/726] Don't paste objects that cannot be placed, show warning only if error occurred --- mapeditor/mapcontroller.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 167ca42cc..fb4e18321 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -369,6 +369,7 @@ void MapController::pasteFromClipboard(int level) if (!canPlaceObject(level, obj, errorMsg)) { errors.push_back(std::move(errorMsg)); + continue; } auto newPos = objUniquePtr->pos + shift; if(_map->isInTheMap(newPos)) @@ -380,8 +381,8 @@ void MapController::pasteFromClipboard(int level) _scenes[level]->selectionObjectsView.selectObject(obj); _mapHandler->invalidate(obj); } - - QMessageBox::warning(main, QObject::tr("Can't place object"), errors.join('\n')); + if(!errors.isEmpty()) + QMessageBox::warning(main, QObject::tr("Can't place object"), errors.join('\n')); _scenes[level]->objectsView.draw(); _scenes[level]->passabilityView.update(); From 3b725947431b159874e484b8a7bbc097986c28fe Mon Sep 17 00:00:00 2001 From: kdmcser Date: Fri, 25 Oct 2024 23:21:32 +0800 Subject: [PATCH 429/726] add support for soft dependencies --- AUTHORS.h | 1 + Mods/vcmi/config/vcmi/chinese.json | 1 + Mods/vcmi/config/vcmi/english.json | 1 + config/schemas/mod.json | 5 +++ docs/modders/Mod_File_Format.md | 6 +++ lib/modding/CModHandler.cpp | 61 ++++++++++++++++++++++++++++-- lib/modding/CModHandler.h | 2 + lib/modding/CModInfo.cpp | 1 + lib/modding/CModInfo.h | 3 ++ lib/modding/ContentTypeHandler.cpp | 4 +- 10 files changed, 81 insertions(+), 4 deletions(-) diff --git a/AUTHORS.h b/AUTHORS.h index a9ff41e66..fb6552959 100644 --- a/AUTHORS.h +++ b/AUTHORS.h @@ -46,6 +46,7 @@ const std::vector> contributors = { { "Developing", "", "vmarkovtsev", "" }, { "Developing", "Tom Zielinski", "Warmonger", "Warmonger@vp.pl" }, { "Developing", "Xiaomin Ding", "", "dingding303@gmail.com" }, + { "Developing", "Fenghuang Rumeng", "kdmcser", "zqtndfj@gmail.com" }, { "Testing", "Ben Yan", "by003", "benyan9110@gmail.com," }, { "Testing", "", "Misiokles", "" }, diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 44960a60a..5e23787f1 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -149,6 +149,7 @@ "vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}", "vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}", "vcmi.server.errors.modNoDependency" : "读取mod包 {'%s'}失败!\n 需要的mod {'%s'} 没有安装或无效!\n", + "vcmi.server.errors.modDependencyLoop" : "读取mod包 {'%s'}失败!\n 这个mod可能存在循环(软)依赖!", "vcmi.server.errors.modConflict" : "读取的mod包 {'%s'}无法运行!\n 与另一个mod {'%s'}冲突!\n", "vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 2cb7b5772..139a29d3d 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -159,6 +159,7 @@ "vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", "vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n", + "vcmi.server.errors.modDependencyLoop" : "Failed to load mod {'%s'}!\n It maybe in a (soft) dependency loop.", "vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!", diff --git a/config/schemas/mod.json b/config/schemas/mod.json index 76d174c28..d98cb4ab3 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -122,6 +122,11 @@ "description" : "List of mods that are required to run this one", "items" : { "type" : "string" } }, + "softDepends" : { + "type" : "array", + "description" : "List of mods if they are enabled, should be loaded before this one. This mod will overwrite any conflicting items from its soft dependency mods", + "items" : { "type" : "string" } + }, "conflicts" : { "type" : "array", "description" : "List of mods that can't be enabled in the same time as this one", diff --git a/docs/modders/Mod_File_Format.md b/docs/modders/Mod_File_Format.md index fd4354fa9..3379ba7f2 100644 --- a/docs/modders/Mod_File_Format.md +++ b/docs/modders/Mod_File_Format.md @@ -48,6 +48,12 @@ [ "baseMod" ], + + // List of mods if they are enabled, should be loaded before this one. This mod will overwrite any conflicting items from its soft dependency mods. + "softDepends" : + [ + "baseMod" + ], // List of mods that can't be enabled in the same time as this one "conflicts" : diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 2f2780c5b..f13b134cd 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -87,8 +87,9 @@ std::vector CModHandler::validateAndSortDependencies(std::vector sortedValidMods; // Vector keeps order of elements (LIFO) sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements + std::set notResolvedModIDs(modsToResolve.begin(), modsToResolve.end()); // Use a set for validation for performance reason - // Mod is resolved if it has not dependencies or all its dependencies are already resolved + // Mod is resolved if it has no dependencies or all its dependencies are already resolved auto isResolved = [&](const CModInfo & mod) -> bool { if(mod.dependencies.size() > resolvedModIDs.size()) @@ -100,6 +101,12 @@ std::vector CModHandler::validateAndSortDependencies(std::vector CModHandler::validateAndSortDependencies(std::vector CModHandler::validateAndSortDependencies(std::vector appendTextID("vcmi.server.errors.modDependencyLoop"); + if (allMods.count(brokenModID)) + modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); + else + modLoadErrors->replaceRawString(brokenModID); + } + } return sortedValidMods; } @@ -352,6 +382,9 @@ void CModHandler::loadModFilesystems() if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) continue; + if (getModSoftDependencies(leftModName).count(rightModName) || getModSoftDependencies(rightModName).count(leftModName)) + continue; + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); @@ -417,6 +450,28 @@ std::set CModHandler::getModDependencies(const TModID & modId, bool & is return {}; } +std::set CModHandler::getModSoftDependencies(const TModID & modId) const +{ + auto it = allMods.find(modId); + if(it != allMods.end()) + return it->second.softDependencies; + logMod->error("Mod not found: '%s'", modId); + return {}; +} + +std::set CModHandler::getModEnabledSoftDependencies(const TModID & modId) const +{ + std::set softDependencies = getModSoftDependencies(modId); + for (auto it = softDependencies.begin(); it != softDependencies.end();) + { + if (allMods.find(*it) == allMods.end()) + it = softDependencies.erase(it); + else + it++; + } + return softDependencies; +} + void CModHandler::initializeConfig() { VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"])); diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index fab2d319b..0f305792f 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -66,6 +66,8 @@ public: std::set getModDependencies(const TModID & modId) const; std::set getModDependencies(const TModID & modId, bool & isModFound) const; + std::set getModSoftDependencies(const TModID & modId) const; + std::set getModEnabledSoftDependencies(const TModID & modId) const; /// returns list of all (active) mods std::vector getAllMods() const; diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp index 66e7421c7..fb0778f6f 100644 --- a/lib/modding/CModInfo.cpp +++ b/lib/modding/CModInfo.cpp @@ -43,6 +43,7 @@ CModInfo::CModInfo(): CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config): identifier(identifier), dependencies(readModList(config["depends"])), + softDependencies(readModList(config["softDepends"])), conflicts(readModList(config["conflicts"])), explicitlyEnabled(false), implicitlyEnabled(true), diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h index 3d6b40320..d5c077167 100644 --- a/lib/modding/CModInfo.h +++ b/lib/modding/CModInfo.h @@ -44,6 +44,9 @@ public: /// list of mods that should be loaded before this one std::set dependencies; + /// list of mods if they are enabled, should be loaded before this one. this mod will overwrite any conflicting items from its soft dependency mods + std::set softDependencies; + /// list of mods that can't be used in the same time as this one std::set conflicts; diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index c5f85add0..ac6bd0abe 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -201,8 +201,10 @@ void ContentTypeHandler::afterLoadFinalization() for (auto const & conflictModEntry: conflictModData.Struct()) conflictingMods.insert(conflictModEntry.first); - for (auto const & modID : conflictingMods) + for (auto const & modID : conflictingMods) { resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); + resolvedConflicts.merge(VLC->modh->getModEnabledSoftDependencies(modID)); + } vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);}); From e5739e49dabba405cffadb3849fabd3a8af702af Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 11:39:06 +0000 Subject: [PATCH 430/726] Fix translation of bank names --- lib/mapObjects/CBank.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 86ab8c876..858281741 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -144,7 +144,7 @@ void CBank::onHeroVisit(const CGHeroInstance * h) const bd.player = h->getOwner(); bd.text.appendLocalString(EMetaText::ADVOB_TXT, 32); bd.components = getPopupComponents(h->getOwner()); - bd.text.replaceRawString(getObjectName()); + bd.text.replaceTextID(getObjectHandler()->getNameTextID()); cb->showBlockingDialog(this, &bd); } @@ -158,7 +158,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const if (!bankConfig) { iw.text.appendRawString(VLC->generaltexth->advobtxt[33]);// This was X, now is completely empty - iw.text.replaceRawString(getObjectName()); + iw.text.replaceTextID(getObjectHandler()->getNameTextID()); cb->showInfoDialog(&iw); } From 7e66bd4a901d0ead510f47b153b01f324ba87a9e Mon Sep 17 00:00:00 2001 From: godric3 Date: Sat, 26 Oct 2024 13:48:12 +0200 Subject: [PATCH 431/726] Don't use separate versioning for map editor --- mapeditor/StdInc.h | 1 - mapeditor/mainwindow.cpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mapeditor/StdInc.h b/mapeditor/StdInc.h index 34532b7ff..4ff74c6b0 100644 --- a/mapeditor/StdInc.h +++ b/mapeditor/StdInc.h @@ -11,7 +11,6 @@ #include "../Global.h" -#define VCMI_EDITOR_VERSION "0.2" #define VCMI_EDITOR_NAME "VCMI Map Editor" #include diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index eeaa8fd31..723c3807f 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -182,6 +182,7 @@ MainWindow::MainWindow(QWidget* parent) : console = new CConsoleHandler(); logConfig = new CBasicLogConfigurator(logPath, console); logConfig->configureDefault(); + logGlobal->info("Starting map editor of '%s'", GameConstants::VCMI_VERSION); logGlobal->info("The log file will be saved to %s", logPath); //init @@ -317,7 +318,7 @@ void MainWindow::setStatusMessage(const QString & status) void MainWindow::setTitle() { - QString title = QString("%1%2 - %3 (v%4)").arg(filename, unsaved ? "*" : "", VCMI_EDITOR_NAME, VCMI_EDITOR_VERSION); + QString title = QString("%1%2 - %3 (%4)").arg(filename, unsaved ? "*" : "", VCMI_EDITOR_NAME, GameConstants::VCMI_VERSION.c_str()); setWindowTitle(title); } From 565c02d61c8aa92de801f50cc6303879cf2c291b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 11:50:08 +0000 Subject: [PATCH 432/726] Added 'translate missing' command for convenience --- client/ClientCommandManager.cpp | 13 ++++++++----- client/ClientCommandManager.h | 2 +- docs/players/Cheat_Codes.md | 1 + docs/translators/Translations.md | 2 ++ lib/modding/CModHandler.cpp | 4 ++-- lib/texts/TextLocalizationContainer.cpp | 18 +++++++++++++----- lib/texts/TextLocalizationContainer.h | 8 +++++--- 7 files changed, 32 insertions(+), 16 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 271c99903..58aa38af1 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -185,12 +185,12 @@ void ClientCommandManager::handleRedrawCommand() GH.windows().totalRedraw(); } -void ClientCommandManager::handleTranslateGameCommand() +void ClientCommandManager::handleTranslateGameCommand(bool onlyMissing) { std::map> textsByMod; - VLC->generaltexth->exportAllTexts(textsByMod); + VLC->generaltexth->exportAllTexts(textsByMod, onlyMissing); - const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation"; + const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / ( onlyMissing ? "translationMissing" : "translation"); boost::filesystem::create_directories(outPath); for(const auto & modEntry : textsByMod) @@ -267,7 +267,7 @@ void ClientCommandManager::handleTranslateMapsCommand() } std::map> textsByMod; - VLC->generaltexth->exportAllTexts(textsByMod); + VLC->generaltexth->exportAllTexts(textsByMod, false); const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation"; boost::filesystem::create_directories(outPath); @@ -598,7 +598,10 @@ void ClientCommandManager::processCommand(const std::string & message, bool call handleRedrawCommand(); else if(message=="translate" || message=="translate game") - handleTranslateGameCommand(); + handleTranslateGameCommand(false); + + else if(message=="translate missing") + handleTranslateGameCommand(true); else if(message=="translate maps") handleTranslateMapsCommand(); diff --git a/client/ClientCommandManager.h b/client/ClientCommandManager.h index 8a4caeacf..45d027075 100644 --- a/client/ClientCommandManager.h +++ b/client/ClientCommandManager.h @@ -46,7 +46,7 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a void handleRedrawCommand(); // Extracts all translateable game texts into Translation directory, separating files on per-mod basis - void handleTranslateGameCommand(); + void handleTranslateGameCommand(bool onlyMissing); // Extracts all translateable texts from maps and campaigns into Translation directory, separating files on per-mod basis void handleTranslateMapsCommand(); diff --git a/docs/players/Cheat_Codes.md b/docs/players/Cheat_Codes.md index f5a915562..288c9f842 100644 --- a/docs/players/Cheat_Codes.md +++ b/docs/players/Cheat_Codes.md @@ -121,6 +121,7 @@ Below a list of supported commands, with their arguments wrapped in `<>` #### Extract commands `translate` - save game texts into json files +`translate missing` - save untranslated game texts into json files `translate maps` - save map and campaign texts into json files `get config` - save game objects data into json files `get scripts` - dumps lua script stuff into files (currently inactive due to scripting disabled for default builds) diff --git a/docs/translators/Translations.md b/docs/translators/Translations.md index 89e6cbba8..9217b1e8c 100644 --- a/docs/translators/Translations.md +++ b/docs/translators/Translations.md @@ -136,6 +136,8 @@ After that, start Launcher, switch to Help tab and open "log files directory". Y If your mod also contains maps or campaigns that you want to translate, then use '/translate maps' command instead. +If you want to update existing translation, you can use '/translate missing' command that will export only strings that were not translated + ### Translating mod information In order to display information in Launcher in language selected by user add following block into your `mod.json`: ``` diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 2f2780c5b..5afd59111 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -446,8 +446,8 @@ void CModHandler::loadTranslation(const TModID & modName) JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]); JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]); - VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation); - VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation); + VLC->generaltexth->loadTranslationOverrides(modName, modBaseLanguage, baseTranslation); + VLC->generaltexth->loadTranslationOverrides(modName, preferredLanguage, extraTranslation); } void CModHandler::load() diff --git a/lib/texts/TextLocalizationContainer.cpp b/lib/texts/TextLocalizationContainer.cpp index eff0a692e..e22e7d3c4 100644 --- a/lib/texts/TextLocalizationContainer.cpp +++ b/lib/texts/TextLocalizationContainer.cpp @@ -22,7 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN std::recursive_mutex TextLocalizationContainer::globalTextMutex; -void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) +void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) { std::lock_guard globalLock(globalTextMutex); @@ -42,6 +42,11 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo entry.identifierModContext = modContext; entry.baseStringModContext = modContext; } + else + { + if (language == VLC->generaltexth->getPreferredLanguage()) + entry.overriden = true; + } } else { @@ -127,10 +132,10 @@ void TextLocalizationContainer::registerString(const std::string & identifierMod } } -void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config) +void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const std::string & language, const JsonNode & config) { for(const auto & node : config.Struct()) - registerStringOverride(modContext, node.first, node.second.String()); + registerStringOverride(modContext, node.first, node.second.String(), language); } bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const @@ -140,15 +145,18 @@ bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) con return stringsLocalizations.count(UID.get()); } -void TextLocalizationContainer::exportAllTexts(std::map> & storage) const +void TextLocalizationContainer::exportAllTexts(std::map> & storage, bool onlyMissing) const { std::lock_guard globalLock(globalTextMutex); for (auto const & subContainer : subContainers) - subContainer->exportAllTexts(storage); + subContainer->exportAllTexts(storage, onlyMissing); for (auto const & entry : stringsLocalizations) { + if (onlyMissing && entry.second.overriden) + continue; + std::string textToWrite; std::string modName = entry.second.baseStringModContext; diff --git a/lib/texts/TextLocalizationContainer.h b/lib/texts/TextLocalizationContainer.h index cde8e070e..523335c57 100644 --- a/lib/texts/TextLocalizationContainer.h +++ b/lib/texts/TextLocalizationContainer.h @@ -32,6 +32,8 @@ protected: /// Different from identifierModContext if mod has modified object from another mod (e.g. rebalance mods) std::string baseStringModContext; + bool overriden = false; + template void serialize(Handler & h) { @@ -47,7 +49,7 @@ protected: std::vector subContainers; /// add selected string to internal storage as high-priority strings - void registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); + void registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language); std::string getModLanguage(const std::string & modContext); @@ -57,7 +59,7 @@ protected: public: /// Loads translation from provided json /// Any entries loaded by this will have priority over texts registered normally - void loadTranslationOverrides(const std::string & modContext, JsonNode const & file); + void loadTranslationOverrides(const std::string & modContext, const std::string & language, JsonNode const & file); /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const JsonNode & localized); @@ -77,7 +79,7 @@ public: /// Debug method, returns all currently stored texts /// Format: [mod ID][string ID] -> human-readable text - void exportAllTexts(std::map> & storage) const; + void exportAllTexts(std::map> & storage, bool onlyMissing) const; /// Add or override subcontainer which can store identifiers void addSubContainer(const TextLocalizationContainer & container); From 87d841db836b48e3cf0efd4080f02a5ed0e6987f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 12:55:10 +0000 Subject: [PATCH 433/726] Moved translatable strings to VCMI mod, spaces -> tabs --- Mods/vcmi/config/vcmi/spells.json | 66 ++ Mods/vcmi/mod.json | 1 + config/spells/moats.json | 1460 ++++++++++++++--------------- config/spells/other.json | 4 +- config/spells/vcmiAbility.json | 14 +- 5 files changed, 806 insertions(+), 739 deletions(-) create mode 100644 Mods/vcmi/config/vcmi/spells.json diff --git a/Mods/vcmi/config/vcmi/spells.json b/Mods/vcmi/config/vcmi/spells.json new file mode 100644 index 000000000..d8b7daa26 --- /dev/null +++ b/Mods/vcmi/config/vcmi/spells.json @@ -0,0 +1,66 @@ +{ + "core:summonDemons" : { + "name": "Summon Demons" + }, + "core:firstAid" : { + "name": "First Aid" + }, + "core:catapultShot" : { + "name": "Catapult shot" + }, + "core:cyclopsShot" : { + "name": "Siege shot" + }, + + "core:fireWallTrigger" : { + "name" : "Fire Wall" + }, + "core:landMineTrigger" : { + "name" : "Land Mine", + }, + "core:castleMoatTrigger" : { + "name": "Moat" + }, + "core:castleMoat": { + "name": "Moat" + }, + "core:rampartMoatTrigger" : { + "name": "Brambles" + }, + "core:rampartMoat": { + "name": "Brambles" + }, + "core:towerMoat": { + "name": "Land Mine" + }, + "core:infernoMoatTrigger" : { + "name": "Lava" + }, + "core:infernoMoat": { + "name": "Lava" + }, + "core:necropolisMoatTrigger" : { + "name": "Boneyard" + }, + "core:necropolisMoat": { + "name": "Boneyard" + }, + "core:dungeonMoatTrigger" : { + "name": "Boiling Oil" + }, + "core:dungeonMoat": { + "name": "Boiling Oil" + }, + "core:strongholdMoatTrigger" : { + "name": "Wooden Spikes" + }, + "core:strongholdMoat": { + "name": "Wooden Spikes" + }, + "core:fortressMoatTrigger" : { + "name": "Boiling Tar" + }, + "core:fortressMoat": { + "name": "Boiling Tar" + } +} diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index 13ccbda7d..b1b6ba9dd 100644 --- a/Mods/vcmi/mod.json +++ b/Mods/vcmi/mod.json @@ -127,6 +127,7 @@ "factions" : [ "config/vcmi/towerFactions" ], "creatures" : [ "config/vcmi/towerCreature" ], + "spells" : [ "config/vcmi/spells" ], "translations" : [ "config/vcmi/english.json" diff --git a/config/spells/moats.json b/config/spells/moats.json index 71eea7fe8..5b864d9d2 100644 --- a/config/spells/moats.json +++ b/config/spells/moats.json @@ -1,732 +1,732 @@ { - "castleMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Moat", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "castleMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Moat", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:castleMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 70, - "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "rampartMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Brambles", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "rampartMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Brambles", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:rampartMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 70, - "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "towerMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Land Mine", - "school" : {}, - "level": 3, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : true, - "trap" : false, - "triggerAbility" : "core:landMineTrigger", - "dispellable" : true, - "removeOnTrigger" : true, - "moatDamage" : 150, - "moatHexes" : [[11], [28], [44], [61], [77], [111], [129], [146], [164], [181]], - "defender" :{ - "animation" : "C09SPF1", - "appearAnimation" : "C09SPF0", - "appearSound" : "LANDMINE" - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "infernoMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Lava", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "infernoMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Lava", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:infernoMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 90, - "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "necropolisMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Boneyard", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "necropolisMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Boneyard", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:necropolisMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 70, - "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "dungeonMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Boiling Oil", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "dungeonMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Boiling Oil", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:dungeonMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 90, - "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "strongholdMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Wooden Spikes", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "strongholdMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Wooden Spikes", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:strongholdMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 70, - "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - }, - "fortressMoatTrigger" : - { - "targetType" : "CREATURE", - "type": "ability", - "name": "Boiling Tar", - "school": {}, - "level": 0, - "power": 0, - "gainChance": {}, - "levels" : { - "base": { - "power" : 0, - "range" : "0", - "description" : "", //For validation - "cost" : 0, //For validation - "aiValue" : 0, //For validation - "battleEffects" : { - "directDamage" : { - "type":"core:damage" - } - }, - "targetModifier":{"smart":false} - }, - "none" : { - }, - "basic" : { - }, - "advanced" : { - }, - "expert" : { - } - }, - "flags" : { - "damage": true, - "negative": true, - "nonMagical" : true, - "special": true - }, - "targetCondition" : { - } - }, - "fortressMoat": { - "targetType" : "NO_TARGET", - "type": "ability", - "name": "Boiling Tar", - "school" : {}, - "level": 0, - "power": 0, - "defaultGainChance": 0, - "gainChance": {}, - "levels" : { - "base":{ - "description" : "", - "aiValue" : 0, - "power" : 0, - "cost" : 0, - "targetModifier":{"smart":false}, - "battleEffects":{ - "moat":{ - "type":"core:moat", - "hidden" : false, - "trap" : true, - "triggerAbility" : "core:fortressMoatTrigger", - "dispellable" : false, - "removeOnTrigger" : false, - "moatDamage" : 90, - "moatHexes" : [[10, 11, 27, 28, 43, 44, 60, 61, 76, 77, 94, 110, 111, 128, 129, 145, 146, 163, 164, 180, 181]], - "defender" :{ - }, - "bonus" :{ - "primarySkill" : { - "val" : -3, - "type" : "PRIMARY_SKILL", - "subtype" : "primarySkill.defence", - "valueType" : "ADDITIVE_VALUE" - } - } - } - }, - "range" : "X" - }, - "none" :{ - }, - "basic" :{ - }, - "advanced" :{ - }, - "expert" :{ - } - }, - "flags" : { - "nonMagical" : true, - "indifferent": true - }, - "targetCondition" : { - } - } + "castleMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "castleMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:castleMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 70, + "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "rampartMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "rampartMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:rampartMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 70, + "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "towerMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 3, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : true, + "trap" : false, + "triggerAbility" : "core:landMineTrigger", + "dispellable" : true, + "removeOnTrigger" : true, + "moatDamage" : 150, + "moatHexes" : [[11], [28], [44], [61], [77], [111], [129], [146], [164], [181]], + "defender" :{ + "animation" : "C09SPF1", + "appearAnimation" : "C09SPF0", + "appearSound" : "LANDMINE" + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "infernoMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "infernoMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:infernoMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 90, + "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "necropolisMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "necropolisMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:necropolisMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 70, + "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "dungeonMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "dungeonMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:dungeonMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 90, + "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "strongholdMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "strongholdMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:strongholdMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 70, + "moatHexes" : [[11, 28, 44, 61, 77, 111, 129, 146, 164, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + }, + "fortressMoatTrigger" : + { + "targetType" : "CREATURE", + "type": "ability", + "name": "", + "school": {}, + "level": 0, + "power": 0, + "gainChance": {}, + "levels" : { + "base": { + "power" : 0, + "range" : "0", + "description" : "", //For validation + "cost" : 0, //For validation + "aiValue" : 0, //For validation + "battleEffects" : { + "directDamage" : { + "type":"core:damage" + } + }, + "targetModifier":{"smart":false} + }, + "none" : { + }, + "basic" : { + }, + "advanced" : { + }, + "expert" : { + } + }, + "flags" : { + "damage": true, + "negative": true, + "nonMagical" : true, + "special": true + }, + "targetCondition" : { + } + }, + "fortressMoat": { + "targetType" : "NO_TARGET", + "type": "ability", + "name": "", + "school" : {}, + "level": 0, + "power": 0, + "defaultGainChance": 0, + "gainChance": {}, + "levels" : { + "base":{ + "description" : "", + "aiValue" : 0, + "power" : 0, + "cost" : 0, + "targetModifier":{"smart":false}, + "battleEffects":{ + "moat":{ + "type":"core:moat", + "hidden" : false, + "trap" : true, + "triggerAbility" : "core:fortressMoatTrigger", + "dispellable" : false, + "removeOnTrigger" : false, + "moatDamage" : 90, + "moatHexes" : [[10, 11, 27, 28, 43, 44, 60, 61, 76, 77, 94, 110, 111, 128, 129, 145, 146, 163, 164, 180, 181]], + "defender" :{ + }, + "bonus" :{ + "primarySkill" : { + "val" : -3, + "type" : "PRIMARY_SKILL", + "subtype" : "primarySkill.defence", + "valueType" : "ADDITIVE_VALUE" + } + } + } + }, + "range" : "X" + }, + "none" :{ + }, + "basic" :{ + }, + "advanced" :{ + }, + "expert" :{ + } + }, + "flags" : { + "nonMagical" : true, + "indifferent": true + }, + "targetCondition" : { + } + } } \ No newline at end of file diff --git a/config/spells/other.json b/config/spells/other.json index 633332d8d..0b339b49d 100644 --- a/config/spells/other.json +++ b/config/spells/other.json @@ -55,7 +55,7 @@ { "targetType" : "CREATURE", "type": "combat", - "name": "Land Mine", + "name": "", "school": { "air": false, @@ -237,7 +237,7 @@ "fireWallTrigger" : { "targetType" : "CREATURE", "type": "combat", - "name": "Fire Wall", + "name": "", "school": { "air": false, diff --git a/config/spells/vcmiAbility.json b/config/spells/vcmiAbility.json index db1212b7e..d34b6c897 100644 --- a/config/spells/vcmiAbility.json +++ b/config/spells/vcmiAbility.json @@ -1,8 +1,8 @@ { - "summonDemons" : { + "summonDemons" : { "type": "ability", "targetType" : "CREATURE", - "name": "Summon Demons", + "name": "", "school" : {}, "level": 2, "power": 50, @@ -46,11 +46,11 @@ "bonus.GARGOYLE" : "absolute" } } - }, - "firstAid" : { + }, + "firstAid" : { "targetType" : "CREATURE", "type": "ability", - "name": "First Aid", + "name": "", "school" : {}, "level": 1, "power": 10, @@ -106,7 +106,7 @@ "catapultShot" : { "targetType" : "LOCATION", "type": "ability", - "name": "Catapult shot", + "name": "", "school" : {}, "level": 1, "power": 1, @@ -187,7 +187,7 @@ "cyclopsShot" : { "targetType" : "LOCATION", "type": "ability", - "name": "Siege shot", + "name": "", "school" : {}, "level": 1, "power": 1, From d1164ab9a9bcab912f14bdaade1f58258541a6ef Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 13:09:09 +0000 Subject: [PATCH 434/726] Integrated exchange window translation into vcmi --- Mods/vcmi/config/vcmi/english.json | 6 ++++ client/windows/CExchangeWindow.cpp | 54 ++++++++++++++++++++++++------ lib/texts/CGeneralTextHandler.cpp | 7 +--- lib/texts/CGeneralTextHandler.h | 2 -- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 600c382c8..58c430e31 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -42,6 +42,12 @@ "vcmi.heroOverview.secondarySkills" : "Secondary Skills", "vcmi.heroOverview.spells" : "Spells", + "vcmi.quickExchange.moveUnit" : "Move Unit", + "vcmi.quickExchange.moveAllUnits" : "Move All Units", + "vcmi.quickExchange.swapAllUnits" : "Swap Armies", + "vcmi.quickExchange.moveAllArtifacts" : "Move All Artifacts", + "vcmi.quickExchange.swapAllArtifacts" : "Swap Artifact" + "vcmi.radialWheel.mergeSameUnit" : "Merge same creatures", "vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures", "vcmi.radialWheel.splitSingleUnit" : "Split off single creature", diff --git a/client/windows/CExchangeWindow.cpp b/client/windows/CExchangeWindow.cpp index 197ef9795..8d0e332f2 100644 --- a/client/windows/CExchangeWindow.cpp +++ b/client/windows/CExchangeWindow.cpp @@ -192,18 +192,52 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, if(qeLayout) { - buttonMoveUnitsFromLeftToRight = std::make_shared(Point(325, 118), AnimationPath::builtin("quick-exchange/armRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]), [this](){ this->moveUnitsShortcut(true); }); - buttonMoveUnitsFromRightToLeft = std::make_shared(Point(425, 118), AnimationPath::builtin("quick-exchange/armLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]), [this](){ this->moveUnitsShortcut(false); }); - buttonMoveArtifactsFromLeftToRight = std::make_shared(Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), [this](){ this->moveArtifactsCallback(true);}); - buttonMoveArtifactsFromRightToLeft = std::make_shared(Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), [this](){ this->moveArtifactsCallback(false);}); + buttonMoveUnitsFromLeftToRight = std::make_shared( + Point(325, 118), + AnimationPath::builtin("quick-exchange/armRight.DEF"), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllUnits")), + [this](){ this->moveUnitsShortcut(true); }); - exchangeUnitsButton = std::make_shared(Point(377, 118), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[2]), [this](){ controller.swapArmy(); }); - exchangeArtifactsButton = std::make_shared(Point(377, 154), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[4]), [this](){ this->swapArtifactsCallback(); }); + buttonMoveUnitsFromRightToLeft = std::make_shared( + Point(425, 118), + AnimationPath::builtin("quick-exchange/armLeft.DEF"), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllUnits")), + [this](){ this->moveUnitsShortcut(false); }); - backpackButtonLeft = std::make_shared(Point(325, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), + buttonMoveArtifactsFromLeftToRight = std::make_shared( + Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllArtifacts")), + [this](){ this->moveArtifactsCallback(true);}); + + buttonMoveArtifactsFromRightToLeft = std::make_shared( + Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllArtifacts")), + [this](){ this->moveArtifactsCallback(false);}); + + exchangeUnitsButton = std::make_shared( + Point(377, 118), + AnimationPath::builtin("quick-exchange/swapAll.DEF"), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.swapAllUnits")), + [this](){ controller.swapArmy(); }); + + exchangeArtifactsButton = std::make_shared( + Point(377, 154), + AnimationPath::builtin("quick-exchange/swapAll.DEF"), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.swapAllArtifacts")), + [this](){ this->swapArtifactsCallback(); }); + + backpackButtonLeft = std::make_shared( + Point(325, 518), + AnimationPath::builtin("heroBackpack"), + CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), [this](){ this->backpackShortcut(true); }); - backpackButtonRight = std::make_shared(Point(419, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), + + backpackButtonRight = std::make_shared( + Point(419, 518), + AnimationPath::builtin("heroBackpack"), + CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), [this](){ this->backpackShortcut(false); }); + backpackButtonLeft->setOverlay(std::make_shared(ImagePath::builtin("heroWindow/backpackButtonIcon"))); backpackButtonRight->setOverlay(std::make_shared(ImagePath::builtin("heroWindow/backpackButtonIcon"))); @@ -227,7 +261,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, std::make_shared( Point(484 + 35 * i, 154), AnimationPath::builtin("quick-exchange/unitLeft.DEF"), - CButton::tooltip(CGI->generaltexth->qeModCommands[1]), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")), std::bind(&CExchangeController::moveStack, &controller, false, SlotID(i)))); moveUnitFromRightToLeftButtons.back()->block(leftHeroBlock); @@ -235,7 +269,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, std::make_shared( Point(66 + 35 * i, 154), AnimationPath::builtin("quick-exchange/unitRight.DEF"), - CButton::tooltip(CGI->generaltexth->qeModCommands[1]), + CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")), std::bind(&CExchangeController::moveStack, &controller, true, SlotID(i)))); moveUnitFromLeftToRightButtons.back()->block(rightHeroBlock); } diff --git a/lib/texts/CGeneralTextHandler.cpp b/lib/texts/CGeneralTextHandler.cpp index 24041d8cc..dcf9bc40c 100644 --- a/lib/texts/CGeneralTextHandler.cpp +++ b/lib/texts/CGeneralTextHandler.cpp @@ -140,8 +140,7 @@ CGeneralTextHandler::CGeneralTextHandler(): seerEmpty (*this, "core.seerhut.empty" ), seerNames (*this, "core.seerhut.names" ), capColors (*this, "vcmi.capitalColors" ), - znpc00 (*this, "vcmi.znpc00" ), // technically - wog - qeModCommands (*this, "vcmi.quickExchange" ) + znpc00 (*this, "vcmi.znpc00" ) // technically - wog { readToVector("core.vcdesc", "DATA/VCDESC.TXT" ); readToVector("core.lcdesc", "DATA/LCDESC.TXT" ); @@ -166,10 +165,6 @@ CGeneralTextHandler::CGeneralTextHandler(): readToVector("core.mineevnt", "DATA/MINEEVNT.TXT" ); readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" ); - static const std::string QE_MOD_COMMANDS = "DATA/QECOMMANDS.TXT"; - if (CResourceHandler::get()->existsResource(TextPath::builtin(QE_MOD_COMMANDS))) - readToVector("vcmi.quickExchange", QE_MOD_COMMANDS); - { CLegacyConfigParser parser(TextPath::builtin("DATA/RANDTVRN.TXT")); parser.endLine(); diff --git a/lib/texts/CGeneralTextHandler.h b/lib/texts/CGeneralTextHandler.h index b312a1d0e..ff893f05a 100644 --- a/lib/texts/CGeneralTextHandler.h +++ b/lib/texts/CGeneralTextHandler.h @@ -62,8 +62,6 @@ public: LegacyTextContainer fcommands; // fort screen LegacyTextContainer tavernInfo; - LegacyTextContainer qeModCommands; - LegacyHelpContainer zelp; //objects From 72b0062ae331485b95c5b05dbfde0d7740e9a8c0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 14:21:05 +0000 Subject: [PATCH 435/726] Better integration of wog commanders translation --- client/windows/CCreatureWindow.cpp | 22 +++++++++++++++++++--- client/windows/CCreatureWindow.h | 1 + lib/texts/CGeneralTextHandler.cpp | 8 +------- lib/texts/CGeneralTextHandler.h | 2 -- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 293324c3e..ab954026d 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -393,7 +393,7 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i auto getSkillDescription = [this](int skillIndex) -> std::string { - return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + (parent->info->commander->secondarySkills[skillIndex] * 2)]; + return parent->getCommanderSkillDescription(skillIndex, parent->info->commander->secondarySkills[skillIndex]); }; for(int index = ECommander::ATTACK; index <= ECommander::SPELL_POWER; ++index) @@ -905,14 +905,30 @@ std::string CStackWindow::generateStackExpDescription() return expText; } +std::string CStackWindow::getCommanderSkillDescription(int skillIndex, int skillLevel) +{ + constexpr std::array skillNames = { + "attack", + "defence", + "health", + "damage", + "speed", + "magic" + }; + + std::string textID = TextIdentifier("vcmi", "commander", "skill", skillNames.at(skillIndex), skillLevel).get(); + + return CGI->generaltexth->translate(textID); +} + void CStackWindow::setSelection(si32 newSkill, std::shared_ptr newIcon) { auto getSkillDescription = [this](int skillIndex, bool selected) -> std::string { if(selected) - return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + ((info->commander->secondarySkills[skillIndex] + 1) * 2)]; //upgrade description + return getCommanderSkillDescription(skillIndex, info->commander->secondarySkills[skillIndex] + 1); //upgrade description else - return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + (info->commander->secondarySkills[skillIndex] * 2)]; + return getCommanderSkillDescription(skillIndex, info->commander->secondarySkills[skillIndex]); }; auto getSkillImage = [this](int skillIndex) diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h index 4728b3b1f..6f6b04bde 100644 --- a/client/windows/CCreatureWindow.h +++ b/client/windows/CCreatureWindow.h @@ -189,6 +189,7 @@ class CStackWindow : public CWindowObject void init(); std::string generateStackExpDescription(); + std::string getCommanderSkillDescription(int skillIndex, int skillLevel); public: // for battles diff --git a/lib/texts/CGeneralTextHandler.cpp b/lib/texts/CGeneralTextHandler.cpp index dcf9bc40c..2f3cff5e8 100644 --- a/lib/texts/CGeneralTextHandler.cpp +++ b/lib/texts/CGeneralTextHandler.cpp @@ -139,8 +139,7 @@ CGeneralTextHandler::CGeneralTextHandler(): // pseudo-array, that don't have H3 file with same name seerEmpty (*this, "core.seerhut.empty" ), seerNames (*this, "core.seerhut.names" ), - capColors (*this, "vcmi.capitalColors" ), - znpc00 (*this, "vcmi.znpc00" ) // technically - wog + capColors (*this, "vcmi.capitalColors" ) { readToVector("core.vcdesc", "DATA/VCDESC.TXT" ); readToVector("core.lcdesc", "DATA/LCDESC.TXT" ); @@ -293,11 +292,6 @@ CGeneralTextHandler::CGeneralTextHandler(): scenariosCountPerCampaign.push_back(region); } } - if (VLC->engineSettings()->getBoolean(EGameSettings::MODULE_COMMANDERS)) - { - if(CResourceHandler::get()->existsResource(TextPath::builtin("DATA/ZNPC00.TXT"))) - readToVector("vcmi.znpc00", "DATA/ZNPC00.TXT" ); - } } int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t count) const diff --git a/lib/texts/CGeneralTextHandler.h b/lib/texts/CGeneralTextHandler.h index ff893f05a..428eea1b0 100644 --- a/lib/texts/CGeneralTextHandler.h +++ b/lib/texts/CGeneralTextHandler.h @@ -73,8 +73,6 @@ public: //sec skills LegacyTextContainer levels; - //commanders - LegacyTextContainer znpc00; //more or less useful content of that file std::vector findStringsWithPrefix(const std::string & prefix); From ec3acec8ccf3bcc8376294d3c549943bdb9841f0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 14:21:23 +0000 Subject: [PATCH 436/726] Implemented JSON5 line ending escapings --- lib/json/JsonParser.cpp | 59 ++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/lib/json/JsonParser.cpp b/lib/json/JsonParser.cpp index eb45d69d8..32b5de0d2 100644 --- a/lib/json/JsonParser.cpp +++ b/lib/json/JsonParser.cpp @@ -158,40 +158,58 @@ bool JsonParser::extractEscaping(std::string & str) switch(input[pos]) { + case '\r': + if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON5 && input.size() > pos && input[pos+1] == '\n') + { + pos += 2; + return true; + } + break; + case '\n': + if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON5) + { + pos += 1; + return true; + } + break; case '\"': str += '\"'; - break; + pos++; + return true; case '\\': str += '\\'; - break; + pos++; + return true; case 'b': str += '\b'; - break; + pos++; + return true; case 'f': str += '\f'; - break; + pos++; + return true; case 'n': str += '\n'; - break; + pos++; + return true; case 'r': str += '\r'; - break; + pos++; + return true; case 't': str += '\t'; - break; + pos++; + return true; case '/': str += '/'; - break; - default: - return error("Unknown escape sequence!", true); + pos++; + return true; } - return true; + return error("Unknown escape sequence!", true); } bool JsonParser::extractString(std::string & str) { - //TODO: JSON5 - line breaks escaping - if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5) { if(input[pos] != '\"') @@ -216,27 +234,30 @@ bool JsonParser::extractString(std::string & str) pos++; return true; } - if(input[pos] == '\\') // Escaping + else if(input[pos] == '\\') // Escaping { str.append(&input[first], pos - first); pos++; if(pos == input.size()) break; + extractEscaping(str); - first = pos + 1; + first = pos; } - if(input[pos] == '\n') // end-of-line + else if(input[pos] == '\n') // end-of-line { str.append(&input[first], pos - first); return error("Closing quote not found!", true); } - if(static_cast(input[pos]) < ' ') // control character + else if(static_cast(input[pos]) < ' ') // control character { str.append(&input[first], pos - first); - first = pos + 1; + pos++; + first = pos; error("Illegal character in the string!", true); } - pos++; + else + pos++; } return error("Unterminated string!"); } From 939e8b4105b297a77d3d22eb9f77685e93c7d8fb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 26 Oct 2024 14:21:41 +0000 Subject: [PATCH 437/726] Fix json formatting --- Mods/vcmi/config/vcmi/english.json | 2 +- Mods/vcmi/config/vcmi/ukrainian.json | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 58c430e31..c74d63814 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -46,7 +46,7 @@ "vcmi.quickExchange.moveAllUnits" : "Move All Units", "vcmi.quickExchange.swapAllUnits" : "Swap Armies", "vcmi.quickExchange.moveAllArtifacts" : "Move All Artifacts", - "vcmi.quickExchange.swapAllArtifacts" : "Swap Artifact" + "vcmi.quickExchange.swapAllArtifacts" : "Swap Artifact", "vcmi.radialWheel.mergeSameUnit" : "Merge same creatures", "vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 9ce7fa79c..22a609083 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -612,18 +612,5 @@ "core.bonus.WIDE_BREATH.name" : "Широкий подих", "core.bonus.WIDE_BREATH.description" : "Атака широким подихом", "core.bonus.LIMITED_SHOOTING_RANGE.name" : "Обмежена дальність стрільби", - "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів", - - "vcmi.stackExperience.description" : "» S t a c k E x p e r i e n c e D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i", - "vcmi.stackExperience.rank.0" : "Початковий", - "vcmi.stackExperience.rank.1" : "Новачок", - "vcmi.stackExperience.rank.2" : "Підготовлений", - "vcmi.stackExperience.rank.3" : "Досвідчений", - "vcmi.stackExperience.rank.4" : "Випробуваний", - "vcmi.stackExperience.rank.5" : "Ветеран", - "vcmi.stackExperience.rank.6" : "Адепт", - "vcmi.stackExperience.rank.7" : "Експерт", - "vcmi.stackExperience.rank.8" : "Еліта", - "vcmi.stackExperience.rank.9" : "Майстер", - "vcmi.stackExperience.rank.10" : "Профі" + "core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів" } From b7d4ffab799a50062dcb844671046b41e3d034a7 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 27 Oct 2024 09:18:21 +0000 Subject: [PATCH 438/726] Add support for Win7 and 32-bit msvc+vcpkg builds --- .github/workflows/github.yml | 24 ++++++++++++++++++++---- CI/before_install/msvc.sh | 8 +------- CI/install_vcpkg_dependencies.sh | 7 +++++++ CMakePresets.json | 18 ++++++++++++++++++ Global.h | 5 ++++- 5 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 CI/install_vcpkg_dependencies.sh diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 1fec92b84..48107afc5 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -69,7 +69,7 @@ jobs: conan_profile: ios-arm64 conan_prebuilts: dependencies-ios conan_options: --options with_apple_system_libs=True - - platform: msvc + - platform: msvc-x64 os: windows-latest test: 0 pack: 1 @@ -77,6 +77,14 @@ jobs: extension: exe before_install: msvc.sh preset: windows-msvc-release + - platform: msvc-x86 + os: windows-latest + test: 0 + pack: 1 + pack_type: RelWithDebInfo + extension: exe + before_install: msvc.sh + preset: windows-msvc-release-x86 - platform: mingw_x86_64 os: ubuntu-24.04 test: 0 @@ -136,6 +144,10 @@ jobs: if: "${{ matrix.conan_prebuilts != '' }}" run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}' + - name: Install vcpkg Dependencies + if: ${{ startsWith(matrix.platform, 'msvc') }} + run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}' + # ensure the ccache for each PR is separate so they don't interfere with each other # fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found - name: ccache for PRs @@ -232,7 +244,7 @@ jobs: elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]] then cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily" --preset ${{ matrix.preset }} - elif [[ ${{matrix.platform}} != msvc ]] + elif [[ (${{matrix.platform}} != msvc-x64) && (${{matrix.platform}} != msvc-x86) ]] then cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} else @@ -279,6 +291,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} + compression-level: 0 path: | ${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }} @@ -299,6 +312,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} + compression-level: 0 path: | ${{ env.ANDROID_APK_PATH }} @@ -307,19 +321,21 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - aab + compression-level: 0 path: | ${{ env.ANDROID_AAB_PATH }} - name: Upload debug symbols - if: ${{ matrix.platform == 'msvc' }} + if: ${{ startsWith(matrix.platform, 'msvc') }} uses: actions/upload-artifact@v4 with: name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols + compression-level: 9 path: | ${{github.workspace}}/**/*.pdb - name: Upload build - if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }} + if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc-x64' && matrix.platform != 'msvc-x86' && matrix.platform != 'mingw-32' }} continue-on-error: true run: | if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then diff --git a/CI/before_install/msvc.sh b/CI/before_install/msvc.sh index 5388e84f8..4e23175ee 100644 --- a/CI/before_install/msvc.sh +++ b/CI/before_install/msvc.sh @@ -1,10 +1,4 @@ -curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \ - "https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.7/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" -7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" - -#rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug -#mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin -#cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin +#!/usr/bin/env bash DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1) dirname "$DUMPBIN_DIR" > $GITHUB_PATH diff --git a/CI/install_vcpkg_dependencies.sh b/CI/install_vcpkg_dependencies.sh new file mode 100644 index 000000000..637491245 --- /dev/null +++ b/CI/install_vcpkg_dependencies.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +RELEASE_TAG="v1.8" +FILENAME="dependencies-$1" +DOWNLOAD_URL="https://github.com/vcmi/vcmi-deps-windows/releases/download/$RELEASE_TAG/$FILENAME.txz" + +curl -L "$DOWNLOAD_URL" | tar -xf - --xz diff --git a/CMakePresets.json b/CMakePresets.json index 3746ce676..500c48d68 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -154,6 +154,19 @@ } }, + { + "name": "windows-msvc-release-x86", + "displayName": "Windows x86 RelWithDebInfo", + "description": "VCMI RelWithDebInfo build", + "inherits": "default-release", + "generator": "Visual Studio 17 2022", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake", + "CMAKE_POLICY_DEFAULT_CMP0091": "NEW", + "FORCE_BUNDLED_MINIZIP": "ON", + "CMAKE_GENERATOR_PLATFORM": "WIN32" + } + }, { "name": "windows-msvc-release-ccache", "displayName": "Windows x64 RelWithDebInfo with ccache", @@ -382,6 +395,11 @@ "configurePreset": "windows-msvc-release", "inherits": "default-release" }, + { + "name": "windows-msvc-release-x86", + "configurePreset": "windows-msvc-release-x86", + "inherits": "default-release" + }, { "name": "windows-msvc-release-ccache", "configurePreset": "windows-msvc-release-ccache", diff --git a/Global.h b/Global.h index 6fa656a17..5e9b2c681 100644 --- a/Global.h +++ b/Global.h @@ -154,7 +154,10 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #endif #define BOOST_THREAD_DONT_PROVIDE_THREAD_DESTRUCTOR_CALLS_TERMINATE_IF_JOINABLE 1 //need to link boost thread dynamically to avoid https://stackoverflow.com/questions/35978572/boost-thread-interupt-does-not-work-when-crossing-a-dll-boundary -#define BOOST_THREAD_USE_DLL //for example VCAI::finish() may freeze on thread join after interrupt when linking this statically +//for example VCAI::finish() may freeze on thread join after interrupt when linking this statically +#ifndef BOOST_THREAD_USE_DLL +# define BOOST_THREAD_USE_DLL +#endif #define BOOST_BIND_NO_PLACEHOLDERS #if BOOST_VERSION >= 106600 From 2ca6f09621dc833d1196ed5c12523adc04b6e4b2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 27 Oct 2024 10:23:47 +0000 Subject: [PATCH 439/726] Try to speed up msvc CI --- .github/workflows/github.yml | 1 + CI/before_install/msvc.sh | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 48107afc5..7c6eeb463 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -285,6 +285,7 @@ jobs: sleep 3 ((counter++)) done + rm -rf _CPack_Packages - name: Artifacts if: ${{ matrix.pack == 1 }} diff --git a/CI/before_install/msvc.sh b/CI/before_install/msvc.sh index 4e23175ee..82d5d2b5d 100644 --- a/CI/before_install/msvc.sh +++ b/CI/before_install/msvc.sh @@ -1,4 +1,18 @@ #!/usr/bin/env bash -DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1) -dirname "$DUMPBIN_DIR" > $GITHUB_PATH +MSVC_INSTALL_PATH=$(vswhere -latest -property installationPath) +echo "MSVC_INSTALL_PATH = $MSVC_INSTALL_PATH" +echo "Installed toolset versions:" +ls -vr "$MSVC_INSTALL_PATH"/VC/Tools/MSVC + +TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH"/VC/Tools/MSVC/ | head -1) +DUMPBIN_PATH="$MSVC_INSTALL_PATH"/VC/Tools/MSVC/"$TOOLS_DIR"/bin/Hostx64/x64/dumpbin.exe + +# This script should also work, but for some reason is *extremely* slow on Github CI (~7 minutes) +#DUMPBIN_PATH=$(vswhere -latest -find **/dumpbin.exe | head -n 1) +# + +echo "TOOLS_DIR = $TOOLS_DIR" +echo "DUMPBIN_PATH = $DUMPBIN_PATH" + +dirname "$DUMPBIN_PATH" > "$GITHUB_PATH" From 90fd70a3ec216f6ad452d2c415d6c602bb817418 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 27 Oct 2024 09:59:59 -0300 Subject: [PATCH 440/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 3ac8dac68..253c9491c 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -96,8 +96,8 @@ "vcmi.lobby.login.connecting" : "Conectando...", "vcmi.lobby.login.error" : "Erro de conexão: %s", "vcmi.lobby.login.create" : "Nova Conta", - "vcmi.lobby.login.login" : "Login", - "vcmi.lobby.login.as" : "Logar como %s", + "vcmi.lobby.login.login" : "Entrar", + "vcmi.lobby.login.as" : "Entrar como %s", "vcmi.lobby.header.rooms" : "Salas de Jogo - %d", "vcmi.lobby.header.channels" : "Canais de Bate-papo", "vcmi.lobby.header.chat.global" : "Bate-papo Global do Jogo - %s", // %s -> nome do idioma @@ -158,9 +158,9 @@ "vcmi.server.errors.existingProcess" : "Outro processo do servidor VCMI está em execução. Por favor, termine-o antes de iniciar um novo jogo.", "vcmi.server.errors.modsToEnable" : "{Os seguintes mods são necessários}", "vcmi.server.errors.modsToDisable" : "{Os seguintes mods devem ser desativados}", - "vcmi.server.errors.modNoDependency" : "Falha ao carregar o mod {'%s'}!\n Ele depende do mod {'%s'} que não está ativo!\n", - "vcmi.server.errors.modConflict" : "Falha ao carregar o mod {'%s'}!\n Conflita com o mod ativo {'%s'}!\n", - "vcmi.server.errors.unknownEntity" : "Falha ao carregar o salvamento! Entidade desconhecida '%s' encontrada no jogo salvo! O salvamento pode não ser compatível com a versão atualmente instalada dos mods!", + "vcmi.server.errors.modNoDependency" : "Falha ao carregar o mod {'%s'}!\n Ele depende do mod {'%s'}, que não está ativo!\n", + "vcmi.server.errors.modConflict" : "Falha ao carregar o mod {'%s'}!\n Conflito com o mod ativo {'%s'}!\n", + "vcmi.server.errors.unknownEntity" : "Falha ao carregar o jogo salvo! Entidade desconhecida '%s' encontrada no jogo salvo! O jogo salvo pode não ser compatível com a versão atualmente instalada dos mods!", "vcmi.dimensionDoor.seaToLandError" : "Não é possível teleportar do mar para a terra ou vice-versa com uma Porta Dimensional.", From 50e0893f4e7b47057942a68ffe234c6142aa4f65 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 27 Oct 2024 10:00:03 -0300 Subject: [PATCH 441/726] Update portuguese.ts --- launcher/translation/portuguese.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index 07369d655..e102efcbe 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -44,7 +44,7 @@ Check for updates - Verificar por atualizações + Verificar atualizações @@ -54,7 +54,7 @@ Log files directory - Diretório do arquivo de registro + Diretório de arquivos de registro @@ -79,7 +79,7 @@ Project homepage - Página da web do projeto + Página do projeto From 8a62d330323e528f8af6e173ff56d80bcf8b788d Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 27 Oct 2024 11:03:00 -0300 Subject: [PATCH 442/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 253c9491c..bea048b1b 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -42,6 +42,12 @@ "vcmi.heroOverview.secondarySkills" : "Habilid. Secundárias", "vcmi.heroOverview.spells" : "Feitiços", + "vcmi.quickExchange.moveUnit" : "Mover Unidade", + "vcmi.quickExchange.moveAllUnits" : "Mover Todas as Unidades", + "vcmi.quickExchange.swapAllUnits" : "Trocar Exércitos", + "vcmi.quickExchange.moveAllArtifacts" : "Mover Todos os Artefatos", + "vcmi.quickExchange.swapAllArtifacts" : "Trocar Artefato", + "vcmi.radialWheel.mergeSameUnit" : "Mesclar criaturas iguais", "vcmi.radialWheel.fillSingleUnit" : "Preencher com criaturas únicas", "vcmi.radialWheel.splitSingleUnit" : "Dividir uma criatura única", @@ -60,6 +66,16 @@ "vcmi.radialWheel.moveUp" : "Mover para cima", "vcmi.radialWheel.moveDown" : "Mover para baixo", "vcmi.radialWheel.moveBottom" : "Mover para o fundo", + + "vcmi.randomMap.description" : "Mapa criado pelo Gerador de Mapas Aleatórios.\nO modelo foi %s, tamanho %dx%d, níveis %d, jogadores %d, computadores %d, água %s, monstros %s, mapa VCMI", + "vcmi.randomMap.description.isHuman" : ", %s é humano", + "vcmi.randomMap.description.townChoice" : ", a escolha de cidade de %s é %s", + "vcmi.randomMap.description.water.none" : "nenhuma", + "vcmi.randomMap.description.water.normal" : "normal", + "vcmi.randomMap.description.water.islands" : "ilhas", + "vcmi.randomMap.description.monster.weak" : "fraco", + "vcmi.randomMap.description.monster.normal" : "normal", + "vcmi.randomMap.description.monster.strong" : "forte", "vcmi.spellBook.search" : "Procurar...", @@ -535,7 +551,9 @@ "core.seerhut.quest.reachDate.visit.3" : "Fechado até %s.", "core.seerhut.quest.reachDate.visit.4" : "Fechado até %s.", "core.seerhut.quest.reachDate.visit.5" : "Fechado até %s.", - + + "mapObject.core.hillFort.object.description" : "Atualiza criaturas. O custo de atualização para os níveis 1 a 4 é mais vantajoso do que na cidade associada.", + "core.bonus.ADDITIONAL_ATTACK.name" : "Ataque Duplo", "core.bonus.ADDITIONAL_ATTACK.description" : "Ataca duas vezes", "core.bonus.ADDITIONAL_RETALIATION.name" : "Contra-ataques Adicionais", From 10b411aa689c652a8907ceda8aa080b4bb51b964 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 27 Oct 2024 21:17:46 +0200 Subject: [PATCH 443/726] Update launcher/settingsView/csettingsview_moc.cpp --- launcher/settingsView/csettingsview_moc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index c7cc2e1e1..c6c136fbb 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -845,7 +845,6 @@ void CSettingsView::on_buttonScalingAuto_toggled(bool checked) else { ui->spinBoxInterfaceScaling->show(); - ui->spinBoxInterfaceScaling->setDisabled(false); ui->spinBoxInterfaceScaling->setValue(100); } From dace4075e424a54cf9de787e8aade33d88d41da6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 27 Oct 2024 17:45:12 +0000 Subject: [PATCH 444/726] Changes according to review --- .github/workflows/github.yml | 14 ++++++++++---- CI/before_install/msvc.sh | 9 ++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 7c6eeb463..5c9ab602a 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -38,6 +38,7 @@ jobs: os: macos-13 test: 0 pack: 1 + upload: 1 pack_type: Release extension: dmg before_install: macos.sh @@ -50,6 +51,7 @@ jobs: os: macos-13 test: 0 pack: 1 + upload: 1 pack_type: Release extension: dmg before_install: macos.sh @@ -62,6 +64,7 @@ jobs: os: macos-13 test: 0 pack: 1 + upload: 1 pack_type: Release extension: ipa before_install: macos.sh @@ -89,6 +92,7 @@ jobs: os: ubuntu-24.04 test: 0 pack: 1 + upload: 1 pack_type: Release extension: exe cmake_args: -G Ninja @@ -109,6 +113,7 @@ jobs: conan_prebuilts: dependencies-mingw-x86 - platform: android-32 os: ubuntu-24.04 + upload: 1 extension: apk preset: android-conan-ninja-release before_install: android.sh @@ -117,6 +122,7 @@ jobs: artifact_platform: armeabi-v7a - platform: android-64 os: ubuntu-24.04 + upload: 1 extension: apk preset: android-conan-ninja-release before_install: android.sh @@ -244,11 +250,11 @@ jobs: elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]] then cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily" --preset ${{ matrix.preset }} - elif [[ (${{matrix.platform}} != msvc-x64) && (${{matrix.platform}} != msvc-x86) ]] + elif [[ ${{startsWith(matrix.platform, 'msvc') }} ]] then - cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} - else cmake --preset ${{ matrix.preset }} + else + cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} fi - name: Build @@ -336,7 +342,7 @@ jobs: ${{github.workspace}}/**/*.pdb - name: Upload build - if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc-x64' && matrix.platform != 'msvc-x86' && matrix.platform != 'mingw-32' }} + if: ${{ (matrix.upload == 1) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) }} continue-on-error: true run: | if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then diff --git a/CI/before_install/msvc.sh b/CI/before_install/msvc.sh index 82d5d2b5d..a0f7687f6 100644 --- a/CI/before_install/msvc.sh +++ b/CI/before_install/msvc.sh @@ -3,14 +3,13 @@ MSVC_INSTALL_PATH=$(vswhere -latest -property installationPath) echo "MSVC_INSTALL_PATH = $MSVC_INSTALL_PATH" echo "Installed toolset versions:" -ls -vr "$MSVC_INSTALL_PATH"/VC/Tools/MSVC +ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC" -TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH"/VC/Tools/MSVC/ | head -1) -DUMPBIN_PATH="$MSVC_INSTALL_PATH"/VC/Tools/MSVC/"$TOOLS_DIR"/bin/Hostx64/x64/dumpbin.exe +TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC/" | head -1) +DUMPBIN_PATH="$MSVC_INSTALL_PATH/VC/Tools/MSVC/$TOOLS_DIR/bin/Hostx64/x64/dumpbin.exe" -# This script should also work, but for some reason is *extremely* slow on Github CI (~7 minutes) +# This command should work as well, but for some reason it is *extremely* slow on the Github CI (~7 minutes) #DUMPBIN_PATH=$(vswhere -latest -find **/dumpbin.exe | head -n 1) -# echo "TOOLS_DIR = $TOOLS_DIR" echo "DUMPBIN_PATH = $DUMPBIN_PATH" From 038067f7e2a01858ad329157a4e80d68dbf8fd21 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 27 Oct 2024 16:45:13 -0300 Subject: [PATCH 445/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index bea048b1b..6e375f06c 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -705,5 +705,26 @@ "core.bonus.DISINTEGRATE.name": "Desintegrar", "core.bonus.DISINTEGRATE.description": "Nenhum corpo permanece após a morte", "core.bonus.INVINCIBLE.name": "Invencível", - "core.bonus.INVINCIBLE.description": "Não pode ser afetado por nada" + "core.bonus.INVINCIBLE.description": "Não pode ser afetado por nada", + "spell.core.castleMoat.name": "Fosso", + "spell.core.castleMoatTrigger.name": "Fosso", + "spell.core.catapultShot.name": "Disparo de Catapulta", + "spell.core.cyclopsShot.name": "Tiro de Cerco", + "spell.core.dungeonMoat.name": "Óleo Fervente", + "spell.core.dungeonMoatTrigger.name": "Óleo Fervente", + "spell.core.fireWallTrigger.name": "Parede de Fogo", + "spell.core.firstAid.name": "Primeiros Socorros", + "spell.core.fortressMoat.name": "Alcatrão Fervente", + "spell.core.fortressMoatTrigger.name": "Alcatrão Fervente", + "spell.core.infernoMoat.name": "Lava", + "spell.core.infernoMoatTrigger.name": "Lava", + "spell.core.landMineTrigger.name": "Mina Terrestre", + "spell.core.necropolisMoat.name": "Cemitério", + "spell.core.necropolisMoatTrigger.name": "Cemitério", + "spell.core.rampartMoat.name": "Espraiamento", + "spell.core.rampartMoatTrigger.name": "Espraiamento", + "spell.core.strongholdMoat.name": "Estacas de Madeira", + "spell.core.strongholdMoatTrigger.name": "Estacas de Madeira", + "spell.core.summonDemons.name": "Invocar Demônios", + "spell.core.towerMoat.name": "Mina Terrestre" } From d557a94951113c871dfe793085a7819e89b0ad48 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 27 Oct 2024 16:51:58 -0300 Subject: [PATCH 446/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 6e375f06c..15b68ec6c 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -706,6 +706,7 @@ "core.bonus.DISINTEGRATE.description": "Nenhum corpo permanece após a morte", "core.bonus.INVINCIBLE.name": "Invencível", "core.bonus.INVINCIBLE.description": "Não pode ser afetado por nada", + "spell.core.castleMoat.name": "Fosso", "spell.core.castleMoatTrigger.name": "Fosso", "spell.core.catapultShot.name": "Disparo de Catapulta", From 123f3923d313027cd59009fe9c3abc6290ff40f4 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Tue, 29 Oct 2024 00:35:03 +0800 Subject: [PATCH 447/726] Update lib/modding/ContentTypeHandler.cpp Co-authored-by: Ivan Savenko --- lib/modding/ContentTypeHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index ac6bd0abe..2696080fe 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -201,7 +201,8 @@ void ContentTypeHandler::afterLoadFinalization() for (auto const & conflictModEntry: conflictModData.Struct()) conflictingMods.insert(conflictModEntry.first); - for (auto const & modID : conflictingMods) { + for (auto const & modID : conflictingMods) + { resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); resolvedConflicts.merge(VLC->modh->getModEnabledSoftDependencies(modID)); } From 5c5bac12ebe75396c683b17cdda10f140345260b Mon Sep 17 00:00:00 2001 From: kdmcser Date: Tue, 29 Oct 2024 00:35:32 +0800 Subject: [PATCH 448/726] Update lib/modding/CModHandler.cpp Co-authored-by: Ivan Savenko --- lib/modding/CModHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index f13b134cd..3fc399c13 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -137,8 +137,8 @@ std::vector CModHandler::validateAndSortDependencies(std::vector Date: Mon, 28 Oct 2024 18:30:30 +0100 Subject: [PATCH 449/726] Possibility to set hero patrol radius in map editor --- lib/mapObjects/CGHeroInstance.cpp | 11 +++++------ lib/mapObjects/CGHeroInstance.h | 3 ++- mapeditor/inspector/inspector.cpp | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 452368d65..68b761f5a 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1749,21 +1749,20 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) CArmedInstance::serializeJsonOptions(handler); { - static constexpr int NO_PATROLING = -1; - int rawPatrolRadius = NO_PATROLING; + int rawPatrolRadius = NO_PATROLLING; if(handler.saving) { - rawPatrolRadius = patrol.patrolling ? patrol.patrolRadius : NO_PATROLING; + rawPatrolRadius = patrol.patrolling ? patrol.patrolRadius : NO_PATROLLING; } - handler.serializeInt("patrolRadius", rawPatrolRadius, NO_PATROLING); + handler.serializeInt("patrolRadius", rawPatrolRadius, NO_PATROLLING); if(!handler.saving) { - patrol.patrolling = (rawPatrolRadius > NO_PATROLING); + patrol.patrolling = (rawPatrolRadius > NO_PATROLLING); patrol.initialPos = visitablePos(); - patrol.patrolRadius = (rawPatrolRadius > NO_PATROLING) ? rawPatrolRadius : 0; + patrol.patrolRadius = (rawPatrolRadius > NO_PATROLLING) ? rawPatrolRadius : 0; } } } diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index be3144d94..f8d01bd89 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -92,6 +92,7 @@ public: static constexpr si32 UNINITIALIZED_MANA = -1; static constexpr ui32 UNINITIALIZED_MOVEMENT = -1; static constexpr auto UNINITIALIZED_EXPERIENCE = std::numeric_limits::max(); + static constexpr int NO_PATROLLING = -1; //std::vector artifacts; //hero's artifacts from bag //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 @@ -99,7 +100,7 @@ public: struct DLL_LINKAGE Patrol { - Patrol(){patrolling=false;initialPos=int3();patrolRadius=-1;}; + Patrol(){patrolling=false;initialPos=int3();patrolRadius=NO_PATROLLING;}; bool patrolling; int3 initialPos; ui32 patrolRadius; diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 259470ccb..fdb018026 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -335,6 +335,15 @@ void Inspector::updateProperties(CGHeroInstance * o) } addProperty("Hero type", o->getHeroTypeID().hasValue() ? o->getHeroType()->getNameTranslated() : "", delegate, false); } + { + const int maxRadius = 60; + auto * patrolDelegate = new InspectorDelegate; + patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(CGHeroInstance::NO_PATROLLING)} }; + for(int i = 0; i <= maxRadius; ++i) + patrolDelegate->options.push_back({ QObject::tr("%1 tile(s)").arg(i), QVariant::fromValue(i) }); + auto patrolRadiusText = o->patrol.patrolling ? QObject::tr("%1 tile(s)").arg(o->patrol.patrolRadius) : QObject::tr("No patrol"); + addProperty("Patrol radius", patrolRadiusText, patrolDelegate, false); + } } void Inspector::updateProperties(CGTownInstance * o) @@ -711,6 +720,13 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->randomizeArmy(o->getHeroType()->heroClass->faction); updateProperties(); //updating other properties after change } + + if(key == "Patrol radius") + { + o->patrol.patrolRadius = value.toInt(); + if(o->patrol.patrolRadius != CGHeroInstance::NO_PATROLLING) + o->patrol.patrolling = true; + } } void Inspector::setProperty(CGShipyard * o, const QString & key, const QVariant & value) From 911961da46bbb20f52a71a6c0a540db4e98754b8 Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 28 Oct 2024 19:59:03 +0100 Subject: [PATCH 450/726] try to fix build --- mapeditor/inspector/inspector.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index fdb018026..52ffcf41a 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -18,6 +18,7 @@ #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/mapObjects/ObjectTemplate.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapping/CMap.h" #include "../lib/constants/StringConstants.h" From 1fcd7507743c12867dddc990ea2afcaa58043a7a Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:30:19 +0200 Subject: [PATCH 451/726] suggested changes --- client/widgets/CArtifactsOfHeroAltar.cpp | 2 +- client/widgets/CArtifactsOfHeroBackpack.cpp | 4 ++-- client/widgets/CArtifactsOfHeroBase.cpp | 4 ++-- client/widgets/CArtifactsOfHeroBase.h | 4 ++-- client/widgets/CArtifactsOfHeroKingdom.cpp | 4 ++-- client/widgets/CArtifactsOfHeroMain.cpp | 2 +- client/widgets/CArtifactsOfHeroMarket.cpp | 4 ++-- client/widgets/CArtifactsOfHeroMarket.h | 2 +- client/widgets/CComponentHolder.cpp | 7 ++++--- client/windows/CHeroOverview.cpp | 2 +- lib/CSkillHandler.cpp | 7 ++++++- lib/CSkillHandler.h | 3 ++- 12 files changed, 26 insertions(+), 19 deletions(-) diff --git a/client/widgets/CArtifactsOfHeroAltar.cpp b/client/widgets/CArtifactsOfHeroAltar.cpp index 436cbc1c4..795243eca 100644 --- a/client/widgets/CArtifactsOfHeroAltar.cpp +++ b/client/widgets/CArtifactsOfHeroAltar.cpp @@ -22,7 +22,7 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position) { init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); - setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); enableGesture(); // The backpack is in the altar window above and to the right diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index 04f6734a0..8c5c2724c 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -40,7 +40,7 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack() visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax; initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap); - setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } @@ -173,7 +173,7 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero) slotsColumnsMax = ceilf(sqrtf(requiredSlots)); slotsRowsMax = calcRows(requiredSlots); initAOHbackpack(requiredSlots, false); - setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); auto artPlace = backpack.begin(); for(auto & art : filteredArts) setSlotData(*artPlace++, curHero->getArtPos(art.second)); diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 686cbafd5..d02a2a751 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -85,7 +85,7 @@ void CArtifactsOfHeroBase::init( setRedrawParent(true); } -void CArtifactsOfHeroBase::setClickPrassedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const +void CArtifactsOfHeroBase::setClickPressedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const { for(const auto & [slot, artPlace] : artWorn) artPlace->setClickPressedCallback(callback); @@ -101,7 +101,7 @@ void CArtifactsOfHeroBase::setShowPopupArtPlacesCallback(const CArtPlace::ClickF artPlace->setShowPopupCallback(callback); } -void CArtifactsOfHeroBase::clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) +void CArtifactsOfHeroBase::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { auto ownedPlace = getArtPlace(cursorPosition); assert(ownedPlace != nullptr); diff --git a/client/widgets/CArtifactsOfHeroBase.h b/client/widgets/CArtifactsOfHeroBase.h index a22ec78dd..2ded1efa7 100644 --- a/client/widgets/CArtifactsOfHeroBase.h +++ b/client/widgets/CArtifactsOfHeroBase.h @@ -33,7 +33,7 @@ public: CArtifactsOfHeroBase(); virtual void putBackPickedArtifact(); - virtual void clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); + virtual void clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); virtual void showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); virtual void gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition); virtual void setHero(const CGHeroInstance * hero); @@ -50,7 +50,7 @@ public: void enableGesture(); const CArtifactInstance * getArt(const ArtifactPosition & slot) const; void enableKeyboardShortcuts(); - void setClickPrassedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const; + void setClickPressedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const; void setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const; const CGHeroInstance * curHero; diff --git a/client/widgets/CArtifactsOfHeroKingdom.cpp b/client/widgets/CArtifactsOfHeroKingdom.cpp index 7bc0853ce..9e4643ce4 100644 --- a/client/widgets/CArtifactsOfHeroKingdom.cpp +++ b/client/widgets/CArtifactsOfHeroKingdom.cpp @@ -30,14 +30,14 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto { artPlace.second->slot = artPlace.first; artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE)); - artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } enableGesture(); for(auto artPlace : backpack) { artPlace->setArtifact(ArtifactID(ArtifactID::NONE)); - artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); } leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1)); diff --git a/client/widgets/CArtifactsOfHeroMain.cpp b/client/widgets/CArtifactsOfHeroMain.cpp index da5c20c31..9ca0ecc23 100644 --- a/client/widgets/CArtifactsOfHeroMain.cpp +++ b/client/widgets/CArtifactsOfHeroMain.cpp @@ -21,7 +21,7 @@ CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position) { init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); - setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2)); enableGesture(); } diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index df66702a8..b41d1c377 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -15,14 +15,14 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int selectionWidth) { init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1)); - setClickPrassedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2)); + setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2)); for(const auto & [slot, artPlace] : artWorn) artPlace->setSelectionWidth(selectionWidth); for(auto artPlace : backpack) artPlace->setSelectionWidth(selectionWidth); }; -void CArtifactsOfHeroMarket::clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) +void CArtifactsOfHeroMarket::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { auto ownedPlace = getArtPlace(cursorPosition); assert(ownedPlace != nullptr); diff --git a/client/widgets/CArtifactsOfHeroMarket.h b/client/widgets/CArtifactsOfHeroMarket.h index 5fd88b9ca..84a80a831 100644 --- a/client/widgets/CArtifactsOfHeroMarket.h +++ b/client/widgets/CArtifactsOfHeroMarket.h @@ -18,5 +18,5 @@ public: std::function onClickNotTradableCallback; CArtifactsOfHeroMarket(const Point & position, const int selectionWidth); - void clickPrassedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) override; + void clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) override; }; diff --git a/client/widgets/CComponentHolder.cpp b/client/widgets/CComponentHolder.cpp index 8ce49fe06..825d4ec67 100644 --- a/client/widgets/CComponentHolder.cpp +++ b/client/widgets/CComponentHolder.cpp @@ -295,14 +295,15 @@ void CSecSkillPlace::setLevel(const uint8_t level) assert(level <= 3); if(skillId != SecondarySkill::NONE && level > 0) { - image->setFrame(skillId.toSkill()->getIconIndex() + level - 1); + const auto secSkill = skillId.toSkill(); + image->setFrame(secSkill->getIconIndex(level - 1)); image->enable(); auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[21]); hoverText.replaceRawString(CGI->generaltexth->levels[level - 1]); - hoverText.replaceTextID(SecondarySkill(skillId).toSkill()->getNameTextID()); + hoverText.replaceTextID(secSkill->getNameTextID()); this->hoverText = hoverText.toString(); component.value = level; - text = CGI->skillh->getByIndex(skillId)->getDescriptionTranslated(level); + text = secSkill->getDescriptionTranslated(level); } else { diff --git a/client/windows/CHeroOverview.cpp b/client/windows/CHeroOverview.cpp index 6043aefac..ac1c9dd00 100644 --- a/client/windows/CHeroOverview.cpp +++ b/client/windows/CHeroOverview.cpp @@ -206,7 +206,7 @@ void CHeroOverview::genControls() i = 0; for(auto & skill : (*CGI->heroh)[heroIdx]->secSkillsInit) { - imageSecSkills.push_back(std::make_shared(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex() * 3 + skill.second + 2, 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset))); + imageSecSkills.push_back(std::make_shared(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex(skill.second + 2), 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset))); labelSecSkillsNames.push_back(std::make_shared(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->levels[skill.second - 1])); labelSecSkillsNames.push_back(std::make_shared(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->skillh)[skill.first]->getNameTranslated())); i++; diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 2c43585e8..1cc651801 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -45,7 +45,12 @@ int32_t CSkill::getIndex() const int32_t CSkill::getIconIndex() const { - return getIndex() * 3 + 3; //TODO: actual value with skill level + return getIndex() * 3 + 3; // Base master level +} + +int32_t CSkill::getIconIndex(uint8_t skillMasterLevel) const +{ + return getIconIndex() + skillMasterLevel; } std::string CSkill::getNameTextID() const diff --git a/lib/CSkillHandler.h b/lib/CSkillHandler.h index a3d22d59b..e54797268 100644 --- a/lib/CSkillHandler.h +++ b/lib/CSkillHandler.h @@ -34,6 +34,7 @@ public: private: std::vector levels; // bonuses provided by basic, advanced and expert level void addNewBonus(const std::shared_ptr & b, int level); + int32_t getIconIndex() const override; SecondarySkill id; std::string modScope; @@ -50,7 +51,7 @@ public: }; int32_t getIndex() const override; - int32_t getIconIndex() const override; + int32_t getIconIndex(uint8_t skillMasterLevel) const; std::string getJsonKey() const override; std::string getModScope() const override; void registerIcons(const IconRegistar & cb) const override; From 394812c02a41811c5870666a521597ac716d2433 Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 28 Oct 2024 20:58:36 +0100 Subject: [PATCH 452/726] try to fix build, use inline constexpr --- lib/mapObjects/CGHeroInstance.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index f8d01bd89..bf0503b17 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -92,7 +92,7 @@ public: static constexpr si32 UNINITIALIZED_MANA = -1; static constexpr ui32 UNINITIALIZED_MOVEMENT = -1; static constexpr auto UNINITIALIZED_EXPERIENCE = std::numeric_limits::max(); - static constexpr int NO_PATROLLING = -1; + static inline constexpr int NO_PATROLLING = -1; //std::vector artifacts; //hero's artifacts from bag //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 From 56efb82b2ea4523625a4434426d001d0441e6424 Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 28 Oct 2024 21:49:25 +0100 Subject: [PATCH 453/726] try to fix build, separate definition of NO_PATROLLING in CGHeroInstance --- lib/mapObjects/CGHeroInstance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 68b761f5a..9b2bf07d9 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1874,5 +1874,6 @@ const IOwnableObject * CGHeroInstance::asOwnable() const return this; } +inline constexpr int CGHeroInstance::NO_PATROLLING; VCMI_LIB_NAMESPACE_END From da5bae301af44808272c54b37bdcf8169a7c5142 Mon Sep 17 00:00:00 2001 From: godric3 Date: Mon, 28 Oct 2024 22:46:48 +0100 Subject: [PATCH 454/726] try to fix build, use plain `-1` in place of `CGHeroInstance::NO_PATROLLING` --- lib/mapObjects/CGHeroInstance.cpp | 1 - mapeditor/inspector/inspector.cpp | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 9b2bf07d9..68b761f5a 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1874,6 +1874,5 @@ const IOwnableObject * CGHeroInstance::asOwnable() const return this; } -inline constexpr int CGHeroInstance::NO_PATROLLING; VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 52ffcf41a..451e892db 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -18,7 +18,6 @@ #include "../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../lib/mapObjects/ObjectTemplate.h" -#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapping/CMap.h" #include "../lib/constants/StringConstants.h" @@ -339,7 +338,7 @@ void Inspector::updateProperties(CGHeroInstance * o) { const int maxRadius = 60; auto * patrolDelegate = new InspectorDelegate; - patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(CGHeroInstance::NO_PATROLLING)} }; + patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(-1)} }; for(int i = 0; i <= maxRadius; ++i) patrolDelegate->options.push_back({ QObject::tr("%1 tile(s)").arg(i), QVariant::fromValue(i) }); auto patrolRadiusText = o->patrol.patrolling ? QObject::tr("%1 tile(s)").arg(o->patrol.patrolRadius) : QObject::tr("No patrol"); @@ -724,9 +723,9 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari if(key == "Patrol radius") { - o->patrol.patrolRadius = value.toInt(); - if(o->patrol.patrolRadius != CGHeroInstance::NO_PATROLLING) - o->patrol.patrolling = true; + auto radius = value.toInt(); + o->patrol.patrolRadius = radius; + o->patrol.patrolling = radius != -1; } } From 719991b33ac00a2204faf2cc4ec1e8e8a712aa64 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 07:47:56 +0100 Subject: [PATCH 455/726] Fixed spacing between sentences --- config/objects/pyramid.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/objects/pyramid.json b/config/objects/pyramid.json index cc8b06a93..e39380a30 100644 --- a/config/objects/pyramid.json +++ b/config/objects/pyramid.json @@ -63,10 +63,10 @@ } ] }, - "message" : [ 106, "%s.", 108 ] // No Wisdom + "message" : [ 106, "%s. ", 108 ] // No Wisdom }, { - "message" : [ 106, "%s.", 109 ] // No spellbook + "message" : [ 106, "%s. ", 109 ] // No spellbook } ] From 42738e20a4d606d5ae117b8644dd8692a3c19fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20=C5=BBak?= Date: Tue, 29 Oct 2024 08:26:31 +0100 Subject: [PATCH 456/726] Update lib/mapObjects/CGHeroInstance.h Co-authored-by: Andrey Filipenkov --- lib/mapObjects/CGHeroInstance.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index bf0503b17..3cf4ec2f7 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -100,10 +100,9 @@ public: struct DLL_LINKAGE Patrol { - Patrol(){patrolling=false;initialPos=int3();patrolRadius=NO_PATROLLING;}; - bool patrolling; + bool patrolling{false}; int3 initialPos; - ui32 patrolRadius; + ui32 patrolRadius{NO_PATROLLING}; template void serialize(Handler &h) { h & patrolling; From 490361fd0676d6bd4e6190e746e1e86e5e6fe54b Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:06:37 +0100 Subject: [PATCH 457/726] Added translation for new strings --- Mods/vcmi/config/vcmi/czech.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 311abe3ad..24d480d28 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -42,6 +42,12 @@ "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", "vcmi.heroOverview.spells" : "Kouzla", + "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", + "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", + "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", + "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", + "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", + "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", @@ -60,8 +66,18 @@ "vcmi.radialWheel.moveUp" : "Posunout výše", "vcmi.radialWheel.moveDown" : "Posunout níže", "vcmi.radialWheel.moveBottom" : "Přesunout dolů", + + "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", + "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", + "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", + "vcmi.randomMap.description.water.none" : "žádná", + "vcmi.randomMap.description.water.normal" : "normální", + "vcmi.randomMap.description.water.islands" : "ostrovy", + "vcmi.randomMap.description.monster.weak" : "nízká", + "vcmi.randomMap.description.monster.normal" : "normální", + "vcmi.randomMap.description.monster.strong" : "vysoká", - "vcmi.spellBook.search" : "Hledat kouzlo", + "vcmi.spellBook.search" : "Hledat", "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", @@ -688,4 +704,6 @@ "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", "core.bonus.INVINCIBLE.name": "Neporazitelný", "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem" + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" } From d04eee177919c1db5bb9c9e5440697de77208b15 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:51:51 +0100 Subject: [PATCH 458/726] Fix for russian Imp Cahe miss-configuration --- Mods/vcmi/config/vcmi/russian.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/russian.json b/Mods/vcmi/config/vcmi/russian.json index ff088d60d..8417255fd 100644 --- a/Mods/vcmi/config/vcmi/russian.json +++ b/Mods/vcmi/config/vcmi/russian.json @@ -203,7 +203,7 @@ "mapObject.core.creatureBank.dragonFlyHive.name" : "Улей летучих змиев", "mapObject.core.creatureBank.dwarvenTreasury.name" : "Сокровищница гномов", "mapObject.core.creatureBank.griffinConservatory.name" : "Консерватория грифонов", - "mapObject.core.creatureBank.inpCache.name" : "Яма бесов", + "mapObject.core.creatureBank.impCache.name" : "Яма бесов", "mapObject.core.creatureBank.medusaStore.name" : "Склады медуз", "mapObject.core.creatureBank.nagaBank.name" : "Хранилище наг", "mapObject.core.crypt.crypt.name" : "Склеп", From 3c2096a188735a37f75491c46da22cfdc7ff7c1b Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:52:51 +0100 Subject: [PATCH 459/726] Fixed Imp Chache typo Fixed InpCache -> ImpCache --- config/objects/creatureBanks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json index 8624e8e4d..4376cb253 100644 --- a/config/objects/creatureBanks.json +++ b/config/objects/creatureBanks.json @@ -261,7 +261,7 @@ } ] }, - "inpCache" : { + "impCache" : { "index" : 3, "name" : "Imp Cache", "aiValue" : 1500, From 10f9ebb1e790cc4535b5a32029913dc777a6ac3b Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:26:31 +0100 Subject: [PATCH 460/726] Fixed missing comma --- Mods/vcmi/config/vcmi/czech.json | 1088 +++++++++++------------------- 1 file changed, 379 insertions(+), 709 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 24d480d28..97358bc9d 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -1,709 +1,379 @@ -{ - "vcmi.adventureMap.monsterThreat.title" : "\n\nHrozba: ", - "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", - "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", - "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", - "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", - "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", - "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", - "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", - "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", - "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", - "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", - "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", - "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", - "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", - "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", - "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", - "vcmi.adventureMap.search.hover" : "Prohledat objekt", - "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", - - "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", - "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", - "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", - "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", - "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", - "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", - "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", - "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", - "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", - - "vcmi.capitalColors.0" : "Červený", - "vcmi.capitalColors.1" : "Modrý", - "vcmi.capitalColors.2" : "Hnědý", - "vcmi.capitalColors.3" : "Zelený", - "vcmi.capitalColors.4" : "Oranžový", - "vcmi.capitalColors.5" : "Fialový", - "vcmi.capitalColors.6" : "Tyrkysový", - "vcmi.capitalColors.7" : "Růžový", - - "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", - "vcmi.heroOverview.warMachine" : "Bojové stroje", - "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", - "vcmi.heroOverview.spells" : "Kouzla", - - "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", - "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", - "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", - "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", - "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", - - "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", - "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", - "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", - "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", - "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", - "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", - - "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", - "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", - "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", - "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", - "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", - "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", - - "vcmi.radialWheel.moveTop" : "Přesunout nahoru", - "vcmi.radialWheel.moveUp" : "Posunout výše", - "vcmi.radialWheel.moveDown" : "Posunout níže", - "vcmi.radialWheel.moveBottom" : "Přesunout dolů", - - "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", - "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", - "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", - "vcmi.randomMap.description.water.none" : "žádná", - "vcmi.randomMap.description.water.normal" : "normální", - "vcmi.randomMap.description.water.islands" : "ostrovy", - "vcmi.randomMap.description.monster.weak" : "nízká", - "vcmi.randomMap.description.monster.normal" : "normální", - "vcmi.randomMap.description.monster.strong" : "vysoká", - - "vcmi.spellBook.search" : "Hledat", - - "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", - "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", - "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", - "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", - "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", - "vcmi.spellResearch.abort" : "Přerušit", - - "vcmi.mainMenu.serverConnecting" : "Připojování...", - "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", - "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", - "vcmi.mainMenu.serverClosing" : "Zavírání...", - "vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP", - "vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP", - - "vcmi.lobby.filepath" : "Název souboru", - "vcmi.lobby.creationDate" : "Datum vytvoření", - "vcmi.lobby.scenarioName" : "Název scénáře", - "vcmi.lobby.mapPreview" : "Náhled mapy", - "vcmi.lobby.noPreview" : "bez náhledu", - "vcmi.lobby.noUnderground" : "bez podzemí", - "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", - "vcmi.lobby.backToLobby" : "Vrátit se do lobby", - "vcmi.lobby.author" : "Autor", - "vcmi.lobby.handicap" : "Postih", - "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", - "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", - "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", - - "vcmi.lobby.login.title" : "Online lobby VCMI", - "vcmi.lobby.login.username" : "Uživatelské jméno:", - "vcmi.lobby.login.connecting" : "Připojování...", - "vcmi.lobby.login.error" : "Chyba při připojování: %s", - "vcmi.lobby.login.create" : "Nový účet", - "vcmi.lobby.login.login" : "Přihlásit se", - "vcmi.lobby.login.as" : "Přilásit se jako %s", - "vcmi.lobby.header.rooms" : "Herní místnosti - %d", - "vcmi.lobby.header.channels" : "Kanály konverzace", - "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name - "vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time - "vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player - "vcmi.lobby.header.history" : "Vaše předchozí hry", - "vcmi.lobby.header.players" : "Online hráči - %d", - "vcmi.lobby.match.solo" : "Hra jednoho hráče", - "vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player - "vcmi.lobby.match.multi" : "%d hráčů", - "vcmi.lobby.room.create" : "Vytvořit novou místnost", - "vcmi.lobby.room.players.limit" : "Omezení počtu hráčů", - "vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.", - "vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.", - "vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.", - "vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", - "vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).", - "vcmi.lobby.invite.header" : "Pozvat hráče", - "vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.", - "vcmi.lobby.preview.title" : "Připojit se do herní místnosti", - "vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host - "vcmi.lobby.preview.version" : "Verze hry:", - "vcmi.lobby.preview.players" : "Hráči:", - "vcmi.lobby.preview.mods" : "Použité modifikace:", - "vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?", - "vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.", - "vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.", - "vcmi.lobby.preview.error.full" : "Místnost je již plná.", - "vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.", - "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", - "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", - "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", - "vcmi.lobby.room.new" : "Nová hra", - "vcmi.lobby.room.load" : "Načíst hru", - "vcmi.lobby.room.type" : "Druh místnosti", - "vcmi.lobby.room.mode" : "Herní režim", - "vcmi.lobby.room.state.public" : "Veřejná", - "vcmi.lobby.room.state.private" : "Soukromá", - "vcmi.lobby.room.state.busy" : "Ve hře", - "vcmi.lobby.room.state.invited" : "Pozvaný", - "vcmi.lobby.mod.state.compatible" : "Kompatibilní", - "vcmi.lobby.mod.state.disabled" : "Musí být povolena", - "vcmi.lobby.mod.state.version" : "Neshoda verze", - "vcmi.lobby.mod.state.excessive" : "Musí být zakázána", - "vcmi.lobby.mod.state.missing" : "Není nainstalována", - "vcmi.lobby.pvp.coin.hover" : "Mince", - "vcmi.lobby.pvp.coin.help" : "Hodí mincí", - "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", - "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", - "vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", - "vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace", - "vcmi.lobby.pvp.versus" : "vs.", - - "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", - "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", - "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", - "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color - "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", - "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", - "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", - "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", - "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", - "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", - - "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", - - "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", - "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", - "vcmi.settingsMainWindow.battleTab.hover" : "Bitva", - "vcmi.settingsMainWindow.battleTab.help" : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.", - "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa", - "vcmi.settingsMainWindow.adventureTab.help" : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).", - - "vcmi.systemOptions.videoGroup" : "Nastavení obrazu", - "vcmi.systemOptions.audioGroup" : "Nastavení zvuku", - "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now - "vcmi.systemOptions.townsGroup" : "Obrazovka města", - - "vcmi.statisticWindow.statistics" : "Statistiky", - "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", - "vcmi.statisticWindow.selectView" : "Vybrat pohled", - "vcmi.statisticWindow.value" : "Hodnota", - "vcmi.statisticWindow.title.overview" : "Přehled", - "vcmi.statisticWindow.title.resources" : "Zdroje", - "vcmi.statisticWindow.title.income" : "Příjem", - "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", - "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", - "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", - "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", - "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", - "vcmi.statisticWindow.title.armyStrength" : "Síla armády", - "vcmi.statisticWindow.title.experience" : "Zkušenosti", - "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", - "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", - "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", - "vcmi.statisticWindow.param.playerName" : "Jméno hráče", - "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", - "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", - "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", - "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", - "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", - "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", - "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", - "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", - "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", - "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", - "vcmi.statisticWindow.icon.defeated" : "Porážka", - - "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", - "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", - "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", - "vcmi.systemOptions.fullscreenExclusive.help" : "{Celá obrazovka}\n\nPokud je vybráno, VCMI poběží v režimu exkluzivní celé obrazovky. V tomto režimu hra změní rozlišení na vybrané.", - "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", - "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", - "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", - "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", - "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", - "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", - "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", - "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", - "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", - "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", - "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", - "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", - "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", - "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", - "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", - "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", - "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", - "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", - "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", - "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", - "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", - - "vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací", - "vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.", - "vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek", - "vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.", - "vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu", - "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", - "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", - "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", - "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", - "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", - "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", - "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", - "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", - "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", - "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", - "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", - "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", - "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", - "vcmi.adventureOptions.mapScrollSpeed1.hover": "", - "vcmi.adventureOptions.mapScrollSpeed5.hover": "", - "vcmi.adventureOptions.mapScrollSpeed6.hover": "", - "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", - "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", - "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", - "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", - "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", - - "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", - "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", - "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", - "vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ", - "vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ", - "vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.", - "vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", - "vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.", - "vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", - "vcmi.battleOptions.animationsSpeed1.hover": "", - "vcmi.battleOptions.animationsSpeed5.hover": "", - "vcmi.battleOptions.animationsSpeed6.hover": "", - "vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.", - "vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.", - "vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.", - "vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí", - "vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.", - "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců", - "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Zobrazit omezení dostřelu střelců při najetí}\n\nZobrazit dostřel střelce při najetí na něj.", - "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Zobrazit okno statistik hrdinů", - "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", - "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", - "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", - "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", - "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", - "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", - "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", - - "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", - "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", - - "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", - "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", - "vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.ranged" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE).", - "vcmi.battleWindow.damageEstimation.rangedKills" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.shots" : "%d střel zbývá", - "vcmi.battleWindow.damageEstimation.shots.1" : "%d střela zbývá", - "vcmi.battleWindow.damageEstimation.damage" : "%d poškození", - "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", - "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", - "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", - - "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", - "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", - "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", - "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", - "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", - - "vcmi.battleWindow.killed" : "Zabito", - "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", - "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", - - "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", - - "vcmi.tutorialWindow.title" : "Úvod ovládání dotykem", - "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", - "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", - "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", - "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", - "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", - "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", - "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", - - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky", - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Zobrazit týdenní přírůstek jednotek}\n\nZobrazit týdenní přírůstek jednotek místo dostupného počtu ke koupení v přehledu města (levý spodní okraj obrazovky města).", - "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", - "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", - - "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", - "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", - - "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", - "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", - - "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", - "vcmi.logicalExpressions.allOf" : "Všechny následující:", - "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", - - "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", - "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", - "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", - "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", - "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", - "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", - "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", - "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", - "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", - "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", - "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", - - "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", - - "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", - - "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", - "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", - "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", - "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", - "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", - "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", - - "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", - "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", - - "vcmi.randomMapTab.widgets.randomTemplate" : "(Náhodná)", - "vcmi.randomMapTab.widgets.templateLabel" : "Šablona", - "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...", - "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Přiřazení týmů", - "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", - - "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", - "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", - - "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", - "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", - "vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy", - "vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky", - "vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.", - "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.", - "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.", - "vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.", - - "vcmi.optionsTab.accumulate" : "Akumulovat", - - "vcmi.optionsTab.simturnsTitle" : "Souběžné tahy", - "vcmi.optionsTab.simturnsMin.hover" : "Alespoň po", - "vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po", - "vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI", - "vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno", - "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", - "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", - - "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", - "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", - "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", - "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", - "vcmi.optionsTab.turnTime.classic.5" : "Klasický časovač: 5 minut", - "vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut", - "vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut", - "vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut", - "vcmi.optionsTab.turnTime.chess.20" : "Šachová: 20:00 + 10:00 + 02:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.16" : "Šachová: 16:00 + 08:00 + 01:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.8" : "Šachová: 08:00 + 04:00 + 01:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.4" : "Šachová: 04:00 + 02:00 + 00:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", - "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", - - "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", - "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", - "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", - "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", - "vcmi.optionsTab.simturns.tillContact2" : "Souběžně: 2 týdny, přerušit při setkání", - "vcmi.optionsTab.simturns.tillContact4" : "Souběžně: 1 mšsíc, přerušit při setkání", - "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", - "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", - "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", - - // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language - // Using this information, VCMI will automatically select correct plural form for every possible amount - "vcmi.optionsTab.simturns.days.0" : " %d dní", - "vcmi.optionsTab.simturns.days.1" : " %d den", - "vcmi.optionsTab.simturns.days.2" : " %d dny", - "vcmi.optionsTab.simturns.weeks.0" : " %d týdnů", - "vcmi.optionsTab.simturns.weeks.1" : " %d týden", - "vcmi.optionsTab.simturns.weeks.2" : " %d týdny", - "vcmi.optionsTab.simturns.months.0" : " %d měsíců", - "vcmi.optionsTab.simturns.months.1" : " %d měsíc", - "vcmi.optionsTab.simturns.months.2" : " %d měsíce", - - "vcmi.optionsTab.extraOptions.hover" : "Další možnosti", - "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", - - "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", - "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", - "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", - "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", - - // Custom victory conditions for H3 campaigns and HotA maps - "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", - "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", - "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", - "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", - "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", - "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", - "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", - "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", - - // few strings from WoG used by vcmi - "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", - "vcmi.stackExperience.rank.0" : "Začátečník", - "vcmi.stackExperience.rank.1" : "Učeň", - "vcmi.stackExperience.rank.2" : "Trénovaný", - "vcmi.stackExperience.rank.3" : "Zručný", - "vcmi.stackExperience.rank.4" : "Prověřený", - "vcmi.stackExperience.rank.5" : "Veterán", - "vcmi.stackExperience.rank.6" : "Adept", - "vcmi.stackExperience.rank.7" : "Expert", - "vcmi.stackExperience.rank.8" : "Elitní", - "vcmi.stackExperience.rank.9" : "Mistr", - "vcmi.stackExperience.rank.10" : "Eso", - - // Strings for HotA Seer Hut / Quest Guards - "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", - "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", - "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", - - "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", - - "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", - "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", - "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", - "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", - "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", - "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", - "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", - "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", - "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", - "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", - "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", - "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", - "core.bonus.CATAPULT.name": "Katapult", - "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", - "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", - "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", - "core.bonus.DARKNESS.name": "Závoj temnoty", - "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", - "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", - "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", - "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", - "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", - "core.bonus.DESTRUCTION.name": "Zničení", - "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", - "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", - "core.bonus.DRAGON_NATURE.name": "Dračí povaha", - "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", - "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", - "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", - "core.bonus.ENCHANTER.name": "Zaklínač", - "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", - "core.bonus.ENCHANTED.name": "Očarovaný", - "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", - "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", - "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", - "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", - "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", - "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", - "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", - "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", - "core.bonus.FIRST_STRIKE.name": "První úder", - "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", - "core.bonus.FEAR.name": "Strach", - "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", - "core.bonus.FEARLESS.name": "Nebojácnost", - "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", - "core.bonus.FEROCITY.name": "Zuřivost", - "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", - "core.bonus.FLYING.name": "Létání", - "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", - "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", - "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", - "core.bonus.GARGOYLE.name": "Chrlič", - "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", - "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", - "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", - "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", - "core.bonus.HEALER.name": "Léčitel", - "core.bonus.HEALER.description": "Léčí spojenecké jednotky", - "core.bonus.HP_REGENERATION.name": "Regenerace", - "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", - "core.bonus.JOUSTING.name": "Nájezd šampionů", - "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", - "core.bonus.KING.name": "Král", - "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", - "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", - "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", - "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", - "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", - "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", - "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", - "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", - "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", - "core.bonus.MANA_DRAIN.name": "Vysávání many", - "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", - "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", - "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", - "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", - "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", - "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", - "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", - "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", - "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", - "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", - "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", - "core.bonus.NO_MORALE.name": "Neutrální morálka", - "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", - "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", - "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", - "core.bonus.NON_LIVING.name": "Neživý", - "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", - "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", - "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", - "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", - "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", - "core.bonus.RECEPTIVE.name": "Vnímavý", - "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", - "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", - "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", - "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", - "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", - "core.bonus.REVENGE.name": "Pomsta", - "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", - "core.bonus.SHOOTER.name": "Střelec", - "core.bonus.SHOOTER.description": "Jednotka může střílet", - "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", - "core.bonus.SOUL_STEAL.name": "Zloděj duší", - "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", - "core.bonus.SPELLCASTER.name": "Kouzelník", - "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", - "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", - "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", - "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", - "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", - "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", - "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", - "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", - "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", - "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", - "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", - "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", - "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", - "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", - "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", - "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", - "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", - "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", - "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", - "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", - "core.bonus.TRANSMUTATION.name": "Transmutace", - "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", - "core.bonus.UNDEAD.name": "Nemrtvý", - "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", - "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", - "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", - "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", - "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", - "core.bonus.WIDE_BREATH.name": "Široký dech", - "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", - "core.bonus.DISINTEGRATE.name": "Rozpad", - "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", - "core.bonus.INVINCIBLE.name": "Neporazitelný", - "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem" - "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", - "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" -} +{ + "creatures.new-monsters-pack.axolotl-creatures-pack.acidicRoper.name.plural" : "Kyselé liany", + "creatures.new-monsters-pack.axolotl-creatures-pack.acidicRoper.name.singular" : "Kyselá liana", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpBabaYaga.name.plural" : "Baby jagy", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpBabaYaga.name.singular" : "Baba jaga", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpBrownie.name.plural" : "Dřeváci", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpBrownie.name.singular" : "Dřevák", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpDrowner.name.plural" : "Mechoví otroci", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpDrowner.name.singular" : "Mechový otrok", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpThug.name.plural" : "Násilníci", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpThug.name.singular" : "Násilník", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpWilloWisp.name.plural" : "Bludičky", + "creatures.new-monsters-pack.axolotl-creatures-pack.acpWilloWisp.name.singular" : "Bludička", + "creatures.new-monsters-pack.axolotl-creatures-pack.adventurer.name.plural" : "Dobrodruzi", + "creatures.new-monsters-pack.axolotl-creatures-pack.adventurer.name.singular" : "Dobrodruh", + "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthHunter.name.plural" : "Mamutí lovci", + "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthHunter.name.singular" : "Mamutí lovec", + "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthThrower.name.plural" : "Mamutí vrhači", + "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthThrower.name.singular" : "Mamutí vrhač", + "creatures.new-monsters-pack.axolotl-creatures-pack.cockatriceHigh.name.plural" : "Lakomé kokatrice", + "creatures.new-monsters-pack.axolotl-creatures-pack.cockatriceHigh.name.singular" : "Lakomá kokatrice", + "creatures.new-monsters-pack.axolotl-creatures-pack.dragonFish.name.plural" : "Dračí ryby", + "creatures.new-monsters-pack.axolotl-creatures-pack.dragonFish.name.singular" : "Dračí ryba", + "creatures.new-monsters-pack.axolotl-creatures-pack.evilTreant.name.plural" : "Zlí enti", + "creatures.new-monsters-pack.axolotl-creatures-pack.evilTreant.name.singular" : "Zlý ent", + "creatures.new-monsters-pack.axolotl-creatures-pack.evilTree.name.plural" : "Zlé stromy", + "creatures.new-monsters-pack.axolotl-creatures-pack.evilTree.name.singular" : "Zlý strom", + "creatures.new-monsters-pack.axolotl-creatures-pack.forestDragon.name.plural" : "Lesní draci", + "creatures.new-monsters-pack.axolotl-creatures-pack.forestDragon.name.singular" : "Lesní drak", + "creatures.new-monsters-pack.axolotl-creatures-pack.greatUrox.name.plural" : "Velcí uroxové", + "creatures.new-monsters-pack.axolotl-creatures-pack.greatUrox.name.singular" : "Velký urox", + "creatures.new-monsters-pack.axolotl-creatures-pack.hillGiant.name.plural" : "Kopcoví obři", + "creatures.new-monsters-pack.axolotl-creatures-pack.hillGiant.name.singular" : "Kopcový obr", + "creatures.new-monsters-pack.axolotl-creatures-pack.hulkBuster.name.plural" : "Ničitelé hulků", + "creatures.new-monsters-pack.axolotl-creatures-pack.hulkBuster.name.singular" : "Ničitel hulků", + "creatures.new-monsters-pack.axolotl-creatures-pack.ironclad.name.plural" : "Železňáci", + "creatures.new-monsters-pack.axolotl-creatures-pack.ironclad.name.singular" : "Železňák", + "creatures.new-monsters-pack.axolotl-creatures-pack.kappa.name.plural" : "Kappy", + "creatures.new-monsters-pack.axolotl-creatures-pack.kappa.name.singular" : "Kappa", + "creatures.new-monsters-pack.axolotl-creatures-pack.kappaShoya.name.plural" : "Kappové šóji", + "creatures.new-monsters-pack.axolotl-creatures-pack.kappaShoya.name.singular" : "Kappa šója", + "creatures.new-monsters-pack.axolotl-creatures-pack.lionman.name.plural" : "Lví muži", + "creatures.new-monsters-pack.axolotl-creatures-pack.lionman.name.singular" : "Lví muž", + "creatures.new-monsters-pack.axolotl-creatures-pack.mountainGiant.name.plural" : "Horští obři", + "creatures.new-monsters-pack.axolotl-creatures-pack.mountainGiant.name.singular" : "Horský obr", + "creatures.new-monsters-pack.axolotl-creatures-pack.owlkin.name.plural" : "Sovy", + "creatures.new-monsters-pack.axolotl-creatures-pack.owlkin.name.singular" : "Sova", + "creatures.new-monsters-pack.axolotl-creatures-pack.owlkinSage.name.plural" : "Soví mudrcové", + "creatures.new-monsters-pack.axolotl-creatures-pack.owlkinSage.name.singular" : "Soví mudrc", + "creatures.new-monsters-pack.axolotl-creatures-pack.roper.name.plural" : "Liany", + "creatures.new-monsters-pack.axolotl-creatures-pack.roper.name.singular" : "Liana", + "creatures.new-monsters-pack.axolotl-creatures-pack.scorpohand.name.plural" : "Štíhoruci", + "creatures.new-monsters-pack.axolotl-creatures-pack.scorpohand.name.singular" : "Štíhoruk", + "creatures.new-monsters-pack.axolotl-creatures-pack.sellsword.name.plural" : "Žoldáci", + "creatures.new-monsters-pack.axolotl-creatures-pack.sellsword.name.singular" : "Žoldák", + "creatures.new-monsters-pack.axolotl-creatures-pack.serpentine.name.plural" : "Hadovití", + "creatures.new-monsters-pack.axolotl-creatures-pack.serpentine.name.singular" : "Hadovitý", + "creatures.new-monsters-pack.axolotl-creatures-pack.serpentineAlpha.name.plural" : "Hadovité alfy", + "creatures.new-monsters-pack.axolotl-creatures-pack.serpentineAlpha.name.singular" : "Hadovitá alfa", + "creatures.new-monsters-pack.axolotl-creatures-pack.shadowLord.name.plural" : "Páni stínů", + "creatures.new-monsters-pack.axolotl-creatures-pack.shadowLord.name.singular" : "Pán stínů", + "creatures.new-monsters-pack.axolotl-creatures-pack.sphinx.name.plural" : "Sfingy", + "creatures.new-monsters-pack.axolotl-creatures-pack.sphinx.name.singular" : "Sfinga", + "creatures.new-monsters-pack.axolotl-creatures-pack.stoneCollossus.name.plural" : "Kamenní kolosi", + "creatures.new-monsters-pack.axolotl-creatures-pack.stoneCollossus.name.singular" : "Kamenný kolos", + "creatures.new-monsters-pack.axolotl-creatures-pack.stoneLord.name.plural" : "Páni kamene", + "creatures.new-monsters-pack.axolotl-creatures-pack.stoneLord.name.singular" : "Pán kamene", + "creatures.new-monsters-pack.axolotl-creatures-pack.swampDragon.name.plural" : "Bažinní draci", + "creatures.new-monsters-pack.axolotl-creatures-pack.swampDragon.name.singular" : "Bažinný drak", + "creatures.new-monsters-pack.axolotl-creatures-pack.tentoslimus.name.plural" : "Pohlcovači", + "creatures.new-monsters-pack.axolotl-creatures-pack.tentoslimus.name.singular" : "Pohlcovač", + "creatures.new-monsters-pack.axolotl-creatures-pack.tortofroid.name.plural" : "Želvouni", + "creatures.new-monsters-pack.axolotl-creatures-pack.tortofroid.name.singular" : "Želvoun", + "creatures.new-monsters-pack.axolotl-creatures-pack.urox.name.plural" : "Uroxové", + "creatures.new-monsters-pack.axolotl-creatures-pack.urox.name.singular" : "Urox", + "creatures.new-monsters-pack.axolotl-creatures-pack.veninScorpohand.name.plural" : "Jedovatí štíhoruci", + "creatures.new-monsters-pack.axolotl-creatures-pack.veninScorpohand.name.singular" : "Jedovatý štíhoruk", + "creatures.new-monsters-pack.axolotl-creatures-pack.vileTortofroid.name.plural" : "Zlomyslní želvíci", + "creatures.new-monsters-pack.axolotl-creatures-pack.vileTortofroid.name.singular" : "Zlomyslný želvík", + "creatures.new-monsters-pack.axolotl-creatures-pack.wyrmFish.name.plural" : "Drakoryby", + "creatures.new-monsters-pack.axolotl-creatures-pack.wyrmFish.name.singular" : "Drakoryba", + "creatures.new-monsters-pack.axolotl-creatures-pack.yeti.name.plural" : "Yetiové", + "creatures.new-monsters-pack.axolotl-creatures-pack.yeti.name.singular" : "Yeti", + "creatures.new-monsters-pack.axolotl-creatures-pack.zombieHigh.name.plural" : "Neodpuštění mrtví", + "creatures.new-monsters-pack.axolotl-creatures-pack.zombieHigh.name.singular" : "Neodpuštěný mrtvý", + "creatures.new-monsters-pack.axolotl-creatures-pack.zombieLow.name.plural" : "Pomstychtiví mrtví", + "creatures.new-monsters-pack.axolotl-creatures-pack.zombieLow.name.singular" : "Pomstychtivý mrtvý", + "creatures.new-monsters-pack.carpet-whisperers.dunesCarpetWhisperer.name.plural" : "Zaříkávači koberců", + "creatures.new-monsters-pack.carpet-whisperers.dunesCarpetWhisperer.name.singular" : "Zaříkávač koberců", + "creatures.new-monsters-pack.dark-dwarf-at-arms.dark elemental.darkElemental.name.plural" : "Temní elementálové", + "creatures.new-monsters-pack.dark-dwarf-at-arms.dark elemental.darkElemental.name.singular" : "Temný elementál", + "creatures.new-monsters-pack.dark-dwarf-at-arms.man at arms.manAtArms.name.plural" : "Zbrojnoši", + "creatures.new-monsters-pack.dark-dwarf-at-arms.man at arms.manAtArms.name.singular" : "Zbrojnoš", + "creatures.new-monsters-pack.dark-dwarf-at-arms.mountain dwarf.mountainDwarf.name.plural" : "Horští trpaslíci", + "creatures.new-monsters-pack.dark-dwarf-at-arms.mountain dwarf.mountainDwarf.name.singular" : "Horský trpaslík", + "creatures.new-monsters-pack.doommod.lostsoul.name.plural" : "Ztracené duše", + "creatures.new-monsters-pack.doommod.lostsoul.name.singular" : "Ztracená duše", + "creatures.new-monsters-pack.dwarf-pack.raDwarCrossbowman.name.plural" : "Trpasličí kušníci", + "creatures.new-monsters-pack.dwarf-pack.raDwarCrossbowman.name.singular" : "Trpasličí kušník", + "creatures.new-monsters-pack.dwarf-pack.raDwarfGuard.name.plural" : "Trpasličí strážci", + "creatures.new-monsters-pack.dwarf-pack.raDwarfGuard.name.singular" : "Trpasličí strážce", + "creatures.new-monsters-pack.dwarf-pack.raDwarfShaman.name.plural" : "Trpasličí šamani", + "creatures.new-monsters-pack.dwarf-pack.raDwarfShaman.name.singular" : "Trpasličí šaman", + "creatures.new-monsters-pack.dwarf-pack.raDwarfberserk.name.plural" : "Trpasličí berserkři", + "creatures.new-monsters-pack.dwarf-pack.raDwarfberserk.name.singular" : "Trpasličí berserk", + "creatures.new-monsters-pack.dwarf-pack.raDwarfsaboteur.name.plural" : "Trpasličí sabotéři", + "creatures.new-monsters-pack.dwarf-pack.raDwarfsaboteur.name.singular" : "Trpasličí sabotér", + "creatures.new-monsters-pack.extra-creature-pack.expBanshee.name.plural" : "Banshee", + "creatures.new-monsters-pack.extra-creature-pack.expBanshee.name.singular" : "Banshee", + "creatures.new-monsters-pack.extra-creature-pack.expBigspider.name.plural" : "Velcí pavouci", + "creatures.new-monsters-pack.extra-creature-pack.expBigspider.name.singular" : "Velký pavouk", + "creatures.new-monsters-pack.extra-creature-pack.expCyberDead.name.plural" : "Kybermrtví", + "creatures.new-monsters-pack.extra-creature-pack.expCyberDead.name.singular" : "Kybermrtvý", + "creatures.new-monsters-pack.extra-creature-pack.expCyberZombie.name.plural" : "Kyberzombíci", + "creatures.new-monsters-pack.extra-creature-pack.expCyberZombie.name.singular" : "Kyberzombík", + "creatures.new-monsters-pack.extra-creature-pack.expDalek.name.plural" : "Dalekové", + "creatures.new-monsters-pack.extra-creature-pack.expDalek.name.singular" : "Dalek", + "creatures.new-monsters-pack.extra-creature-pack.expDwarfTechnic.name.plural" : "Trpasličí technici", + "creatures.new-monsters-pack.extra-creature-pack.expDwarfTechnic.name.singular" : "Trpasličí technik", + "creatures.new-monsters-pack.extra-creature-pack.expEnormCrab.name.plural" : "Obří krabi", + "creatures.new-monsters-pack.extra-creature-pack.expEnormCrab.name.singular" : "Obří krab", + "creatures.new-monsters-pack.extra-creature-pack.expFanaticGuardian.name.plural" : "Fanatičtí strážci", + "creatures.new-monsters-pack.extra-creature-pack.expFanaticGuardian.name.singular" : "Fanatický strážce", + "creatures.new-monsters-pack.extra-creature-pack.expGiantDragon.name.plural" : "Obří draci", + "creatures.new-monsters-pack.extra-creature-pack.expGiantDragon.name.singular" : "Obří drak", + "creatures.new-monsters-pack.extra-creature-pack.expGroundDalek.name.plural" : "Pozemní dalekové", + "creatures.new-monsters-pack.extra-creature-pack.expGroundDalek.name.singular" : "Pozemní dalek", + "creatures.new-monsters-pack.extra-creature-pack.expJadeDragon.name.plural" : "Nefritoví draci", + "creatures.new-monsters-pack.extra-creature-pack.expJadeDragon.name.singular" : "Nefritový drak", + "creatures.new-monsters-pack.extra-creature-pack.expMechDragon.name.plural" : "Mechanizovaní draci", + "creatures.new-monsters-pack.extra-creature-pack.expMechDragon.name.singular" : "Mechanizovaný drak", + "creatures.new-monsters-pack.extra-creature-pack.expMonstrousMimic.name.plural" : "Monstrózní mimikové", + "creatures.new-monsters-pack.extra-creature-pack.expMonstrousMimic.name.singular" : "Monstrózní mimik", + "creatures.new-monsters-pack.extra-creature-pack.expNecroticBeast.name.plural" : "Nekrotické bestie", + "creatures.new-monsters-pack.extra-creature-pack.expNecroticBeast.name.singular" : "Nekrotická bestie", + "creatures.new-monsters-pack.extra-creature-pack.expRoboDragon.name.plural" : "Robo draci", + "creatures.new-monsters-pack.extra-creature-pack.expRoboDragon.name.singular" : "Robo drak", + "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffAssu.name.plural" : "Útoční gryfové", + "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffAssu.name.singular" : "Útočný gryf", + "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffon.name.plural" : "Robo gryfové", + "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffon.name.singular" : "Robo gryf", + "creatures.new-monsters-pack.extra-creature-pack.expSwampGorynych.name.plural" : "Bažinní Goryniši", + "creatures.new-monsters-pack.extra-creature-pack.expSwampGorynych.name.singular" : "Bažinná Gorynycha", + "creatures.new-monsters-pack.extra-creature-pack.expSword1.name.plural" : "Mystické meče", + "creatures.new-monsters-pack.extra-creature-pack.expSword1.name.singular" : "Mystický meč", + "creatures.new-monsters-pack.extra-creature-pack.expSword2.name.plural" : "Legendární meče", + "creatures.new-monsters-pack.extra-creature-pack.expSword2.name.singular" : "Legendární meč", + "creatures.new-monsters-pack.extra-creature-pack.expTRex.name.plural" : "T-Rexové", + "creatures.new-monsters-pack.extra-creature-pack.expTRex.name.singular" : "T-Rex", + "creatures.new-monsters-pack.extra-creature-pack.expTank.name.plural" : "Naga tanky", + "creatures.new-monsters-pack.extra-creature-pack.expTank.name.singular" : "Naga tank", + "creatures.new-monsters-pack.extra-creature-pack.expYaoGuai.name.plural" : "Yao Guaiové", + "creatures.new-monsters-pack.extra-creature-pack.expYaoGuai.name.singular" : "Yao Guai", + "creatures.new-monsters-pack.flugel.flugel.name.plural" : "Flügelové", + "creatures.new-monsters-pack.flugel.flugel.name.singular" : "Flügel", + "creatures.new-monsters-pack.invisible-man.invisibleMan.name.plural" : "Neviditelní", + "creatures.new-monsters-pack.invisible-man.invisibleMan.name.singular" : "Neviditelný", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.kurekCherub.name.plural" : "Cherubíní", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.kurekCherub.name.singular" : "Cherub", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-fortress.black-gorgon.blackgorgon.name.plural" : "Černé gorgony", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-fortress.black-gorgon.blackgorgon.name.singular" : "Černá gorgona", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.eagle.eagle.name.plural" : "Orli", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.eagle.eagle.name.singular" : "Orel", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.purpledragon.name.plural" : "Fialoví draci", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.purpledragon.name.singular" : "Fialový drak", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.kurekBloodeyedCyclops.name.plural" : "Krvaví kyklopové", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.kurekBloodeyedCyclops.name.singular" : "Krvavý kyklop", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.kurekCyclopsWarrior.name.plural" : "Kyklopští válečníci", + "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.kurekCyclopsWarrior.name.singular" : "Kyklopský válečník", + "creatures.new-monsters-pack.tok.more creatures.cr_bugbear.name.plural" : "Medvědovci", + "creatures.new-monsters-pack.tok.more creatures.cr_bugbear.name.singular" : "Medvědovec", + "creatures.new-monsters-pack.tok.more creatures.cr_bugbear_warlord.name.plural" : "Váleční Medvědovci", + "creatures.new-monsters-pack.tok.more creatures.cr_bugbear_warlord.name.singular" : "Válečný Medvědovec", + "creatures.new-monsters-pack.tok.more creatures.cr_gnome.name.plural" : "Skřítci", + "creatures.new-monsters-pack.tok.more creatures.cr_gnome.name.singular" : "Skřítek", + "creatures.new-monsters-pack.tok.more creatures.cr_kobold.name.plural" : "Koboldi", + "creatures.new-monsters-pack.tok.more creatures.cr_kobold.name.singular" : "Kobold", + "creatures.new-monsters-pack.tok.more creatures.cr_monk_loxodon.name.plural" : "Sloní mnichové", + "creatures.new-monsters-pack.tok.more creatures.cr_monk_loxodon.name.singular" : "Sloní mnich", + "creatures.new-monsters-pack.tok.more creatures.cr_orangutan.name.plural" : "Orangutani", + "creatures.new-monsters-pack.tok.more creatures.cr_orangutan.name.singular" : "Orangutan", + "creatures.new-monsters-pack.tok.more creatures.cr_plaguedoctor.name.plural" : "Moroví doktoři", + "creatures.new-monsters-pack.tok.more creatures.cr_plaguedoctor.name.singular" : "Morový doktor", + "creatures.new-monsters-pack.tok.more creatures.cr_shaman.name.plural" : "Lesní šamani", + "creatures.new-monsters-pack.tok.more creatures.cr_shaman.name.singular" : "Lesní šaman", + "creatures.new-monsters-pack.tok.more creatures.cr_tyrannosaurus.name.plural" : "Tyrannosauři", + "creatures.new-monsters-pack.tok.more creatures.cr_tyrannosaurus.name.singular" : "Tyrannosaurus", + "creatures.new-monsters-pack.undeadsphinxes.ashenSphinx.name.plural" : "Popelavé sfingy", + "creatures.new-monsters-pack.undeadsphinxes.ashenSphinx.name.singular" : "Popelavá sfinga", + "creatures.new-monsters-pack.undeadsphinxes.entombedSphinx.name.plural" : "Zapečetěné sfingy", + "creatures.new-monsters-pack.undeadsphinxes.entombedSphinx.name.singular" : "Zapečetěná sfinga", + "creatures.new-monsters-pack.undeadsphinxes.madMummy.name.plural" : "Mumie", + "creatures.new-monsters-pack.undeadsphinxes.madMummy.name.singular" : "Mumie", + "creatures.new-monsters-pack.wicked-witch.kraven.name.plural" : "Havrani", + "creatures.new-monsters-pack.wicked-witch.kraven.name.singular" : "Havran", + "creatures.new-monsters-pack.wicked-witch.wwickedWitch.name.plural" : "Zlé čarodějnice", + "creatures.new-monsters-pack.wicked-witch.wwickedWitch.name.singular" : "Zlá čarodějnice", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.name" : "Cynthia", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.specialty.description" : "Získává bonus 5% na každý level v umění rušení.", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.specialty.name" : "Rušení", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.specialty.tooltip" : "Bonus k dovednostem: Rušení", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.name" : "Marlon", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.specialty.description" : "Získává 5% bonus na magii za každou úroveň.", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.specialty.name" : "Magie", + "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.specialty.tooltip" : "Bonus k dovednostem: Magie", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.name" : "Velochi", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.specialty.description" : "Získává bonus 5 % za úroveň k dovednosti Střelec.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.specialty.name" : "Střelec", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.specialty.tooltip" : "Bonus k vedlejší dovednosti: Střelec", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.name" : "Bha", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.specialty.description" : "Po dosažení 6. úrovně zvyšuje útočnou a obrannou sílu sloních mnichů.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.specialty.name" : "Sloní mnichové", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.specialty.tooltip" : "Posílení jednotek: Sloní mnichové", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.biography" : "Gromm je respektovaný náčelník bugbearů, který během orkských nepokojů sjednotil rozptýlené kmeny a stal se vizionářským vůdcem. Jeho cílem není jen dobývání, ale sjednocení bugbearů do mocné síly.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.name" : "Gromm", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.specialty.description" : "Po dosažení 4. úrovně zvyšuje útočnou a obrannou sílu bugbearů.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.specialty.name" : "Bugbearové", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.specialty.tooltip" : "Posílení jednotek: Bugbearové", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.name" : "Kurik", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.specialty.description" : "Po dosažení 1. úrovně zvyšuje útočnou a obrannou sílu koboldů.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.specialty.name" : "Koboldi", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.specialty.tooltip" : "Posílení jednotek: Koboldi", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.name" : "Lamount", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.specialty.description" : "Zvyšuje Rychlost jakéhokoli orangutana o 1 a jejich Obranu o 1 za každou úroveň dosaženou po 3. úrovni. Navíc, orangutani získávají trvalý bonus 25 % k Zdraví.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.specialty.name" : "Orangutan", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.specialty.tooltip" : "Bonus za jednotku: Orangutan", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.name" : "Lorme", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.specialty.description" : "Zvyšuje Rychlost jakéhokoli Morového doktora o 1 a jejich Útok a Obranu o 5 % každé 4 úrovně.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.specialty.name" : "Morový doktor", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.specialty.tooltip" : "Bonus za jednotku: Morový doktor", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.name" : "Melanchalot", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.specialty.description" : "Všechny jednotky v boji mají o 5% vyšší životy.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.specialty.name" : "Vytrvalost", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.specialty.tooltip" : "Bonus k dovednostem: Vytrvalost", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.name" : "Veles", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.specialty.description" : "{Protimagie}", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.specialty.name" : "Oslabení", + "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.specialty.tooltip" : "Oslabení", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.biography" : "Už jako dítě utekla a žila v lesích na úpatí velké hory, říkajíc si dcera vánku. Tvořila své mládí slovy větru a získala tak své vědomosti a dovednosti.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.name" : "Birog", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.specialty.description" : "Použití kouzla zrychlení má velký efekt, ale záleží na rozdílu úrovní mezi hrdinou a cílem (nižší úroveň cíle, lepší efekt).", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.specialty.name" : "Zrychlení", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.specialty.tooltip" : "Magické zesílení: Zrychlení", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.biography" : "Zahradník, který je hrdý na své sazenice, žije pomalým životem v souladu s přírodou. Možná jeho nadpřirozené štěstí zajistilo, že jeho obyčejné dny byly plné překvapení a jeho rostliny kvetly a plodily.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.name" : "Mabon", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.specialty.description" : "+1 drahokam každý den", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.specialty.name" : "+1 Drahokam", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.specialty.tooltip" : "Zvyšuje příjem království o +1 drahokam denně.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.name" : "Boudicca", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.specialty.description" : "Nepřátelé nemohou v boji utéct.", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.specialty.name" : "Zakázání útěku", + "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.specialty.tooltip" : "Nepřátelé nemohou v boji utéct.", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.name" : "Larami", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.specialty.description" : "Po dosažení 7. úrovně zvyšuje útočnou a obrannou sílu tyrannosaurů.", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.specialty.name" : "Tyrannosauři", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.specialty.tooltip" : "Posílení jednotek: Tyrannosauři", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_nana.name" : "Nana", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_nana.specialty.description" : "{Prokletí}\n", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_nana.specialty.name" : "Prokletí", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_rudi.name" : "Rudi", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_rudi.specialty.description" : "{První pomoc}\n", + "hero.new-monsters-pack.tok.more heroes.tow heroes.h_rudi.specialty.name" : "{První pomoc", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_agatha.name" : "Agatha", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_agatha.specialty.description" : "{Andělé}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_agatha.specialty.name" : "Andělé", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_auris.name" : "Auris", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_auris.specialty.description" : "{Vzdušný Elementál}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_auris.specialty.name" : "Vzdušný Elementál", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_bel.name" : "Bel", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_bel.specialty.description" : "{Ohnivý Elementál}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_bel.specialty.name" : "Ohnivý Elementál", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_elena.name" : "Elena", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_elena.specialty.description" : "{Kopiník}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_elena.specialty.name" : "Kopiník", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_eordan.name" : "Eordan", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_eordan.specialty.description" : "{Zemní Elementál}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_eordan.specialty.name" : "Zemní Elementál", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_gre.name" : "Gre", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_gre.specialty.description" : "{Pohyblivý písek}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_gre.specialty.name" : "Pohyblivý písek", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_hana.name" : "Hana", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_hana.specialty.description" : "{Vodní Elementál}\n", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_hana.specialty.name" : "Vodní Elementál", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.name" : "Liana", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.specialty.description" : "Po dosažení 1. úrovně zvyšuje útočnou a obrannou sílu pixií nebo spriteů.", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.specialty.name" : "Skřítci", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.specialty.tooltip" : "Posílení jednotek: Skřítci", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.name" : "Pandora", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.specialty.description" : "Po dosažení 7. úrovně zvyšuje útočnou a obrannou sílu fénixů nebo ohnivých ptáků.", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.specialty.name" : "Fénixové", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.specialty.tooltip" : "Posílení jednotek: Fénixové", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.name" : "Third", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.specialty.description" : "Po dosažení 5. úrovně zvyšuje útočnou a obrannou sílu golemů.", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.specialty.name" : "Golemové", + "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.specialty.tooltip" : "Posílení jednotek: Golemové", + "hero.new-monsters-pack.wicked-witch.elfaba.biography" : "{Západní zlá čarodějnice} přišla na Enroth, aby zotročila celou zemi. Temní, pochmurní havrani ji vždy doprovázejí, děsí a straší lidi. Elfaba má ráda zápach bažin, proto se rozhodla zůstat s gorgonami. Nikdo neví, jak zastavit tuto smrtící krásku.", + "hero.new-monsters-pack.wicked-witch.elfaba.name" : "Elfaba", + "hero.new-monsters-pack.wicked-witch.elfaba.specialty.description" : "Zvyšuje útočné a obranné dovednosti jakékoliv zlé čarodějnice s každou úrovní dosaženou po 4. úrovni.", + "hero.new-monsters-pack.wicked-witch.elfaba.specialty.name" : "Zlé čarodějnice", + "hero.new-monsters-pack.wicked-witch.elfaba.specialty.tooltip" : "Bonus pro jednotku: Zlé čarodějnice", + "heroClass.new-monsters-pack.tok.more heroes.neutral classes.hc_explorer.name" : "Průzkumník", + "heroClass.new-monsters-pack.tok.more heroes.neutral classes.hc_mystic.name" : "Mystik", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpAshTree.name" : "Jasanový strom", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpButteExcavation.name" : "Výkop na kopci", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpCenotaph.name" : "Cenotaf", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpChalet.name" : "Chalupa", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpCluster.name" : "Shluk", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpDarkPortal.name" : "Temný portál", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpForsakenPond.name" : "Opuštěný rybník", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpFrozenCavity.name" : "Zmrzlá dutina", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpHutHensLeg.name" : "Chalupa na kuří noze", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpLostUshnu.name" : "Ztracený oltář", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpMire.name" : "Bažina", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpMurkyChasm.name" : "Temná propast", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpNiveousLair.name" : "Sněhové doupě", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpQuaggyRelic.name" : "Bahenní relikvie", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpRoughcastPlant.name" : "Odlévací závod", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpRustyHabitation.name" : "Zrezivělá obydlí", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpShatteredGravestone.name" : "Rozbitý náhrobek", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSinfulWorkshop.name" : "Hříšná dílna", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSordidPost.name" : "Nechvalná pošta", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSparkyMeadow.name" : "Jiskřivá louka", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSqualidNide.name" : "Zchátralé hnízdo", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpTombOfRiddles.name" : "Hrobka záhad", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpTupik.name" : "Kožený stan", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpTurquoisePassage.name" : "Tyrkysový průchod", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpViciousWorkshop.name" : "Zlomyslná dílna", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpblackAlder.name" : "Temná olše", + "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpslimyBurrow.name" : "Slizké doupě", + "mapObject.new-monsters-pack.carpet-whisperers.creatureGeneratorCommon.carpetSeller.name" : "Prodejce koberců", + "mapObject.new-monsters-pack.dark-dwarf-at-arms.dark elemental.creatureGeneratorCommon.temple.name" : "Chrám temnoty", + "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raDwarfsBarracks.name" : "Trpasličí kasárna", + "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raForestCottage.name" : "Lesní chalupa", + "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raHill.name" : "Kopec", + "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raSanctuary.name" : "Svatyně", + "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raStatue.name" : "Socha vůdce", + "mapObject.new-monsters-pack.flugel.creatureGeneratorCommon.fpFlugelDwell.name" : "Nebeská knihovna", + "mapObject.new-monsters-pack.invisible-man.creatureBank.quarkPalace.name" : "Kvarkový palác", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.creatureGeneratorCommon.kurekAltarOfProphecies.name" : "Oltář proroctví", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-fortress.black-gorgon.creatureGeneratorCommon.blackLair.name" : "Černé doupě", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-haven.marine-serail.creatureBank.marineSerail.name" : "Námořní pevnost", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-neutral.eagle.creatureGeneratorCommon.eaglenest.name" : "Orlí hnízdo", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.creatureGeneratorCommon.purpleTower.name" : "Fialová věž", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.creatureGeneratorCommon.kurekCyclopsPyramid.name" : "Kyklopská pyramida", + "mapObject.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.creatureGeneratorCommon.kurekCyclopsLarder.name" : "Kyklopská spižírna", + "mapObject.new-monsters-pack.undeadsphinxes.creatureGeneratorCommon.pestilentPyramid.name" : "Morová pyramida", + "mapObject.new-monsters-pack.wicked-witch.creatureGeneratorCommon.louryEstate.name" : "Louryho sídlo", + "skill.new-monsters-pack.tok.more secondary.endurance.description.advanced" : "{Střední vytrvalost}\n\nVšechny jednotky v boji mají o 10% vyšší životy.", + "skill.new-monsters-pack.tok.more secondary.endurance.description.basic" : "{Základní vytrvalost}\n\nVšechny jednotky v boji mají o 5% vyšší životy.", + "skill.new-monsters-pack.tok.more secondary.endurance.description.expert" : "{Expertní vytrvalost}\n\nVšechny jednotky v boji mají o 15% vyšší životy.", + "skill.new-monsters-pack.tok.more secondary.endurance.name" : "Vytrvalost", + "skill.new-monsters-pack.tok.more secondary.fame.description.advanced" : "{Střední sláva}\n\nHrdina bránící město zvýší produkci jednotek 1. úrovně o +4, 2. úrovně o +2 a 3. úrovně o +1.", + "skill.new-monsters-pack.tok.more secondary.fame.description.basic" : "{Základní sláva}\n\nHrdina bránící město zvýší produkci jednotek 1. úrovně o +2 a 2. úrovně o +1.", + "skill.new-monsters-pack.tok.more secondary.fame.description.expert" : "{Expertní sláva}\n\nHrdina bránící město zvýší produkci všech jednotek o 25%.", + "skill.new-monsters-pack.tok.more secondary.fame.name" : "Sláva", + "skill.new-monsters-pack.tok.more secondary.warding.description.advanced" : "{Střední ochrana}\n\nVšechny jednotky získají magii magického zrcadla s pravděpodobností odrazu 50%.", + "skill.new-monsters-pack.tok.more secondary.warding.description.basic" : "{Základní ochrana}\n\nVšechny jednotky získají magii magického zrcadla s pravděpodobností odrazu 25%.", + "skill.new-monsters-pack.tok.more secondary.warding.description.expert" : "{Expertní ochrana}\n\nVšechny jednotky získají magii magického zrcadla s pravděpodobností odrazu 75%.", + "skill.new-monsters-pack.tok.more secondary.warding.name" : "Ochrana", + "spell.new-monsters-pack.axolotl-creatures-pack.acpDepletion.name" : "Vyčerpání", + "spell.new-monsters-pack.axolotl-creatures-pack.degradation.name" : "Degradace", + "spell.new-monsters-pack.axolotl-creatures-pack.fungus.name" : "Houbová infekce", + "spell.new-monsters-pack.axolotl-creatures-pack.paraFungus.name" : "Parazitická houba", + "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.advanced" : "{Kouzelný koberec}", + "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.basic" : "{Kouzelný koberec}", + "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.expert" : "{Kouzelný koberec}", + "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.none" : "{Kouzelný koberec}", + "spell.new-monsters-pack.carpet-whisperers.magicCarpet.name" : "Kouzelný koberec", + "spell.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.holyBlade.name" : "Meč nebes", + "spell.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.bluefire.name" : "Modrý oheň", + "spell.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.bluefiredamageeffect.name" : "Modrý oheň", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.fieryeye.name" : "Ohnivé oko", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.advanced" : "Lepší červená paralýza.", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.basic" : "Základní červená paralýza.", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.expert" : "Expert červená paralýza.", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.none" : "Na této úrovni nejsou žádné speciální dovednosti.", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.name" : "Červená paralýza", + "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.crushingblow.name" : "Zničující úder" +} \ No newline at end of file From 3ab5c12ac394693b2a4ccebb5b2b725c7e4618fa Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:27:33 +0100 Subject: [PATCH 461/726] Fixed missing comma --- Mods/vcmi/config/vcmi/czech.json | 1086 +++++++++++++++++++----------- 1 file changed, 708 insertions(+), 378 deletions(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 97358bc9d..56f0d23b5 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -1,379 +1,709 @@ -{ - "creatures.new-monsters-pack.axolotl-creatures-pack.acidicRoper.name.plural" : "Kyselé liany", - "creatures.new-monsters-pack.axolotl-creatures-pack.acidicRoper.name.singular" : "Kyselá liana", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpBabaYaga.name.plural" : "Baby jagy", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpBabaYaga.name.singular" : "Baba jaga", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpBrownie.name.plural" : "Dřeváci", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpBrownie.name.singular" : "Dřevák", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpDrowner.name.plural" : "Mechoví otroci", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpDrowner.name.singular" : "Mechový otrok", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpThug.name.plural" : "Násilníci", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpThug.name.singular" : "Násilník", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpWilloWisp.name.plural" : "Bludičky", - "creatures.new-monsters-pack.axolotl-creatures-pack.acpWilloWisp.name.singular" : "Bludička", - "creatures.new-monsters-pack.axolotl-creatures-pack.adventurer.name.plural" : "Dobrodruzi", - "creatures.new-monsters-pack.axolotl-creatures-pack.adventurer.name.singular" : "Dobrodruh", - "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthHunter.name.plural" : "Mamutí lovci", - "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthHunter.name.singular" : "Mamutí lovec", - "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthThrower.name.plural" : "Mamutí vrhači", - "creatures.new-monsters-pack.axolotl-creatures-pack.centamonthThrower.name.singular" : "Mamutí vrhač", - "creatures.new-monsters-pack.axolotl-creatures-pack.cockatriceHigh.name.plural" : "Lakomé kokatrice", - "creatures.new-monsters-pack.axolotl-creatures-pack.cockatriceHigh.name.singular" : "Lakomá kokatrice", - "creatures.new-monsters-pack.axolotl-creatures-pack.dragonFish.name.plural" : "Dračí ryby", - "creatures.new-monsters-pack.axolotl-creatures-pack.dragonFish.name.singular" : "Dračí ryba", - "creatures.new-monsters-pack.axolotl-creatures-pack.evilTreant.name.plural" : "Zlí enti", - "creatures.new-monsters-pack.axolotl-creatures-pack.evilTreant.name.singular" : "Zlý ent", - "creatures.new-monsters-pack.axolotl-creatures-pack.evilTree.name.plural" : "Zlé stromy", - "creatures.new-monsters-pack.axolotl-creatures-pack.evilTree.name.singular" : "Zlý strom", - "creatures.new-monsters-pack.axolotl-creatures-pack.forestDragon.name.plural" : "Lesní draci", - "creatures.new-monsters-pack.axolotl-creatures-pack.forestDragon.name.singular" : "Lesní drak", - "creatures.new-monsters-pack.axolotl-creatures-pack.greatUrox.name.plural" : "Velcí uroxové", - "creatures.new-monsters-pack.axolotl-creatures-pack.greatUrox.name.singular" : "Velký urox", - "creatures.new-monsters-pack.axolotl-creatures-pack.hillGiant.name.plural" : "Kopcoví obři", - "creatures.new-monsters-pack.axolotl-creatures-pack.hillGiant.name.singular" : "Kopcový obr", - "creatures.new-monsters-pack.axolotl-creatures-pack.hulkBuster.name.plural" : "Ničitelé hulků", - "creatures.new-monsters-pack.axolotl-creatures-pack.hulkBuster.name.singular" : "Ničitel hulků", - "creatures.new-monsters-pack.axolotl-creatures-pack.ironclad.name.plural" : "Železňáci", - "creatures.new-monsters-pack.axolotl-creatures-pack.ironclad.name.singular" : "Železňák", - "creatures.new-monsters-pack.axolotl-creatures-pack.kappa.name.plural" : "Kappy", - "creatures.new-monsters-pack.axolotl-creatures-pack.kappa.name.singular" : "Kappa", - "creatures.new-monsters-pack.axolotl-creatures-pack.kappaShoya.name.plural" : "Kappové šóji", - "creatures.new-monsters-pack.axolotl-creatures-pack.kappaShoya.name.singular" : "Kappa šója", - "creatures.new-monsters-pack.axolotl-creatures-pack.lionman.name.plural" : "Lví muži", - "creatures.new-monsters-pack.axolotl-creatures-pack.lionman.name.singular" : "Lví muž", - "creatures.new-monsters-pack.axolotl-creatures-pack.mountainGiant.name.plural" : "Horští obři", - "creatures.new-monsters-pack.axolotl-creatures-pack.mountainGiant.name.singular" : "Horský obr", - "creatures.new-monsters-pack.axolotl-creatures-pack.owlkin.name.plural" : "Sovy", - "creatures.new-monsters-pack.axolotl-creatures-pack.owlkin.name.singular" : "Sova", - "creatures.new-monsters-pack.axolotl-creatures-pack.owlkinSage.name.plural" : "Soví mudrcové", - "creatures.new-monsters-pack.axolotl-creatures-pack.owlkinSage.name.singular" : "Soví mudrc", - "creatures.new-monsters-pack.axolotl-creatures-pack.roper.name.plural" : "Liany", - "creatures.new-monsters-pack.axolotl-creatures-pack.roper.name.singular" : "Liana", - "creatures.new-monsters-pack.axolotl-creatures-pack.scorpohand.name.plural" : "Štíhoruci", - "creatures.new-monsters-pack.axolotl-creatures-pack.scorpohand.name.singular" : "Štíhoruk", - "creatures.new-monsters-pack.axolotl-creatures-pack.sellsword.name.plural" : "Žoldáci", - "creatures.new-monsters-pack.axolotl-creatures-pack.sellsword.name.singular" : "Žoldák", - "creatures.new-monsters-pack.axolotl-creatures-pack.serpentine.name.plural" : "Hadovití", - "creatures.new-monsters-pack.axolotl-creatures-pack.serpentine.name.singular" : "Hadovitý", - "creatures.new-monsters-pack.axolotl-creatures-pack.serpentineAlpha.name.plural" : "Hadovité alfy", - "creatures.new-monsters-pack.axolotl-creatures-pack.serpentineAlpha.name.singular" : "Hadovitá alfa", - "creatures.new-monsters-pack.axolotl-creatures-pack.shadowLord.name.plural" : "Páni stínů", - "creatures.new-monsters-pack.axolotl-creatures-pack.shadowLord.name.singular" : "Pán stínů", - "creatures.new-monsters-pack.axolotl-creatures-pack.sphinx.name.plural" : "Sfingy", - "creatures.new-monsters-pack.axolotl-creatures-pack.sphinx.name.singular" : "Sfinga", - "creatures.new-monsters-pack.axolotl-creatures-pack.stoneCollossus.name.plural" : "Kamenní kolosi", - "creatures.new-monsters-pack.axolotl-creatures-pack.stoneCollossus.name.singular" : "Kamenný kolos", - "creatures.new-monsters-pack.axolotl-creatures-pack.stoneLord.name.plural" : "Páni kamene", - "creatures.new-monsters-pack.axolotl-creatures-pack.stoneLord.name.singular" : "Pán kamene", - "creatures.new-monsters-pack.axolotl-creatures-pack.swampDragon.name.plural" : "Bažinní draci", - "creatures.new-monsters-pack.axolotl-creatures-pack.swampDragon.name.singular" : "Bažinný drak", - "creatures.new-monsters-pack.axolotl-creatures-pack.tentoslimus.name.plural" : "Pohlcovači", - "creatures.new-monsters-pack.axolotl-creatures-pack.tentoslimus.name.singular" : "Pohlcovač", - "creatures.new-monsters-pack.axolotl-creatures-pack.tortofroid.name.plural" : "Želvouni", - "creatures.new-monsters-pack.axolotl-creatures-pack.tortofroid.name.singular" : "Želvoun", - "creatures.new-monsters-pack.axolotl-creatures-pack.urox.name.plural" : "Uroxové", - "creatures.new-monsters-pack.axolotl-creatures-pack.urox.name.singular" : "Urox", - "creatures.new-monsters-pack.axolotl-creatures-pack.veninScorpohand.name.plural" : "Jedovatí štíhoruci", - "creatures.new-monsters-pack.axolotl-creatures-pack.veninScorpohand.name.singular" : "Jedovatý štíhoruk", - "creatures.new-monsters-pack.axolotl-creatures-pack.vileTortofroid.name.plural" : "Zlomyslní želvíci", - "creatures.new-monsters-pack.axolotl-creatures-pack.vileTortofroid.name.singular" : "Zlomyslný želvík", - "creatures.new-monsters-pack.axolotl-creatures-pack.wyrmFish.name.plural" : "Drakoryby", - "creatures.new-monsters-pack.axolotl-creatures-pack.wyrmFish.name.singular" : "Drakoryba", - "creatures.new-monsters-pack.axolotl-creatures-pack.yeti.name.plural" : "Yetiové", - "creatures.new-monsters-pack.axolotl-creatures-pack.yeti.name.singular" : "Yeti", - "creatures.new-monsters-pack.axolotl-creatures-pack.zombieHigh.name.plural" : "Neodpuštění mrtví", - "creatures.new-monsters-pack.axolotl-creatures-pack.zombieHigh.name.singular" : "Neodpuštěný mrtvý", - "creatures.new-monsters-pack.axolotl-creatures-pack.zombieLow.name.plural" : "Pomstychtiví mrtví", - "creatures.new-monsters-pack.axolotl-creatures-pack.zombieLow.name.singular" : "Pomstychtivý mrtvý", - "creatures.new-monsters-pack.carpet-whisperers.dunesCarpetWhisperer.name.plural" : "Zaříkávači koberců", - "creatures.new-monsters-pack.carpet-whisperers.dunesCarpetWhisperer.name.singular" : "Zaříkávač koberců", - "creatures.new-monsters-pack.dark-dwarf-at-arms.dark elemental.darkElemental.name.plural" : "Temní elementálové", - "creatures.new-monsters-pack.dark-dwarf-at-arms.dark elemental.darkElemental.name.singular" : "Temný elementál", - "creatures.new-monsters-pack.dark-dwarf-at-arms.man at arms.manAtArms.name.plural" : "Zbrojnoši", - "creatures.new-monsters-pack.dark-dwarf-at-arms.man at arms.manAtArms.name.singular" : "Zbrojnoš", - "creatures.new-monsters-pack.dark-dwarf-at-arms.mountain dwarf.mountainDwarf.name.plural" : "Horští trpaslíci", - "creatures.new-monsters-pack.dark-dwarf-at-arms.mountain dwarf.mountainDwarf.name.singular" : "Horský trpaslík", - "creatures.new-monsters-pack.doommod.lostsoul.name.plural" : "Ztracené duše", - "creatures.new-monsters-pack.doommod.lostsoul.name.singular" : "Ztracená duše", - "creatures.new-monsters-pack.dwarf-pack.raDwarCrossbowman.name.plural" : "Trpasličí kušníci", - "creatures.new-monsters-pack.dwarf-pack.raDwarCrossbowman.name.singular" : "Trpasličí kušník", - "creatures.new-monsters-pack.dwarf-pack.raDwarfGuard.name.plural" : "Trpasličí strážci", - "creatures.new-monsters-pack.dwarf-pack.raDwarfGuard.name.singular" : "Trpasličí strážce", - "creatures.new-monsters-pack.dwarf-pack.raDwarfShaman.name.plural" : "Trpasličí šamani", - "creatures.new-monsters-pack.dwarf-pack.raDwarfShaman.name.singular" : "Trpasličí šaman", - "creatures.new-monsters-pack.dwarf-pack.raDwarfberserk.name.plural" : "Trpasličí berserkři", - "creatures.new-monsters-pack.dwarf-pack.raDwarfberserk.name.singular" : "Trpasličí berserk", - "creatures.new-monsters-pack.dwarf-pack.raDwarfsaboteur.name.plural" : "Trpasličí sabotéři", - "creatures.new-monsters-pack.dwarf-pack.raDwarfsaboteur.name.singular" : "Trpasličí sabotér", - "creatures.new-monsters-pack.extra-creature-pack.expBanshee.name.plural" : "Banshee", - "creatures.new-monsters-pack.extra-creature-pack.expBanshee.name.singular" : "Banshee", - "creatures.new-monsters-pack.extra-creature-pack.expBigspider.name.plural" : "Velcí pavouci", - "creatures.new-monsters-pack.extra-creature-pack.expBigspider.name.singular" : "Velký pavouk", - "creatures.new-monsters-pack.extra-creature-pack.expCyberDead.name.plural" : "Kybermrtví", - "creatures.new-monsters-pack.extra-creature-pack.expCyberDead.name.singular" : "Kybermrtvý", - "creatures.new-monsters-pack.extra-creature-pack.expCyberZombie.name.plural" : "Kyberzombíci", - "creatures.new-monsters-pack.extra-creature-pack.expCyberZombie.name.singular" : "Kyberzombík", - "creatures.new-monsters-pack.extra-creature-pack.expDalek.name.plural" : "Dalekové", - "creatures.new-monsters-pack.extra-creature-pack.expDalek.name.singular" : "Dalek", - "creatures.new-monsters-pack.extra-creature-pack.expDwarfTechnic.name.plural" : "Trpasličí technici", - "creatures.new-monsters-pack.extra-creature-pack.expDwarfTechnic.name.singular" : "Trpasličí technik", - "creatures.new-monsters-pack.extra-creature-pack.expEnormCrab.name.plural" : "Obří krabi", - "creatures.new-monsters-pack.extra-creature-pack.expEnormCrab.name.singular" : "Obří krab", - "creatures.new-monsters-pack.extra-creature-pack.expFanaticGuardian.name.plural" : "Fanatičtí strážci", - "creatures.new-monsters-pack.extra-creature-pack.expFanaticGuardian.name.singular" : "Fanatický strážce", - "creatures.new-monsters-pack.extra-creature-pack.expGiantDragon.name.plural" : "Obří draci", - "creatures.new-monsters-pack.extra-creature-pack.expGiantDragon.name.singular" : "Obří drak", - "creatures.new-monsters-pack.extra-creature-pack.expGroundDalek.name.plural" : "Pozemní dalekové", - "creatures.new-monsters-pack.extra-creature-pack.expGroundDalek.name.singular" : "Pozemní dalek", - "creatures.new-monsters-pack.extra-creature-pack.expJadeDragon.name.plural" : "Nefritoví draci", - "creatures.new-monsters-pack.extra-creature-pack.expJadeDragon.name.singular" : "Nefritový drak", - "creatures.new-monsters-pack.extra-creature-pack.expMechDragon.name.plural" : "Mechanizovaní draci", - "creatures.new-monsters-pack.extra-creature-pack.expMechDragon.name.singular" : "Mechanizovaný drak", - "creatures.new-monsters-pack.extra-creature-pack.expMonstrousMimic.name.plural" : "Monstrózní mimikové", - "creatures.new-monsters-pack.extra-creature-pack.expMonstrousMimic.name.singular" : "Monstrózní mimik", - "creatures.new-monsters-pack.extra-creature-pack.expNecroticBeast.name.plural" : "Nekrotické bestie", - "creatures.new-monsters-pack.extra-creature-pack.expNecroticBeast.name.singular" : "Nekrotická bestie", - "creatures.new-monsters-pack.extra-creature-pack.expRoboDragon.name.plural" : "Robo draci", - "creatures.new-monsters-pack.extra-creature-pack.expRoboDragon.name.singular" : "Robo drak", - "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffAssu.name.plural" : "Útoční gryfové", - "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffAssu.name.singular" : "Útočný gryf", - "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffon.name.plural" : "Robo gryfové", - "creatures.new-monsters-pack.extra-creature-pack.expRoboGriffon.name.singular" : "Robo gryf", - "creatures.new-monsters-pack.extra-creature-pack.expSwampGorynych.name.plural" : "Bažinní Goryniši", - "creatures.new-monsters-pack.extra-creature-pack.expSwampGorynych.name.singular" : "Bažinná Gorynycha", - "creatures.new-monsters-pack.extra-creature-pack.expSword1.name.plural" : "Mystické meče", - "creatures.new-monsters-pack.extra-creature-pack.expSword1.name.singular" : "Mystický meč", - "creatures.new-monsters-pack.extra-creature-pack.expSword2.name.plural" : "Legendární meče", - "creatures.new-monsters-pack.extra-creature-pack.expSword2.name.singular" : "Legendární meč", - "creatures.new-monsters-pack.extra-creature-pack.expTRex.name.plural" : "T-Rexové", - "creatures.new-monsters-pack.extra-creature-pack.expTRex.name.singular" : "T-Rex", - "creatures.new-monsters-pack.extra-creature-pack.expTank.name.plural" : "Naga tanky", - "creatures.new-monsters-pack.extra-creature-pack.expTank.name.singular" : "Naga tank", - "creatures.new-monsters-pack.extra-creature-pack.expYaoGuai.name.plural" : "Yao Guaiové", - "creatures.new-monsters-pack.extra-creature-pack.expYaoGuai.name.singular" : "Yao Guai", - "creatures.new-monsters-pack.flugel.flugel.name.plural" : "Flügelové", - "creatures.new-monsters-pack.flugel.flugel.name.singular" : "Flügel", - "creatures.new-monsters-pack.invisible-man.invisibleMan.name.plural" : "Neviditelní", - "creatures.new-monsters-pack.invisible-man.invisibleMan.name.singular" : "Neviditelný", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.kurekCherub.name.plural" : "Cherubíní", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.kurekCherub.name.singular" : "Cherub", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-fortress.black-gorgon.blackgorgon.name.plural" : "Černé gorgony", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-fortress.black-gorgon.blackgorgon.name.singular" : "Černá gorgona", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.eagle.eagle.name.plural" : "Orli", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.eagle.eagle.name.singular" : "Orel", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.purpledragon.name.plural" : "Fialoví draci", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.purpledragon.name.singular" : "Fialový drak", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.kurekBloodeyedCyclops.name.plural" : "Krvaví kyklopové", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.kurekBloodeyedCyclops.name.singular" : "Krvavý kyklop", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.kurekCyclopsWarrior.name.plural" : "Kyklopští válečníci", - "creatures.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.kurekCyclopsWarrior.name.singular" : "Kyklopský válečník", - "creatures.new-monsters-pack.tok.more creatures.cr_bugbear.name.plural" : "Medvědovci", - "creatures.new-monsters-pack.tok.more creatures.cr_bugbear.name.singular" : "Medvědovec", - "creatures.new-monsters-pack.tok.more creatures.cr_bugbear_warlord.name.plural" : "Váleční Medvědovci", - "creatures.new-monsters-pack.tok.more creatures.cr_bugbear_warlord.name.singular" : "Válečný Medvědovec", - "creatures.new-monsters-pack.tok.more creatures.cr_gnome.name.plural" : "Skřítci", - "creatures.new-monsters-pack.tok.more creatures.cr_gnome.name.singular" : "Skřítek", - "creatures.new-monsters-pack.tok.more creatures.cr_kobold.name.plural" : "Koboldi", - "creatures.new-monsters-pack.tok.more creatures.cr_kobold.name.singular" : "Kobold", - "creatures.new-monsters-pack.tok.more creatures.cr_monk_loxodon.name.plural" : "Sloní mnichové", - "creatures.new-monsters-pack.tok.more creatures.cr_monk_loxodon.name.singular" : "Sloní mnich", - "creatures.new-monsters-pack.tok.more creatures.cr_orangutan.name.plural" : "Orangutani", - "creatures.new-monsters-pack.tok.more creatures.cr_orangutan.name.singular" : "Orangutan", - "creatures.new-monsters-pack.tok.more creatures.cr_plaguedoctor.name.plural" : "Moroví doktoři", - "creatures.new-monsters-pack.tok.more creatures.cr_plaguedoctor.name.singular" : "Morový doktor", - "creatures.new-monsters-pack.tok.more creatures.cr_shaman.name.plural" : "Lesní šamani", - "creatures.new-monsters-pack.tok.more creatures.cr_shaman.name.singular" : "Lesní šaman", - "creatures.new-monsters-pack.tok.more creatures.cr_tyrannosaurus.name.plural" : "Tyrannosauři", - "creatures.new-monsters-pack.tok.more creatures.cr_tyrannosaurus.name.singular" : "Tyrannosaurus", - "creatures.new-monsters-pack.undeadsphinxes.ashenSphinx.name.plural" : "Popelavé sfingy", - "creatures.new-monsters-pack.undeadsphinxes.ashenSphinx.name.singular" : "Popelavá sfinga", - "creatures.new-monsters-pack.undeadsphinxes.entombedSphinx.name.plural" : "Zapečetěné sfingy", - "creatures.new-monsters-pack.undeadsphinxes.entombedSphinx.name.singular" : "Zapečetěná sfinga", - "creatures.new-monsters-pack.undeadsphinxes.madMummy.name.plural" : "Mumie", - "creatures.new-monsters-pack.undeadsphinxes.madMummy.name.singular" : "Mumie", - "creatures.new-monsters-pack.wicked-witch.kraven.name.plural" : "Havrani", - "creatures.new-monsters-pack.wicked-witch.kraven.name.singular" : "Havran", - "creatures.new-monsters-pack.wicked-witch.wwickedWitch.name.plural" : "Zlé čarodějnice", - "creatures.new-monsters-pack.wicked-witch.wwickedWitch.name.singular" : "Zlá čarodějnice", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.name" : "Cynthia", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.specialty.description" : "Získává bonus 5% na každý level v umění rušení.", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.specialty.name" : "Rušení", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_cintia.specialty.tooltip" : "Bonus k dovednostem: Rušení", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.name" : "Marlon", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.specialty.description" : "Získává 5% bonus na magii za každou úroveň.", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.specialty.name" : "Magie", - "hero.new-monsters-pack.tok.more heroes.hota heroes.h_marlon.specialty.tooltip" : "Bonus k dovednostem: Magie", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.name" : "Velochi", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.specialty.description" : "Získává bonus 5 % za úroveň k dovednosti Střelec.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.specialty.name" : "Střelec", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_ann.specialty.tooltip" : "Bonus k vedlejší dovednosti: Střelec", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.name" : "Bha", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.specialty.description" : "Po dosažení 6. úrovně zvyšuje útočnou a obrannou sílu sloních mnichů.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.specialty.name" : "Sloní mnichové", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_bha.specialty.tooltip" : "Posílení jednotek: Sloní mnichové", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.biography" : "Gromm je respektovaný náčelník bugbearů, který během orkských nepokojů sjednotil rozptýlené kmeny a stal se vizionářským vůdcem. Jeho cílem není jen dobývání, ale sjednocení bugbearů do mocné síly.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.name" : "Gromm", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.specialty.description" : "Po dosažení 4. úrovně zvyšuje útočnou a obrannou sílu bugbearů.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.specialty.name" : "Bugbearové", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_gromm.specialty.tooltip" : "Posílení jednotek: Bugbearové", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.name" : "Kurik", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.specialty.description" : "Po dosažení 1. úrovně zvyšuje útočnou a obrannou sílu koboldů.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.specialty.name" : "Koboldi", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_kurik.specialty.tooltip" : "Posílení jednotek: Koboldi", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.name" : "Lamount", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.specialty.description" : "Zvyšuje Rychlost jakéhokoli orangutana o 1 a jejich Obranu o 1 za každou úroveň dosaženou po 3. úrovni. Navíc, orangutani získávají trvalý bonus 25 % k Zdraví.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.specialty.name" : "Orangutan", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lamount.specialty.tooltip" : "Bonus za jednotku: Orangutan", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.name" : "Lorme", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.specialty.description" : "Zvyšuje Rychlost jakéhokoli Morového doktora o 1 a jejich Útok a Obranu o 5 % každé 4 úrovně.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.specialty.name" : "Morový doktor", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_lorme.specialty.tooltip" : "Bonus za jednotku: Morový doktor", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.name" : "Melanchalot", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.specialty.description" : "Všechny jednotky v boji mají o 5% vyšší životy.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.specialty.name" : "Vytrvalost", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_melaunchalot.specialty.tooltip" : "Bonus k dovednostem: Vytrvalost", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.name" : "Veles", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.specialty.description" : "{Protimagie}", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.specialty.name" : "Oslabení", - "hero.new-monsters-pack.tok.more heroes.tok heroes.h_veles.specialty.tooltip" : "Oslabení", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.biography" : "Už jako dítě utekla a žila v lesích na úpatí velké hory, říkajíc si dcera vánku. Tvořila své mládí slovy větru a získala tak své vědomosti a dovednosti.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.name" : "Birog", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.specialty.description" : "Použití kouzla zrychlení má velký efekt, ale záleží na rozdílu úrovní mezi hrdinou a cílem (nižší úroveň cíle, lepší efekt).", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.specialty.name" : "Zrychlení", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_birog.specialty.tooltip" : "Magické zesílení: Zrychlení", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.biography" : "Zahradník, který je hrdý na své sazenice, žije pomalým životem v souladu s přírodou. Možná jeho nadpřirozené štěstí zajistilo, že jeho obyčejné dny byly plné překvapení a jeho rostliny kvetly a plodily.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.name" : "Mabon", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.specialty.description" : "+1 drahokam každý den", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.specialty.name" : "+1 Drahokam", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rn_mabon.specialty.tooltip" : "Zvyšuje příjem království o +1 drahokam denně.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.name" : "Boudicca", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.specialty.description" : "Nepřátelé nemohou v boji utéct.", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.specialty.name" : "Zakázání útěku", - "hero.new-monsters-pack.tok.more heroes.tok heroes.rna_boudicca.specialty.tooltip" : "Nepřátelé nemohou v boji utéct.", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.name" : "Larami", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.specialty.description" : "Po dosažení 7. úrovně zvyšuje útočnou a obrannou sílu tyrannosaurů.", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.specialty.name" : "Tyrannosauři", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_larami.specialty.tooltip" : "Posílení jednotek: Tyrannosauři", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_nana.name" : "Nana", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_nana.specialty.description" : "{Prokletí}\n", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_nana.specialty.name" : "Prokletí", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_rudi.name" : "Rudi", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_rudi.specialty.description" : "{První pomoc}\n", - "hero.new-monsters-pack.tok.more heroes.tow heroes.h_rudi.specialty.name" : "{První pomoc", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_agatha.name" : "Agatha", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_agatha.specialty.description" : "{Andělé}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_agatha.specialty.name" : "Andělé", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_auris.name" : "Auris", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_auris.specialty.description" : "{Vzdušný Elementál}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_auris.specialty.name" : "Vzdušný Elementál", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_bel.name" : "Bel", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_bel.specialty.description" : "{Ohnivý Elementál}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_bel.specialty.name" : "Ohnivý Elementál", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_elena.name" : "Elena", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_elena.specialty.description" : "{Kopiník}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_elena.specialty.name" : "Kopiník", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_eordan.name" : "Eordan", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_eordan.specialty.description" : "{Zemní Elementál}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_eordan.specialty.name" : "Zemní Elementál", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_gre.name" : "Gre", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_gre.specialty.description" : "{Pohyblivý písek}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_gre.specialty.name" : "Pohyblivý písek", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_hana.name" : "Hana", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_hana.specialty.description" : "{Vodní Elementál}\n", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_hana.specialty.name" : "Vodní Elementál", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.name" : "Liana", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.specialty.description" : "Po dosažení 1. úrovně zvyšuje útočnou a obrannou sílu pixií nebo spriteů.", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.specialty.name" : "Skřítci", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_liana.specialty.tooltip" : "Posílení jednotek: Skřítci", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.name" : "Pandora", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.specialty.description" : "Po dosažení 7. úrovně zvyšuje útočnou a obrannou sílu fénixů nebo ohnivých ptáků.", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.specialty.name" : "Fénixové", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_pandora.specialty.tooltip" : "Posílení jednotek: Fénixové", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.name" : "Third", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.specialty.description" : "Po dosažení 5. úrovně zvyšuje útočnou a obrannou sílu golemů.", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.specialty.name" : "Golemové", - "hero.new-monsters-pack.tok.more heroes.vanilla heroes.h_third.specialty.tooltip" : "Posílení jednotek: Golemové", - "hero.new-monsters-pack.wicked-witch.elfaba.biography" : "{Západní zlá čarodějnice} přišla na Enroth, aby zotročila celou zemi. Temní, pochmurní havrani ji vždy doprovázejí, děsí a straší lidi. Elfaba má ráda zápach bažin, proto se rozhodla zůstat s gorgonami. Nikdo neví, jak zastavit tuto smrtící krásku.", - "hero.new-monsters-pack.wicked-witch.elfaba.name" : "Elfaba", - "hero.new-monsters-pack.wicked-witch.elfaba.specialty.description" : "Zvyšuje útočné a obranné dovednosti jakékoliv zlé čarodějnice s každou úrovní dosaženou po 4. úrovni.", - "hero.new-monsters-pack.wicked-witch.elfaba.specialty.name" : "Zlé čarodějnice", - "hero.new-monsters-pack.wicked-witch.elfaba.specialty.tooltip" : "Bonus pro jednotku: Zlé čarodějnice", - "heroClass.new-monsters-pack.tok.more heroes.neutral classes.hc_explorer.name" : "Průzkumník", - "heroClass.new-monsters-pack.tok.more heroes.neutral classes.hc_mystic.name" : "Mystik", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpAshTree.name" : "Jasanový strom", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpButteExcavation.name" : "Výkop na kopci", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpCenotaph.name" : "Cenotaf", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpChalet.name" : "Chalupa", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpCluster.name" : "Shluk", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpDarkPortal.name" : "Temný portál", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpForsakenPond.name" : "Opuštěný rybník", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpFrozenCavity.name" : "Zmrzlá dutina", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpHutHensLeg.name" : "Chalupa na kuří noze", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpLostUshnu.name" : "Ztracený oltář", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpMire.name" : "Bažina", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpMurkyChasm.name" : "Temná propast", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpNiveousLair.name" : "Sněhové doupě", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpQuaggyRelic.name" : "Bahenní relikvie", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpRoughcastPlant.name" : "Odlévací závod", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpRustyHabitation.name" : "Zrezivělá obydlí", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpShatteredGravestone.name" : "Rozbitý náhrobek", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSinfulWorkshop.name" : "Hříšná dílna", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSordidPost.name" : "Nechvalná pošta", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSparkyMeadow.name" : "Jiskřivá louka", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpSqualidNide.name" : "Zchátralé hnízdo", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpTombOfRiddles.name" : "Hrobka záhad", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpTupik.name" : "Kožený stan", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpTurquoisePassage.name" : "Tyrkysový průchod", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpViciousWorkshop.name" : "Zlomyslná dílna", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpblackAlder.name" : "Temná olše", - "mapObject.new-monsters-pack.axolotl-creatures-pack.creatureGeneratorCommon.acpslimyBurrow.name" : "Slizké doupě", - "mapObject.new-monsters-pack.carpet-whisperers.creatureGeneratorCommon.carpetSeller.name" : "Prodejce koberců", - "mapObject.new-monsters-pack.dark-dwarf-at-arms.dark elemental.creatureGeneratorCommon.temple.name" : "Chrám temnoty", - "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raDwarfsBarracks.name" : "Trpasličí kasárna", - "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raForestCottage.name" : "Lesní chalupa", - "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raHill.name" : "Kopec", - "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raSanctuary.name" : "Svatyně", - "mapObject.new-monsters-pack.dwarf-pack.creatureGeneratorCommon.raStatue.name" : "Socha vůdce", - "mapObject.new-monsters-pack.flugel.creatureGeneratorCommon.fpFlugelDwell.name" : "Nebeská knihovna", - "mapObject.new-monsters-pack.invisible-man.creatureBank.quarkPalace.name" : "Kvarkový palác", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.creatureGeneratorCommon.kurekAltarOfProphecies.name" : "Oltář proroctví", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-fortress.black-gorgon.creatureGeneratorCommon.blackLair.name" : "Černé doupě", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-haven.marine-serail.creatureBank.marineSerail.name" : "Námořní pevnost", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-neutral.eagle.creatureGeneratorCommon.eaglenest.name" : "Orlí hnízdo", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.creatureGeneratorCommon.purpleTower.name" : "Fialová věž", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.creatureGeneratorCommon.kurekCyclopsPyramid.name" : "Kyklopská pyramida", - "mapObject.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.creatureGeneratorCommon.kurekCyclopsLarder.name" : "Kyklopská spižírna", - "mapObject.new-monsters-pack.undeadsphinxes.creatureGeneratorCommon.pestilentPyramid.name" : "Morová pyramida", - "mapObject.new-monsters-pack.wicked-witch.creatureGeneratorCommon.louryEstate.name" : "Louryho sídlo", - "skill.new-monsters-pack.tok.more secondary.endurance.description.advanced" : "{Střední vytrvalost}\n\nVšechny jednotky v boji mají o 10% vyšší životy.", - "skill.new-monsters-pack.tok.more secondary.endurance.description.basic" : "{Základní vytrvalost}\n\nVšechny jednotky v boji mají o 5% vyšší životy.", - "skill.new-monsters-pack.tok.more secondary.endurance.description.expert" : "{Expertní vytrvalost}\n\nVšechny jednotky v boji mají o 15% vyšší životy.", - "skill.new-monsters-pack.tok.more secondary.endurance.name" : "Vytrvalost", - "skill.new-monsters-pack.tok.more secondary.fame.description.advanced" : "{Střední sláva}\n\nHrdina bránící město zvýší produkci jednotek 1. úrovně o +4, 2. úrovně o +2 a 3. úrovně o +1.", - "skill.new-monsters-pack.tok.more secondary.fame.description.basic" : "{Základní sláva}\n\nHrdina bránící město zvýší produkci jednotek 1. úrovně o +2 a 2. úrovně o +1.", - "skill.new-monsters-pack.tok.more secondary.fame.description.expert" : "{Expertní sláva}\n\nHrdina bránící město zvýší produkci všech jednotek o 25%.", - "skill.new-monsters-pack.tok.more secondary.fame.name" : "Sláva", - "skill.new-monsters-pack.tok.more secondary.warding.description.advanced" : "{Střední ochrana}\n\nVšechny jednotky získají magii magického zrcadla s pravděpodobností odrazu 50%.", - "skill.new-monsters-pack.tok.more secondary.warding.description.basic" : "{Základní ochrana}\n\nVšechny jednotky získají magii magického zrcadla s pravděpodobností odrazu 25%.", - "skill.new-monsters-pack.tok.more secondary.warding.description.expert" : "{Expertní ochrana}\n\nVšechny jednotky získají magii magického zrcadla s pravděpodobností odrazu 75%.", - "skill.new-monsters-pack.tok.more secondary.warding.name" : "Ochrana", - "spell.new-monsters-pack.axolotl-creatures-pack.acpDepletion.name" : "Vyčerpání", - "spell.new-monsters-pack.axolotl-creatures-pack.degradation.name" : "Degradace", - "spell.new-monsters-pack.axolotl-creatures-pack.fungus.name" : "Houbová infekce", - "spell.new-monsters-pack.axolotl-creatures-pack.paraFungus.name" : "Parazitická houba", - "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.advanced" : "{Kouzelný koberec}", - "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.basic" : "{Kouzelný koberec}", - "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.expert" : "{Kouzelný koberec}", - "spell.new-monsters-pack.carpet-whisperers.magicCarpet.description.none" : "{Kouzelný koberec}", - "spell.new-monsters-pack.carpet-whisperers.magicCarpet.name" : "Kouzelný koberec", - "spell.new-monsters-pack.kurek-creatures.new-content-for-castle.cherub.holyBlade.name" : "Meč nebes", - "spell.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.bluefire.name" : "Modrý oheň", - "spell.new-monsters-pack.kurek-creatures.new-content-for-neutral.purpledragon.bluefiredamageeffect.name" : "Modrý oheň", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.fieryeye.name" : "Ohnivé oko", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.advanced" : "Lepší červená paralýza.", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.basic" : "Základní červená paralýza.", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.expert" : "Expert červená paralýza.", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.description.none" : "Na této úrovni nejsou žádné speciální dovednosti.", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.bloodeyed_cyclops.redparalyze.name" : "Červená paralýza", - "spell.new-monsters-pack.kurek-creatures.new-content-for-stronghold.cyclops_warrior.crushingblow.name" : "Zničující úder" +{ + "vcmi.adventureMap.monsterThreat.title" : "\n\nHrozba: ", + "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", + "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", + "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", + "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", + "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", + "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", + "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", + "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", + "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", + "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", + "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", + "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", + "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", + "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", + "vcmi.adventureMap.search.hover" : "Prohledat objekt", + "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", + + "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", + "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", + "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", + "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", + "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", + "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", + + "vcmi.capitalColors.0" : "Červený", + "vcmi.capitalColors.1" : "Modrý", + "vcmi.capitalColors.2" : "Hnědý", + "vcmi.capitalColors.3" : "Zelený", + "vcmi.capitalColors.4" : "Oranžový", + "vcmi.capitalColors.5" : "Fialový", + "vcmi.capitalColors.6" : "Tyrkysový", + "vcmi.capitalColors.7" : "Růžový", + + "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", + "vcmi.heroOverview.warMachine" : "Bojové stroje", + "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", + "vcmi.heroOverview.spells" : "Kouzla", + + "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", + "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", + "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", + "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", + "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", + + "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", + "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", + "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", + "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", + "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", + "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", + + "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", + "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", + "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", + "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", + "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", + "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", + + "vcmi.radialWheel.moveTop" : "Přesunout nahoru", + "vcmi.radialWheel.moveUp" : "Posunout výše", + "vcmi.radialWheel.moveDown" : "Posunout níže", + "vcmi.radialWheel.moveBottom" : "Přesunout dolů", + + "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", + "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", + "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", + "vcmi.randomMap.description.water.none" : "žádná", + "vcmi.randomMap.description.water.normal" : "normální", + "vcmi.randomMap.description.water.islands" : "ostrovy", + "vcmi.randomMap.description.monster.weak" : "nízká", + "vcmi.randomMap.description.monster.normal" : "normální", + "vcmi.randomMap.description.monster.strong" : "vysoká", + + "vcmi.spellBook.search" : "Hledat", + + "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", + "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", + "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", + "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", + "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", + "vcmi.spellResearch.abort" : "Přerušit", + + "vcmi.mainMenu.serverConnecting" : "Připojování...", + "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", + "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", + "vcmi.mainMenu.serverClosing" : "Zavírání...", + "vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP", + "vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP", + + "vcmi.lobby.filepath" : "Název souboru", + "vcmi.lobby.creationDate" : "Datum vytvoření", + "vcmi.lobby.scenarioName" : "Název scénáře", + "vcmi.lobby.mapPreview" : "Náhled mapy", + "vcmi.lobby.noPreview" : "bez náhledu", + "vcmi.lobby.noUnderground" : "bez podzemí", + "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", + "vcmi.lobby.backToLobby" : "Vrátit se do lobby", + "vcmi.lobby.author" : "Autor", + "vcmi.lobby.handicap" : "Postih", + "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", + "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", + "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", + + "vcmi.lobby.login.title" : "Online lobby VCMI", + "vcmi.lobby.login.username" : "Uživatelské jméno:", + "vcmi.lobby.login.connecting" : "Připojování...", + "vcmi.lobby.login.error" : "Chyba při připojování: %s", + "vcmi.lobby.login.create" : "Nový účet", + "vcmi.lobby.login.login" : "Přihlásit se", + "vcmi.lobby.login.as" : "Přilásit se jako %s", + "vcmi.lobby.header.rooms" : "Herní místnosti - %d", + "vcmi.lobby.header.channels" : "Kanály konverzace", + "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name + "vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time + "vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player + "vcmi.lobby.header.history" : "Vaše předchozí hry", + "vcmi.lobby.header.players" : "Online hráči - %d", + "vcmi.lobby.match.solo" : "Hra jednoho hráče", + "vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player + "vcmi.lobby.match.multi" : "%d hráčů", + "vcmi.lobby.room.create" : "Vytvořit novou místnost", + "vcmi.lobby.room.players.limit" : "Omezení počtu hráčů", + "vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.", + "vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.", + "vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.", + "vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", + "vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).", + "vcmi.lobby.invite.header" : "Pozvat hráče", + "vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.", + "vcmi.lobby.preview.title" : "Připojit se do herní místnosti", + "vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host + "vcmi.lobby.preview.version" : "Verze hry:", + "vcmi.lobby.preview.players" : "Hráči:", + "vcmi.lobby.preview.mods" : "Použité modifikace:", + "vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?", + "vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.", + "vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.", + "vcmi.lobby.preview.error.full" : "Místnost je již plná.", + "vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.", + "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", + "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", + "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", + "vcmi.lobby.room.new" : "Nová hra", + "vcmi.lobby.room.load" : "Načíst hru", + "vcmi.lobby.room.type" : "Druh místnosti", + "vcmi.lobby.room.mode" : "Herní režim", + "vcmi.lobby.room.state.public" : "Veřejná", + "vcmi.lobby.room.state.private" : "Soukromá", + "vcmi.lobby.room.state.busy" : "Ve hře", + "vcmi.lobby.room.state.invited" : "Pozvaný", + "vcmi.lobby.mod.state.compatible" : "Kompatibilní", + "vcmi.lobby.mod.state.disabled" : "Musí být povolena", + "vcmi.lobby.mod.state.version" : "Neshoda verze", + "vcmi.lobby.mod.state.excessive" : "Musí být zakázána", + "vcmi.lobby.mod.state.missing" : "Není nainstalována", + "vcmi.lobby.pvp.coin.hover" : "Mince", + "vcmi.lobby.pvp.coin.help" : "Hodí mincí", + "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", + "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", + "vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", + "vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace", + "vcmi.lobby.pvp.versus" : "vs.", + + "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", + "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", + "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", + "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color + "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", + "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", + "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", + "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", + "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", + "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", + + "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", + + "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", + "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", + "vcmi.settingsMainWindow.battleTab.hover" : "Bitva", + "vcmi.settingsMainWindow.battleTab.help" : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.", + "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa", + "vcmi.settingsMainWindow.adventureTab.help" : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).", + + "vcmi.systemOptions.videoGroup" : "Nastavení obrazu", + "vcmi.systemOptions.audioGroup" : "Nastavení zvuku", + "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now + "vcmi.systemOptions.townsGroup" : "Obrazovka města", + + "vcmi.statisticWindow.statistics" : "Statistiky", + "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", + "vcmi.statisticWindow.selectView" : "Vybrat pohled", + "vcmi.statisticWindow.value" : "Hodnota", + "vcmi.statisticWindow.title.overview" : "Přehled", + "vcmi.statisticWindow.title.resources" : "Zdroje", + "vcmi.statisticWindow.title.income" : "Příjem", + "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", + "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", + "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", + "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", + "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", + "vcmi.statisticWindow.title.armyStrength" : "Síla armády", + "vcmi.statisticWindow.title.experience" : "Zkušenosti", + "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", + "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", + "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", + "vcmi.statisticWindow.param.playerName" : "Jméno hráče", + "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", + "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", + "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", + "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", + "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", + "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", + "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", + "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", + "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", + "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", + "vcmi.statisticWindow.icon.defeated" : "Porážka", + + "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", + "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", + "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", + "vcmi.systemOptions.fullscreenExclusive.help" : "{Celá obrazovka}\n\nPokud je vybráno, VCMI poběží v režimu exkluzivní celé obrazovky. V tomto režimu hra změní rozlišení na vybrané.", + "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", + "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", + "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", + "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", + "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", + "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", + "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", + "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" + "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", + "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", + "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", + "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", + "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", + "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", + "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", + "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", + "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", + "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", + "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", + + "vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací", + "vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.", + "vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek", + "vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.", + "vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu", + "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", + "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", + "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", + "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", + "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", + "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", + "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", + "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", + "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", + "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", + "vcmi.adventureOptions.mapScrollSpeed1.hover": "", + "vcmi.adventureOptions.mapScrollSpeed5.hover": "", + "vcmi.adventureOptions.mapScrollSpeed6.hover": "", + "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", + "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", + "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", + "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", + "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", + + "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", + "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", + "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", + "vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ", + "vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ", + "vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.", + "vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", + "vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.", + "vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", + "vcmi.battleOptions.animationsSpeed1.hover": "", + "vcmi.battleOptions.animationsSpeed5.hover": "", + "vcmi.battleOptions.animationsSpeed6.hover": "", + "vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.", + "vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.", + "vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.", + "vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí", + "vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Zobrazit omezení dostřelu střelců při najetí}\n\nZobrazit dostřel střelce při najetí na něj.", + "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Zobrazit okno statistik hrdinů", + "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", + "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", + "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", + "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", + "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", + "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", + "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", + + "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", + "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", + + "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", + "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", + "vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.ranged" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d střel zbývá", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d střela zbývá", + "vcmi.battleWindow.damageEstimation.damage" : "%d poškození", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", + "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", + + "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", + "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", + "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", + "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", + "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", + + "vcmi.battleWindow.killed" : "Zabito", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", + + "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", + + "vcmi.tutorialWindow.title" : "Úvod ovládání dotykem", + "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", + "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", + "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", + "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", + "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", + "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", + "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", + + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Zobrazit týdenní přírůstek jednotek}\n\nZobrazit týdenní přírůstek jednotek místo dostupného počtu ke koupení v přehledu města (levý spodní okraj obrazovky města).", + "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", + "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", + + "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", + "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", + + "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", + "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", + + "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", + "vcmi.logicalExpressions.allOf" : "Všechny následující:", + "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", + + "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", + "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", + "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", + "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", + "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", + "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", + "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", + + "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", + + "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", + + "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", + "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", + "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", + "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", + "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", + "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", + + "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", + "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", + + "vcmi.randomMapTab.widgets.randomTemplate" : "(Náhodná)", + "vcmi.randomMapTab.widgets.templateLabel" : "Šablona", + "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...", + "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Přiřazení týmů", + "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", + + "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", + "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", + + "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", + "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", + "vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy", + "vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky", + "vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.", + "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.", + "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.", + "vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.", + + "vcmi.optionsTab.accumulate" : "Akumulovat", + + "vcmi.optionsTab.simturnsTitle" : "Souběžné tahy", + "vcmi.optionsTab.simturnsMin.hover" : "Alespoň po", + "vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po", + "vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI", + "vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno", + "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", + "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", + + "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", + "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", + "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", + "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", + "vcmi.optionsTab.turnTime.classic.5" : "Klasický časovač: 5 minut", + "vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut", + "vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut", + "vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut", + "vcmi.optionsTab.turnTime.chess.20" : "Šachová: 20:00 + 10:00 + 02:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.16" : "Šachová: 16:00 + 08:00 + 01:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.8" : "Šachová: 08:00 + 04:00 + 01:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.4" : "Šachová: 04:00 + 02:00 + 00:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", + "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", + + "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", + "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", + "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", + "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", + "vcmi.optionsTab.simturns.tillContact2" : "Souběžně: 2 týdny, přerušit při setkání", + "vcmi.optionsTab.simturns.tillContact4" : "Souběžně: 1 mšsíc, přerušit při setkání", + "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", + "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", + "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d dní", + "vcmi.optionsTab.simturns.days.1" : " %d den", + "vcmi.optionsTab.simturns.days.2" : " %d dny", + "vcmi.optionsTab.simturns.weeks.0" : " %d týdnů", + "vcmi.optionsTab.simturns.weeks.1" : " %d týden", + "vcmi.optionsTab.simturns.weeks.2" : " %d týdny", + "vcmi.optionsTab.simturns.months.0" : " %d měsíců", + "vcmi.optionsTab.simturns.months.1" : " %d měsíc", + "vcmi.optionsTab.simturns.months.2" : " %d měsíce", + + "vcmi.optionsTab.extraOptions.hover" : "Další možnosti", + "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", + + "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", + "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", + "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", + "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", + + // Custom victory conditions for H3 campaigns and HotA maps + "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", + "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", + "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", + "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", + "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", + "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", + + // few strings from WoG used by vcmi + "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", + "vcmi.stackExperience.rank.0" : "Začátečník", + "vcmi.stackExperience.rank.1" : "Učeň", + "vcmi.stackExperience.rank.2" : "Trénovaný", + "vcmi.stackExperience.rank.3" : "Zručný", + "vcmi.stackExperience.rank.4" : "Prověřený", + "vcmi.stackExperience.rank.5" : "Veterán", + "vcmi.stackExperience.rank.6" : "Adept", + "vcmi.stackExperience.rank.7" : "Expert", + "vcmi.stackExperience.rank.8" : "Elitní", + "vcmi.stackExperience.rank.9" : "Mistr", + "vcmi.stackExperience.rank.10" : "Eso", + + // Strings for HotA Seer Hut / Quest Guards + "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", + + "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", + + "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", + "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", + "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", + "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", + "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", + "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", + "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", + "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", + "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", + "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", + "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", + "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", + "core.bonus.CATAPULT.name": "Katapult", + "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", + "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", + "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", + "core.bonus.DARKNESS.name": "Závoj temnoty", + "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", + "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", + "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", + "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", + "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", + "core.bonus.DESTRUCTION.name": "Zničení", + "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", + "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", + "core.bonus.DRAGON_NATURE.name": "Dračí povaha", + "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", + "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", + "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", + "core.bonus.ENCHANTER.name": "Zaklínač", + "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", + "core.bonus.ENCHANTED.name": "Očarovaný", + "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", + "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", + "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", + "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", + "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", + "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", + "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", + "core.bonus.FIRST_STRIKE.name": "První úder", + "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", + "core.bonus.FEAR.name": "Strach", + "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", + "core.bonus.FEARLESS.name": "Nebojácnost", + "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", + "core.bonus.FEROCITY.name": "Zuřivost", + "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", + "core.bonus.FLYING.name": "Létání", + "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", + "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", + "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", + "core.bonus.GARGOYLE.name": "Chrlič", + "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", + "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", + "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", + "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", + "core.bonus.HEALER.name": "Léčitel", + "core.bonus.HEALER.description": "Léčí spojenecké jednotky", + "core.bonus.HP_REGENERATION.name": "Regenerace", + "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", + "core.bonus.JOUSTING.name": "Nájezd šampionů", + "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", + "core.bonus.KING.name": "Král", + "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", + "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", + "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", + "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", + "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", + "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", + "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", + "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", + "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", + "core.bonus.MANA_DRAIN.name": "Vysávání many", + "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", + "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", + "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", + "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", + "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", + "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", + "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", + "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", + "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", + "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", + "core.bonus.NO_MORALE.name": "Neutrální morálka", + "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", + "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", + "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", + "core.bonus.NON_LIVING.name": "Neživý", + "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", + "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", + "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", + "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", + "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", + "core.bonus.RECEPTIVE.name": "Vnímavý", + "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", + "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", + "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", + "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", + "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", + "core.bonus.REVENGE.name": "Pomsta", + "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", + "core.bonus.SHOOTER.name": "Střelec", + "core.bonus.SHOOTER.description": "Jednotka může střílet", + "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", + "core.bonus.SOUL_STEAL.name": "Zloděj duší", + "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", + "core.bonus.SPELLCASTER.name": "Kouzelník", + "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", + "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", + "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", + "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", + "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", + "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", + "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", + "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", + "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", + "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", + "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", + "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", + "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", + "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", + "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", + "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", + "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", + "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", + "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", + "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", + "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", + "core.bonus.TRANSMUTATION.name": "Transmutace", + "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", + "core.bonus.UNDEAD.name": "Nemrtvý", + "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", + "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", + "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", + "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", + "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", + "core.bonus.WIDE_BREATH.name": "Široký dech", + "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", + "core.bonus.DISINTEGRATE.name": "Rozpad", + "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", + "core.bonus.INVINCIBLE.name": "Neporazitelný", + "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" } \ No newline at end of file From e7204447a9f66df763d6a5fca39c2dd46dc33978 Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 29 Oct 2024 15:46:46 +0300 Subject: [PATCH 462/726] Update fortress.json --- config/factions/fortress.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 62f3d1d51..d238f9d52 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -187,7 +187,7 @@ "bonuses": [ { "type": "PRIMARY_SKILL", - "subtype": "primarySkill.defence", + "subtype": "primarySkill.attack", "val": 2 } ], From 05f2aa6af9ab6008722afd4a3f57d447cc8aed70 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:26:32 +0100 Subject: [PATCH 463/726] Update config/objects/creatureBanks.json Co-authored-by: Ivan Savenko --- config/objects/creatureBanks.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json index 4376cb253..dc69e117b 100644 --- a/config/objects/creatureBanks.json +++ b/config/objects/creatureBanks.json @@ -262,6 +262,7 @@ ] }, "impCache" : { + "compatibilityIdentifiers" : [ "inpCache" ], "index" : 3, "name" : "Imp Cache", "aiValue" : 1500, From b4367dd43219e661c0374609e76be2a0a9745977 Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 29 Oct 2024 16:34:53 +0300 Subject: [PATCH 464/726] Mutare fix --- config/heroes/special.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/heroes/special.json b/config/heroes/special.json index b6b945461..d6846093f 100644 --- a/config/heroes/special.json +++ b/config/heroes/special.json @@ -176,7 +176,7 @@ "mutare": { "index": 151, - "class" : "warlock", + "class" : "overlord", "female": true, "special" : true, "spellbook": [ "magicArrow" ], @@ -220,7 +220,7 @@ "mutareDrake": { "index": 153, - "class" : "warlock", + "class" : "overlord", "female": true, "special" : true, "spellbook": [ "magicArrow" ], From b40a1a57e5d457454b47434ff4a1a809d6afbcb1 Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 29 Oct 2024 16:35:26 +0300 Subject: [PATCH 465/726] Update dungeon.json --- config/heroes/dungeon.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/heroes/dungeon.json b/config/heroes/dungeon.json index 04fb2d3c4..44f3b245d 100644 --- a/config/heroes/dungeon.json +++ b/config/heroes/dungeon.json @@ -198,7 +198,7 @@ { "index": 91, "class" : "warlock", - "female": true, + "female": false, "spellbook": [ "resurrection" ], "skills": [ From 12e78cc0e0db246b8093abe8454766754e9e5a88 Mon Sep 17 00:00:00 2001 From: Olegmods Date: Tue, 29 Oct 2024 16:41:43 +0300 Subject: [PATCH 466/726] Elixir of Life fix --- config/artifacts.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/artifacts.json b/config/artifacts.json index ae5501f30..e12580711 100644 --- a/config/artifacts.json +++ b/config/artifacts.json @@ -1998,6 +1998,10 @@ { "type" : "HAS_ANOTHER_BONUS_LIMITER", "parameters" : [ "GARGOYLE" ] + }, + { + "type" : "HAS_ANOTHER_BONUS_LIMITER", + "parameters" : [ "SIEGE_WEAPON" ] } ] }, From 0983a1a32f47c434ab61dd6a39ab95bdbc34dbce Mon Sep 17 00:00:00 2001 From: kdmcser Date: Tue, 29 Oct 2024 22:25:56 +0800 Subject: [PATCH 467/726] update Chinese translation --- Mods/vcmi/config/vcmi/chinese.json | 37 +++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 44960a60a..96adec1ed 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -15,6 +15,8 @@ "vcmi.adventureMap.monsterLevel" : "\n\n%TOWN%LEVEL级%ATTACK_TYPE生物", "vcmi.adventureMap.monsterMeleeType" : "近战", "vcmi.adventureMap.monsterRangedType" : "远程", + "vcmi.adventureMap.search.hover" : "搜索地图物体", + "vcmi.adventureMap.search.help" : "选择要再地图上搜索的物体。", "vcmi.adventureMap.confirmRestartGame" : "你想要重新开始游戏吗?", "vcmi.adventureMap.noTownWithMarket" : "没有足够的市场。", @@ -40,6 +42,12 @@ "vcmi.heroOverview.secondarySkills" : "初始技能", "vcmi.heroOverview.spells" : "魔法", + "vcmi.quickExchange.moveUnit" : "移动生物", + "vcmi.quickExchange.moveAllUnits" : "移动所有生物", + "vcmi.quickExchange.swapAllUnits" : "交换生物", + "vcmi.quickExchange.moveAllArtifacts" : "移动所有宝物", + "vcmi.quickExchange.swapAllArtifacts" : "交换宝物", + "vcmi.radialWheel.mergeSameUnit" : "合并相同生物", "vcmi.radialWheel.fillSingleUnit" : "单个生物填充空格", "vcmi.radialWheel.splitSingleUnit" : "分割单个生物", @@ -59,8 +67,25 @@ "vcmi.radialWheel.moveDown" : "下移", "vcmi.radialWheel.moveBottom" : "移到底端", + "vcmi.randomMap.description" : "这是随机生成的地图。\\n模版为%s,大小为%dx%d,层数为%d,人类玩家数量为%d,电脑玩家数量为%d,水域面积%s,怪物等级%s,VCMI地图", + "vcmi.randomMap.description.isHuman" : ", %s为人类玩家", + "vcmi.randomMap.description.townChoice" : ", %s的城镇选择为%s", + "vcmi.randomMap.description.water.none" : "无", + "vcmi.randomMap.description.water.normal" : "普通", + "vcmi.randomMap.description.water.islands" : "岛屿", + "vcmi.randomMap.description.monster.weak" : "弱", + "vcmi.randomMap.description.monster.normal" : "普通", + "vcmi.randomMap.description.monster.strong" : "强", + "vcmi.spellBook.search" : "搜索中...", + "vcmi.spellResearch.canNotAfford" : "你没有足够的资源来将{%SPELL1}替换为{%SPELL2}。但你依然可以弃掉此法术,继续法术研究。", + "vcmi.spellResearch.comeAgain" : "今日已研究过法术,请明日再来。", + "vcmi.spellResearch.pay" : "你想将{%SPELL1}替换为{%SPELL2}吗?或者弃掉此法术,继续法术研究?", + "vcmi.spellResearch.research" : "研究此法术", + "vcmi.spellResearch.skip" : "跳过此法术", + "vcmi.spellResearch.abort" : "中止", + "vcmi.mainMenu.serverConnecting" : "连接中...", "vcmi.mainMenu.serverAddressEnter" : "使用地址:", "vcmi.mainMenu.serverConnectionFailed" : "连接失败", @@ -145,6 +170,7 @@ "vcmi.client.errors.invalidMap" : "{非法地图或战役}\n\n启动游戏失败,选择的地图或者战役,无效或被污染。原因:\n%s", "vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。", "vcmi.server.errors.disconnected" : "{网络错误}\n\n与游戏服务器的连接已断开!", + "vcmi.server.errors.playerLeft" : "{玩家离开}\n\n%s玩家已断开游戏!", //%s -> player color "vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。", "vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}", "vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}", @@ -342,6 +368,13 @@ "vcmi.heroWindow.openCommander.help" : "显示该英雄指挥官详细信息", "vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面", "vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物", + "vcmi.heroWindow.sortBackpackByCost.hover" : "按价格排序", + "vcmi.heroWindow.sortBackpackByCost.help" : "将行囊里的宝物按价格排序。.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "按装备槽排序", + "vcmi.heroWindow.sortBackpackBySlot.help" : "将行囊里的宝物按装备槽排序。", + "vcmi.heroWindow.sortBackpackByClass.hover" : "按类型排序", + "vcmi.heroWindow.sortBackpackByClass.help" : "将行囊里的宝物按装备槽排序:低级宝物、中级宝物、高级宝物、圣物。", + "vcmi.heroWindow.fusingArtifact.fusing" : "你已拥有融合%s所需的全部组件,想现在进行融合吗?{所有组件在融合后将被消耗。}", "vcmi.tavernWindow.inviteHero" : "邀请英雄", @@ -670,5 +703,7 @@ "core.bonus.DISINTEGRATE.name": "解体", "core.bonus.DISINTEGRATE.description": "死亡后不会留下尸体", "core.bonus.INVINCIBLE.name": "无敌", - "core.bonus.INVINCIBLE.description": "不受任何效果影响" + "core.bonus.INVINCIBLE.description": "不受任何效果影响", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "棱光吐息", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "攻击后向三方向扩散攻击" } From ed087b7a5bbe55b3f8cbf9bf362d72a7ddf0fa61 Mon Sep 17 00:00:00 2001 From: godric3 Date: Tue, 29 Oct 2024 18:34:02 +0100 Subject: [PATCH 468/726] Use max ui32 value instead of -1 for NO_PATROLLING --- lib/mapObjects/CGHeroInstance.cpp | 6 +++--- lib/mapObjects/CGHeroInstance.h | 2 +- mapeditor/inspector/inspector.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 68b761f5a..349eaba0f 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1749,7 +1749,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) CArmedInstance::serializeJsonOptions(handler); { - int rawPatrolRadius = NO_PATROLLING; + ui32 rawPatrolRadius = NO_PATROLLING; if(handler.saving) { @@ -1760,9 +1760,9 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler) if(!handler.saving) { - patrol.patrolling = (rawPatrolRadius > NO_PATROLLING); + patrol.patrolling = (rawPatrolRadius != NO_PATROLLING); patrol.initialPos = visitablePos(); - patrol.patrolRadius = (rawPatrolRadius > NO_PATROLLING) ? rawPatrolRadius : 0; + patrol.patrolRadius = patrol.patrolling ? rawPatrolRadius : 0; } } } diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 3cf4ec2f7..0ac09c18c 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -92,7 +92,7 @@ public: static constexpr si32 UNINITIALIZED_MANA = -1; static constexpr ui32 UNINITIALIZED_MOVEMENT = -1; static constexpr auto UNINITIALIZED_EXPERIENCE = std::numeric_limits::max(); - static inline constexpr int NO_PATROLLING = -1; + static inline constexpr ui32 NO_PATROLLING = std::numeric_limits::max() ; //std::vector artifacts; //hero's artifacts from bag //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 451e892db..70086fdae 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -338,7 +338,7 @@ void Inspector::updateProperties(CGHeroInstance * o) { const int maxRadius = 60; auto * patrolDelegate = new InspectorDelegate; - patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(-1)} }; + patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(std::numeric_limits::max())} }; for(int i = 0; i <= maxRadius; ++i) patrolDelegate->options.push_back({ QObject::tr("%1 tile(s)").arg(i), QVariant::fromValue(i) }); auto patrolRadiusText = o->patrol.patrolling ? QObject::tr("%1 tile(s)").arg(o->patrol.patrolRadius) : QObject::tr("No patrol"); @@ -725,7 +725,7 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari { auto radius = value.toInt(); o->patrol.patrolRadius = radius; - o->patrol.patrolling = radius != -1; + o->patrol.patrolling = radius != std::numeric_limits::max(); } } From 389f8b678befdb4f3dc3bdcdf4d5847fc0f5129d Mon Sep 17 00:00:00 2001 From: kodobi Date: Tue, 29 Oct 2024 20:44:31 +0100 Subject: [PATCH 469/726] Fix ballista damage range display - Adjusted the displayed damage range of ballista to reflect the changes in hero/es attack skill like in OH3. - Added checks to ensure the battle interface and relevant heroes are valid before calculating damage. - Correctly retrieve the ballista status from the hero's war machine slot. --- client/windows/CCreatureWindow.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 293324c3e..94f49115a 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -25,6 +25,7 @@ #include "../windows/InfoWindows.h" #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" +#include "../battle/BattleInterface.h" #include "../../CCallback.h" #include "../../lib/ArtifactUtils.h" @@ -523,6 +524,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s CRClickPopup::createAndPush(parent->info->creature->getDescriptionTranslated()); }); + if(parent->info->stackNode != nullptr && parent->info->commander == nullptr) { //normal stack, not a commander and not non-existing stack (e.g. recruitment dialog) @@ -531,14 +533,26 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s name = std::make_shared(215, 12, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName()); + const BattleInterface* battleInterface = LOCPLINT->battleInt.get(); + const CStack* battleStack = parent->info->stack; + int dmgMultiply = 1; - if(parent->info->owner && parent->info->stackNode->hasBonusOfType(BonusType::SIEGE_WEAPON)) - dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK); + if (battleInterface && battleInterface->getBattle() != nullptr && battleStack->hasBonusOfType(BonusType::SIEGE_WEAPON)) + { + // Determine the relevant hero based on the unit side + const auto hero = (battleStack->unitSide() == BattleSide::ATTACKER) + ? battleInterface->attackingHeroInstance + : battleInterface->defendingHeroInstance; + // Check if the hero has a ballista in the war machine slot + if (hero && hero->getStack(SlotID::WAR_MACHINES_SLOT).type->warMachine.BALLISTA) + { + dmgMultiply += hero->getPrimSkillLevel(PrimarySkill::ATTACK); + } + } + icons = std::make_shared(ImagePath::builtin("stackWindow/icons"), 117, 32); - const CStack * battleStack = parent->info->stack; - morale = std::make_shared(true, Rect(Point(321, 110), Point(42, 42) )); luck = std::make_shared(false, Rect(Point(375, 110), Point(42, 42) )); @@ -566,7 +580,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter)); addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter)); - addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply); + addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter), parent->info->stackNode->getMaxDamage(shooter)); addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth()); addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), parent->info->stackNode->getMovementRange()); From 37f7ce0ad6e27ccae4baa15bc7235869e3dfdc14 Mon Sep 17 00:00:00 2001 From: godric3 Date: Tue, 29 Oct 2024 21:22:10 +0100 Subject: [PATCH 470/726] improve translation for patrol radius --- mapeditor/inspector/inspector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 70086fdae..82354de7c 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -340,8 +340,8 @@ void Inspector::updateProperties(CGHeroInstance * o) auto * patrolDelegate = new InspectorDelegate; patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(std::numeric_limits::max())} }; for(int i = 0; i <= maxRadius; ++i) - patrolDelegate->options.push_back({ QObject::tr("%1 tile(s)").arg(i), QVariant::fromValue(i) }); - auto patrolRadiusText = o->patrol.patrolling ? QObject::tr("%1 tile(s)").arg(o->patrol.patrolRadius) : QObject::tr("No patrol"); + patrolDelegate->options.push_back({ QObject::tr("%n tile(s)", "", i), QVariant::fromValue(i)}); + auto patrolRadiusText = o->patrol.patrolling ? QObject::tr("%n tile(s)", "", o->patrol.patrolRadius) : QObject::tr("No patrol"); addProperty("Patrol radius", patrolRadiusText, patrolDelegate, false); } } From 9e9f118b09a1a3f4f2cac17a7c29e3ba80d86519 Mon Sep 17 00:00:00 2001 From: godric3 Date: Tue, 29 Oct 2024 21:41:42 +0100 Subject: [PATCH 471/726] another attempt of using `CGHeroInstance::NO_PATROLLING` in map editor --- lib/mapObjects/CGHeroInstance.cpp | 2 ++ lib/mapObjects/CGHeroInstance.h | 2 +- mapeditor/inspector/inspector.cpp | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 349eaba0f..beea5a779 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -49,6 +49,8 @@ VCMI_LIB_NAMESPACE_BEGIN +const ui32 CGHeroInstance::NO_PATROLLING = std::numeric_limits::max(); + void CGHeroPlaceholder::serializeJsonOptions(JsonSerializeFormat & handler) { serializeJsonOwner(handler); diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 0ac09c18c..0b8ddd257 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -92,7 +92,7 @@ public: static constexpr si32 UNINITIALIZED_MANA = -1; static constexpr ui32 UNINITIALIZED_MOVEMENT = -1; static constexpr auto UNINITIALIZED_EXPERIENCE = std::numeric_limits::max(); - static inline constexpr ui32 NO_PATROLLING = std::numeric_limits::max() ; + static const ui32 NO_PATROLLING; //std::vector artifacts; //hero's artifacts from bag //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 82354de7c..ef1696d73 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -338,7 +338,7 @@ void Inspector::updateProperties(CGHeroInstance * o) { const int maxRadius = 60; auto * patrolDelegate = new InspectorDelegate; - patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(std::numeric_limits::max())} }; + patrolDelegate->options = { {QObject::tr("No patrol"), QVariant::fromValue(CGHeroInstance::NO_PATROLLING)} }; for(int i = 0; i <= maxRadius; ++i) patrolDelegate->options.push_back({ QObject::tr("%n tile(s)", "", i), QVariant::fromValue(i)}); auto patrolRadiusText = o->patrol.patrolling ? QObject::tr("%n tile(s)", "", o->patrol.patrolRadius) : QObject::tr("No patrol"); @@ -725,7 +725,7 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari { auto radius = value.toInt(); o->patrol.patrolRadius = radius; - o->patrol.patrolling = radius != std::numeric_limits::max(); + o->patrol.patrolling = radius != CGHeroInstance::NO_PATROLLING; } } From bb73a35412b2cc06ee2ec04d997eede85c9fe12f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 00:35:50 +0100 Subject: [PATCH 472/726] code review + pause handling --- client/media/CSoundHandler.cpp | 12 ++++++++++++ client/media/CSoundHandler.h | 2 ++ client/media/CVideoHandler.cpp | 22 +++++++++++++++++++--- client/media/CVideoHandler.h | 5 ++++- client/media/ISoundPlayer.h | 2 ++ client/media/IVideoPlayer.h | 4 ++++ client/widgets/VideoWidget.cpp | 14 +++++++++++--- 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/client/media/CSoundHandler.cpp b/client/media/CSoundHandler.cpp index adfb1a2f7..bd099e728 100644 --- a/client/media/CSoundHandler.cpp +++ b/client/media/CSoundHandler.cpp @@ -240,6 +240,18 @@ void CSoundHandler::stopSound(int handler) Mix_HaltChannel(handler); } +void CSoundHandler::pauseSound(int handler) +{ + if(isInitialized() && handler != -1) + Mix_Pause(handler); +} + +void CSoundHandler::resumeSound(int handler) +{ + if(isInitialized() && handler != -1) + Mix_Resume(handler); +} + ui32 CSoundHandler::getVolume() const { return volume; diff --git a/client/media/CSoundHandler.h b/client/media/CSoundHandler.h index 5a10a5493..3450cbffb 100644 --- a/client/media/CSoundHandler.h +++ b/client/media/CSoundHandler.h @@ -67,6 +67,8 @@ public: int playSound(std::pair, si64> & data, int repeats = 0, bool cache = false) final; int playSoundFromSet(std::vector & sound_vec) final; void stopSound(int handler) final; + void pauseSound(int handler) final; + void resumeSound(int handler) final; void setCallback(int channel, std::function function) final; void resetCallback(int channel) final; diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 809af5b26..a2a0ef939 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -391,10 +391,10 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); - if(startTime == std::chrono::high_resolution_clock::time_point()) - startTime = std::chrono::high_resolution_clock::now(); + if(startTime == std::chrono::steady_clock::time_point()) + startTime = std::chrono::steady_clock::now(); - auto nowTime = std::chrono::high_resolution_clock::now(); + auto nowTime = std::chrono::steady_clock::now(); double difference = std::chrono::duration_cast(nowTime - startTime).count() / 1000.0; int frameskipCounter = 0; @@ -407,6 +407,22 @@ void CVideoInstance::tick(uint32_t msPassed) loadNextFrame(); } + +void CVideoInstance::activate() +{ + if(deactivationStartTime != std::chrono::steady_clock::time_point()) + { + auto pauseDuration = std::chrono::steady_clock::now() - deactivationStartTime; + startTime += pauseDuration; + deactivationStartTime = std::chrono::steady_clock::time_point(); + } +} + +void CVideoInstance::deactivate() +{ + deactivationStartTime = std::chrono::steady_clock::now(); +} + struct FFMpegFormatDescription { uint8_t sampleSizeBytes; diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index b4a3c7c81..63e4a6176 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -78,7 +78,8 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream Point dimensions; /// video playback start time point - std::chrono::high_resolution_clock::time_point startTime; + std::chrono::steady_clock::time_point startTime; + std::chrono::steady_clock::time_point deactivationStartTime; void prepareOutput(float scaleFactor, bool useTextureOutput); @@ -96,6 +97,8 @@ public: void show(const Point & position, Canvas & canvas) final; void tick(uint32_t msPassed) final; + void activate() final; + void deactivate() final; }; class CVideoPlayer final : public IVideoPlayer diff --git a/client/media/ISoundPlayer.h b/client/media/ISoundPlayer.h index 9b3d9d5e9..ffcb90dce 100644 --- a/client/media/ISoundPlayer.h +++ b/client/media/ISoundPlayer.h @@ -22,6 +22,8 @@ public: virtual int playSound(std::pair, si64> & data, int repeats = 0, bool cache = false) = 0; virtual int playSoundFromSet(std::vector & sound_vec) = 0; virtual void stopSound(int handler) = 0; + virtual void pauseSound(int handler) = 0; + virtual void resumeSound(int handler) = 0; virtual ui32 getVolume() const = 0; virtual void setVolume(ui32 percent) = 0; diff --git a/client/media/IVideoPlayer.h b/client/media/IVideoPlayer.h index 6f7380166..3f2784c16 100644 --- a/client/media/IVideoPlayer.h +++ b/client/media/IVideoPlayer.h @@ -35,6 +35,10 @@ public: /// Advances video playback by specified duration virtual void tick(uint32_t msPassed) = 0; + /// activate or deactivate video + virtual void activate() = 0; + virtual void deactivate() = 0; + virtual ~IVideoInstance() = default; }; diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 348548935..35fe4adcb 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -50,7 +50,8 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { pos.w = videoInstance->size().x; pos.h = videoInstance->size().y; - subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE); + if(!subTitleData.isNull()) + subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE); } if (playAudio) @@ -121,13 +122,20 @@ std::string VideoWidgetBase::getSubTitleLine(double timestamp) void VideoWidgetBase::activate() { CIntObject::activate(); - startAudio(); + if(audioHandle != -1) + CCS->soundh->resumeSound(audioHandle); + else + startAudio(); + if(videoInstance) + videoInstance->activate(); } void VideoWidgetBase::deactivate() { CIntObject::deactivate(); - stopAudio(); + CCS->soundh->pauseSound(audioHandle); + if(videoInstance) + videoInstance->deactivate(); } void VideoWidgetBase::showAll(Canvas & to) From 22f517686d76deaa379b31765e31b633a7c428e6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 30 Oct 2024 11:54:35 +0000 Subject: [PATCH 473/726] Better handling of encoding detection for maps and campaigns Now VCMI will use either preferred language or install language to load maps and campaigns that are part of "core" mod, or, in other words - placed in Maps directory of H3 data (like most of manually downloaded maps and campaigns are) If game data is in English, then game can safely use encoding of player- selected language (such as Chinese) to load maps. After all, both GBK and all Win-125X encoding are superset of ASCII, so English map will always load up correctly. Maps that are part of a mod still use mod language as before - it is up to mod maker to correctly set up mod language. --- client/renderSDL/CBitmapFont.cpp | 4 +--- client/renderSDL/FontChain.cpp | 4 +--- lib/campaign/CampaignHandler.cpp | 7 ++----- lib/mapping/CMapService.cpp | 7 ++----- lib/modding/CModHandler.cpp | 30 ++++++++++++++++++++++++++++++ lib/modding/CModHandler.h | 6 ++++++ lib/texts/CLegacyConfigParser.cpp | 4 +--- 7 files changed, 43 insertions(+), 19 deletions(-) diff --git a/client/renderSDL/CBitmapFont.cpp b/client/renderSDL/CBitmapFont.cpp index 13de8dcdd..a932e6368 100644 --- a/client/renderSDL/CBitmapFont.cpp +++ b/client/renderSDL/CBitmapFont.cpp @@ -99,9 +99,7 @@ static AtlasLayout doAtlasPacking(const std::map & images) void CBitmapFont::loadFont(const ResourcePath & resource, std::unordered_map & loadedChars) { auto data = CResourceHandler::get()->load(resource)->readAll(); - std::string modName = VLC->modh->findResourceOrigin(resource); - std::string modLanguage = VLC->modh->getModLanguage(modName); - std::string modEncoding = Languages::getLanguageOptions(modLanguage).encoding; + std::string modEncoding = VLC->modh->findResourceEncoding(resource); height = data.first[5]; diff --git a/client/renderSDL/FontChain.cpp b/client/renderSDL/FontChain.cpp index 3c1e6446e..331d1c71a 100644 --- a/client/renderSDL/FontChain.cpp +++ b/client/renderSDL/FontChain.cpp @@ -60,11 +60,9 @@ bool FontChain::bitmapFontsPrioritized(const std::string & bitmapFontName) const if (!vstd::isAlmostEqual(1.0, settings["video"]["fontScalingFactor"].Float())) return false; // If player requested non-100% scaling - use scalable fonts - std::string modName = CGI->modh->findResourceOrigin(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT)); - std::string fontLanguage = CGI->modh->getModLanguage(modName); std::string gameLanguage = CGI->generaltexth->getPreferredLanguage(); - std::string fontEncoding = Languages::getLanguageOptions(fontLanguage).encoding; std::string gameEncoding = Languages::getLanguageOptions(gameLanguage).encoding; + std::string fontEncoding = CGI->modh->findResourceEncoding(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT)); // player uses language with different encoding than his bitmap fonts // for example, Polish language with English fonts or Chinese language which can't use H3 fonts at all diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index 563437cb0..7d94bb1b8 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -25,7 +25,6 @@ #include "../modding/IdentifierStorage.h" #include "../modding/ModScope.h" #include "../texts/CGeneralTextHandler.h" -#include "../texts/Languages.h" #include "../texts/TextOperations.h" VCMI_LIB_NAMESPACE_BEGIN @@ -64,8 +63,7 @@ std::unique_ptr CampaignHandler::getHeader( const std::string & name) { ResourcePath resourceID(name, EResType::CAMPAIGN); std::string modName = VLC->modh->findResourceOrigin(resourceID); - std::string language = VLC->modh->getModLanguage(modName); - std::string encoding = Languages::getLanguageOptions(language).encoding; + std::string encoding = VLC->modh->findResourceEncoding(resourceID); auto ret = std::make_unique(); auto fileStream = CResourceHandler::get(modName)->load(resourceID); @@ -80,8 +78,7 @@ std::shared_ptr CampaignHandler::getCampaign( const std::string & { ResourcePath resourceID(name, EResType::CAMPAIGN); std::string modName = VLC->modh->findResourceOrigin(resourceID); - std::string language = VLC->modh->getModLanguage(modName); - std::string encoding = Languages::getLanguageOptions(language).encoding; + std::string encoding = VLC->modh->findResourceEncoding(resourceID); auto ret = std::make_unique(); diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 2b7af0de3..830245d12 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -19,7 +19,6 @@ #include "../modding/CModHandler.h" #include "../modding/ModScope.h" #include "../modding/CModInfo.h" -#include "../texts/Languages.h" #include "../VCMI_Lib.h" #include "CMap.h" @@ -34,8 +33,7 @@ VCMI_LIB_NAMESPACE_BEGIN std::unique_ptr CMapService::loadMap(const ResourcePath & name, IGameCallback * cb) const { std::string modName = VLC->modh->findResourceOrigin(name); - std::string language = VLC->modh->getModLanguage(modName); - std::string encoding = Languages::getLanguageOptions(language).encoding; + std::string encoding = VLC->modh->findResourceEncoding(name); auto stream = getStreamFromFS(name); return getMapLoader(stream, name.getName(), modName, encoding)->loadMap(cb); @@ -44,8 +42,7 @@ std::unique_ptr CMapService::loadMap(const ResourcePath & name, IGameCallb std::unique_ptr CMapService::loadMapHeader(const ResourcePath & name) const { std::string modName = VLC->modh->findResourceOrigin(name); - std::string language = VLC->modh->getModLanguage(modName); - std::string encoding = Languages::getLanguageOptions(language).encoding; + std::string encoding = VLC->modh->findResourceEncoding(name); auto stream = getStreamFromFS(name); return getMapLoader(stream, name.getName(), modName, encoding)->loadMapHeader(); diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 5afd59111..0aa5fa43e 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -390,6 +390,36 @@ TModID CModHandler::findResourceOrigin(const ResourcePath & name) const throw std::runtime_error("Resource with name " + name.getName() + " and type " + EResTypeHelper::getEResTypeAsString(name.getType()) + " wasn't found."); } +std::string CModHandler::findResourceLanguage(const ResourcePath & name) const +{ + std::string modName = findResourceOrigin(name); + std::string modLanguage = getModLanguage(modName); + return modLanguage; +} + +std::string CModHandler::findResourceEncoding(const ResourcePath & resource) const +{ + std::string modName = findResourceOrigin(resource); + std::string modLanguage = findResourceLanguage(resource); + + bool potentiallyUserMadeContent = resource.getType() == EResType::MAP || resource.getType() == EResType::CAMPAIGN; + if (potentiallyUserMadeContent && modName == ModScope::scopeBuiltin() && modLanguage == "english") + { + // this might be a map or campaign that player downloaded manually and placed in Maps/ directory + // in this case, this file may be in user-preferred language, and not in same language as the rest of H3 data + // however at the moment we have no way to detect that for sure - file can be either in English or in user-preferred language + // but since all known H3 encodings (Win125X or GBK) are supersets of ASCII, we can safely load English data using encoding of user-preferred language + std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); + std::string fileEncoding = Languages::getLanguageOptions(modLanguage).encoding; + return fileEncoding; + } + else + { + std::string fileEncoding = Languages::getLanguageOptions(modLanguage).encoding; + return fileEncoding; + } +} + std::string CModHandler::getModLanguage(const TModID& modId) const { if(modId == "core") diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index fab2d319b..ac1729491 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -62,6 +62,12 @@ public: /// returns ID of mod that provides selected file resource TModID findResourceOrigin(const ResourcePath & name) const; + /// Returns assumed language ID of mod that provides selected file resource + std::string findResourceLanguage(const ResourcePath & name) const; + + /// Returns assumed encoding of language of mod that provides selected file resource + std::string findResourceEncoding(const ResourcePath & name) const; + std::string getModLanguage(const TModID & modId) const; std::set getModDependencies(const TModID & modId) const; diff --git a/lib/texts/CLegacyConfigParser.cpp b/lib/texts/CLegacyConfigParser.cpp index 15ea93c74..90eb9a206 100644 --- a/lib/texts/CLegacyConfigParser.cpp +++ b/lib/texts/CLegacyConfigParser.cpp @@ -32,9 +32,7 @@ protected: CLegacyConfigParser::CLegacyConfigParser(const TextPath & resource) { auto input = CResourceHandler::get()->load(resource); - std::string modName = VLC->modh->findResourceOrigin(resource); - std::string language = VLC->modh->getModLanguage(modName); - fileEncoding = Languages::getLanguageOptions(language).encoding; + fileEncoding = VLC->modh->findResourceEncoding(resource); data.reset(new char[input->getSize()]); input->read(reinterpret_cast(data.get()), input->getSize()); From dd7d190a58544c4f0db55bf20ecd4e32c80e04a0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 30 Oct 2024 13:20:21 +0000 Subject: [PATCH 474/726] Implemented optional descriptions for market map objects It is now possible to define description of an object with 'market' handler that will be shown on right-clicking the object. Similarly, added description to right-click popup to Hill Fort. --- .../CommonConstructors.cpp | 11 +++++++++ .../CommonConstructors.h | 2 ++ lib/mapObjects/CGMarket.cpp | 24 +++++++++++++++++-- lib/mapObjects/CGMarket.h | 7 ++++++ lib/mapObjects/MiscObjects.cpp | 16 +++++++++++++ lib/mapObjects/MiscObjects.h | 3 +++ 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index b8194937c..ef2969b62 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -205,6 +205,12 @@ AnimationPath BoatInstanceConstructor::getBoatAnimationName() const void MarketInstanceConstructor::initTypeData(const JsonNode & input) { + if (!input["description"].isNull()) + { + description = input["description"].String(); + VLC->generaltexth->registerString(input.getModScope(), TextIdentifier(getBaseTextID(), "description"), description); + } + for(auto & element : input["modes"].Vector()) { if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String())) @@ -218,6 +224,11 @@ void MarketInstanceConstructor::initTypeData(const JsonNode & input) speech = input["speech"].String(); } +bool MarketInstanceConstructor::hasDescription() const +{ + return !description.empty(); +} + CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const { if(marketModes.size() == 1) diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index 2089acdf7..1a048df20 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -118,6 +118,7 @@ protected: JsonNode predefinedOffer; int marketEfficiency; + std::string description; std::string title; std::string speech; @@ -127,6 +128,7 @@ public: void randomizeObject(CGMarket * object, vstd::RNG & rng) const override; const std::set & availableModes() const; + bool hasDescription() const; }; diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index b2a44455c..772aa3930 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -39,6 +39,22 @@ void CGMarket::onHeroVisit(const CGHeroInstance * h) const cb->showObjectWindow(this, EOpenWindowMode::MARKET_WINDOW, h, true); } +std::string CGMarket::getPopupText(PlayerColor player) const +{ + if (!getMarketHandler()->hasDescription()) + return getHoverText(player); + + MetaString message = MetaString::createFromRawString("{%s}\r\n\r\n%s"); + message.replaceName(ID); + message.replaceTextID(TextIdentifier(getObjectHandler()->getBaseTextID(), "description").get()); + return message.toString(); +} + +std::string CGMarket::getPopupText(const CGHeroInstance * hero) const +{ + return getPopupText(hero->getOwner()); +} + int CGMarket::getMarketEfficiency() const { return marketEfficiency; @@ -49,12 +65,16 @@ int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const return -1; } -std::set CGMarket::availableModes() const +std::shared_ptr CGMarket::getMarketHandler() const { const auto & baseHandler = getObjectHandler(); const auto & ourHandler = std::dynamic_pointer_cast(baseHandler); + return ourHandler; +} - return ourHandler->availableModes(); +std::set CGMarket::availableModes() const +{ + return getMarketHandler()->availableModes(); } CGMarket::CGMarket(IGameCallback *cb): diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index e1bc5b7df..b3423125b 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -15,8 +15,12 @@ VCMI_LIB_NAMESPACE_BEGIN +class MarketInstanceConstructor; + class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket { + std::shared_ptr getMarketHandler() const; + public: int marketEfficiency; @@ -25,6 +29,9 @@ public: void onHeroVisit(const CGHeroInstance * h) const override; //open trading window void initObj(vstd::RNG & rand) override;//set skills for trade + std::string getPopupText(PlayerColor player) const override; + std::string getPopupText(const CGHeroInstance * hero) const override; + ///IMarket ObjectInstanceID getObjInstanceID() const override; int getMarketEfficiency() const override; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index c959292a9..720615921 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1333,6 +1333,22 @@ void HillFort::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) } } +std::string HillFort::getPopupText(PlayerColor player) const +{ + MetaString message = MetaString::createFromRawString("{%s}\r\n\r\n%s"); + + message.replaceName(ID); + message.replaceTextID(getDescriptionToolTip()); + + return message.toString(); +} + +std::string HillFort::getPopupText(const CGHeroInstance * hero) const +{ + return getPopupText(hero->getOwner()); +} + + std::string HillFort::getDescriptionToolTip() const { return TextIdentifier(getObjectHandler()->getBaseTextID(), "description").get(); diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index d6c40b3ca..aa5d2ac46 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -437,6 +437,9 @@ protected: public: using CGObjectInstance::CGObjectInstance; + std::string getPopupText(PlayerColor player) const override; + std::string getPopupText(const CGHeroInstance * hero) const override; + std::string getDescriptionToolTip() const; std::string getUnavailableUpgradeMessage() const; From dc2f8d52eec0a2eb911a6d0d054b5418d0d802ac Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 28 Oct 2024 13:21:04 +0000 Subject: [PATCH 475/726] Added all 1.6 changes so far to ChangeLog.md --- ChangeLog.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0063094e3..c045b41bf 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,15 +7,29 @@ * Implemented adventure map overlay accessible via Alt key that highlights all interactive objects on screen * Implemented xBRZ upscaling filter * It is now possible to import data from Heroes Chronicles (gog.com installer only) as custom campaigns +* Added simple support for spell research feature from HotA that can be enabled via mod or game configuration editing +* Implemented automatic selection of interface scaling. Selecting interface scaling manually will restore old behavior +* VCMI will now launch in fullscreen on desktop systems. Use F4 hotkey or toggle option in settings to restore old behavior ### General * Saved game size reduced by approximately 3 times, especially for large maps or games with a large number of mods. +* Mods that modify game texts, such as descriptions of secondary skills, will now correctly override translation mods +* Game will now correctly restore information such as hero path, order of heroes and towns, and list of sleeping heroes on loading a save game +* Added translation for missing texts, such as random map descriptions, quick exchange buttons, wog commander abilities, moat names + +### Multiplayer * Added option to start vcmi server on randomly selected TCP port * Fixed potential desynchronization between server and clients on randomization of map objects if client and server run on different operating systems +* Fixed possible freeze on receiving turn in multiplayer when player has town window opened +* Fixed possible freeze if player is attacked by another player on first day of the week +* If player disconnects from a multiplayer game, all other players will now receive notification in form of popup message instead of chat message +* Fixed potentially missing disconnection notification in multiplayer if player disconnects due to connection loss +* Game will now correctly show turn timers and simultaneous turns state on loading game ### Stability * Fixed possible crash on connecting bluetooth mouse during gameplay on Android * VCMI will now write more detailed information to log file on crash due to uncaught exception +* Fixed crash on transfer of multiple artifacts in a backpack to another hero on starting next campaign scenario without hero that held these artifacts before ### Mechanics * Arrow tower will now prefer to attack more units that are viewed most dangerous instead of simply attacking top-most unit @@ -24,15 +38,38 @@ * Map events and town events are now triggered on start of turn of player affected by event, in line with H3 instead of triggering on start of new day for all players * Neutral towns should now have initial garrison and weekly growth of garrison identical to H3 * It is now possible to buy a new war machine in town if hero has different war machine in the slot +* Fixed possible integer overflow if hero has unit with total AI value of over 2^31 +* Unicorn Glade in Rampart now correctly requires Dendroid Arches and not Homestead +* Game will no longer place obstacles on ship-to-ship battles, in line with H3 +* Game will now place obstacles in battles in villages (towns without forts) +* Battles in villages (towns without forts) now always occur on battlefield of native terrain +* Fixed pathfinding through subterranean gates located on right edge of the map or next to terra incognita +* Chain Lightning will now skip over creatures that are immune to this spell instead of targeting them but dealing no damage +* Commanders spell resistance now uses golem-like logic which reduces damage instead of using dwarf-style change to block spell +* It is now possible to target empty hex for shooters with area attack, such as Magog or Lich -### Interface -* Added option to drag map with right-click -* Added hotkeys to reorder list of owned towns or heroes -* The number of units resurrected using the Life Drain ability is now written to the combat log. +### Video / Audio * Fixed playback of audio stream with different formats from video files in some Heroes 3 versions * Video playback will not be replaced by a black square when another dialogue box is on top of the video. * When resuming video playback, the video will now be continued instead of being restarted. * Reduced video decompression artefacts for video formats that store RGB rather than YUV data. +* Intro videos are now played inside a frame on resolutions higher than 800x600 instead of filling entire screen +* Re-enabled idle animations for Conflux creatures in battles +* .webm video with vp8 / vp9 codec are now supported on every platform +* It is now possible to provide external audio stream for a video file (e.g. for translations) +* It is now possible to provide external subtitles for a video file +* Game will now correctly resume playback of terrain music on closing scenario information window in campaigns instead of playing main theme +* Background music theme will now play at lower volume while intro speech in campaign intro / outro is playing +* Added workaround for playback of corrupted `BladeFWCampaign.mp3` music file +* Fixed computation of audio length for formats other than .wav. This fixes incorrect text scrolling speed in campaign intro/outro +* Game will now use Noto family true type font to display characters not preset in Heroes III fonts +* Added option to scale all in-game fonts when scalable true type fonts are in use + +### Interface +* It is now possible to search for a map object using Ctrl+F hotkey +* Added option to drag map with right-click +* Added hotkeys to reorder list of owned towns or heroes +* The number of units resurrected using the Life Drain ability is now written to the combat log. * Fixed order of popup dialogs after battle. * Right-click on wandering monster on adventure map will now also show creature level and faction it belongs to * Added additional information to map right-click popup dialog: map author, map creation date, map version @@ -47,13 +84,22 @@ * Fixed hero path not updating correctly after hiring or dismissing creatures * Fixed missing description of a stack artifact when accessed through unit window * Fixed text overflow on campaign scenario window if campaign name is too long -* Intro videos are now played inside a frame on resolutions higher than 800x600 instead of filling entire screen +* Recruiting hero in town will now play "new construction" sound +* Game will now correctly update displayed hero path when hiring or dismissing creatures that give movement penalty +* Game will now show level, faction and attack range of wandering monsters in right-click popup window +* Hovering over owned hero will now show movement points information in status bar +* Quick backpack window is now also accessible via Shift+mouse click, similar to HD Mod +* It is now possible to sort artifacts in backpack by cost, slot, or rarity class +* Fixed incorrect display of names of VCMI maps in scenario selection if multiple VCMI map are present in list ### Random Maps Generator * Implemented connection option 'forcePortal' * It is now possible to connect zone to itself using pair of portals * It is now possible for a random map template to change game settings * Road settings will now be correctly loaded when opening random map setup tab +* Added support for banning objects per zones +* Added support for customizing objects frequency, value, and count per zone +* Fixed values of Pandora Boxes with creatures to be in line with H3:SoD ### Campaigns * It is now possible to use .zip archive for VCMI campaigns instead of raw gzip stream @@ -65,6 +111,7 @@ * Added support for custom region definitions (such as background images) for VCMI campaigns ### AI +* VCMI will now use BattleAI for battles with neutral enemies by default * Fixed bug where BattleAI attempts to move double-wide unit to an unreachable hex * Fixed several cases where Nullkiller AI can count same dangerous object twice, doubling expected army loss. * Nullkiller is now capable of visiting configurable objects from mods @@ -74,6 +121,8 @@ * Fixed case where BattleAI will go around the map to attack ranged units if direct path is blocked by another unit * Fixed evaluation of effects of waiting if unit is under haste effect by Battle AI * Battle AI can now use location spells +* Battle AI will now correctly avoid moving flying units into dangerous obstacles such as moat +* Fixed possible crash on AI attempting to visit town that is already being visited by this hero ### Launcher * Added Swedish translation @@ -81,13 +130,23 @@ ### Map Editor * Implemented tracking of building requirements for Building Dialog * Added build/demolish/enable/disable all buildings options to Building Dialog in town properties +* Implemented configuration of patrol radius for heroes * It is now possible to set spells allowed or required to be present in Mages Guild * It is now possible to add timed events to a town * Fixed editor not marking mod as dependency if spells from mod are used in town Mages Guild or in hero starting spells * It is now possible to choose road types for random map generation in editor * Validator will now warn in case if map has players with no heroes or towns +* Fixed broken transparency handling on some adventure map objects from mods +* Fixed duplicated list of spells in Mage Guild in copy-pasted towns +* Removed separate versioning of map editor. Map editor now has same version as VCMI +* Timed events interfaces now counts days from 1, instead of from 0 ### Modding +* Added support for configurable flaggable objects that can provide bonuses or daily income to owning player +* Added support for soft dependencies for mods, that only affect mod loading order (and as result - override order), without requiring dependent mod or allowing access to its identifiers +* It is now possible to provide translations for mods that modify strings from original game, such as secondary skill descriptions +* It is now possible to embed json data directly into mod.json instead of using list of files +* Implemented detection of potential conflicts between mods. To enable, open Launcher and set "Mod Validation" option to "Full" * Fixed multiple issues with configurable town buildings * Added documentation for configurable town buildings. See docs/Moddders/Entities_Format/Town_Buildings_Format.md * Replaced some of hardcoded town buildings with configurable buildings. These building types are now deprecated and will be removed in future. @@ -95,7 +154,9 @@ * It is now possible to add guards to a configurable objects. All H3 creature banks are now implemented as configurable object. * It is now possible to define starting position of units in a guarded configurable object * Added `canCastWithoutSkip` parameter to a spell. If such spell is cast by a creature, its turn will not end after a spellcast +* Added `castOnlyOnSelf` parameter to a spell. Creature that can cast this spell can only cast it on themselves * Mod can now provide pregenerated assets in place of autogenerated, such as large spellbook. +* Added support for 'fused' artifacts, as alternative to combined artifacts * Added support for custom music and opening sound for a battlefield * Added support for multiple music tracks for towns * Added support for multiple music tracks for terrains on adventure map @@ -108,11 +169,12 @@ * Town building can now define provided fortifications - health of walls, towers, presence of moat, identifier of creature shooter on tower * Added DISINTEGRATE bonus * Added INVINCIBLE bonus +* Added PRISM_HEX_ATTACK_BREATH bonus * Added THIEVES_GUILD_ACCESS bonus that changes amount of information available in thieves guild * TimesStackLevelUpdater now supports commanders * Black market restock period setting now correctly restocks on specified date instead of restocking on all dates other than specified one -* Game now supports vp8 and vp9 encoding for video files on all platforms * Json Validator will now attempt to detect typos when encountering unknown property in Json +* Added `translate missing` command that will export only untranslated strings into `translationsMissing` directory, separated per mod # 1.5.6 -> 1.5.7 From 7a9875f77bae43f9679faaf8da9714b23aea6848 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 30 Oct 2024 14:39:56 +0000 Subject: [PATCH 476/726] Use msvc build for daily Windows builds --- .github/workflows/github.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 5c9ab602a..4507ca072 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -3,7 +3,6 @@ name: VCMI on: push: branches: - - features/* - beta - master - develop @@ -76,6 +75,7 @@ jobs: os: windows-latest test: 0 pack: 1 + upload: 1 pack_type: RelWithDebInfo extension: exe before_install: msvc.sh @@ -92,7 +92,6 @@ jobs: os: ubuntu-24.04 test: 0 pack: 1 - upload: 1 pack_type: Release extension: exe cmake_args: -G Ninja @@ -342,7 +341,7 @@ jobs: ${{github.workspace}}/**/*.pdb - name: Upload build - if: ${{ (matrix.upload == 1) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) }} + if: ${{ (matrix.upload == 1) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') }} continue-on-error: true run: | if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then From 08fbcd52398209921cb40601bc38f8123ff1aefe Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 13 Jul 2024 18:37:13 +0000 Subject: [PATCH 477/726] TerrainTile now uses identifiers instead of pointers to VLC --- AI/Nullkiller/AIUtility.cpp | 2 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 +- AI/VCAI/AIUtility.cpp | 2 +- AI/VCAI/Pathfinding/AINodeStorage.cpp | 4 +- client/HeroMovementController.cpp | 8 +- client/adventureMap/CMinimap.cpp | 6 +- client/adventureMap/MapAudioPlayer.cpp | 2 +- client/mapView/MapRenderer.cpp | 24 ++--- client/mapView/mapHandler.cpp | 2 +- client/windows/CMapOverview.cpp | 6 +- lib/CGameInfoCallback.cpp | 2 +- lib/IGameCallback.cpp | 2 +- lib/gameState/CGameState.cpp | 20 ++-- .../CommonConstructors.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 10 +- lib/mapObjects/CGObjectInstance.cpp | 6 +- lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/IObjectInterface.cpp | 4 +- lib/mapping/CDrawRoadsOperation.cpp | 12 +-- lib/mapping/CMap.cpp | 95 ++++++++++++++----- lib/mapping/CMapDefines.h | 31 ++++-- lib/mapping/CMapOperation.cpp | 30 +++--- lib/mapping/MapEditUtils.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 10 +- lib/mapping/MapFormatJson.cpp | 32 +++---- lib/mapping/MapFormatJson.h | 6 +- lib/pathfinder/CPathfinder.cpp | 20 ++-- lib/pathfinder/NodeStorage.cpp | 4 +- lib/pathfinder/PathfinderUtil.h | 6 +- lib/pathfinder/PathfindingRules.cpp | 4 +- lib/rewardable/Interface.cpp | 8 +- lib/rmg/RmgObject.cpp | 2 +- lib/rmg/modificators/RoadPlacer.cpp | 2 +- lib/rmg/modificators/RockFiller.cpp | 2 +- lib/rmg/modificators/RockPlacer.cpp | 4 +- lib/rmg/modificators/WaterProxy.cpp | 4 +- lib/spells/AdventureSpellMechanics.cpp | 2 +- mapeditor/mainwindow.cpp | 2 +- mapeditor/mapcontroller.cpp | 4 +- mapeditor/maphandler.cpp | 16 ++-- mapeditor/mapview.cpp | 2 +- mapeditor/scenelayer.cpp | 4 +- server/CGameHandler.cpp | 17 ++-- server/battles/BattleProcessor.cpp | 2 +- 44 files changed, 237 insertions(+), 194 deletions(-) diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index a5daee212..23a28262c 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -193,7 +193,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater) { // TODO: Such information should be provided by pathfinder // Tile must be free or with unoccupied boat - if(!t->blocked) + if(!t->blocked()) { return true; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index c7c318c95..f7807dc5c 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -130,10 +130,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta for(pos.y = 0; pos.y < sizes.y; ++pos.y) { const TerrainTile & tile = gs->map->getTile(pos); - if (!tile.terType->isPassable()) + if (!tile.getTerrain()->isPassable()) continue; - if (tile.terType->isWater()) + if (tile.isWater()) { resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if (useFlying) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 63a64c43b..d6538504c 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -186,7 +186,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater) { // TODO: Such information should be provided by pathfinder // Tile must be free or with unoccupied boat - if(!t->blocked) + if(!t->blocked()) { return true; } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 63bd2bd4e..1ff94d8c0 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -46,10 +46,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta for(pos.y=0; pos.y < sizes.y; ++pos.y) { const TerrainTile & tile = gs->map->getTile(pos); - if(!tile.terType->isPassable()) + if(!tile.getTerrain()->isPassable()) continue; - if(tile.terType->isWater()) + if(tile.getTerrain()->isWater()) { resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) diff --git a/client/HeroMovementController.cpp b/client/HeroMovementController.cpp index 2ee599017..8584ddb44 100644 --- a/client/HeroMovementController.cpp +++ b/client/HeroMovementController.cpp @@ -291,14 +291,12 @@ AudioPath HeroMovementController::getMovementSoundFor(const CGHeroInstance * her auto prevTile = LOCPLINT->cb->getTile(posPrev); auto nextTile = LOCPLINT->cb->getTile(posNext); - auto prevRoad = prevTile->roadType; - auto nextRoad = nextTile->roadType; - bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD; + bool movingOnRoad = prevTile->hasRoad() && nextTile->hasRoad(); if(movingOnRoad) - return nextTile->terType->horseSound; + return nextTile->getTerrain()->horseSound; else - return nextTile->terType->horseSoundPenalty; + return nextTile->getTerrain()->horseSoundPenalty; }; void HeroMovementController::updateMovementSound(const CGHeroInstance * h, int3 posPrev, int3 nextCoord, EPathNodeAction moveType) diff --git a/client/adventureMap/CMinimap.cpp b/client/adventureMap/CMinimap.cpp index bc3cd2c10..fad393eae 100644 --- a/client/adventureMap/CMinimap.cpp +++ b/client/adventureMap/CMinimap.cpp @@ -50,10 +50,10 @@ ColorRGBA CMinimapInstance::getTileColor(const int3 & pos) const return graphics->playerColors[player.getNum()]; } - if (tile->blocked && (!tile->visitable)) - return tile->terType->minimapBlocked; + if (tile->blocked() && !tile->visitable()) + return tile->getTerrain()->minimapBlocked; else - return tile->terType->minimapUnblocked; + return tile->getTerrain()->minimapUnblocked; } void CMinimapInstance::refreshTile(const int3 &tile) diff --git a/client/adventureMap/MapAudioPlayer.cpp b/client/adventureMap/MapAudioPlayer.cpp index 356b5577b..a6e360b3a 100644 --- a/client/adventureMap/MapAudioPlayer.cpp +++ b/client/adventureMap/MapAudioPlayer.cpp @@ -182,7 +182,7 @@ void MapAudioPlayer::updateMusic() const auto * tile = LOCPLINT->cb->getTile(currentSelection->visitablePos()); if (tile) - CCS->musich->playMusicFromSet("terrain", tile->terType->getJsonKey(), true, false); + CCS->musich->playMusicFromSet("terrain", tile->getTerrain()->getJsonKey(), true, false); } if(audioPlaying && enemyMakingTurn) diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index a274fe5ec..294d0bcc1 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -143,7 +143,7 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ { const TerrainTile & mapTile = context.getMapTile(coordinates); - int32_t terrainIndex = mapTile.terType->getIndex(); + int32_t terrainIndex = mapTile.getTerrainID(); int32_t imageIndex = mapTile.terView; int32_t rotationIndex = mapTile.extTileFlags % 4; @@ -152,11 +152,11 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ assert(image); if (!image) { - logGlobal->error("Failed to find image %d for terrain %s on tile %s", imageIndex, mapTile.terType->getNameTranslated(), coordinates.toString()); + logGlobal->error("Failed to find image %d for terrain %s on tile %s", imageIndex, mapTile.getTerrain()->getNameTranslated(), coordinates.toString()); return; } - for( auto const & element : mapTile.terType->paletteAnimation) + for( auto const & element : mapTile.getTerrain()->paletteAnimation) image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length)); target.draw(image, Point(0, 0)); @@ -166,7 +166,7 @@ uint8_t MapRendererTerrain::checksum(IMapRendererContext & context, const int3 & { const TerrainTile & mapTile = context.getMapTile(coordinates); - if(!mapTile.terType->paletteAnimation.empty()) + if(!mapTile.getTerrain()->paletteAnimation.empty()) return context.terrainImageIndex(250); return 0xff - 1; } @@ -184,16 +184,16 @@ void MapRendererRiver::renderTile(IMapRendererContext & context, Canvas & target { const TerrainTile & mapTile = context.getMapTile(coordinates); - if(mapTile.riverType->getId() == River::NO_RIVER) + if(!mapTile.hasRiver()) return; - int32_t terrainIndex = mapTile.riverType->getIndex(); + int32_t terrainIndex = mapTile.getRiverID(); int32_t imageIndex = mapTile.riverDir; int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4; const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex); - for( auto const & element : mapTile.riverType->paletteAnimation) + for( auto const & element : mapTile.getRiver()->paletteAnimation) image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length)); target.draw(image, Point(0, 0)); @@ -203,7 +203,7 @@ uint8_t MapRendererRiver::checksum(IMapRendererContext & context, const int3 & c { const TerrainTile & mapTile = context.getMapTile(coordinates); - if(!mapTile.riverType->paletteAnimation.empty()) + if(!mapTile.getRiver()->paletteAnimation.empty()) return context.terrainImageIndex(250); return 0xff-1; } @@ -224,9 +224,9 @@ void MapRendererRoad::renderTile(IMapRendererContext & context, Canvas & target, if(context.isInMap(coordinatesAbove)) { const TerrainTile & mapTileAbove = context.getMapTile(coordinatesAbove); - if(mapTileAbove.roadType->getId() != Road::NO_ROAD) + if(mapTileAbove.hasRoad()) { - int32_t terrainIndex = mapTileAbove.roadType->getIndex(); + int32_t terrainIndex = mapTileAbove.getRoadID(); int32_t imageIndex = mapTileAbove.roadDir; int32_t rotationIndex = (mapTileAbove.extTileFlags >> 4) % 4; @@ -236,9 +236,9 @@ void MapRendererRoad::renderTile(IMapRendererContext & context, Canvas & target, } const TerrainTile & mapTile = context.getMapTile(coordinates); - if(mapTile.roadType->getId() != Road::NO_ROAD) + if(mapTile.hasRoad()) { - int32_t terrainIndex = mapTile.roadType->getIndex(); + int32_t terrainIndex = mapTile.getRoadID(); int32_t imageIndex = mapTile.roadDir; int32_t rotationIndex = (mapTile.extTileFlags >> 4) % 4; diff --git a/client/mapView/mapHandler.cpp b/client/mapView/mapHandler.cpp index ede4b3ea5..eb30680fd 100644 --- a/client/mapView/mapHandler.cpp +++ b/client/mapView/mapHandler.cpp @@ -55,7 +55,7 @@ std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) cons if(t.hasFavorableWinds()) return CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0); - std::string result = t.terType->getNameTranslated(); + std::string result = t.getTerrain()->getNameTranslated(); for(const auto & object : map->objects) { diff --git a/client/windows/CMapOverview.cpp b/client/windows/CMapOverview.cpp index 9e96ef8dc..c0845133d 100644 --- a/client/windows/CMapOverview.cpp +++ b/client/windows/CMapOverview.cpp @@ -67,9 +67,9 @@ Canvas CMapOverviewWidget::createMinimapForLayer(std::unique_ptr & map, in { TerrainTile & tile = map->getTile(int3(x, y, layer)); - ColorRGBA color = tile.terType->minimapUnblocked; - if (tile.blocked && (!tile.visitable)) - color = tile.terType->minimapBlocked; + ColorRGBA color = tile.getTerrain()->minimapUnblocked; + if (tile.blocked() && !tile.visitable()) + color = tile.getTerrain()->minimapBlocked; if(drawPlayerElements) // if object at tile is owned - it will be colored as its owner diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index e24d7fd1e..e5cc2370f 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -630,7 +630,7 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu { const TerrainTile *tile = getTile(t->bestLocation(), false); - if(!tile || !tile->terType->isWater()) + if(!tile || !tile->isWater()) return EBuildingState::NO_WATER; //lack of water } diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index cb32dcb50..84b736d78 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -72,7 +72,7 @@ void CPrivilegedInfoCallback::getFreeTiles(std::vector & tiles) const for (int yd = 0; yd < gs->map->height; yd++) { tinfo = getTile(int3 (xd,yd,zd)); - if (tinfo->terType->isLand() && tinfo->terType->isPassable() && !tinfo->blocked) //land and free + if (tinfo->isLand() && tinfo->getTerrain()->isPassable() && !tinfo->blocked()) //land and free tiles.emplace_back(xd, yd, zd); } } diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 1ef3d9a12..ad78c00b3 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -437,10 +437,10 @@ void CGameState::initGrailPosition() for(int y = BORDER_WIDTH; y < map->height - BORDER_WIDTH; y++) { const TerrainTile &t = map->getTile(int3(x, y, z)); - if(!t.blocked - && !t.visitable - && t.terType->isLand() - && t.terType->isPassable() + if(!t.blocked() + && !t.visitable() + && t.isLand() + && t.getTerrain()->isPassable() && (int)map->grailPos.dist2dSQ(int3(x, y, z)) <= (map->grailRadius * map->grailRadius)) allowedPos.emplace_back(x, y, z); } @@ -608,7 +608,7 @@ void CGameState::initHeroes() { assert(map->isInTheMap(hero->visitablePos())); const auto & tile = map->getTile(hero->visitablePos()); - if (tile.terType->isWater()) + if (tile.isWater()) { auto handler = VLC->objtypeh->getHandlerFor(Obj::BOAT, hero->getBoatType().getNum()); auto boat = dynamic_cast(handler->create(callback, nullptr)); @@ -1074,10 +1074,10 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand) if(map->isCoastalTile(tile)) //coastal tile is always ground return BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.sand_shore")); - if (t.terType->battleFields.empty()) - throw std::runtime_error("Failed to find battlefield for terrain " + t.terType->getJsonKey()); + if (t.getTerrain()->battleFields.empty()) + throw std::runtime_error("Failed to find battlefield for terrain " + t.getTerrain()->getJsonKey()); - return BattleField(*RandomGeneratorUtil::nextItem(t.terType->battleFields, rand)); + return BattleField(*RandomGeneratorUtil::nextItem(t.getTerrain()->battleFields, rand)); } void CGameState::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const @@ -1171,7 +1171,7 @@ std::vector CGameState::guardingCreatures (int3 pos) const return guards; const TerrainTile &posTile = map->getTile(pos); - if (posTile.visitable) + if (posTile.visitable()) { for (CGObjectInstance* obj : posTile.visitableObjects) { @@ -1190,7 +1190,7 @@ std::vector CGameState::guardingCreatures (int3 pos) const if (map->isInTheMap(pos)) { const auto & tile = map->getTile(pos); - if (tile.visitable && (tile.isWater() == posTile.isWater())) + if (tile.visitable() && (tile.isWater() == posTile.isWater())) { for (CGObjectInstance* obj : tile.visitableObjects) { diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index b8194937c..69db58662 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -101,7 +101,7 @@ void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, vstd::RNG & rng) const { - auto templ = getOverride(object->cb->getTile(object->pos)->terType->getId(), object); + auto templ = getOverride(object->cb->getTile(object->pos)->getTerrainID(), object); if(templ) object->appearance = templ; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index beea5a779..18ffe6588 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -102,16 +102,16 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain int64_t ret = GameConstants::BASE_MOVEMENT_COST; //if there is road both on dest and src tiles - use src road movement cost - if(dest.roadType->getId() != Road::NO_ROAD && from.roadType->getId() != Road::NO_ROAD) + if(dest.hasRoad() && from.hasRoad()) { - ret = from.roadType->movementCost; + ret = from.getRoad()->movementCost; } - else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native + else if(ti->nativeTerrain != from.getTerrainID() &&//the terrain is not native ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus - !ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.terType->getId()))) //no special movement bonus + !ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.getTerrainID()))) //no special movement bonus { - ret = VLC->terrainTypeHandler->getById(from.terType->getId())->moveCost; + ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost; ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT); if(ret < GameConstants::BASE_MOVEMENT_COST) ret = GameConstants::BASE_MOVEMENT_COST; diff --git a/lib/mapObjects/CGObjectInstance.cpp b/lib/mapObjects/CGObjectInstance.cpp index 7d53bfbb9..8511a9875 100644 --- a/lib/mapObjects/CGObjectInstance.cpp +++ b/lib/mapObjects/CGObjectInstance.cpp @@ -128,13 +128,13 @@ void CGObjectInstance::setType(MapObjectID newID, MapObjectSubID newSubID) cb->gameState()->map->removeBlockVisTiles(this, true); auto handler = VLC->objtypeh->getHandlerFor(newID, newSubID); - if(!handler->getTemplates(tile.terType->getId()).empty()) + if(!handler->getTemplates(tile.getTerrainID()).empty()) { - appearance = handler->getTemplates(tile.terType->getId())[0]; + appearance = handler->getTemplates(tile.getTerrainID())[0]; } else { - logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", newID, newSubID, visitablePos().toString(), tile.terType->getNameTranslated()); + logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", newID, newSubID, visitablePos().toString(), tile.getTerrain()->getNameTranslated()); appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash } diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 5b1a115ce..291a289f9 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -692,7 +692,7 @@ ObjectInstanceID CGTownInstance::getObjInstanceID() const void CGTownInstance::updateAppearance() { - auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); + auto terrain = cb->gameState()->getTile(visitablePos())->getTerrainID(); //FIXME: not the best way to do this auto app = getObjectHandler()->getOverride(terrain, this); if (app) diff --git a/lib/mapObjects/IObjectInterface.cpp b/lib/mapObjects/IObjectInterface.cpp index 80cbec7e4..acf4eed14 100644 --- a/lib/mapObjects/IObjectInterface.cpp +++ b/lib/mapObjects/IObjectInterface.cpp @@ -90,10 +90,10 @@ int3 IBoatGenerator::bestLocation() const if(!tile) continue; // tile not visible / outside the map - if(!tile->terType->isWater()) + if(!tile->isWater()) continue; - if (tile->blocked) + if (tile->blocked()) { bool hasBoat = false; for (auto const * object : tile->blockingObjects) diff --git a/lib/mapping/CDrawRoadsOperation.cpp b/lib/mapping/CDrawRoadsOperation.cpp index c67684ceb..b3df0efa1 100644 --- a/lib/mapping/CDrawRoadsOperation.cpp +++ b/lib/mapping/CDrawRoadsOperation.cpp @@ -344,12 +344,12 @@ std::string CDrawRiversOperation::getLabel() const void CDrawRoadsOperation::executeTile(TerrainTile & tile) { - tile.roadType = const_cast(VLC->roadTypeHandler->getByIndex(roadType.getNum())); + tile.roadType = roadType; } void CDrawRiversOperation::executeTile(TerrainTile & tile) { - tile.riverType = const_cast(VLC->riverTypeHandler->getByIndex(riverType.getNum())); + tile.riverType = riverType; } bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const @@ -364,22 +364,22 @@ bool CDrawRiversOperation::canApplyPattern(const LinePattern & pattern) const bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const { - return tile.roadType->getId() != Road::NO_ROAD; + return tile.hasRoad(); } bool CDrawRiversOperation::needUpdateTile(const TerrainTile & tile) const { - return tile.riverType->getId() != River::NO_RIVER; + return tile.hasRiver(); } bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const { - return map->getTile(pos).roadType->getId() != Road::NO_ROAD; + return map->getTile(pos).hasRoad(); } bool CDrawRiversOperation::tileHasSomething(const int3& pos) const { - return map->getTile(pos).riverType->getId() != River::NO_RIVER; + return map->getTile(pos).hasRiver(); } void CDrawRoadsOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 18f266758..db4650ce1 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -131,32 +131,29 @@ void CCastleEvent::serializeJson(JsonSerializeFormat & handler) } TerrainTile::TerrainTile(): - terType(nullptr), - riverType(VLC->riverTypeHandler->getById(River::NO_RIVER)), - roadType(VLC->roadTypeHandler->getById(Road::NO_ROAD)), + riverType(River::NO_RIVER), + roadType(Road::NO_ROAD), terView(0), riverDir(0), roadDir(0), - extTileFlags(0), - visitable(false), - blocked(false) + extTileFlags(0) { } bool TerrainTile::entrableTerrain(const TerrainTile * from) const { - return entrableTerrain(from ? from->terType->isLand() : true, from ? from->terType->isWater() : true); + return entrableTerrain(from ? from->isLand() : true, from ? from->isWater() : true); } bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const { - return terType->isPassable() - && ((allowSea && terType->isWater()) || (allowLand && terType->isLand())); + return getTerrain()->isPassable() + && ((allowSea && isWater()) || (allowLand && isLand())); } bool TerrainTile::isClear(const TerrainTile * from) const { - return entrableTerrain(from) && !blocked; + return entrableTerrain(from) && !blocked(); } Obj TerrainTile::topVisitableId(bool excludeTop) const @@ -177,7 +174,7 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const { - if(terType->isWater() || !terType->isPassable()) + if(isWater() || !getTerrain()->isPassable()) return EDiggingStatus::WRONG_TERRAIN; int allowedBlocked = excludeTop ? 1 : 0; @@ -194,9 +191,65 @@ bool TerrainTile::hasFavorableWinds() const bool TerrainTile::isWater() const { - return terType->isWater(); + return getTerrain()->isWater(); } +bool TerrainTile::isLand() const +{ + return getTerrain()->isLand(); +} + +bool TerrainTile::visitable() const +{ + return !visitableObjects.empty(); +} + +bool TerrainTile::blocked() const +{ + return !blockingObjects.empty(); +} + +bool TerrainTile::hasRiver() const +{ + return getRiverID() != RiverId::NO_RIVER; +} + +bool TerrainTile::hasRoad() const +{ + return getRoadID() != RoadId::NO_ROAD; +} + +const TerrainType * TerrainTile::getTerrain() const +{ + return terrainType.toEntity(VLC); +} + +const RiverType * TerrainTile::getRiver() const +{ + return riverType.toEntity(VLC); +} + +const RoadType * TerrainTile::getRoad() const +{ + return roadType.toEntity(VLC); +} + +TerrainId TerrainTile::getTerrainID() const +{ + return terrainType; +} + +RiverId TerrainTile::getRiverID() const +{ + return riverType; +} + +RoadId TerrainTile::getRoadID() const +{ + return roadType; +} + + CMap::CMap(IGameCallback * cb) : GameCallbackHolder(cb) , checksum(0) @@ -243,15 +296,10 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total) { TerrainTile & curt = terrain[zVal][xVal][yVal]; if(total || obj->visitableAt(int3(xVal, yVal, zVal))) - { curt.visitableObjects -= obj; - curt.visitable = curt.visitableObjects.size(); - } + if(total || obj->blockingAt(int3(xVal, yVal, zVal))) - { curt.blockingObjects -= obj; - curt.blocked = curt.blockingObjects.size(); - } } } } @@ -270,15 +318,10 @@ void CMap::addBlockVisTiles(CGObjectInstance * obj) { TerrainTile & curt = terrain[zVal][xVal][yVal]; if(obj->visitableAt(int3(xVal, yVal, zVal))) - { curt.visitableObjects.push_back(obj); - curt.visitable = true; - } + if(obj->blockingAt(int3(xVal, yVal, zVal))) - { curt.blockingObjects.push_back(obj); - curt.blocked = true; - } } } } @@ -381,7 +424,7 @@ int3 CMap::guardingCreaturePosition (int3 pos) const if (!isInTheMap(pos)) return int3(-1, -1, -1); const TerrainTile &posTile = getTile(pos); - if (posTile.visitable) + if (posTile.visitable()) { for (CGObjectInstance* obj : posTile.visitableObjects) { @@ -401,7 +444,7 @@ int3 CMap::guardingCreaturePosition (int3 pos) const if (isInTheMap(pos)) { const auto & tile = getTile(pos); - if (tile.visitable && (tile.isWater() == water)) + if (tile.visitable() && (tile.isWater() == water)) { for (CGObjectInstance* obj : tile.visitableObjects) { diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 36034db33..911541666 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -104,20 +104,33 @@ struct DLL_LINKAGE TerrainTile Obj topVisitableId(bool excludeTop = false) const; CGObjectInstance * topVisitableObj(bool excludeTop = false) const; bool isWater() const; - EDiggingStatus getDiggingStatus(const bool excludeTop = true) const; + bool isLand() const; + EDiggingStatus getDiggingStatus(bool excludeTop = true) const; bool hasFavorableWinds() const; - const TerrainType * terType; - const RiverType * riverType; - const RoadType * roadType; + bool visitable() const; + bool blocked() const; + + const TerrainType * getTerrain() const; + const RiverType * getRiver() const; + const RoadType * getRoad() const; + + TerrainId getTerrainID() const; + RiverId getRiverID() const; + RoadId getRoadID() const; + + bool hasRiver() const; + bool hasRoad() const; + + TerrainId terrainType; + RiverId riverType; + RoadId roadType; ui8 terView; ui8 riverDir; ui8 roadDir; /// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road); /// 7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect ui8 extTileFlags; - bool visitable; - bool blocked; std::vector visitableObjects; std::vector blockingObjects; @@ -125,15 +138,15 @@ struct DLL_LINKAGE TerrainTile template void serialize(Handler & h) { - h & terType; + h & terrainType; h & terView; h & riverType; h & riverDir; h & roadType; h & roadDir; h & extTileFlags; - h & visitable; - h & blocked; + // h & visitable; + // h & blocked; h & visitableObjects; h & blockingObjects; } diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp index f6c8deb22..88a3100e5 100644 --- a/lib/mapping/CMapOperation.cpp +++ b/lib/mapping/CMapOperation.cpp @@ -103,7 +103,7 @@ void CDrawTerrainOperation::execute() for(const auto & pos : terrainSel.getSelectedItems()) { auto & tile = map->getTile(pos); - tile.terType = const_cast(VLC->terrainTypeHandler->getById(terType)); + tile.terrainType = terType; invalidateTerrainViews(pos); } @@ -137,7 +137,7 @@ void CDrawTerrainOperation::updateTerrainTypes() auto tiles = getInvalidTiles(centerPos); auto updateTerrainType = [&](const int3& pos) { - map->getTile(pos).terType = centerTile.terType; + map->getTile(pos).terrainType = centerTile.terrainType; positions.insert(pos); invalidateTerrainViews(pos); //logGlobal->debug("Set additional terrain tile at pos '%s' to type '%s'", pos, centerTile.terType); @@ -161,10 +161,10 @@ void CDrawTerrainOperation::updateTerrainTypes() rect.forEach([&](const int3& posToTest) { auto & terrainTile = map->getTile(posToTest); - if(centerTile.terType->getId() != terrainTile.terType->getId()) + if(centerTile.getTerrain() != terrainTile.getTerrain()) { - const auto * formerTerType = terrainTile.terType; - terrainTile.terType = centerTile.terType; + const auto formerTerType = terrainTile.terrainType; + terrainTile.terrainType = centerTile.terrainType; auto testTile = getInvalidTiles(posToTest); int nativeTilesCntNorm = testTile.nativeTiles.empty() ? std::numeric_limits::max() : static_cast(testTile.nativeTiles.size()); @@ -221,7 +221,7 @@ void CDrawTerrainOperation::updateTerrainTypes() suitableTiles.insert(posToTest); } - terrainTile.terType = formerTerType; + terrainTile.terrainType = formerTerType; } }); @@ -264,7 +264,7 @@ void CDrawTerrainOperation::updateTerrainViews() { for(const auto & pos : invalidatedTerViews) { - const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType->getId()); + const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).getTerrainID()); // Detect a pattern which fits best int bestPattern = -1; @@ -340,7 +340,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3& pos, const TerrainViewPattern& pattern, int recDepth) const { - const auto * centerTerType = map->getTile(pos).terType; + const auto * centerTerType = map->getTile(pos).getTerrain(); int totalPoints = 0; std::string transitionReplacement; @@ -372,24 +372,24 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi } else if(widthTooHigh) { - terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).terType; + terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).getTerrain(); } else if(heightTooHigh) { - terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).terType; + terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).getTerrain(); } else if(widthTooLess) { - terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).terType; + terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).getTerrain(); } else if(heightTooLess) { - terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).terType; + terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).getTerrain(); } } else { - terType = map->getTile(currentPos).terType; + terType = map->getTile(currentPos).getTerrain(); if(terType != centerTerType && (terType->isPassable() || centerTerType->isPassable())) { isAlien = true; @@ -509,13 +509,13 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const { //TODO: this is very expensive function for RMG, needs optimization InvalidTiles tiles; - const auto * centerTerType = map->getTile(centerPos).terType; + const auto * centerTerType = map->getTile(centerPos).getTerrain(); auto rect = extendTileAround(centerPos); rect.forEach([&](const int3& pos) { if(map->isInTheMap(pos)) { - const auto * terType = map->getTile(pos).terType; + const auto * terType = map->getTile(pos).getTerrain(); auto valid = validateTerrainView(pos, VLC->terviewh->getTerrainTypePatternById("n1")).result; // Special validity check for rock & water diff --git a/lib/mapping/MapEditUtils.cpp b/lib/mapping/MapEditUtils.cpp index cee52194f..9a4588727 100644 --- a/lib/mapping/MapEditUtils.cpp +++ b/lib/mapping/MapEditUtils.cpp @@ -356,7 +356,7 @@ void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, con { auto debugTile = map->getTile(debugPos); - std::string terType = debugTile.terType->shortIdentifier; + std::string terType = debugTile.getTerrain()->shortIdentifier; line += terType; line.insert(line.end(), PADDED_LENGTH - terType.size(), ' '); } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index b72bdd176..831e0bb49 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -988,17 +988,13 @@ void CMapLoaderH3M::readTerrain() for(pos.x = 0; pos.x < map->width; pos.x++) { auto & tile = map->getTile(pos); - tile.terType = VLC->terrainTypeHandler->getById(reader->readTerrain()); + tile.terrainType = reader->readTerrain(); tile.terView = reader->readUInt8(); - tile.riverType = VLC->riverTypeHandler->getById(reader->readRiver()); + tile.riverType = reader->readRiver(); tile.riverDir = reader->readUInt8(); - tile.roadType = VLC->roadTypeHandler->getById(reader->readRoad()); + tile.roadType = reader->readRoad(); tile.roadDir = reader->readUInt8(); tile.extTileFlags = reader->readUInt8(); - tile.blocked = !tile.terType->isPassable(); - tile.visitable = false; - - assert(tile.terType->getId() != ETerrainId::NONE); } } } diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index a5e33a325..2e8868b03 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -260,34 +260,34 @@ CMapFormatJson::CMapFormatJson(): } -TerrainType * CMapFormatJson::getTerrainByCode(const std::string & code) +TerrainId CMapFormatJson::getTerrainByCode(const std::string & code) { for(const auto & object : VLC->terrainTypeHandler->objects) { if(object->shortIdentifier == code) - return const_cast(object.get()); + return object->getId(); } - return nullptr; + return TerrainId::NONE; } -RiverType * CMapFormatJson::getRiverByCode(const std::string & code) +RiverId CMapFormatJson::getRiverByCode(const std::string & code) { for(const auto & object : VLC->riverTypeHandler->objects) { if (object->shortIdentifier == code) - return const_cast(object.get()); + return object->getId(); } - return nullptr; + return RiverId::NO_RIVER; } -RoadType * CMapFormatJson::getRoadByCode(const std::string & code) +RoadId CMapFormatJson::getRoadByCode(const std::string & code) { for(const auto & object : VLC->roadTypeHandler->objects) { if (object->shortIdentifier == code) - return const_cast(object.get()); + return object->getId(); } - return nullptr; + return RoadId::NO_ROAD; } void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value) const @@ -890,7 +890,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile using namespace TerrainDetail; {//terrain type const std::string typeCode = src.substr(0, 2); - tile.terType = getTerrainByCode(typeCode); + tile.terrainType = getTerrainByCode(typeCode); } int startPos = 2; //0+typeCode fixed length {//terrain view @@ -920,7 +920,7 @@ void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile tile.roadType = getRoadByCode(typeCode); if(!tile.roadType) //it's not a road, it's a river { - tile.roadType = VLC->roadTypeHandler->getById(Road::NO_ROAD); + tile.roadType = Road::NO_ROAD; tile.riverType = getRiverByCode(typeCode); hasRoad = false; if(!tile.riverType) @@ -1254,13 +1254,13 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) out.setf(std::ios::dec, std::ios::basefield); out.unsetf(std::ios::showbase); - out << tile.terType->shortIdentifier << static_cast(tile.terView) << flipCodes[tile.extTileFlags % 4]; + out << tile.getTerrain()->shortIdentifier << static_cast(tile.terView) << flipCodes[tile.extTileFlags % 4]; - if(tile.roadType->getId() != Road::NO_ROAD) - out << tile.roadType->shortIdentifier << static_cast(tile.roadDir) << flipCodes[(tile.extTileFlags >> 4) % 4]; + if(tile.hasRoad()) + out << tile.getRoad()->shortIdentifier << static_cast(tile.roadDir) << flipCodes[(tile.extTileFlags >> 4) % 4]; - if(tile.riverType->getId() != River::NO_RIVER) - out << tile.riverType->shortIdentifier << static_cast(tile.riverDir) << flipCodes[(tile.extTileFlags >> 2) % 4]; + if(tile.hasRiver()) + out << tile.getRiver()->shortIdentifier << static_cast(tile.riverDir) << flipCodes[(tile.extTileFlags >> 2) % 4]; return out.str(); } diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index 117e9f2bb..5688b0ca9 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -60,9 +60,9 @@ protected: CMapFormatJson(); - static TerrainType * getTerrainByCode(const std::string & code); - static RiverType * getRiverByCode(const std::string & code); - static RoadType * getRoadByCode(const std::string & code); + static TerrainId getTerrainByCode(const std::string & code); + static RiverId getRiverByCode(const std::string & code); + static RoadId getRoadByCode(const std::string & code); void serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value) const; diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp index 28f8fa178..7a7fc1af6 100644 --- a/lib/pathfinder/CPathfinder.cpp +++ b/lib/pathfinder/CPathfinder.cpp @@ -596,25 +596,19 @@ void CPathfinderHelper::getNeighbours( continue; const TerrainTile & destTile = map->getTile(destCoord); - if(!destTile.terType->isPassable()) + if(!destTile.getTerrain()->isPassable()) continue; -// //we cannot visit things from blocked tiles -// if(srcTile.blocked && !srcTile.visitable && destTile.visitable && srcTile.blockingObjects.front()->ID != HEROI_TYPE) -// { -// continue; -// } - /// Following condition let us avoid diagonal movement over coast when sailing - if(srcTile.terType->isWater() && limitCoastSailing && destTile.terType->isWater() && dir.x && dir.y) //diagonal move through water + if(srcTile.isWater() && limitCoastSailing && destTile.isWater() && dir.x && dir.y) //diagonal move through water { const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0}; const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; - if(map->getTile(horizontalNeighbour).terType->isLand() || map->getTile(verticalNeighbour).terType->isLand()) + if(map->getTile(horizontalNeighbour).isLand() || map->getTile(verticalNeighbour).isLand()) continue; } - if(indeterminate(onLand) || onLand == destTile.terType->isLand()) + if(indeterminate(onLand) || onLand == destTile.isLand()) { vec.push_back(destCoord); } @@ -662,13 +656,13 @@ int CPathfinderHelper::getMovementCost( bool isSailLayer; if(indeterminate(isDstSailLayer)) - isSailLayer = hero->boat && hero->boat->layer == EPathfindingLayer::SAIL && dt->terType->isWater(); + isSailLayer = hero->boat && hero->boat->layer == EPathfindingLayer::SAIL && dt->isWater(); else isSailLayer = static_cast(isDstSailLayer); bool isWaterLayer; if(indeterminate(isDstWaterLayer)) - isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->terType->isWater(); + isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->isWater(); else isWaterLayer = static_cast(isDstWaterLayer); @@ -703,7 +697,7 @@ int CPathfinderHelper::getMovementCost( { NeighbourTilesVector vec; - getNeighbours(*dt, dst, vec, ct->terType->isLand(), true); + getNeighbours(*dt, dst, vec, ct->isLand(), true); for(const auto & elem : vec) { int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); diff --git a/lib/pathfinder/NodeStorage.cpp b/lib/pathfinder/NodeStorage.cpp index a2ca9a0db..dd9c7a198 100644 --- a/lib/pathfinder/NodeStorage.cpp +++ b/lib/pathfinder/NodeStorage.cpp @@ -41,7 +41,7 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState for(pos.y=0; pos.y < sizes.y; ++pos.y) { const TerrainTile & tile = gs->map->getTile(pos); - if(tile.terType->isWater()) + if(tile.isWater()) { resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) @@ -49,7 +49,7 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState if(useWaterWalking) resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); } - if(tile.terType->isLand()) + if(tile.isLand()) { resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); if(useFlying) diff --git a/lib/pathfinder/PathfinderUtil.h b/lib/pathfinder/PathfinderUtil.h index bef955e68..3e1f4cbaf 100644 --- a/lib/pathfinder/PathfinderUtil.h +++ b/lib/pathfinder/PathfinderUtil.h @@ -32,7 +32,7 @@ namespace PathfinderUtil { case ELayer::LAND: case ELayer::SAIL: - if(tinfo.visitable) + if(tinfo.visitable()) { if(tinfo.visitableObjects.front()->ID == Obj::SANCTUARY && tinfo.visitableObjects.back()->ID == Obj::HERO && tinfo.visitableObjects.back()->tempOwner != player) //non-owned hero stands on Sanctuary { @@ -51,7 +51,7 @@ namespace PathfinderUtil } } } - else if(tinfo.blocked) + else if(tinfo.blocked()) { return EPathAccessibility::BLOCKED; } @@ -64,7 +64,7 @@ namespace PathfinderUtil break; case ELayer::WATER: - if(tinfo.blocked || tinfo.terType->isLand()) + if(tinfo.blocked() || tinfo.isLand()) return EPathAccessibility::BLOCKED; break; diff --git a/lib/pathfinder/PathfindingRules.cpp b/lib/pathfinder/PathfindingRules.cpp index 500b04665..8f90a3fca 100644 --- a/lib/pathfinder/PathfindingRules.cpp +++ b/lib/pathfinder/PathfindingRules.cpp @@ -380,7 +380,7 @@ void LayerTransitionRule::process( case EPathfindingLayer::SAIL: // have to disembark first before visiting objects on land - if (destination.tile->visitable) + if (destination.tile->visitable()) destination.blocked = true; //can disembark only on accessible tiles or tiles guarded by nearby monster @@ -397,7 +397,7 @@ void LayerTransitionRule::process( if (destination.node->accessible == EPathAccessibility::BLOCKVIS) { // Can't visit 'blockvisit' objects on coast if hero will end up on water terrain - if (source.tile->blocked || !destination.tile->entrableTerrain(source.tile)) + if (source.tile->blocked() || !destination.tile->entrableTerrain(source.tile)) destination.blocked = true; } } diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index 7bef36108..212e1f17e 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -63,16 +63,16 @@ void Rewardable::Interface::grantRewardBeforeLevelup(const Rewardable::VisitInfo const auto functor = [&props](const TerrainTile * tile) { int score = 0; - if (tile->terType->isSurface()) + if (tile->getTerrain()->isSurface()) score += props.scoreSurface; - if (tile->terType->isUnderground()) + if (tile->getTerrain()->isUnderground()) score += props.scoreSubterra; - if (tile->terType->isWater()) + if (tile->getTerrain()->isWater()) score += props.scoreWater; - if (tile->terType->isRock()) + if (tile->getTerrain()->isRock()) score += props.scoreRock; return score > 0; diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 691bb23b8..08c5d1096 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -484,7 +484,7 @@ void Object::Instance::finalize(RmgMap & map, vstd::RNG & rng) //If no specific template was defined for this object, select any matching if (!dObject.appearance) { - const auto * terrainType = map.getTile(getPosition(true)).terType; + const auto * terrainType = map.getTile(getPosition(true)).getTerrain(); auto templates = dObject.getObjectHandler()->getTemplates(terrainType->getId()); if (templates.empty()) { diff --git a/lib/rmg/modificators/RoadPlacer.cpp b/lib/rmg/modificators/RoadPlacer.cpp index 61b934659..a2a404766 100644 --- a/lib/rmg/modificators/RoadPlacer.cpp +++ b/lib/rmg/modificators/RoadPlacer.cpp @@ -149,7 +149,7 @@ void RoadPlacer::drawRoads(bool secondary) //Do not draw roads on underground rock or water roads.erase_if([this](const int3& pos) -> bool { - const auto* terrain = map.getTile(pos).terType; + const auto* terrain = map.getTile(pos).getTerrain(); return !terrain->isPassable() || !terrain->isLand(); }); diff --git a/lib/rmg/modificators/RockFiller.cpp b/lib/rmg/modificators/RockFiller.cpp index 116885706..e7cf8848b 100644 --- a/lib/rmg/modificators/RockFiller.cpp +++ b/lib/rmg/modificators/RockFiller.cpp @@ -72,7 +72,7 @@ void RockFiller::init() char RockFiller::dump(const int3 & t) { - if(!map.getTile(t).terType->isPassable()) + if(!map.getTile(t).getTerrain()->isPassable()) { return zone.area()->contains(t) ? 'R' : 'E'; } diff --git a/lib/rmg/modificators/RockPlacer.cpp b/lib/rmg/modificators/RockPlacer.cpp index 3fefee83b..bf51be7e8 100644 --- a/lib/rmg/modificators/RockPlacer.cpp +++ b/lib/rmg/modificators/RockPlacer.cpp @@ -60,7 +60,7 @@ void RockPlacer::postProcess() //Finally mark rock tiles as occupied, spawn no obstacles there rockArea = zone.area()->getSubarea([this](const int3 & t) { - return !map.getTile(t).terType->isPassable(); + return !map.getTile(t).getTerrain()->isPassable(); }); // Do not place rock on roads @@ -96,7 +96,7 @@ void RockPlacer::init() char RockPlacer::dump(const int3 & t) { - if(!map.getTile(t).terType->isPassable()) + if(!map.getTile(t).getTerrain()->isPassable()) { return zone.area()->contains(t) ? 'R' : 'E'; } diff --git a/lib/rmg/modificators/WaterProxy.cpp b/lib/rmg/modificators/WaterProxy.cpp index f50c1c555..544c6f197 100644 --- a/lib/rmg/modificators/WaterProxy.cpp +++ b/lib/rmg/modificators/WaterProxy.cpp @@ -51,7 +51,7 @@ void WaterProxy::process() for([[maybe_unused]] const auto & t : area->getTilesVector()) { assert(map.isOnMap(t)); - assert(map.getTile(t).terType->getId() == zone.getTerrainType()); + assert(map.getTile(t).getTerrainID() == zone.getTerrainType()); } // FIXME: Possible deadlock for 2 zones @@ -66,7 +66,7 @@ void WaterProxy::process() auto secondAreaPossible = z.second->areaPossible(); for(const auto & t : secondArea->getTilesVector()) { - if(map.getTile(t).terType->getId() == zone.getTerrainType()) + if(map.getTile(t).getTerrainID() == zone.getTerrainType()) { secondArea->erase(t); secondAreaPossible->erase(t); diff --git a/lib/spells/AdventureSpellMechanics.cpp b/lib/spells/AdventureSpellMechanics.cpp index 089c691b2..e197e2cd5 100644 --- a/lib/spells/AdventureSpellMechanics.cpp +++ b/lib/spells/AdventureSpellMechanics.cpp @@ -374,7 +374,7 @@ bool DimensionDoorMechanics::canBeCastAtImpl(spells::Problem & problem, const CG } else { - if (dest->blocked) + if (dest->blocked()) return false; } diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 723c3807f..14c0cf5d3 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -1123,7 +1123,7 @@ void MainWindow::on_actionUpdate_appearance_triggered() continue; } - auto * terrain = controller.map()->getTile(obj->visitablePos()).terType; + auto * terrain = controller.map()->getTile(obj->visitablePos()).getTerrain(); if(handler->isStaticObject()) { diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 91f5134c7..7b50a9e09 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -429,10 +429,10 @@ void MapController::commitObstacleFill(int level) for(auto & t : selection) { auto tl = _map->getTile(t); - if(tl.blocked || tl.visitable) + if(tl.blocked() || tl.visitable()) continue; - auto terrain = tl.terType->getId(); + auto terrain = tl.getTerrainID(); _obstaclePainters[terrain]->addBlockedTile(t); } diff --git a/mapeditor/maphandler.cpp b/mapeditor/maphandler.cpp index 70aac50b0..a7f0b2023 100644 --- a/mapeditor/maphandler.cpp +++ b/mapeditor/maphandler.cpp @@ -93,7 +93,7 @@ void MapHandler::drawTerrainTile(QPainter & painter, int x, int y, int z) auto & tinfo = map->getTile(int3(x, y, z)); ui8 rotation = tinfo.extTileFlags % 4; - auto terrainName = tinfo.terType->getJsonKey(); + auto terrainName = tinfo.getTerrain()->getJsonKey(); if(terrainImages.at(terrainName).size() <= tinfo.terView) return; @@ -110,7 +110,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) if(tinfoUpper && tinfoUpper->roadType) { - auto roadName = tinfoUpper->roadType->getJsonKey(); + auto roadName = tinfoUpper->getRoad()->getJsonKey(); QRect source(0, tileSize / 2, tileSize, tileSize / 2); ui8 rotation = (tinfoUpper->extTileFlags >> 4) % 4; bool hflip = (rotation == 1 || rotation == 3); @@ -123,7 +123,7 @@ void MapHandler::drawRoad(QPainter & painter, int x, int y, int z) if(tinfo.roadType) //print road from this tile { - auto roadName = tinfo.roadType->getJsonKey(); + auto roadName = tinfo.getRoad()->getJsonKey(); QRect source(0, 0, tileSize, tileSize / 2); ui8 rotation = (tinfo.extTileFlags >> 4) % 4; bool hflip = (rotation == 1 || rotation == 3); @@ -139,11 +139,11 @@ void MapHandler::drawRiver(QPainter & painter, int x, int y, int z) { auto & tinfo = map->getTile(int3(x, y, z)); - if(tinfo.riverType->getId() == River::NO_RIVER) + if(!tinfo.hasRiver()) return; //TODO: use ui8 instead of string key - auto riverName = tinfo.riverType->getJsonKey(); + auto riverName = tinfo.getRiver()->getJsonKey(); if(riverImages.at(riverName).size() <= tinfo.riverDir) return; @@ -441,9 +441,9 @@ QRgb MapHandler::getTileColor(int x, int y, int z) auto & tile = map->getTile(int3(x, y, z)); - auto color = tile.terType->minimapUnblocked; - if (tile.blocked && (!tile.visitable)) - color = tile.terType->minimapBlocked; + auto color = tile.getTerrain()->minimapUnblocked; + if (tile.blocked() && (!tile.visitable())) + color = tile.getTerrain()->minimapBlocked; return qRgb(color.r, color.g, color.b); } diff --git a/mapeditor/mapview.cpp b/mapeditor/mapview.cpp index d8c055af4..29aeb9aa4 100644 --- a/mapeditor/mapview.cpp +++ b/mapeditor/mapview.cpp @@ -364,7 +364,7 @@ void MapView::mousePressEvent(QMouseEvent *event) else if(controller->map()->getTile(tile).riverType && controller->map()->getTile(tile).riverType != controller->map()->getTile(tilen).riverType) continue; - else if(controller->map()->getTile(tile).terType != controller->map()->getTile(tilen).terType) + else if(controller->map()->getTile(tile).terrainType != controller->map()->getTile(tilen).terrainType) continue; } if(event->button() == Qt::LeftButton && sc->selectionTerrainView.selection().count(tilen)) diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index 628fb932c..c055a05df 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -101,9 +101,9 @@ void PassabilityLayer::update() for(int i = 0; i < map->width; ++i) { auto tl = map->getTile(int3(i, j, scene->level)); - if(tl.blocked || tl.visitable) + if(tl.blocked() || tl.visitable()) { - painter.fillRect(i * 32, j * 32, 31, 31, tl.visitable ? QColor(200, 200, 0, 64) : QColor(255, 0, 0, 64)); + painter.fillRect(i * 32, j * 32, 31, 31, tl.visitable() ? QColor(200, 200, 0, 64) : QColor(255, 0, 0, 64)); } } } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 373988702..12bcf0b7a 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -830,9 +830,8 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme const bool embarking = !h->boat && objectToVisit && objectToVisit->ID == Obj::BOAT; const bool disembarking = h->boat - && t.terType->isLand() - && (dst == h->pos - || (h->boat->layer == EPathfindingLayer::SAIL && !t.blocked)); + && t.isLand() + && (dst == h->pos || (h->boat->layer == EPathfindingLayer::SAIL && !t.blocked())); //result structure for start - movement failed, no move points used TryMoveHero tmh; @@ -850,9 +849,9 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(BonusType::WATER_WALKING) || (h->boat && h->boat->layer == EPathfindingLayer::WATER); const int cost = pathfinderHelper->getMovementCost(h->visitablePos(), hmpos, nullptr, nullptr, h->movementPointsRemaining()); - const bool movingOntoObstacle = t.blocked && !t.visitable; + const bool movingOntoObstacle = t.blocked() && !t.visitable(); const bool objectCoastVisitable = objectToVisit && objectToVisit->isCoastVisitable(); - const bool movingOntoWater = !h->boat && t.terType->isWater() && !objectCoastVisitable; + const bool movingOntoWater = !h->boat && t.isWater() && !objectCoastVisitable; const auto complainRet = [&](const std::string & message) { @@ -876,14 +875,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) - if (!t.terType->isPassable() || (movingOntoObstacle && !canFly)) + if (!t.getTerrain()->isPassable() || (movingOntoObstacle && !canFly)) return complainRet("Cannot move hero, destination tile is blocked!"); //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) if(movingOntoWater && !canFly && !canWalkOnSea) return complainRet("Cannot move hero, destination tile is on water!"); - if(h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.terType->isLand() && t.blocked) + if(h->boat && h->boat->layer == EPathfindingLayer::SAIL && t.isLand() && t.blocked()) return complainRet("Cannot disembark hero, tile is blocked!"); if(distance(h->pos, dst) >= 1.5 && movementMode == EMovementMode::STANDARD) @@ -895,7 +894,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme if(h->movementPointsRemaining() < cost && dst != h->pos && movementMode == EMovementMode::STANDARD) return complainRet("Hero doesn't have any movement points left!"); - if (transit && !canFly && !(canWalkOnSea && t.terType->isWater()) && !CGTeleport::isTeleport(objectToVisit)) + if (transit && !canFly && !(canWalkOnSea && t.isWater()) && !CGTeleport::isTeleport(objectToVisit)) return complainRet("Hero cannot transit over this tile!"); //several generic blocks of code @@ -1017,7 +1016,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme if (CGTeleport::isTeleport(objectToVisit)) visitDest = DONT_VISIT_DEST; - if (canFly || (canWalkOnSea && t.terType->isWater())) + if (canFly || (canWalkOnSea && t.isWater())) { lookForGuards = IGNORE_GUARDS; visitDest = DONT_VISIT_DEST; diff --git a/server/battles/BattleProcessor.cpp b/server/battles/BattleProcessor.cpp index d16b77a9b..7a799d5db 100644 --- a/server/battles/BattleProcessor.cpp +++ b/server/battles/BattleProcessor.cpp @@ -157,7 +157,7 @@ void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInsta BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray armies, BattleSideArray heroes, const BattleLayout & layout, const CGTownInstance *town) { const auto & t = *gameHandler->getTile(tile); - TerrainId terrain = t.terType->getId(); + TerrainId terrain = t.getTerrainID(); if (town) terrain = town->getNativeTerrain(); else if (gameHandler->gameState()->map->isCoastalTile(tile)) //coastal tile is always ground From 04ca8aca9f3d3d61806b171df0024f82af46cf29 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 15 Jul 2024 07:41:53 +0000 Subject: [PATCH 478/726] Update tests --- client/mapView/MapRendererContext.cpp | 4 ++-- lib/gameState/GameStatistics.cpp | 2 +- server/CGameHandler.cpp | 2 +- server/processors/NewTurnProcessor.cpp | 4 ++-- test/game/CGameStateTest.cpp | 2 +- test/map/CMapEditManagerTest.cpp | 16 ++++++++-------- test/map/MapComparer.cpp | 5 +---- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 291322f31..d6644b394 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -275,7 +275,7 @@ std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) c const auto & tile = getMapTile(coordinates); - if (!tile.visitable) + if (!tile.visitable()) return {}; return tile.visitableObjects.back()->getObjectName(); @@ -288,7 +288,7 @@ ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates const auto & tile = getMapTile(coordinates); - if (!tile.visitable) + if (!tile.visitable()) return {}; const auto * object = tile.visitableObjects.back(); diff --git a/lib/gameState/GameStatistics.cpp b/lib/gameState/GameStatistics.cpp index a6ee5041c..46b797845 100644 --- a/lib/gameState/GameStatistics.cpp +++ b/lib/gameState/GameStatistics.cpp @@ -291,7 +291,7 @@ float Statistic::getMapExploredRatio(const CGameState * gs, PlayerColor player) { TerrainTile tile = gs->map->getTile(int3(x, y, layer)); - if(tile.blocked && (!tile.visitable)) + if(tile.blocked() && !tile.visitable()) continue; if(gs->isVisible(int3(x, y, layer), player)) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 12bcf0b7a..1c21ef43f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4211,7 +4211,7 @@ CGObjectInstance * CGameHandler::createNewObject(const int3 & visitablePosition, throw std::runtime_error("Attempt to create object outside map at " + visitablePosition.toString()); const TerrainTile & t = gs->map->getTile(visitablePosition); - terrainType = t.terType->getId(); + terrainType = t.getTerrainID(); auto handler = VLC->objtypeh->getHandlerFor(objectID, subID); diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index a850cb99f..73bbf9bf4 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -423,7 +423,7 @@ RumorState NewTurnProcessor::pickNewRumor() rumorId = *RandomGeneratorUtil::nextItem(sRumorTypes, rand); if(rumorId == RumorState::RUMOR_GRAIL) { - rumorExtra = gameHandler->gameState()->getTile(gameHandler->gameState()->map->grailPos)->terType->getIndex(); + rumorExtra = gameHandler->gameState()->getTile(gameHandler->gameState()->map->grailPos)->getTerrainID().getNum(); break; } @@ -555,7 +555,7 @@ std::vector NewTurnProcessor::updateHeroesMovementPoints() { auto ti = std::make_unique(h, 1); // NOTE: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356 - int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).terType->isLand(), ti.get()); + int32_t newMovementPoints = h->movementPointsLimitCached(gameHandler->gameState()->map->getTile(h->visitablePos()).isLand(), ti.get()); if (newMovementPoints != h->movementPointsRemaining()) result.emplace_back(h->id, newMovementPoints, true); diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 1047da97c..9313a0f8b 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -196,7 +196,7 @@ public: const auto & t = *gameCallback->getTile(tile); - auto terrain = t.terType->getId(); + auto terrain = t.getTerrainID(); BattleField terType(0); BattleLayout layout = BattleLayout::createDefaultLayout(gameState->callback, attacker, defender); diff --git a/test/map/CMapEditManagerTest.cpp b/test/map/CMapEditManagerTest.cpp index b1e7f56e6..542167082 100644 --- a/test/map/CMapEditManagerTest.cpp +++ b/test/map/CMapEditManagerTest.cpp @@ -38,17 +38,17 @@ TEST(MapManager, DrawTerrain_Type) static const int3 squareCheck[] = { int3(5,5,0), int3(5,4,0), int3(4,4,0), int3(4,5,0) }; for(const auto & tile : squareCheck) { - EXPECT_EQ(map->getTile(tile).terType->getId(), ETerrainId::GRASS); + EXPECT_EQ(map->getTile(tile).getTerrainID(), ETerrainId::GRASS); } // Concat to square editManager->getTerrainSelection().select(int3(6, 5, 0)); editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); - EXPECT_EQ(map->getTile(int3(6, 4, 0)).terType->getId(), ETerrainId::GRASS); + EXPECT_EQ(map->getTile(int3(6, 4, 0)).getTerrainID(), ETerrainId::GRASS); editManager->getTerrainSelection().select(int3(6, 5, 0)); editManager->drawTerrain(ETerrainId::LAVA, 10, &rand); - EXPECT_EQ(map->getTile(int3(4, 4, 0)).terType->getId(), ETerrainId::GRASS); - EXPECT_EQ(map->getTile(int3(7, 4, 0)).terType->getId(), ETerrainId::LAVA); + EXPECT_EQ(map->getTile(int3(4, 4, 0)).getTerrainID(), ETerrainId::GRASS); + EXPECT_EQ(map->getTile(int3(7, 4, 0)).getTerrainID(), ETerrainId::LAVA); // Special case water,rock editManager->getTerrainSelection().selectRange(MapRect(int3(10, 10, 0), 10, 5)); @@ -57,7 +57,7 @@ TEST(MapManager, DrawTerrain_Type) editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); editManager->getTerrainSelection().select(int3(21, 16, 0)); editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); - EXPECT_EQ(map->getTile(int3(20, 15, 0)).terType->getId(), ETerrainId::GRASS); + EXPECT_EQ(map->getTile(int3(20, 15, 0)).getTerrainID(), ETerrainId::GRASS); // Special case non water,rock static const int3 diagonalCheck[] = { int3(31,42,0), int3(32,42,0), int3(32,43,0), int3(33,43,0), int3(33,44,0), @@ -68,7 +68,7 @@ TEST(MapManager, DrawTerrain_Type) editManager->getTerrainSelection().select(tile); } editManager->drawTerrain(ETerrainId::GRASS, 10, &rand); - EXPECT_EQ(map->getTile(int3(35, 44, 0)).terType->getId(), ETerrainId::WATER); + EXPECT_EQ(map->getTile(int3(35, 44, 0)).getTerrainID(), ETerrainId::WATER); // Rock case editManager->getTerrainSelection().selectRange(MapRect(int3(1, 1, 1), 15, 15)); @@ -77,7 +77,7 @@ TEST(MapManager, DrawTerrain_Type) int3(8, 7, 1), int3(4, 8, 1), int3(5, 8, 1), int3(6, 8, 1)}); editManager->getTerrainSelection().setSelection(vec); editManager->drawTerrain(ETerrainId::ROCK, 10, &rand); - EXPECT_TRUE(!map->getTile(int3(5, 6, 1)).terType->isPassable() || !map->getTile(int3(7, 8, 1)).terType->isPassable()); + EXPECT_TRUE(!map->getTile(int3(5, 6, 1)).getTerrain()->isPassable() || !map->getTile(int3(7, 8, 1)).getTerrain()->isPassable()); //todo: add checks here and enable, also use smaller size #if 0 @@ -144,7 +144,7 @@ TEST(MapManager, DrawTerrain_View) int3 pos((si32)posVector[0].Float(), (si32)posVector[1].Float(), (si32)posVector[2].Float()); const auto & originalTile = originalMap->getTile(pos); editManager->getTerrainSelection().selectRange(MapRect(pos, 1, 1)); - editManager->drawTerrain(originalTile.terType->getId(), 10, &gen); + editManager->drawTerrain(originalTile.getTerrainID(), 10, &gen); const auto & tile = map->getTile(pos); bool isInRange = false; for(const auto & range : mapping) diff --git a/test/map/MapComparer.cpp b/test/map/MapComparer.cpp index 8d1744520..61e1f018e 100644 --- a/test/map/MapComparer.cpp +++ b/test/map/MapComparer.cpp @@ -133,7 +133,7 @@ void checkEqual(const ObjectTemplate & actual, const ObjectTemplate & expected) void checkEqual(const TerrainTile & actual, const TerrainTile & expected) { //fatal fail here on any error - VCMI_REQUIRE_FIELD_EQUAL(terType); + VCMI_REQUIRE_FIELD_EQUAL(terrainType); VCMI_REQUIRE_FIELD_EQUAL(terView); VCMI_REQUIRE_FIELD_EQUAL(riverType); VCMI_REQUIRE_FIELD_EQUAL(riverDir); @@ -143,9 +143,6 @@ void checkEqual(const TerrainTile & actual, const TerrainTile & expected) ASSERT_EQ(actual.blockingObjects.size(), expected.blockingObjects.size()); ASSERT_EQ(actual.visitableObjects.size(), expected.visitableObjects.size()); - - VCMI_REQUIRE_FIELD_EQUAL(visitable); - VCMI_REQUIRE_FIELD_EQUAL(blocked); } //MapComparer From c98ac01e7acdf1b08c385a8ff4aedba50046f8eb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Oct 2024 08:41:59 +0000 Subject: [PATCH 479/726] Replaced public artType member of ArtifactInstance with getter --- AI/Nullkiller/AIGateway.cpp | 8 +++--- AI/Nullkiller/AIUtility.cpp | 4 +-- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- AI/VCAI/AIUtility.cpp | 4 +-- AI/VCAI/MapObjectsEvaluator.cpp | 2 +- AI/VCAI/VCAI.cpp | 4 +-- client/ArtifactsUIController.cpp | 6 ++--- client/widgets/CArtifactsOfHeroBackpack.cpp | 2 +- client/widgets/CArtifactsOfHeroBase.cpp | 2 +- client/widgets/CArtifactsOfHeroMarket.cpp | 2 +- client/widgets/markets/CAltarArtifacts.cpp | 2 +- client/windows/CWindowWithArtifacts.cpp | 10 +++---- lib/CArtHandler.cpp | 14 +++++----- lib/CArtifactInstance.cpp | 29 ++++++++++++--------- lib/CArtifactInstance.h | 5 ++-- lib/CStack.cpp | 2 +- lib/battle/BattleInfo.cpp | 2 +- lib/gameState/CGameStateCampaign.cpp | 10 +++---- lib/mapObjects/MiscObjects.cpp | 6 ++--- lib/networkPacks/NetPacksLib.cpp | 14 +++++----- server/CGameHandler.cpp | 20 +++++++------- 21 files changed, 78 insertions(+), 72 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index d683a9657..a059a1685 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1055,7 +1055,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance //FIXME: why are the above possible to be null? bool emptySlotFound = false; - for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType())) + for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType())) { if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move { @@ -1068,7 +1068,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance } if(!emptySlotFound) //try to put that atifact in already occupied slot { - for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType())) + for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType())) { auto otherSlot = target->getSlot(slot); if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one @@ -1079,8 +1079,8 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance { logAi->trace( "Exchange artifacts %s <-> %s", - artifact->artType->getNameTranslated(), - otherSlot->artifact->artType->getNameTranslated()); + artifact->getType()->getNameTranslated(), + otherSlot->artifact->getType()->getNameTranslated()); if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true)) { diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 23a28262c..85416b69a 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -267,8 +267,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2) bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2) { - auto art1 = a1->artType; - auto art2 = a2->artType; + auto art1 = a1->getType(); + auto art2 = a2->getType(); if(art1->getPrice() == art2->getPrice()) return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 8e20c4e54..26cf07150 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -290,7 +290,7 @@ uint64_t RewardEvaluator::getArmyReward( case Obj::CREATURE_GENERATOR4: return getDwellingArmyValue(ai->cb.get(), target, checkGold); case Obj::ARTIFACT: - return evaluateArtifactArmyValue(dynamic_cast(target)->storedArtifact->artType); + return evaluateArtifactArmyValue(dynamic_cast(target)->storedArtifact->getType()); case Obj::HERO: return relations == PlayerRelations::ENEMIES ? enemyArmyEliminationRewardRatio * dynamic_cast(target)->getArmyStrength() diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index d6538504c..ff391587f 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -247,8 +247,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2) bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2) { - auto art1 = a1->artType; - auto art2 = a2->artType; + auto art1 = a1->getType(); + auto art2 = a2->getType(); if(art1->getPrice() == art2->getPrice()) return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL); diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index fa038725e..9833dc45d 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -92,7 +92,7 @@ std::optional MapObjectsEvaluator::getObjectValue(const CGObjectInstance * else if(obj->ID == Obj::ARTIFACT) { auto artifactObject = dynamic_cast(obj); - switch(artifactObject->storedArtifact->artType->aClass) + switch(artifactObject->storedArtifact->getType()->aClass) { case CArtifact::EartClass::ART_TREASURE: return 2000; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 462023437..c55ec55ab 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1180,7 +1180,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot //FIXME: why are the above possible to be null? bool emptySlotFound = false; - for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType())) + for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType())) { if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move { @@ -1193,7 +1193,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot } if(!emptySlotFound) //try to put that atifact in already occupied slot { - for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType())) + for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType())) { auto otherSlot = target->getSlot(slot); if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one diff --git a/client/ArtifactsUIController.cpp b/client/ArtifactsUIController.cpp index 649cd7304..064b22244 100644 --- a/client/ArtifactsUIController.cpp +++ b/client/ArtifactsUIController.cpp @@ -71,7 +71,7 @@ bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const Art } bool assembleConfirmed = false; - MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID()); + MetaString message = MetaString::createFromTextID(art->getType()->getDescriptionTextID()); message.appendEOL(); message.appendEOL(); if(combinedArt->isFused()) @@ -107,10 +107,10 @@ bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const if(art->hasParts()) { - if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1)) + if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->getType()->getConstituents().size() - 1)) return false; - MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID()); + MetaString message = MetaString::createFromTextID(art->getType()->getDescriptionTextID()); message.appendEOL(); message.appendEOL(); message.appendRawString(CGI->generaltexth->allTexts[733]); // Do you wish to disassemble this artifact? diff --git a/client/widgets/CArtifactsOfHeroBackpack.cpp b/client/widgets/CArtifactsOfHeroBackpack.cpp index 8c5c2724c..4cfde13ec 100644 --- a/client/widgets/CArtifactsOfHeroBackpack.cpp +++ b/client/widgets/CArtifactsOfHeroBackpack.cpp @@ -152,7 +152,7 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero) std::map filteredArts; for(auto & slotInfo : curHero->artifactsInBackpack) if(slotInfo.artifact->getTypeId() != artInSlotId && !slotInfo.artifact->isScroll() && - slotInfo.artifact->artType->canBePutAt(curHero, filterBySlot, true)) + slotInfo.artifact->getType()->canBePutAt(curHero, filterBySlot, true)) { filteredArts.insert(std::pair(slotInfo.artifact->getTypeId(), slotInfo.artifact)); } diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index d02a2a751..22666df42 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -273,7 +273,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit // If the artifact is part of at least one combined artifact, add additional information std::map> arts; - for(const auto combinedArt : slotInfo->artifact->artType->getPartOf()) + for(const auto combinedArt : slotInfo->artifact->getType()->getPartOf()) { assert(combinedArt->isCombined()); arts.try_emplace(combinedArt->getId()); diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index b41d1c377..0e94fd34c 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -32,7 +32,7 @@ void CArtifactsOfHeroMarket::clickPressedArtPlace(CComponentHolder & artPlace, c if(const auto art = getArt(ownedPlace->slot)) { - if(onSelectArtCallback && art->artType->isTradable()) + if(onSelectArtCallback && art->getType()->isTradable()) { unmarkSlots(); artPlace.selectSlot(true); diff --git a/client/widgets/markets/CAltarArtifacts.cpp b/client/widgets/markets/CAltarArtifacts.cpp index 4d88e1d8e..81ab7146f 100644 --- a/client/widgets/markets/CAltarArtifacts.cpp +++ b/client/widgets/markets/CAltarArtifacts.cpp @@ -201,7 +201,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr & { if(pickedArtInst->canBePutAt(altarArtifactsStorage)) { - if(pickedArtInst->artType->isTradable()) + if(pickedArtInst->getType()->isTradable()) { if(altarSlot->id == -1) tradeSlotsMap.try_emplace(altarSlot, pickedArtInst); diff --git a/client/windows/CWindowWithArtifacts.cpp b/client/windows/CWindowWithArtifacts.cpp index b8d20b8f4..f5a64db39 100644 --- a/client/windows/CWindowWithArtifacts.cpp +++ b/client/windows/CWindowWithArtifacts.cpp @@ -198,7 +198,7 @@ void CWindowWithArtifacts::markPossibleSlots() const continue; if(getHeroPickedArtifact() == hero || !std::dynamic_pointer_cast(artSet)) - artSet->markPossibleSlots(pickedArtInst->artType, hero->tempOwner == LOCPLINT->playerID); + artSet->markPossibleSlots(pickedArtInst->getType(), hero->tempOwner == LOCPLINT->playerID); } } } @@ -219,7 +219,7 @@ bool CWindowWithArtifacts::checkSpecialArts(const CArtifactInstance & artInst, c std::vector>(1, std::make_shared(ComponentType::ARTIFACT, ArtifactID(ArtifactID::CATAPULT)))); return false; } - if(isTrade && !artInst.artType->isTradable()) + if(isTrade && !artInst.getType()->isTradable()) { LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21], std::vector>(1, std::make_shared(ComponentType::ARTIFACT, artId))); @@ -240,7 +240,7 @@ void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst) } else { - CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), artInst.artType->getIconIndex()); + CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), artInst.getType()->getIconIndex()); } } @@ -253,10 +253,10 @@ void CWindowWithArtifacts::putPickedArtifact(const CGHeroInstance & curHero, con if(ArtifactUtils::isSlotBackpack(dstLoc.slot)) { - if(pickedArt->artType->isBig()) + if(pickedArt->getType()->isBig()) { // War machines cannot go to backpack - LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % pickedArt->artType->getNameTranslated())); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % pickedArt->getType()->getNameTranslated())); } else { diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 1cd5afdda..c566e8420 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -732,7 +732,7 @@ ArtifactPosition CArtifactSet::getArtPos(const ArtifactID & aid, bool onlyWorn, for(const auto & artInfo : artifactsInBackpack) { const auto art = artInfo.getArt(); - if(art && art->artType->getId() == aid) + if(art && art->getType()->getId() == aid) return ArtifactPosition(backpackPositionIdx); backpackPositionIdx++; } @@ -757,7 +757,7 @@ ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance * artInst) cons { if(artInst) { - for(const auto & slot : artInst->artType->getPossibleSlots().at(bearerType())) + for(const auto & slot : artInst->getType()->getPossibleSlots().at(bearerType())) if(getArt(slot) == artInst) return slot; @@ -805,11 +805,11 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & }; putToSlot(slot, art, false); - if(art->artType->isCombined() && ArtifactUtils::isSlotEquipment(slot)) + if(art->getType()->isCombined() && ArtifactUtils::isSlotEquipment(slot)) { const CArtifactInstance * mainPart = nullptr; for(const auto & part : art->getPartsInfo()) - if(vstd::contains(part.art->artType->getPossibleSlots().at(bearerType()), slot) + if(vstd::contains(part.art->getType()->getPossibleSlots().at(bearerType()), slot) && (part.slot == ArtifactPosition::PRE_FIRST)) { mainPart = part.art; @@ -821,7 +821,7 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & if(part.art != mainPart) { auto partSlot = part.slot; - if(!part.art->artType->canBePutAt(this, partSlot)) + if(!part.art->getType()->canBePutAt(this, partSlot)) partSlot = ArtifactUtils::getArtAnyPosition(this, part.art->getTypeId()); assert(ArtifactUtils::isSlotEquipment(partSlot)); @@ -995,7 +995,7 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler) { auto * artifact = ArtifactUtils::createArtifact(artifactID); auto slot = ArtifactPosition::BACKPACK_START + artifactsInBackpack.size(); - if(artifact->artType->canBePutAt(this, slot)) + if(artifact->getType()->canBePutAt(this, slot)) { auto artsMap = putArtifact(slot, artifact); artifact->addPlacementMap(artsMap); @@ -1036,7 +1036,7 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa { auto * artifact = ArtifactUtils::createArtifact(artifactID.toEnum()); - if(artifact->artType->canBePutAt(this, slot)) + if(artifact->getType()->canBePutAt(this, slot)) { auto artsMap = putArtifact(slot, artifact); artifact->addPlacementMap(artsMap); diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index cf01339db..24e7f21db 100644 --- a/lib/CArtifactInstance.cpp +++ b/lib/CArtifactInstance.cpp @@ -20,12 +20,12 @@ VCMI_LIB_NAMESPACE_BEGIN void CCombinedArtifactInstance::addPart(CArtifactInstance * art, const ArtifactPosition & slot) { auto artInst = static_cast(this); - assert(vstd::contains_if(artInst->artType->getConstituents(), + assert(vstd::contains_if(artInst->getType()->getConstituents(), [=](const CArtifact * partType) { return partType->getId() == art->getTypeId(); })); - assert(art->getParentNodes().size() == 1 && art->getParentNodes().front() == art->artType); + assert(art->getParentNodes().size() == 1 && art->getParentNodes().front() == art->getType()); partsInfo.emplace_back(art, slot); artInst->attachTo(*art); } @@ -77,7 +77,7 @@ void CGrowingArtifactInstance::growingUp() { auto artInst = static_cast(this); - if(artInst->artType->isGrowing()) + if(artInst->getType()->isGrowing()) { auto bonus = std::make_shared(); @@ -86,7 +86,7 @@ void CGrowingArtifactInstance::growingUp() bonus->duration = BonusDuration::COMMANDER_KILLED; artInst->accumulateBonus(bonus); - for(const auto & bonus : artInst->artType->getBonusesPerLevel()) + for(const auto & bonus : artInst->getType()->getBonusesPerLevel()) { // Every n levels if(artInst->valOfBonuses(BonusType::LEVEL_COUNTER) % bonus.first == 0) @@ -94,7 +94,7 @@ void CGrowingArtifactInstance::growingUp() artInst->accumulateBonus(std::make_shared(bonus.second)); } } - for(const auto & bonus : artInst->artType->getThresholdBonuses()) + for(const auto & bonus : artInst->getType()->getThresholdBonuses()) { // At n level if(artInst->valOfBonuses(BonusType::LEVEL_COUNTER) == bonus.first) @@ -125,18 +125,23 @@ CArtifactInstance::CArtifactInstance() void CArtifactInstance::setType(const CArtifact * art) { - artType = art; + artTypeID = art->getId(); attachToSource(*art); } std::string CArtifactInstance::nodeName() const { - return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type"; + return "Artifact instance of " + (getType() ? getType()->getJsonKey() : std::string("uninitialized")) + " type"; } ArtifactID CArtifactInstance::getTypeId() const { - return artType->getId(); + return artTypeID; +} + +const CArtifact * CArtifactInstance::getType() const +{ + return artTypeID.toArtifact(); } ArtifactInstanceID CArtifactInstance::getId() const @@ -151,22 +156,22 @@ void CArtifactInstance::setId(ArtifactInstanceID id) bool CArtifactInstance::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) const { - return artType->canBePutAt(artSet, slot, assumeDestRemoved); + return getType()->canBePutAt(artSet, slot, assumeDestRemoved); } bool CArtifactInstance::isCombined() const { - return artType->isCombined(); + return getType()->isCombined(); } bool CArtifactInstance::isScroll() const { - return artType->isScroll(); + return getType()->isScroll(); } void CArtifactInstance::deserializationFix() { - setType(artType); + setType(artTypeID.toArtifact()); for(PartInfo & part : partsInfo) attachTo(*part.art); } diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index e1c621c0e..4e244764c 100644 --- a/lib/CArtifactInstance.h +++ b/lib/CArtifactInstance.h @@ -73,14 +73,15 @@ protected: void init(); ArtifactInstanceID id; + ArtifactID artTypeID; public: - const CArtifact * artType = nullptr; CArtifactInstance(const CArtifact * art); CArtifactInstance(); void setType(const CArtifact * art); std::string nodeName() const override; ArtifactID getTypeId() const; + const CArtifact * getType() const; ArtifactInstanceID getId() const; void setId(ArtifactInstanceID id); @@ -94,7 +95,7 @@ public: { h & static_cast(*this); h & static_cast(*this); - h & artType; + h & artTypeID; h & id; BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 61e177047..348974985 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -352,7 +352,7 @@ bool CStack::unitHasAmmoCart(const battle::Unit * unit) const const auto * ownerHero = battle->battleGetOwnerHero(unit); if(ownerHero && ownerHero->artifactsWorn.find(ArtifactPosition::MACH2) != ownerHero->artifactsWorn.end()) { - if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->artType->getId() == ArtifactID::AMMO_CART) + if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->getTypeId() == ArtifactID::AMMO_CART) { return true; } diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 25583f30c..309f253ff 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -303,7 +303,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const if(nullptr != warMachineArt && hex.isValid()) { - CreatureID cre = warMachineArt->artType->getWarMachine(); + CreatureID cre = warMachineArt->getType()->getWarMachine(); if(cre != CreatureID::NONE) currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex); diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index 0416a8507..892435cc6 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -137,18 +137,18 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr ArtifactLocation al(hero.hero->id, artifactPosition); - bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId()); + bool takeable = travelOptions.artifactsKeptByHero.count(art->getTypeId()); bool locked = hero.hero->getSlot(al.slot)->locked; if (!locked && takeable) { - logGlobal->debug("Artifact %s from slot %d of hero %s will be transferred to next scenario", art->artType->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName()); + logGlobal->debug("Artifact %s from slot %d of hero %s will be transferred to next scenario", art->getType()->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName()); hero.transferrableArtifacts.push_back(artifactPosition); } if (!locked && !takeable) { - logGlobal->debug("Removing artifact %s from slot %d of hero %s", art->artType->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName()); + logGlobal->debug("Removing artifact %s from slot %d of hero %s", art->getType()->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName()); gameState->map->removeArtifactInstance(*hero.hero, al.slot); return true; } @@ -424,12 +424,12 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO { auto * artifact = donorHero->getArt(artLocation); - logGlobal->debug("Removing artifact %s from slot %d of hero %s for transfer", artifact->artType->getJsonKey(), artLocation.getNum(), donorHero->getHeroTypeName()); + logGlobal->debug("Removing artifact %s from slot %d of hero %s for transfer", artifact->getType()->getJsonKey(), artLocation.getNum(), donorHero->getHeroTypeName()); gameState->map->removeArtifactInstance(*donorHero, artLocation); if (receiver) { - logGlobal->debug("Granting artifact %s to hero %s for transfer", artifact->artType->getJsonKey(), receiver->getHeroTypeName()); + logGlobal->debug("Granting artifact %s to hero %s for transfer", artifact->getType()->getJsonKey(), receiver->getHeroTypeName()); const auto slot = ArtifactUtils::getArtAnyPosition(receiver, artifact->getTypeId()); if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot)) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index c959292a9..2809db7b1 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -775,13 +775,13 @@ void CGArtifact::initObj(vstd::RNG & rand) storedArtifact = ArtifactUtils::createArtifact(ArtifactID()); cb->gameState()->map->addNewArtifactInstance(storedArtifact); } - if(!storedArtifact->artType) + if(!storedArtifact->getType()) storedArtifact->setType(getArtifact().toArtifact()); } if(ID == Obj::SPELL_SCROLL) subID = 1; - assert(storedArtifact->artType); + assert(storedArtifact->getType()); assert(!storedArtifact->getParentNodes().empty()); //assert(storedArtifact->artType->id == subID); //this does not stop desync @@ -825,7 +825,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const iw.type = EInfoWindowMode::AUTO; iw.player = h->tempOwner; - if(storedArtifact->artType->canBePutAt(h)) + if(storedArtifact->getType()->canBePutAt(h)) { switch (ID.toEnum()) { diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 225fe9365..f8f8e49d6 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1208,7 +1208,7 @@ void RemoveObject::applyGs(CGameState *gs) beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi) { - return asi.artifact->artType->getId() == ArtifactID::GRAIL; + return asi.artifact->getTypeId() == ArtifactID::GRAIL; }); if(beatenHero->visitedTown) @@ -1733,7 +1733,7 @@ void BulkEraseArtifacts::applyGs(CGameState *gs) const auto slotInfo = artSet->getSlot(slot); if(slotInfo->locked) { - logGlobal->debug("Erasing locked artifact: %s", slotInfo->artifact->artType->getNameTranslated()); + logGlobal->debug("Erasing locked artifact: %s", slotInfo->artifact->getType()->getNameTranslated()); DisassembledArtifact dis; dis.al.artHolder = artHolder; @@ -1747,12 +1747,12 @@ void BulkEraseArtifacts::applyGs(CGameState *gs) } } assert((dis.al.slot != ArtifactPosition::PRE_FIRST) && "Failed to determine the assembly this locked artifact belongs to"); - logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->artType->getNameTranslated()); + logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->getType()->getNameTranslated()); dis.applyGs(gs); } else { - logGlobal->debug("Erasing artifact %s", slotInfo->artifact->artType->getNameTranslated()); + logGlobal->debug("Erasing artifact %s", slotInfo->artifact->getType()->getNameTranslated()); } gs->map->removeArtifactInstance(*artSet, slot); } @@ -1840,8 +1840,8 @@ void AssembledArtifact::applyGs(CGameState *gs) break; } - if(!vstd::contains(combinedArt->artType->getPossibleSlots().at(hero->bearerType()), al.slot) - && vstd::contains(combinedArt->artType->getPossibleSlots().at(hero->bearerType()), slot)) + if(!vstd::contains(combinedArt->getType()->getPossibleSlots().at(hero->bearerType()), al.slot) + && vstd::contains(combinedArt->getType()->getPossibleSlots().at(hero->bearerType()), slot)) al.slot = slot; } else @@ -1857,7 +1857,7 @@ void AssembledArtifact::applyGs(CGameState *gs) const auto constituentInstance = hero->getArt(slot); gs->map->removeArtifactInstance(*hero, slot); - if(!combinedArt->artType->isFused()) + if(!combinedArt->getType()->isFused()) { if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot) combinedArt->addPart(constituentInstance, slot); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1c21ef43f..81cf167ee 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2600,7 +2600,7 @@ bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocati if((srcSlotInfo && srcSlotInfo->locked) || (dstSlotInfo && dstSlotInfo->locked)) COMPLAIN_RET("Cannot move artifact locks."); - if(isDstSlotBackpack && srcArtifact->artType->isBig()) + if(isDstSlotBackpack && srcArtifact->getType()->isBig()) COMPLAIN_RET("Cannot put big artifacts in backpack!"); if(src.slot == ArtifactPosition::MACH4 || dstSlot == ArtifactPosition::MACH4) COMPLAIN_RET("Cannot move catapult!"); @@ -2625,7 +2625,7 @@ bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocati } auto hero = getHero(dst.artHolder); - if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->artType->getId(), dstSlot)) + if(ArtifactUtils::checkSpellbookIsNeeded(hero, srcArtifact->getTypeId(), dstSlot)) giveHeroNewArtifact(hero, ArtifactID::SPELLBOOK, ArtifactPosition::SPELLBOOK); ma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(src.slot, dstSlot)); @@ -2771,21 +2771,21 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj { makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t { - return inf.getArt()->artType->getPossibleSlots().at(ArtBearer::HERO).front().num; + return inf.getArt()->getType()->getPossibleSlots().at(ArtBearer::HERO).front().num; }); } else if(sortType == ManageBackpackArtifacts::ManageCmd::SORT_BY_COST) { makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t { - return inf.getArt()->artType->getPrice(); + return inf.getArt()->getType()->getPrice(); }); } else if(sortType == ManageBackpackArtifacts::ManageCmd::SORT_BY_CLASS) { makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t { - return inf.getArt()->artType->aClass; + return inf.getArt()->getType()->aClass; }); } else @@ -2924,7 +2924,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble is fused combined artifact!"); if(ArtifactUtils::isSlotBackpack(artifactSlot) - && !ArtifactUtils::isBackpackFreeSlots(hero, destArtifact->artType->getConstituents().size() - 1)) + && !ArtifactUtils::isBackpackFreeSlots(hero, destArtifact->getType()->getConstituents().size() - 1)) COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble but backpack is full!"); DisassembledArtifact da; @@ -3058,11 +3058,11 @@ bool CGameHandler::sellArtifact(const IMarket *m, const CGHeroInstance *h, Artif COMPLAIN_RET_FALSE_IF((!h), "Only hero can sell artifacts!"); const CArtifactInstance *art = h->getArtByInstanceId(aid); COMPLAIN_RET_FALSE_IF((!art), "There is no artifact to sell!"); - COMPLAIN_RET_FALSE_IF((!art->artType->isTradable()), "Cannot sell a war machine or spellbook!"); + COMPLAIN_RET_FALSE_IF((!art->getType()->isTradable()), "Cannot sell a war machine or spellbook!"); int resVal = 0; int dump = 1; - m->getOffer(art->artType->getId(), rid, dump, resVal, EMarketMode::ARTIFACT_RESOURCE); + m->getOffer(art->getType()->getId(), rid, dump, resVal, EMarketMode::ARTIFACT_RESOURCE); removeArtifact(ArtifactLocation(h->id, h->getArtPos(art))); giveResource(h->tempOwner, rid, resVal); @@ -3686,7 +3686,7 @@ bool CGameHandler::sacrificeArtifact(const IMarket * market, const CGHeroInstanc { if(auto art = artSet->getArtByInstanceId(artInstId)) { - if(art->artType->isTradable()) + if(art->getType()->isTradable()) { int dmp; int expToGive; @@ -3892,7 +3892,7 @@ bool CGameHandler::swapStacks(const StackLocation & sl1, const StackLocation & s bool CGameHandler::putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional askAssemble) { const auto artInst = getArtInstance(id); - assert(artInst && artInst->artType); + assert(artInst && artInst->getType()); ArtifactLocation dst(al.artHolder, ArtifactPosition::PRE_FIRST); dst.creature = al.creature; auto putTo = getArtSet(al); From d3af9f1c675997f7f0768be25e025bde836d5cec Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Oct 2024 16:02:35 +0000 Subject: [PATCH 480/726] Removed pointer to VLC entity from CStackBasicDescriptor --- AI/Nullkiller/AIGateway.cpp | 4 +- AI/Nullkiller/AIUtility.cpp | 4 +- AI/Nullkiller/Analyzers/ArmyManager.cpp | 2 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 +- AI/VCAI/ArmyManager.cpp | 2 +- AI/VCAI/Goals/CompleteQuest.cpp | 2 +- AI/VCAI/VCAI.cpp | 2 +- client/widgets/CGarrisonInt.cpp | 6 +- client/widgets/MiscWidgets.cpp | 2 +- client/windows/CCreatureWindow.cpp | 12 +- client/windows/GUIClasses.cpp | 2 +- lib/CCreatureHandler.cpp | 5 - lib/CCreatureHandler.h | 2 - lib/CCreatureSet.cpp | 118 +++++++++--------- lib/CCreatureSet.h | 23 ++-- lib/CGameInfoCallback.cpp | 10 +- lib/CStack.cpp | 4 +- lib/bonuses/Limiters.cpp | 2 +- lib/gameState/CGameState.cpp | 4 +- lib/gameState/InfoAboutArmy.cpp | 6 +- lib/json/JsonRandom.cpp | 6 +- .../DwellingInstanceConstructor.cpp | 2 +- lib/mapObjects/CBank.cpp | 8 +- lib/mapObjects/CGCreature.cpp | 8 +- lib/mapObjects/CGHeroInstance.cpp | 6 +- lib/mapObjects/CGTownInstance.cpp | 6 +- lib/mapObjects/CQuest.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 8 +- lib/mapping/MapFormatH3M.cpp | 2 +- lib/rewardable/Interface.cpp | 4 +- lib/rewardable/Limiter.cpp | 4 +- lib/rewardable/Reward.cpp | 2 +- lib/texts/MetaString.cpp | 2 +- mapeditor/inspector/questwidget.cpp | 2 +- mapeditor/inspector/rewardswidget.cpp | 4 +- server/CGameHandler.cpp | 12 +- server/battles/BattleResultProcessor.cpp | 4 +- server/processors/NewTurnProcessor.cpp | 2 +- 38 files changed, 140 insertions(+), 160 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index a059a1685..d3d1b8401 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1129,10 +1129,10 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re { for(auto stack : recruiter->Slots()) { - if(!stack.second->type) + if(!stack.second->getType()) continue; - auto duplicatingSlot = recruiter->getSlotFor(stack.second->type); + auto duplicatingSlot = recruiter->getSlotFor(stack.second->getCreature()); if(duplicatingSlot != stack.first) { diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 85416b69a..a8c325f98 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -312,7 +312,7 @@ int getDuplicatingSlots(const CArmedInstance * army) for(auto stack : army->Slots()) { - if(stack.second->type && army->getSlotFor(stack.second->type) != stack.first) + if(stack.second->getCreature() && army->getSlotFor(stack.second->getCreature()) != stack.first) duplicatingSlots++; } @@ -387,7 +387,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject { for(auto slot : h->Slots()) { - if(slot.second->type->hasUpgrades()) + if(slot.second->getType()->hasUpgrades()) return true; //TODO: check price? } return false; diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index e2bbbb1fc..120252f7d 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -90,7 +90,7 @@ std::vector ArmyManager::getSortedSlots(const CCreatureSet * target, c { for(auto & i : armyPtr->Slots()) { - auto cre = dynamic_cast(i.second->type); + auto cre = dynamic_cast(i.second->getType()); auto & slotInfp = creToPower[cre]; slotInfp.creature = cre; diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 26cf07150..2290fda00 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -155,10 +155,10 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero for (auto c : creatures) { //Only if hero has slot for this creature in the army - auto ccre = dynamic_cast(c.data.type); + auto ccre = dynamic_cast(c.data.getType()); if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0) { - result += (c.data.type->getAIValue() * c.data.count) * c.chance; + result += (c.data.getType()->getAIValue() * c.data.count) * c.chance; } /*else { diff --git a/AI/VCAI/ArmyManager.cpp b/AI/VCAI/ArmyManager.cpp index 72ab24d6b..efe6a17ea 100644 --- a/AI/VCAI/ArmyManager.cpp +++ b/AI/VCAI/ArmyManager.cpp @@ -36,7 +36,7 @@ std::vector ArmyManager::getSortedSlots(const CCreatureSet * target, c { for(auto & i : armyPtr->Slots()) { - auto cre = dynamic_cast(i.second->type); + auto cre = dynamic_cast(i.second->getType()); auto & slotInfp = creToPower[cre]; slotInfp.creature = cre; diff --git a/AI/VCAI/Goals/CompleteQuest.cpp b/AI/VCAI/Goals/CompleteQuest.cpp index 39fa64245..fc085052d 100644 --- a/AI/VCAI/Goals/CompleteQuest.cpp +++ b/AI/VCAI/Goals/CompleteQuest.cpp @@ -162,7 +162,7 @@ TGoalVec CompleteQuest::missionArmy() const for(auto creature : q.quest->mission.creatures) { - solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count))); + solutions.push_back(sptr(GatherTroops(creature.getId(), creature.count))); } return solutions; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index c55ec55ab..ccdea26ad 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2818,7 +2818,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj) { for(auto slot : h->Slots()) { - if(slot.second->type->hasUpgrades()) + if(slot.second->getType()->hasUpgrades()) return true; //TODO: check price? } return false; diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp index 2880ffcaf..f8ebd2347 100644 --- a/client/widgets/CGarrisonInt.cpp +++ b/client/widgets/CGarrisonInt.cpp @@ -373,7 +373,7 @@ void CGarrisonSlot::gesture(bool on, const Point & initialPosition, const Point const auto * otherArmy = upg == EGarrisonType::UPPER ? owner->lowerArmy() : owner->upperArmy(); bool stackExists = myStack != nullptr; - bool hasSameUnit = stackExists && !owner->army(upg)->getCreatureSlots(myStack->type, ID).empty(); + bool hasSameUnit = stackExists && !owner->army(upg)->getCreatureSlots(myStack->getCreature(), ID).empty(); bool hasOwnEmptySlots = stackExists && owner->army(upg)->getFreeSlot() != SlotID(); bool exchangeMode = stackExists && owner->upperArmy() && owner->lowerArmy(); bool hasOtherEmptySlots = exchangeMode && otherArmy->getFreeSlot() != SlotID(); @@ -398,7 +398,7 @@ void CGarrisonSlot::update() { addUsedEvents(LCLICK | SHOW_POPUP | GESTURE | HOVER); myStack = getObj()->getStackPtr(ID); - creature = myStack ? myStack->type : nullptr; + creature = myStack ? myStack->getCreature() : nullptr; } else { @@ -426,7 +426,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa : ID(IID), owner(Owner), myStack(creature_), - creature(creature_ ? creature_->type : nullptr), + creature(creature_ ? creature_->getCreature() : nullptr), upg(Upg) { OBJECT_CONSTRUCTION; diff --git a/client/widgets/MiscWidgets.cpp b/client/widgets/MiscWidgets.cpp index bff6a0407..63da9303d 100644 --- a/client/widgets/MiscWidgets.cpp +++ b/client/widgets/MiscWidgets.cpp @@ -280,7 +280,7 @@ void CArmyTooltip::init(const InfoAboutArmy &army) continue; } - icons.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), slot.second.type->getIconIndex(), 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y)); + icons.push_back(std::make_shared(AnimationPath::builtin("CPRSMALL"), slot.second.getType()->getIconIndex(), 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y)); std::string subtitle; if(army.army.isDetailed) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index f5b2fd1c1..fb56a34e6 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -89,7 +89,7 @@ public: std::string getName() const { if(commander) - return commander->type->getNameSingularTranslated(); + return commander->getType()->getNameSingularTranslated(); else return creature->getNamePluralTranslated(); } @@ -695,7 +695,7 @@ CStackWindow::CStackWindow(const CStackInstance * stack, bool popup) info(new UnitView()) { info->stackNode = stack; - info->creature = stack->type; + info->creature = stack->getCreature(); info->creatureCount = stack->count; info->popupWindow = popup; info->owner = dynamic_cast (stack->armyObj); @@ -707,7 +707,7 @@ CStackWindow::CStackWindow(const CStackInstance * stack, std::function d info(new UnitView()) { info->stackNode = stack; - info->creature = stack->type; + info->creature = stack->getCreature(); info->creatureCount = stack->count; info->upgradeInfo = std::make_optional(UnitView::StackUpgradeInfo()); @@ -724,7 +724,7 @@ CStackWindow::CStackWindow(const CCommanderInstance * commander, bool popup) info(new UnitView()) { info->stackNode = commander; - info->creature = commander->type; + info->creature = commander->getCreature(); info->commander = commander; info->creatureCount = 1; info->popupWindow = popup; @@ -737,7 +737,7 @@ CStackWindow::CStackWindow(const CCommanderInstance * commander, std::vectorstackNode = commander; - info->creature = commander->type; + info->creature = commander->getCreature(); info->commander = commander; info->creatureCount = 1; info->levelupInfo = std::make_optional(UnitView::CommanderLevelInfo()); @@ -869,7 +869,7 @@ std::string CStackWindow::generateStackExpDescription() const CStackInstance * stack = info->stackNode; const CCreature * creature = info->creature; - int tier = stack->type->getLevel(); + int tier = stack->getType()->getLevel(); int rank = stack->getExpRank(); if (!vstd::iswithin(tier, 1, 7)) tier = 0; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 344b18deb..9350831ce 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1056,7 +1056,7 @@ CGarrisonWindow::CGarrisonWindow(const CArmedInstance * up, const CGHeroInstance if(up->Slots().size() > 0) { titleText = CGI->generaltexth->allTexts[35]; - boost::algorithm::replace_first(titleText, "%s", up->Slots().begin()->second->type->getNamePluralTranslated()); + boost::algorithm::replace_first(titleText, "%s", up->Slots().begin()->second->getType()->getNamePluralTranslated()); } else { diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 2eed440ed..a84e7ad69 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -343,11 +343,6 @@ bool CCreature::isMyUpgrade(const CCreature *anotherCre) const return vstd::contains(upgrades, anotherCre->getId()); } -bool CCreature::valid() const -{ - return this == (*VLC->creh)[idNumber]; -} - std::string CCreature::nodeName() const { return "\"" + getNamePluralTextID() + "\""; diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 0e6c8011b..979978ba0 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -166,8 +166,6 @@ public: static int estimateCreatureCount(ui32 countID); //reverse version of above function, returns middle of range bool isMyUpgrade(const CCreature *anotherCre) const; - bool valid() const; - void addBonus(int val, BonusType type); void addBonus(int val, BonusType type, BonusSubtypeID subtype); std::string nodeName() const override; diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 3a9d971dc..401611094 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -48,7 +48,7 @@ const CCreature * CCreatureSet::getCreature(const SlotID & slot) const { auto i = stacks.find(slot); if (i != stacks.end()) - return i->second->type; + return i->second->getCreature(); else return nullptr; } @@ -84,11 +84,10 @@ SlotID CCreatureSet::getSlotFor(const CreatureID & creature, ui32 slotsAmount) c SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const { - assert(c && c->valid()); + assert(c); for(const auto & elem : stacks) { - assert(elem.second->type->valid()); - if(elem.second->type == c) + if(elem.second->getType() == c) { return elem.first; //if there is already such creature we return its slot id } @@ -98,18 +97,16 @@ SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const bool CCreatureSet::hasCreatureSlots(const CCreature * c, const SlotID & exclude) const { - assert(c && c->valid()); + assert(c); for(const auto & elem : stacks) // elem is const { if(elem.first == exclude) // Check slot continue; - if(!elem.second || !elem.second->type) // Check creature + if(!elem.second || !elem.second->getType()) // Check creature continue; - assert(elem.second->type->valid()); - - if(elem.second->type == c) + if(elem.second->getType() == c) return true; } return false; @@ -117,7 +114,7 @@ bool CCreatureSet::hasCreatureSlots(const CCreature * c, const SlotID & exclude) std::vector CCreatureSet::getCreatureSlots(const CCreature * c, const SlotID & exclude, TQuantity ignoreAmount) const { - assert(c && c->valid()); + assert(c); std::vector result; for(const auto & elem : stacks) @@ -125,13 +122,12 @@ std::vector CCreatureSet::getCreatureSlots(const CCreature * c, const Sl if(elem.first == exclude) continue; - if(!elem.second || !elem.second->type || elem.second->type != c) + if(!elem.second || !elem.second->getType() || elem.second->getType() != c) continue; if(elem.second->count == ignoreAmount || elem.second->count < 1) continue; - assert(elem.second->type->valid()); result.push_back(elem.first); } return result; @@ -139,13 +135,13 @@ std::vector CCreatureSet::getCreatureSlots(const CCreature * c, const Sl bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmount) const { - assert(c && c->valid()); + assert(c); TQuantity max = 0; auto min = std::numeric_limits::max(); for(const auto & elem : stacks) { - if(!elem.second || !elem.second->type || elem.second->type != c) + if(!elem.second || !elem.second->getType() || elem.second->getType() != c) continue; const auto count = elem.second->count; @@ -153,7 +149,6 @@ bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmoun if(count == ignoreAmount || count < 1) continue; - assert(elem.second->type->valid()); if(count > max) max = count; @@ -214,7 +209,7 @@ TMapCreatureSlot CCreatureSet::getCreatureMap() const // https://www.cplusplus.com/reference/map/map/key_comp/ for(const auto & pair : stacks) { - const auto * creature = pair.second->type; + const auto * creature = pair.second->getCreature(); auto slot = pair.first; auto lb = creatureMap.lower_bound(creature); @@ -234,7 +229,7 @@ TCreatureQueue CCreatureSet::getCreatureQueue(const SlotID & exclude) const { if(pair.first == exclude) continue; - creatureQueue.push(std::make_pair(pair.second->type, pair.first)); + creatureQueue.push(std::make_pair(pair.second->getCreature(), pair.first)); } return creatureQueue; } @@ -262,10 +257,10 @@ bool CCreatureSet::mergeableStacks(std::pair & out, const SlotID //try to match creature to our preferred stack if(preferable.validSlot() && vstd::contains(stacks, preferable)) { - const CCreature *cr = stacks.find(preferable)->second->type; + const CCreature *cr = stacks.find(preferable)->second->getCreature(); for(const auto & elem : stacks) { - if(cr == elem.second->type && elem.first != preferable) + if(cr == elem.second->getType() && elem.first != preferable) { out.first = preferable; out.second = elem.first; @@ -278,7 +273,7 @@ bool CCreatureSet::mergeableStacks(std::pair & out, const SlotID { for(const auto & elem : stacks) { - if(stack.second->type == elem.second->type && stack.first != elem.first) + if(stack.second->getType() == elem.second->getType() && stack.first != elem.first) { out.first = stack.first; out.second = elem.first; @@ -328,7 +323,7 @@ void CCreatureSet::addToSlot(const SlotID & slot, CStackInstance * stack, bool a { putStack(slot, stack); } - else if(allowMerging && stack->type == getCreature(slot)) + else if(allowMerging && stack->getType() == getCreature(slot)) { joinStack(slot, stack); } @@ -514,7 +509,7 @@ void CCreatureSet::putStack(const SlotID & slot, CStackInstance * stack) void CCreatureSet::joinStack(const SlotID & slot, CStackInstance * stack) { [[maybe_unused]] const CCreature *c = getCreature(slot); - assert(c == stack->type); + assert(c == stack->getType()); assert(c); //TODO move stuff @@ -577,9 +572,9 @@ bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStac std::set cresToAdd; for(const auto & elem : cs.stacks) { - SlotID dest = getSlotFor(elem.second->type); + SlotID dest = getSlotFor(elem.second->getCreature()); if(!dest.validSlot() || hasStackAtSlot(dest)) - cresToAdd.insert(elem.second->type); + cresToAdd.insert(elem.second->getCreature()); } return cresToAdd.size() <= freeSlots; } @@ -590,13 +585,13 @@ bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStac //get types of creatures that need their own slot for(const auto & elem : cs.stacks) - if ((j = cres.getSlotFor(elem.second->type)).validSlot()) - cres.addToSlot(j, elem.second->type->getId(), 1, true); //merge if possible + if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot()) + cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible //cres.addToSlot(elem.first, elem.second->type->getId(), 1, true); for(const auto & elem : stacks) { - if ((j = cres.getSlotFor(elem.second->type)).validSlot()) - cres.addToSlot(j, elem.second->type->getId(), 1, true); //merge if possible + if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot()) + cres.addToSlot(j, elem.second->getId(), 1, true); //merge if possible else return false; //no place found } @@ -693,7 +688,7 @@ void CStackInstance::init() { experience = 0; count = 0; - type = nullptr; + setType(nullptr); _armyObj = nullptr; setNodeType(STACK_INSTANCE); } @@ -707,7 +702,7 @@ int CStackInstance::getExpRank() const { if (!VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) return 0; - int tier = type->getLevel(); + int tier = getType()->getLevel(); if (vstd::iswithin(tier, 1, 7)) { for(int i = static_cast(VLC->creh->expRanks[tier].size()) - 2; i > -1; --i) //sic! @@ -730,12 +725,12 @@ int CStackInstance::getExpRank() const int CStackInstance::getLevel() const { - return std::max(1, static_cast(type->getLevel())); + return std::max(1, static_cast(getType()->getLevel())); } void CStackInstance::giveStackExp(TExpType exp) { - int level = type->getLevel(); + int level = getType()->getLevel(); if (!vstd::iswithin(level, 1, 7)) level = 0; @@ -756,17 +751,17 @@ void CStackInstance::setType(const CreatureID & creID) void CStackInstance::setType(const CCreature *c) { - if(type) + if(getCreature()) { - detachFromSource(*type); - if (type->isMyUpgrade(c) && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) + detachFromSource(*getCreature()); + if (getCreature()->isMyUpgrade(c) && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) experience = static_cast(experience * VLC->creh->expAfterUpgrade / 100.0); } CStackBasicDescriptor::setType(c); - if(type) - attachToSource(*type); + if(getCreature()) + attachToSource(*getCreature()); } std::string CStackInstance::bonusToString(const std::shared_ptr& bonus, bool description) const { @@ -808,7 +803,7 @@ bool CStackInstance::valid(bool allowUnrandomized) const { if(!randomStack) { - return (type && type == type->getId().toEntity(VLC)); + return (getType() && getType() == getId().toEntity(VLC)); } else return allowUnrandomized; @@ -818,8 +813,8 @@ std::string CStackInstance::nodeName() const { std::ostringstream oss; oss << "Stack of " << count << " of "; - if(type) - oss << type->getNamePluralTextID(); + if(getType()) + oss << getType()->getNamePluralTextID(); else oss << "[UNDEFINED TYPE]"; @@ -841,21 +836,21 @@ void CStackInstance::deserializationFix() CreatureID CStackInstance::getCreatureID() const { - if(type) - return type->getId(); + if(getType()) + return getType()->getId(); else return CreatureID::NONE; } std::string CStackInstance::getName() const { - return (count > 1) ? type->getNamePluralTranslated() : type->getNameSingularTranslated(); + return (count > 1) ? getType()->getNamePluralTranslated() : getType()->getNameSingularTranslated(); } ui64 CStackInstance::getPower() const { - assert(type); - return static_cast(type->getAIValue()) * count; + assert(getType()); + return static_cast(getType()->getAIValue()) * count; } ArtBearer::ArtBearer CStackInstance::bearerType() const @@ -899,7 +894,7 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler) else { //type set by CStackBasicDescriptor::serializeJson - if(type == nullptr) + if(getType() == nullptr) { uint8_t level = 0; uint8_t upgrade = 0; @@ -914,8 +909,8 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler) FactionID CStackInstance::getFactionID() const { - if(type) - return type->getFactionID(); + if(getType()) + return getType()->getFactionID(); return FactionID::NEUTRAL; } @@ -943,7 +938,7 @@ void CCommanderInstance::init() experience = 0; level = 1; count = 1; - type = nullptr; + setType(nullptr); _armyObj = nullptr; setNodeType (CBonusSystemNode::COMMANDER); secondarySkills.resize (ECommander::SPELL_POWER + 1); @@ -998,24 +993,29 @@ bool CCommanderInstance::gainsLevel() const CStackBasicDescriptor::CStackBasicDescriptor() = default; CStackBasicDescriptor::CStackBasicDescriptor(const CreatureID & id, TQuantity Count): - type(id.toCreature()), + typeID(id), count(Count) { } CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count) - : type(c), count(Count) + : typeID(c ? c->getId() : CreatureID()), count(Count) { } +const CCreature * CStackBasicDescriptor::getCreature() const +{ + return typeID.toCreature(); +} + const Creature * CStackBasicDescriptor::getType() const { - return type; + return typeID.toEntity(VLC); } CreatureID CStackBasicDescriptor::getId() const { - return type->getId(); + return typeID; } TQuantity CStackBasicDescriptor::getCount() const @@ -1023,18 +1023,14 @@ TQuantity CStackBasicDescriptor::getCount() const return count; } - void CStackBasicDescriptor::setType(const CCreature * c) { - type = c; + typeID = c ? c->getId() : CreatureID(); } bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r) { - return (!l.type && !r.type) - || (l.type && r.type - && l.type->getId() == r.type->getId() - && l.count == r.count); + return l.typeID == r.typeID && l.count == r.count; } void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler) @@ -1043,9 +1039,9 @@ void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler) if(handler.saving) { - if(type) + if(typeID.hasValue()) { - std::string typeName = type->getJsonKey(); + std::string typeName = typeID.toEntity(VLC)->getJsonKey(); handler.serializeString("type", typeName); } } diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index 75b377a11..e40c800a1 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -31,8 +31,8 @@ class JsonSerializeFormat; class DLL_LINKAGE CStackBasicDescriptor { + CreatureID typeID; public: - const CCreature *type = nullptr; TQuantity count = -1; //exact quantity or quantity ID from CCreature::getQuantityID when getting info about enemy army CStackBasicDescriptor(); @@ -41,29 +41,20 @@ public: virtual ~CStackBasicDescriptor() = default; const Creature * getType() const; + const CCreature * getCreature() const; CreatureID getId() const; TQuantity getCount() const; virtual void setType(const CCreature * c); - + friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r); template void serialize(Handler &h) { - if(h.saving) - { - auto idNumber = type ? type->getId() : CreatureID(CreatureID::NONE); - h & idNumber; - } - else - { - CreatureID idNumber; - h & idNumber; - if(idNumber != CreatureID::NONE) - setType(dynamic_cast(VLC->creatures()->getById(idNumber))); - else - type = nullptr; - } + h & typeID; + if(!h.saving) + setType(typeID.toCreature()); + h & count; } diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index e5cc2370f..94e0cea5a 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -345,10 +345,10 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero for(auto & elem : info.army) { - if(static_cast(elem.second.type->getAIValue()) > maxAIValue) + if(static_cast(elem.second.getCreature()->getAIValue()) > maxAIValue) { - maxAIValue = elem.second.type->getAIValue(); - mostStrong = elem.second.type; + maxAIValue = elem.second.getCreature()->getAIValue(); + mostStrong = elem.second.getCreature(); } } @@ -357,7 +357,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero else for(auto & elem : info.army) { - elem.second.type = mostStrong; + elem.second.setType(mostStrong); } }; @@ -390,7 +390,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero if(nullptr != mostStrong) //possible, faction may have no creatures at all for(auto & elem : info.army) - elem.second.type = mostStrong; + elem.second.setType(mostStrong); }; diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 348974985..5095ee592 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -28,7 +28,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle CBonusSystemNode(STACK_BATTLE), base(Base), ID(I), - type(Base->type), + type(Base->getCreature()), baseAmount(Base->count), owner(O), slot(S), @@ -48,7 +48,7 @@ CStack::CStack(): CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, BattleSide Side, const SlotID & S): CBonusSystemNode(STACK_BATTLE), ID(I), - type(stack->type), + type(stack->getCreature()), baseAmount(stack->count), owner(O), slot(S), diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 0fa15f9d4..d4e3be118 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -75,7 +75,7 @@ static const CCreature * retrieveCreature(const CBonusSystemNode *node) default: const CStackInstance * csi = retrieveStackInstance(node); if(csi) - return csi->type; + return csi->getCreature(); return nullptr; } } diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index ad78c00b3..91a58b796 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1091,7 +1091,7 @@ void CGameState::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, Upg UpgradeInfo CGameState::fillUpgradeInfo(const CStackInstance &stack) const { UpgradeInfo ret; - const CCreature *base = stack.type; + const CCreature *base = stack.getCreature(); if (stack.armyObj->ID == Obj::HERO) { @@ -1571,7 +1571,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level) { for(const auto & it : elem->Slots()) { - CreatureID toCmp = it.second->type->getId(); //ID of creature we should compare with the best one + CreatureID toCmp = it.second->getId(); //ID of creature we should compare with the best one if(bestCre == CreatureID::NONE || bestCre.toEntity(VLC)->getAIValue() < toCmp.toEntity(VLC)->getAIValue()) { bestCre = toCmp; diff --git a/lib/gameState/InfoAboutArmy.cpp b/lib/gameState/InfoAboutArmy.cpp index 54e8528a8..8e2a4cf0c 100644 --- a/lib/gameState/InfoAboutArmy.cpp +++ b/lib/gameState/InfoAboutArmy.cpp @@ -26,7 +26,7 @@ ArmyDescriptor::ArmyDescriptor(const CArmedInstance *army, bool detailed) if(detailed) (*this)[elem.first] = *elem.second; else - (*this)[elem.first] = CStackBasicDescriptor(elem.second->type, (int)elem.second->getQuantityID()); + (*this)[elem.first] = CStackBasicDescriptor(elem.second->getCreature(), (int)elem.second->getQuantityID()); } } @@ -42,12 +42,12 @@ int ArmyDescriptor::getStrength() const if(isDetailed) { for(const auto & elem : *this) - ret += elem.second.type->getAIValue() * elem.second.count; + ret += elem.second.getType()->getAIValue() * elem.second.count; } else { for(const auto & elem : *this) - ret += elem.second.type->getAIValue() * CCreature::estimateCreatureCount(elem.second.count); + ret += elem.second.getType()->getAIValue() * CCreature::estimateCreatureCount(elem.second.count); } return static_cast(ret); } diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index bfc65fa9d..7b1f9dabd 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -485,13 +485,13 @@ VCMI_LIB_NAMESPACE_BEGIN else logMod->warn("Failed to select suitable random creature!"); - stack.type = pickedCreature.toCreature(); + stack.setType(pickedCreature.toCreature()); stack.count = loadValue(value, rng, variables); - if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty()) + if (!value["upgradeChance"].isNull() && !stack.getCreature()->upgrades.empty()) { if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade { - stack.type = RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)->toCreature(); + stack.setType(RandomGeneratorUtil::nextItem(stack.getCreature()->upgrades, rng)->toCreature()); } } return stack; diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index 24fb98a72..5254bc70a 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -102,7 +102,7 @@ void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::R JsonRandom::Variables emptyVariables; for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables)) { - dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->getId(), stack.count)); + dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.getId(), stack.count)); } } else //default condition - creatures are of level 5 or higher diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 858281741..716390e6e 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -94,7 +94,7 @@ void CBank::setConfig(const BankConfig & config) clearSlots(); // remove all stacks, if any for(const auto & stack : config.guards) - setCreature (SlotID(stacksCount()), stack.type->getId(), stack.count); + setCreature (SlotID(stacksCount()), stack.getId(), stack.count); daycounter = 1; //yes, 1 since "today" daycounter won't be incremented } @@ -190,8 +190,8 @@ void CBank::doVisit(const CGHeroInstance * hero) const iw.text.appendLocalString(EMetaText::ADVOB_TXT, 34); const auto * strongest = boost::range::max_element(bankConfig->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b) { - return a.type->getFightValue() < b.type->getFightValue(); - })->type; + return a.getType()->getFightValue() < b.getType()->getFightValue(); + })->getType(); iw.text.replaceNamePlural(strongest->getId()); iw.text.replaceRawString(loot.buildList()); @@ -244,7 +244,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const CCreatureSet ourArmy; for(const auto & slot : bankConfig->creatures) { - ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->getId()), slot.type->getId(), slot.count); + ourArmy.addToSlot(ourArmy.getSlotFor(slot.getId()), slot.getId(), slot.count); } for(const auto & elem : ourArmy.Slots()) diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index 2d595bd5f..b1589c8e3 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -359,7 +359,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const for(const auto & elem : h->Slots()) { bool isOurUpgrade = vstd::contains(getCreature()->upgrades, elem.second->getCreatureID()); - bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreatureID()); + bool isOurDowngrade = vstd::contains(elem.second->getCreature()->upgrades, getCreatureID()); if(isOurUpgrade || isOurDowngrade) count += elem.second->count; @@ -480,7 +480,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const if (containsUpgradedStack()) //upgrade { SlotID slotID = SlotID(static_cast(std::floor(static_cast(stacks.size()) / 2.0f))); - const auto & upgrades = getStack(slotID).type->upgrades; + const auto & upgrades = getStack(slotID).getCreature()->upgrades; if(!upgrades.empty()) { auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); @@ -521,7 +521,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult & const CCreature * cre = getCreature(); for(i = stacks.begin(); i != stacks.end(); i++) { - if(cre->isMyUpgrade(i->second->type)) + if(cre->isMyUpgrade(i->second->getCreature())) { cb->changeStackType(StackLocation(this, i->first), cre); //un-upgrade creatures } @@ -536,7 +536,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult & // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) i = stacks.end(); i--; - SlotID slot = getSlotFor(i->second->type); + SlotID slot = getSlotFor(i->second->getCreature()); if(slot == i->first) //no reason to move stack to its own slot break; else diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 18ffe6588..539da4345 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1806,14 +1806,14 @@ bool CGHeroInstance::isMissionCritical() const void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const { - TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.type->getId()))); + TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.getId()))); for(const auto & it : *lista) { auto nid = CreatureID(it->additionalInfo[0]); - if (nid != stack.type->getId()) //in very specific case the upgrade is available by default (?) + if (nid != stack.getId()) //in very specific case the upgrade is available by default (?) { info.newID.push_back(nid); - info.cost.push_back(nid.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost()); + info.cost.push_back(nid.toCreature()->getFullRecruitCost() - stack.getType()->getFullRecruitCost()); } } } diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 291a289f9..9c35bbe45 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1227,14 +1227,14 @@ void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &s { for(const CGTownInstance::TCreaturesSet::value_type & dwelling : creatures) { - if (vstd::contains(dwelling.second, stack.type->getId())) //Dwelling with our creature + if (vstd::contains(dwelling.second, stack.getId())) //Dwelling with our creature { for(const auto & upgrID : dwelling.second) { - if(vstd::contains(stack.type->upgrades, upgrID)) //possible upgrade + if(vstd::contains(stack.getCreature()->upgrades, upgrID)) //possible upgrade { info.newID.push_back(upgrID); - info.cost.push_back(upgrID.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost()); + info.cost.push_back(upgrID.toCreature()->getFullRecruitCost() - stack.getType()->getFullRecruitCost()); } } } diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 1cc466324..9e2e83c47 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -110,7 +110,7 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army) { for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it) { - if(it->second->type == cre->type) + if(it->second->getType() == cre->getType()) { count += it->second->count; slotsCount++; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 2809db7b1..2161f00f9 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1152,7 +1152,7 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const if(drown) { cb->changeStackCount(StackLocation(h, i->first), -drown); - xp += drown * i->second->type->getMaxHealth(); + xp += drown * i->second->getType()->getMaxHealth(); } } @@ -1318,7 +1318,7 @@ void HillFort::onHeroVisit(const CGHeroInstance * h) const void HillFort::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const { - int32_t level = stack.type->getLevel(); + int32_t level = stack.getType()->getLevel(); int32_t index = std::clamp(level - 1, 0, upgradeCostPercentage.size() - 1); int costModifier = upgradeCostPercentage[index]; @@ -1326,10 +1326,10 @@ void HillFort::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) if (costModifier < 0) return; // upgrade not allowed - for(const auto & nid : stack.type->upgrades) + for(const auto & nid : stack.getCreature()->upgrades) { info.newID.push_back(nid); - info.cost.push_back((nid.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost()) * costModifier / 100); + info.cost.push_back((nid.toCreature()->getFullRecruitCost() - stack.getType()->getFullRecruitCost()) * costModifier / 100); } } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 831e0bb49..5340538f7 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2116,7 +2116,7 @@ EQuestMission CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & positi guard->quest->mission.creatures.resize(typeNumber); for(size_t hh = 0; hh < typeNumber; ++hh) { - guard->quest->mission.creatures[hh].type = reader->readCreature().toCreature(); + guard->quest->mission.creatures[hh].setType(reader->readCreature().toCreature()); guard->quest->mission.creatures[hh].count = reader->readUInt16(); } break; diff --git a/lib/rewardable/Interface.cpp b/lib/rewardable/Interface.cpp index 212e1f17e..5544a0ba2 100644 --- a/lib/rewardable/Interface.cpp +++ b/lib/rewardable/Interface.cpp @@ -185,7 +185,7 @@ void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo for(const auto & change : info.reward.creaturesChange) { - if (heroStack->type->getId() == change.first) + if (heroStack->getId() == change.first) { StackLocation location(hero, slot.first); cb->changeStackType(location, change.second.toCreature()); @@ -199,7 +199,7 @@ void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo { CCreatureSet creatures; for(const auto & crea : info.reward.creatures) - creatures.addToSlot(creatures.getFreeSlot(), new CStackInstance(crea.type, crea.count)); + creatures.addToSlot(creatures.getFreeSlot(), new CStackInstance(crea.getCreature(), crea.count)); if(auto * army = dynamic_cast(this)) //TODO: to fix that, CArmedInstance must be split on map instance part and interface part cb->giveCreatures(army, hero, creatures, false); diff --git a/lib/rewardable/Limiter.cpp b/lib/rewardable/Limiter.cpp index 5e0d4bd80..58b66c66c 100644 --- a/lib/rewardable/Limiter.cpp +++ b/lib/rewardable/Limiter.cpp @@ -84,7 +84,7 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const for(const auto & slot : hero->Slots()) { const CStackInstance * heroStack = slot.second; - if (heroStack->type == reqStack.type) + if (heroStack->getType() == reqStack.getType()) count += heroStack->count; } if (count < reqStack.count) //not enough creatures of this kind @@ -233,7 +233,7 @@ void Rewardable::Limiter::loadComponents(std::vector & comps, comps.emplace_back(ComponentType::SPELL, entry); for(const auto & entry : creatures) - comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count); + comps.emplace_back(ComponentType::CREATURE, entry.getId(), entry.count); for(const auto & entry : players) comps.emplace_back(ComponentType::FLAG, entry); diff --git a/lib/rewardable/Reward.cpp b/lib/rewardable/Reward.cpp index c52501a6b..22240a737 100644 --- a/lib/rewardable/Reward.cpp +++ b/lib/rewardable/Reward.cpp @@ -121,7 +121,7 @@ void Rewardable::Reward::loadComponents(std::vector & comps, const CG } for(const auto & entry : creatures) - comps.emplace_back(ComponentType::CREATURE, entry.type->getId(), entry.count); + comps.emplace_back(ComponentType::CREATURE, entry.getId(), entry.count); for (size_t i=0; igetId(), stack.count); + replaceName(stack.getId(), stack.count); } VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/inspector/questwidget.cpp b/mapeditor/inspector/questwidget.cpp index 8edd018de..395d729e6 100644 --- a/mapeditor/inspector/questwidget.cpp +++ b/mapeditor/inspector/questwidget.cpp @@ -169,7 +169,7 @@ void QuestWidget::obtainData() } for(auto & i : quest.mission.creatures) { - int index = i.type->getIndex(); + int index = i.getType()->getIndex(); ui->lCreatureId->setCurrentIndex(index); ui->lCreatureAmount->setValue(i.count); onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index b4710ff88..614846d2b 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -459,7 +459,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index) } for(auto & i : vinfo.reward.creatures) { - int index = i.type->getIndex(); + int index = i.getType()->getIndex(); ui->rCreatureId->setCurrentIndex(index); ui->rCreatureAmount->setValue(i.count); onCreatureAdd(ui->rCreatures, ui->rCreatureId, ui->rCreatureAmount); @@ -527,7 +527,7 @@ void RewardsWidget::loadCurrentVisitInfo(int index) } for(auto & i : vinfo.limiter.creatures) { - int index = i.type->getIndex(); + int index = i.getType()->getIndex(); ui->lCreatureId->setCurrentIndex(index); ui->lCreatureAmount->setValue(i.count); onCreatureAdd(ui->lCreatures, ui->lCreatureId, ui->lCreatureAmount); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 81cf167ee..1657cc56c 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1139,7 +1139,7 @@ void CGameHandler::giveCreatures(const CArmedInstance *obj, const CGHeroInstance //first we move creatures to give to make them army of object-source for (auto & elem : creatures.Slots()) { - addToSlot(StackLocation(obj, obj->getSlotFor(elem.second->type)), elem.second->type, elem.second->count); + addToSlot(StackLocation(obj, obj->getSlotFor(elem.second->getCreature())), elem.second->getCreature(), elem.second->count); } tryJoiningArmy(obj, h, remove, true); @@ -1160,7 +1160,7 @@ void CGameHandler::takeCreatures(ObjectInstanceID objid, const std::vectorSlots().begin(); i != obj->Slots().end(); i++) { - if (i->second->type == sbd.type) + if (i->second->getType() == sbd.getType()) { TQuantity take = std::min(sbd.count - collected, i->second->count); //collect as much cres as we can changeStackCount(StackLocation(obj, i->first), -take, false); @@ -2455,7 +2455,7 @@ void CGameHandler::moveArmy(const CArmedInstance *src, const CArmedInstance *dst auto i = src->Slots().begin(); //iterator to stack to move StackLocation sl(src, i->first); //location of stack to move - SlotID pos = dst->getSlotFor(i->second->type); + SlotID pos = dst->getSlotFor(i->second->getCreature()); if (!pos.validSlot()) { //try to merge two other stacks to make place @@ -3137,7 +3137,7 @@ bool CGameHandler::sellCreatures(ui32 count, const IMarket *market, const CGHero int b1; //base quantities for trade int b2; - market->getOffer(s.type->getId(), resourceID, b1, b2, EMarketMode::CREATURE_RESOURCE); + market->getOffer(s.getId(), resourceID, b1, b2, EMarketMode::CREATURE_RESOURCE); int units = count / b1; //how many base quantities we trade if (count%b1) //all offered units of resource should be used, if not -> somewhere in calculations must be an error @@ -3648,7 +3648,7 @@ bool CGameHandler::sacrificeCreatures(const IMarket * market, const CGHeroInstan COMPLAIN_RET("Cannot sacrifice last creature!"); } - int crid = hero->getStack(slot[i]).type->getId(); + int crid = hero->getStack(slot[i]).getId(); changeStackCount(StackLocation(hero, slot[i]), -(TQuantity)count[i]); @@ -3801,7 +3801,7 @@ void CGameHandler::tryJoiningArmy(const CArmedInstance *src, const CArmedInstanc { for (auto i = src->stacks.begin(); i != src->stacks.end(); i++)//while there are unmoved creatures { - SlotID pos = dst->getSlotFor(i->second->type); + SlotID pos = dst->getSlotFor(i->second->getCreature()); if (pos.validSlot()) { moveStack(StackLocation(src, i->first), StackLocation(dst, pos)); diff --git a/server/battles/BattleResultProcessor.cpp b/server/battles/BattleResultProcessor.cpp index ab4d07dbc..411343e16 100644 --- a/server/battles/BattleResultProcessor.cpp +++ b/server/battles/BattleResultProcessor.cpp @@ -556,12 +556,12 @@ void BattleResultProcessor::battleAfterLevelUp(const BattleID & battleID, const const CStackBasicDescriptor raisedStack = finishingBattle->winnerHero ? finishingBattle->winnerHero->calculateNecromancy(result) : CStackBasicDescriptor(); // Give raised units to winner and show dialog, if any were raised, // units will be given after casualties are taken - const SlotID necroSlot = raisedStack.type ? finishingBattle->winnerHero->getSlotFor(raisedStack.type) : SlotID(); + const SlotID necroSlot = raisedStack.getCreature() ? finishingBattle->winnerHero->getSlotFor(raisedStack.getCreature()) : SlotID(); if (necroSlot != SlotID() && !finishingBattle->isDraw()) { finishingBattle->winnerHero->showNecromancyDialog(raisedStack, gameHandler->getRandomGenerator()); - gameHandler->addToSlot(StackLocation(finishingBattle->winnerHero, necroSlot), raisedStack.type, raisedStack.count); + gameHandler->addToSlot(StackLocation(finishingBattle->winnerHero, necroSlot), raisedStack.getCreature(), raisedStack.count); } BattleResultsApplied resultsApplied; diff --git a/server/processors/NewTurnProcessor.cpp b/server/processors/NewTurnProcessor.cpp index 73bbf9bf4..27ae99bc4 100644 --- a/server/processors/NewTurnProcessor.cpp +++ b/server/processors/NewTurnProcessor.cpp @@ -345,7 +345,7 @@ void NewTurnProcessor::updateNeutralTownGarrison(const CGTownInstance * t, int c // Check if town garrison already has unit of specified tier for(const auto & slot : t->Slots()) { - const auto * creature = slot.second->type; + const auto * creature = slot.second->getCreature(); if (creature->getFactionID() != t->getFactionID()) continue; From c1e125b1867c1598f3074e6318ddaed2788d383e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Oct 2024 16:52:58 +0000 Subject: [PATCH 481/726] Remove CStack::type pointer to VLC entity --- lib/CStack.cpp | 16 ++++++++-------- lib/CStack.h | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 5095ee592..1819e57df 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -28,7 +28,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle CBonusSystemNode(STACK_BATTLE), base(Base), ID(I), - type(Base->getCreature()), + typeID(Base->getId()), baseAmount(Base->count), owner(O), slot(S), @@ -48,7 +48,7 @@ CStack::CStack(): CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, BattleSide Side, const SlotID & S): CBonusSystemNode(STACK_BATTLE), ID(I), - type(stack->getCreature()), + typeID(stack->getId()), baseAmount(stack->count), owner(O), slot(S), @@ -60,7 +60,7 @@ CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I void CStack::localInit(BattleInfo * battleInfo) { battle = battleInfo; - assert(type); + assert(typeID.hasValue()); exportBonuses(); if(base) //stack originating from "real" stack in garrison -> attach to it @@ -72,7 +72,7 @@ void CStack::localInit(BattleInfo * battleInfo) CArmedInstance * army = battle->battleGetArmyObject(side); assert(army); attachTo(*army); - attachToSource(*type); + attachToSource(*typeID.toCreature()); } nativeTerrain = getNativeTerrain(); //save nativeTerrain in the variable on the battle start to avoid dead lock CUnitState::localInit(this); //it causes execution of the CStack::isOnNativeTerrain where nativeTerrain will be considered @@ -164,8 +164,8 @@ std::string CStack::nodeName() const std::ostringstream oss; oss << owner.toString(); oss << " battle stack [" << ID << "]: " << getCount() << " of "; - if(type) - oss << type->getNamePluralTextID(); + if(typeID.hasValue()) + oss << typeID.toEntity(VLC)->getNamePluralTextID(); else oss << "[UNDEFINED TYPE]"; @@ -304,7 +304,7 @@ bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle:: std::string CStack::getName() const { - return (getCount() == 1) ? type->getNameSingularTranslated() : type->getNamePluralTranslated(); //War machines can't use base + return (getCount() == 1) ? typeID.toEntity(VLC)->getNameSingularTranslated() : typeID.toEntity(VLC)->getNamePluralTranslated(); //War machines can't use base } bool CStack::canBeHealed() const @@ -326,7 +326,7 @@ bool CStack::isOnTerrain(TerrainId terrain) const const CCreature * CStack::unitType() const { - return type; + return typeID.toCreature(); } int32_t CStack::unitBaseAmount() const diff --git a/lib/CStack.h b/lib/CStack.h index 456b44bc2..d339eba40 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -27,7 +27,7 @@ class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, p { private: ui32 ID = -1; //unique ID of stack - const CCreature * type = nullptr; + CreatureID typeID; TerrainId nativeTerrain; //tmp variable to save native terrain value on battle init ui32 baseAmount = -1; @@ -98,7 +98,7 @@ public: //stackState is not serialized here assert(isIndependentNode()); h & static_cast(*this); - h & type; + h & typeID; h & ID; h & baseAmount; h & owner; @@ -133,7 +133,7 @@ public: else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot)) { base = nullptr; - logGlobal->warn("%s doesn't have a base stack!", type->getNameSingularTranslated()); + logGlobal->warn("%s doesn't have a base stack!", typeID.toEntity(VLC)->getNameSingularTranslated()); } else { From 01d787fb5acb86b7411d5ef24985da97178dd85e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Oct 2024 18:19:58 +0000 Subject: [PATCH 482/726] Removed remaining cases of serialization of VLC entities --- lib/IGameCallback.cpp | 8 ++++---- lib/IGameCallback.h | 2 +- lib/bonuses/Limiters.cpp | 10 +++++----- lib/bonuses/Limiters.h | 4 ++-- lib/mapObjects/CGHeroInstance.h | 7 +++++-- lib/mapObjects/CGMarket.cpp | 7 ++----- lib/mapObjects/CGMarket.h | 2 +- lib/mapObjects/CGTownInstance.cpp | 8 +++----- lib/mapObjects/CGTownInstance.h | 16 ++++------------ lib/mapping/CMap.h | 2 +- lib/networkPacks/PacksForClient.h | 2 +- server/CGameHandler.cpp | 8 ++++---- 12 files changed, 33 insertions(+), 43 deletions(-) diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 84b736d78..6c7bf82c4 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -149,14 +149,14 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set & tiles, std: } } -void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector & out, vstd::RNG & rand) +void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector & out, vstd::RNG & rand) { for (int j = 0; j < 3 ; j++) - out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_TREASURE).toArtifact()); + out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_TREASURE)); for (int j = 0; j < 3 ; j++) - out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MINOR).toArtifact()); + out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MINOR)); - out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MAJOR).toArtifact()); + out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MAJOR)); } void CPrivilegedInfoCallback::getAllowedSpells(std::vector & out, std::optional level) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 1de95812d..c93f31d68 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -75,7 +75,7 @@ public: void getAllTiles(std::unordered_set &tiles, std::optional player, int level, std::function filter) const; //gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant - void pickAllowedArtsSet(std::vector & out, vstd::RNG & rand); + void pickAllowedArtsSet(std::vector & out, vstd::RNG & rand); void getAllowedSpells(std::vector &out, std::optional level = std::nullopt); void saveCommonState(CSaveFile &out) const; //stores GS and VLC diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index d4e3be118..e5aa2fae3 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -103,25 +103,25 @@ ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &co if(!c) return ILimiter::EDecision::DISCARD; - auto accept = c->getId() == creature->getId() || (includeUpgrades && creature->isMyUpgrade(c)); + auto accept = c->getId() == creatureID || (includeUpgrades && creatureID.toCreature()->isMyUpgrade(c)); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; //drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade) } CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades) - : creature(&creature_), includeUpgrades(IncludeUpgrades) + : creatureID(creature_.getId()), includeUpgrades(IncludeUpgrades) { } void CCreatureTypeLimiter::setCreature(const CreatureID & id) { - creature = id.toCreature(); + creatureID = id; } std::string CCreatureTypeLimiter::toString() const { boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)"); - fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false"); + fmt % creatureID.toEntity(VLC)->getJsonKey() % (includeUpgrades ? "true" : "false"); return fmt.str(); } @@ -130,7 +130,7 @@ JsonNode CCreatureTypeLimiter::toJsonNode() const JsonNode root; root["type"].String() = "CREATURE_TYPE_LIMITER"; - root["parameters"].Vector().emplace_back(creature->getJsonKey()); + root["parameters"].Vector().emplace_back(creatureID.toEntity(VLC)->getJsonKey()); root["parameters"].Vector().emplace_back(includeUpgrades); return root; diff --git a/lib/bonuses/Limiters.h b/lib/bonuses/Limiters.h index f485559b7..7a1813bab 100644 --- a/lib/bonuses/Limiters.h +++ b/lib/bonuses/Limiters.h @@ -94,7 +94,7 @@ public: class DLL_LINKAGE CCreatureTypeLimiter : public ILimiter //affect only stacks of given creature (and optionally it's upgrades) { public: - const CCreature * creature = nullptr; + CreatureID creatureID; bool includeUpgrades = false; CCreatureTypeLimiter() = default; @@ -108,7 +108,7 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - h & creature; + h & creatureID; h & includeUpgrades; } }; diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 0b8ddd257..53a59ec13 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -359,8 +359,11 @@ public: h & boat; if (h.version < Handler::Version::REMOVE_TOWN_PTR) { - CHero * type = nullptr; - h & type; + HeroTypeID type; + bool isNull = false; + h & isNull; + if(!isNull) + h & type; } h & commander; h & visitedObjects; diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index b2a44455c..5971d1be6 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -68,11 +68,8 @@ std::vector CGBlackMarket::availableItemsIds(EMarketMode mode) con case EMarketMode::RESOURCE_ARTIFACT: { std::vector ret; - for(const CArtifact *a : artifacts) - if(a) - ret.push_back(a->getId()); - else - ret.push_back(ArtifactID{}); + for(const auto & a : artifacts) + ret.push_back(a); return ret; } default: diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index e1bc5b7df..f45846651 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -63,7 +63,7 @@ class DLL_LINKAGE CGBlackMarket : public CGMarket public: using CGMarket::CGMarket; - std::vector artifacts; //available artifacts + std::vector artifacts; //available artifacts void newTurn(vstd::RNG & rand) const override; //reset artifacts for black market every month std::vector availableItemsIds(EMarketMode mode) const override; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 9c35bbe45..f314499db 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -670,11 +670,9 @@ std::vector CGTownInstance::availableItemsIds(EMarketMode mode) co if(mode == EMarketMode::RESOURCE_ARTIFACT) { std::vector ret; - for(const CArtifact *a : cb->gameState()->map->townMerchantArtifacts) - if(a) - ret.push_back(a->getId()); - else - ret.push_back(ArtifactID{}); + for(const ArtifactID a : cb->gameState()->map->townMerchantArtifacts) + ret.push_back(a); + return ret; } else if ( mode == EMarketMode::RESOURCE_SKILL ) diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 636f2bc5c..64eeaf9e2 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -114,19 +114,11 @@ public: if (h.version < Handler::Version::REMOVE_TOWN_PTR) { - CTown * town = nullptr; - - if (h.saving) - { - CFaction * faction = town ? town->faction : nullptr; + FactionID faction; + bool isNull = false; + h & isNull; + if (!isNull) h & faction; - } - else - { - CFaction * faction = nullptr; - h & faction; - town = faction ? faction->town : nullptr; - } } h & townAndVis; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 847f81b06..7d48dc931 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -180,7 +180,7 @@ public: ui8 obeliskCount = 0; //how many obelisks are on map std::map obelisksVisited; //map: team_id => how many obelisks has been visited - std::vector townMerchantArtifacts; + std::vector townMerchantArtifacts; std::vector townUniversitySkills; void overrideGameSettings(const JsonNode & input); diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 0413113dc..9a0b20f45 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -819,7 +819,7 @@ struct DLL_LINKAGE SetAvailableArtifacts : public CPackForClient //two variants: id < 0: set artifact pool for Artifact Merchants in towns; id >= 0: set pool for adv. map Black Market (id is the id of Black Market instance then) ObjectInstanceID id; - std::vector arts; + std::vector arts; void visitTyped(ICPackVisitor & visitor) override; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1657cc56c..56a7b626c 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2912,7 +2912,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a AssembledArtifact aa; aa.al = dstLoc; - aa.artId = assembleTo; + aa.artId = assembleTo->getId(); sendAndApply(aa); } else @@ -3035,11 +3035,11 @@ bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, GameRe COMPLAIN_RET("Wrong marktet..."); bool found = false; - for (const CArtifact *&art : saa.arts) + for (ArtifactID & art : saa.arts) { - if (art && art->getId() == aid) + if (art == aid) { - art = nullptr; + art = ArtifactID(); found = true; break; } From c4481f3797e83b622cdf9f46dc83e8cdde3ab95a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Oct 2024 18:23:45 +0000 Subject: [PATCH 483/726] Remove logic for serialization of VLC entities --- lib/serializer/BinaryDeserializer.h | 19 ------------------- lib/serializer/BinarySerializer.h | 13 ------------- 2 files changed, 32 deletions(-) diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index edcbb634f..136d95db1 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -235,25 +235,6 @@ public: return; } - loadPointerImpl(data); - } - - template < typename T, typename std::enable_if_t < std::is_base_of_v>, int > = 0 > - void loadPointerImpl(T &data) - { - using DataType = std::remove_pointer_t; - - typename DataType::IdentifierType index; - load(index); - - auto * constEntity = index.toEntity(VLC); - auto * constData = dynamic_cast(constEntity); - data = const_cast(constData); - } - - template < typename T, typename std::enable_if_t < !std::is_base_of_v>, int > = 0 > - void loadPointerImpl(T &data) - { if(reader->smartVectorMembersSerialization) { typedef typename std::remove_const_t> TObjectType; //eg: const CGHeroInstance * => CGHeroInstance diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 71d985de9..851d53836 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -186,19 +186,6 @@ public: if(data == nullptr) return; - savePointerImpl(data); - } - - template < typename T, typename std::enable_if_t < std::is_base_of_v>, int > = 0 > - void savePointerImpl(const T &data) - { - auto index = data->getId(); - save(index); - } - - template < typename T, typename std::enable_if_t < !std::is_base_of_v>, int > = 0 > - void savePointerImpl(const T &data) - { typedef typename std::remove_const_t> TObjectType; if(writer->smartVectorMembersSerialization) From a39469d16526a0d5893a050d83437e95d5f70d56 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 14 Oct 2024 12:54:17 +0000 Subject: [PATCH 484/726] Add save compatibility --- lib/CArtifactInstance.h | 12 ++++++- lib/mapping/CMapDefines.h | 43 +++++++++++++++++++++++--- lib/serializer/ESerializationVersion.h | 3 +- server/CGameHandler.cpp | 2 +- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/CArtifactInstance.h b/lib/CArtifactInstance.h index 4e244764c..1cb5f6508 100644 --- a/lib/CArtifactInstance.h +++ b/lib/CArtifactInstance.h @@ -95,7 +95,17 @@ public: { h & static_cast(*this); h & static_cast(*this); - h & artTypeID; + if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) + { + h & artTypeID; + } + else + { + bool isNull = false; + h & isNull; + if (!isNull) + h & artTypeID; + } h & id; BONUS_TREE_DESERIALIZATION_FIX } diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 911541666..407d594d7 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -138,15 +138,50 @@ struct DLL_LINKAGE TerrainTile template void serialize(Handler & h) { + if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) + { + h & terrainType; + } + else + { + bool isNull = false; + h & isNull; + if (isNull) + h & terrainType; + } h & terrainType; h & terView; - h & riverType; + if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) + { + h & riverType; + } + else + { + bool isNull = false; + h & isNull; + if (isNull) + h & riverType; + } h & riverDir; - h & roadType; + if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) + { + h & roadType; + } + else + { + bool isNull = false; + h & isNull; + if (isNull) + h & roadType; + } h & roadDir; h & extTileFlags; - // h & visitable; - // h & blocked; + if (h.version < Handler::Version::REMOVE_VLC_POINTERS) + { + bool unused; + h & unused; + h & unused; + } h & visitableObjects; h & blockingObjects; } diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 7b4270bc9..978541f65 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -65,6 +65,7 @@ enum class ESerializationVersion : int32_t LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance + REMOVE_VLC_POINTERS, // 869 removed remaining pointers to VLC entities - CURRENT = REMOVE_OBJECT_TYPENAME + CURRENT = REMOVE_VLC_POINTERS }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 56a7b626c..822b876ad 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2912,7 +2912,7 @@ bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition a AssembledArtifact aa; aa.al = dstLoc; - aa.artId = assembleTo->getId(); + aa.artId = assembleTo; sendAndApply(aa); } else From d39dad6cb441285aa9b4883d616ef83c84e8280d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 30 Oct 2024 17:03:48 +0000 Subject: [PATCH 485/726] Fix build --- lib/mapping/CMapDefines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 407d594d7..49f27fa33 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -178,7 +178,7 @@ struct DLL_LINKAGE TerrainTile h & extTileFlags; if (h.version < Handler::Version::REMOVE_VLC_POINTERS) { - bool unused; + bool unused = false; h & unused; h & unused; } From b82444ba43cd8bb4fd4972acb47d3a178baa5f33 Mon Sep 17 00:00:00 2001 From: kodobi Date: Tue, 29 Oct 2024 20:44:31 +0100 Subject: [PATCH 486/726] Fix ballista damage range display - Adjusted the displayed damage range of ballista to reflect the changes in hero/es attack skill like in OH3. - Added checks to ensure the battle interface and relevant heroes are valid before calculating damage. --- client/windows/CCreatureWindow.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 293324c3e..ba7331a24 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -25,6 +25,7 @@ #include "../windows/InfoWindows.h" #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" +#include "../battle/BattleInterface.h" #include "../../CCallback.h" #include "../../lib/ArtifactUtils.h" @@ -523,6 +524,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s CRClickPopup::createAndPush(parent->info->creature->getDescriptionTranslated()); }); + if(parent->info->stackNode != nullptr && parent->info->commander == nullptr) { //normal stack, not a commander and not non-existing stack (e.g. recruitment dialog) @@ -531,14 +533,22 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s name = std::make_shared(215, 12, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName()); + const BattleInterface* battleInterface = LOCPLINT->battleInt.get(); + const CStack* battleStack = parent->info->stack; + int dmgMultiply = 1; - if(parent->info->owner && parent->info->stackNode->hasBonusOfType(BonusType::SIEGE_WEAPON)) - dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK); - + if (battleInterface && battleInterface->getBattle() != nullptr && battleStack->hasBonusOfType(BonusType::SIEGE_WEAPON)) + { + // Determine the relevant hero based on the unit side + const auto hero = (battleStack->unitSide() == BattleSide::ATTACKER) + ? battleInterface->attackingHeroInstance + : battleInterface->defendingHeroInstance; + + dmgMultiply += hero->getPrimSkillLevel(PrimarySkill::ATTACK); + } + icons = std::make_shared(ImagePath::builtin("stackWindow/icons"), 117, 32); - const CStack * battleStack = parent->info->stack; - morale = std::make_shared(true, Rect(Point(321, 110), Point(42, 42) )); luck = std::make_shared(false, Rect(Point(375, 110), Point(42, 42) )); @@ -566,7 +576,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter)); addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter)); - addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply); + addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter), parent->info->stackNode->getMaxDamage(shooter)); addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth()); addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), parent->info->stackNode->getMovementRange()); From 2678665f670be90de2aae7389af32fb33d23524f Mon Sep 17 00:00:00 2001 From: kodobi Date: Wed, 30 Oct 2024 18:48:09 +0100 Subject: [PATCH 487/726] Resolve merge conflict --- client/windows/CCreatureWindow.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 41b8407ad..04a230daf 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -545,17 +545,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s const auto hero = (battleStack->unitSide() == BattleSide::ATTACKER) ? battleInterface->attackingHeroInstance : battleInterface->defendingHeroInstance; -<<<<<<< HEAD - - dmgMultiply += hero->getPrimSkillLevel(PrimarySkill::ATTACK); -======= - // Check if the hero has a ballista in the war machine slot - if (hero && hero->getStack(SlotID::WAR_MACHINES_SLOT).type->warMachine.BALLISTA) - { - dmgMultiply += hero->getPrimSkillLevel(PrimarySkill::ATTACK); - } ->>>>>>> 389f8b678befdb4f3dc3bdcdf4d5847fc0f5129d + dmgMultiply += hero->getPrimSkillLevel(PrimarySkill::ATTACK); } icons = std::make_shared(ImagePath::builtin("stackWindow/icons"), 117, 32); From a54f4e1bd1767c63b4931aae1f34eed64387141b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:18:55 +0100 Subject: [PATCH 488/726] change autosave folder name; align autosave folder --- client/CPlayerInterface.cpp | 2 +- client/lobby/SelectionTab.cpp | 21 ++++++++++++++++++++- client/lobby/SelectionTab.h | 3 +++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index abf5abb9a..77085d5fe 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -249,7 +249,7 @@ void CPlayerInterface::performAutosave() }; std::replace_if(name.begin(), name.end(), isSymbolIllegal, '_' ); - prefix = name + "_" + cb->getStartInfo()->startTimeIso8601 + "/"; + prefix = cb->getStartInfo()->startTimeIso8601 + "_" + name + "/"; } } diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 8029dac48..982989c56 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -501,6 +501,7 @@ void SelectionTab::filter(int size, bool selectFirst) auto folder = std::make_shared(); folder->isFolder = true; folder->folderName = folderName; + folder->isAutoSaveFolder = boost::starts_with(baseFolder, "Autosave/") && folderName != "Autosave"; auto itemIt = boost::range::find_if(curItems, [folder](std::shared_ptr e) { return e->folderName == folder->folderName; }); if (itemIt == curItems.end() && folderName != "") { curItems.push_back(folder); @@ -561,7 +562,11 @@ void SelectionTab::sort() int firstMapIndex = boost::range::find_if(curItems, [](std::shared_ptr e) { return !e->isFolder; }) - curItems.begin(); if(!sortModeAscending) + { + if(firstMapIndex) + std::reverse(std::next(curItems.begin(), boost::starts_with(curItems[0]->folderName, "..") ? 1 : 0), std::next(curItems.begin(), firstMapIndex - 1)); std::reverse(std::next(curItems.begin(), firstMapIndex), curItems.end()); + } updateListItems(); redraw(); @@ -903,7 +908,7 @@ SelectionTab::ListItem::ListItem(Point position) { OBJECT_CONSTRUCTION; pictureEmptyLine = std::make_shared(ImagePath::builtin("camcust"), Rect(25, 121, 349, 26), -8, -14); - labelName = std::make_shared(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "", 185); + labelName = std::make_shared(LABEL_POS_X, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "", 185); labelName->setAutoRedraw(false); labelAmountOfPlayers = std::make_shared(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); labelAmountOfPlayers->setAutoRedraw(false); @@ -947,6 +952,16 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool labelNumberOfCampaignMaps->disable(); labelName->enable(); labelName->setMaxWidth(316); + if(info->isAutoSaveFolder) // align autosave folder left (starting timestamps in list should be in one line) + { + labelName->alignment = ETextAlignment::CENTERLEFT; + labelName->moveTo(Point(pos.x + 95, labelName->pos.y)); + } + else + { + labelName->alignment = ETextAlignment::CENTER; + labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y)); + } labelName->setText(info->folderName); labelName->setColor(color); return; @@ -968,6 +983,8 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool labelNumberOfCampaignMaps->setText(ostr.str()); labelNumberOfCampaignMaps->setColor(color); labelName->setMaxWidth(316); + labelName->alignment = ETextAlignment::CENTER; + labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y)); } else { @@ -989,6 +1006,8 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool iconLossCondition->enable(); iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0); labelName->setMaxWidth(185); + labelName->alignment = ETextAlignment::CENTER; + labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y)); } labelName->setText(info->name); labelName->setColor(color); diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index 86c625a58..d5c5fc362 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -35,6 +35,7 @@ public: std::string folderName = ""; std::string name = ""; bool isFolder = false; + bool isAutoSaveFolder = false; }; /// Class which handles map sorting by different criteria @@ -60,6 +61,8 @@ class SelectionTab : public CIntObject std::shared_ptr pictureEmptyLine; std::shared_ptr labelName; + const int LABEL_POS_X = 184; + ListItem(Point position); void updateItem(std::shared_ptr info = {}, bool selected = false); }; From 3beca1bd79fa2b266439c05bc0547a807d2c7f8a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:25:20 +0100 Subject: [PATCH 489/726] fix order (docs) --- docs/players/Installation_iOS.md | 2 +- docs/players/Installation_macOS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/players/Installation_iOS.md b/docs/players/Installation_iOS.md index fc9f2cef4..254bf5dcf 100644 --- a/docs/players/Installation_iOS.md +++ b/docs/players/Installation_iOS.md @@ -26,7 +26,7 @@ Note: if you don't need in-game videos, you can omit downloading/copying file VI ### Step 2.a: Installing data files with GOG offline installer -If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .exe file first, then the .bin file. This may take a few seconds. Please be patient. +If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .bin file first, then the .exe file. This may take a few seconds. Please be patient. ### Step 2.b: Installing data files with Finder or Windows explorer diff --git a/docs/players/Installation_macOS.md b/docs/players/Installation_macOS.md index 1ce876d59..8d767b079 100644 --- a/docs/players/Installation_macOS.md +++ b/docs/players/Installation_macOS.md @@ -17,7 +17,7 @@ Please report about gameplay problem on forums: [Help & Bugs](https://forum.vcmi ### Step 2.a: Installing data files with GOG offline installer -If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .exe file first, then the .bin file. This may take a few seconds. Please be patient. +If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .bin file first, then the .exe file. This may take a few seconds. Please be patient. ### Step 2.b: Installing by the classic way From 1423951b9ea500f5478dc94ee3895ce49a2e0c2f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:36:37 +0100 Subject: [PATCH 490/726] disable selection by empty slot --- client/lobby/SelectionTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 982989c56..4dd3b8e96 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -307,7 +307,7 @@ void SelectionTab::clickReleased(const Point & cursorPosition) { int line = getLine(); - if(line != -1) + if(line != -1 && curItems.size() > line) { select(line); } From cb913976889f207f95743e16b16787fd983b600d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:50:03 +0100 Subject: [PATCH 491/726] fix pos --- client/lobby/SelectionTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 4dd3b8e96..150526d6c 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -955,7 +955,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool if(info->isAutoSaveFolder) // align autosave folder left (starting timestamps in list should be in one line) { labelName->alignment = ETextAlignment::CENTERLEFT; - labelName->moveTo(Point(pos.x + 95, labelName->pos.y)); + labelName->moveTo(Point(pos.x + 80, labelName->pos.y)); } else { From 9dfb6f4233ee75900e1d8b0e8daf8e98dd8f16a3 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Wed, 30 Oct 2024 19:09:55 -0300 Subject: [PATCH 492/726] Update portuguese.json --- Mods/vcmi/config/vcmi/portuguese.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/vcmi/portuguese.json index 15b68ec6c..4185649af 100644 --- a/Mods/vcmi/config/vcmi/portuguese.json +++ b/Mods/vcmi/config/vcmi/portuguese.json @@ -706,7 +706,10 @@ "core.bonus.DISINTEGRATE.description": "Nenhum corpo permanece após a morte", "core.bonus.INVINCIBLE.name": "Invencível", "core.bonus.INVINCIBLE.description": "Não pode ser afetado por nada", - + "core.bonus.PRISM_HEX_ATTACK_BREATH.name" : "Sopro Prismático", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description" : "Ataque de Sopro Prismático (três direções)", + "vcmi.server.errors.modDependencyLoop" : "Falha ao carregar o mod {'%s'}!\n Ele pode estar em um ciclo de dependência.", + "spell.core.castleMoat.name": "Fosso", "spell.core.castleMoatTrigger.name": "Fosso", "spell.core.catapultShot.name": "Disparo de Catapulta", From 452762cd78fc408545d2dcbe59e05b1f93d1f87d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:23:56 +0100 Subject: [PATCH 493/726] change datetime format --- client/CPlayerInterface.cpp | 4 ++-- lib/StartInfo.h | 16 +++++++++++++--- lib/serializer/ESerializationVersion.h | 3 ++- server/CVCMIServer.cpp | 4 ++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 77085d5fe..23a73a933 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -238,7 +238,7 @@ void CPlayerInterface::performAutosave() std::string name = cb->getMapHeader()->name.toString(); int txtlen = TextOperations::getUnicodeCharactersCount(name); - TextOperations::trimRightUnicode(name, std::max(0, txtlen - 15)); + TextOperations::trimRightUnicode(name, std::max(0, txtlen - 14)); auto const & isSymbolIllegal = [&](char c) { static const std::string forbiddenChars("\\/:*?\"<>| "); @@ -249,7 +249,7 @@ void CPlayerInterface::performAutosave() }; std::replace_if(name.begin(), name.end(), isSymbolIllegal, '_' ); - prefix = cb->getStartInfo()->startTimeIso8601 + "_" + name + "/"; + prefix = vstd::getFormattedDateTime(cb->getStartInfo()->startTime, "%Y-%m-%d_%H-%M") + "_" + name + "/"; } } diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 6a79ff862..e6f427650 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -146,7 +146,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable using TPlayerInfos = std::map; TPlayerInfos playerInfos; //color indexed - std::string startTimeIso8601; + time_t startTime; std::string fileURI; SimturnsInfo simturnsInfo; TurnTimerInfo turnTimerInfo; @@ -180,7 +180,17 @@ struct DLL_LINKAGE StartInfo : public Serializeable h & oldSeeds; h & oldSeeds; } - h & startTimeIso8601; + if (h.version < Handler::Version::FOLDER_NAME_REWORK) + { + std::string startTimeLegacy; + h & startTimeLegacy; + struct std::tm tm; + std::istringstream ss(startTimeLegacy); + ss >> std::get_time(&tm, "%Y%m%dT%H%M%S"); + startTime = mktime(&tm); + } + else + h & startTime; h & fileURI; h & simturnsInfo; h & turnTimerInfo; @@ -193,7 +203,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable StartInfo() : mode(EStartMode::INVALID) , difficulty(1) - , startTimeIso8601(vstd::getDateTimeISO8601Basic(std::time(nullptr))) + , startTime(std::time(nullptr)) { } diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 7b4270bc9..7abf6983b 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -65,6 +65,7 @@ enum class ESerializationVersion : int32_t LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance + FOLDER_NAME_REWORK, // 869 - rework foldername - CURRENT = REMOVE_OBJECT_TYPENAME + CURRENT = FOLDER_NAME_REWORK }; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index ef92050c1..54fa8a25f 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -245,7 +245,7 @@ bool CVCMIServer::prepareToStartGame() { case EStartMode::CAMPAIGN: logNetwork->info("Preparing to start new campaign"); - si->startTimeIso8601 = vstd::getDateTimeISO8601Basic(std::time(nullptr)); + si->startTime = std::time(nullptr); si->fileURI = mi->fileURI; si->campState->setCurrentMap(campaignMap); si->campState->setCurrentMapBonus(campaignBonus); @@ -254,7 +254,7 @@ bool CVCMIServer::prepareToStartGame() case EStartMode::NEW_GAME: logNetwork->info("Preparing to start new game"); - si->startTimeIso8601 = vstd::getDateTimeISO8601Basic(std::time(nullptr)); + si->startTime = std::time(nullptr); si->fileURI = mi->fileURI; gh->init(si.get(), progressTracking); break; From 883cc12c94908a5923e97feeedcf86f6aaf246a3 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:57:19 +0100 Subject: [PATCH 494/726] Updated Czech translation --- Mods/vcmi/config/vcmi/czech.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index 56f0d23b5..4ccc83653 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -175,6 +175,7 @@ "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", + "vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.", "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", @@ -551,7 +552,9 @@ "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", - + + "mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.", + "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", From 9d2fc1b1c999a1a734ab91fe23cc0efa89463d17 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 31 Oct 2024 00:41:51 +0100 Subject: [PATCH 495/726] Update DefenceBehavior.cpp Fixed an issue that caused the AI to try buying the same hero in two different towns. --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 95f1d6980..710492fbc 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -41,6 +41,9 @@ Goals::TGoalVec DefenceBehavior::decompose(const Nullkiller * ai) const for(auto town : ai->cb->getTownsInfo()) { evaluateDefence(tasks, town, ai); + //Let's do only one defence-task per pass since otherwise it can try to hire the same hero twice + if (!tasks.empty()) + break; } return tasks; From 5979f53a264a0fc545bf4c376ab8e9a3251217ee Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 31 Oct 2024 00:42:33 +0100 Subject: [PATCH 496/726] Upgrade priority New priority to upgrade existing armies to make it less likely to fight the AI with only part of its army. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 14 ++++++++++++++ AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 15 insertions(+) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index ce2f94570..b192bfefe 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1423,6 +1423,20 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) score /= evaluationContext.movementCost; break; } + case PriorityTier::UPGRADE: + { + if (!evaluationContext.isArmyUpgrade) + return 0; + if (evaluationContext.enemyHeroDangerRatio > 1) + return 0; + if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) + return 0; + score = 1000; + score *= evaluationContext.closestWayRatio; + if (evaluationContext.movementCost > 0) + score /= evaluationContext.movementCost; + break; + } case PriorityTier::HIGH_PRIO_EXPLORE: { if (evaluationContext.enemyHeroDangerRatio > 1) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 2e757c819..ee983e43b 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -114,6 +114,7 @@ public: INSTAKILL, INSTADEFEND, KILL, + UPGRADE, HIGH_PRIO_EXPLORE, HUNTER_GATHER, LOW_PRIO_EXPLORE, From 6056d385ed539776a5a1a1a7b943fcf06651c0d2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 30 Oct 2024 10:51:02 +0000 Subject: [PATCH 497/726] Always load json configs from mod that references it This should fix rather common problem with mods, where two unrelated mods accidentally use same file name for a config file, leading to very unclear conflict since this result in a file override. Now all config files referenced in mod.json are loaded specifically from filesystem of mod that referenced it. In other words, it is no longer possible for one mod to override config from another mod. As a side effect, this allows mods to use shorter directory layout, e.g. `config/modName/xxx.json` can now be safely replaced with `config/ xxx.json` without fear of broken mod if there is another mod with same path to config. Similarly, now all mods can use `config/translation/ language.json` scheme for translation files Since this is no longer a problem, I've also simplified directory layout of our built-in 'vcmi' mod, by moving all files from `config/vcmi` directory directly to `config` directory. - Overrides for miscellaneous configs like mainmenu.json should works as before - Images / animations (png's or def's) work as before (and may still result in confict) - Rebalance mods work as before and can modify another mod via standard `modName:objectName` syntax --- Mods/vcmi/config/{vcmi => }/chinese.json | 0 Mods/vcmi/config/{vcmi => }/czech.json | 0 Mods/vcmi/config/{vcmi => }/english.json | 0 Mods/vcmi/config/{vcmi => }/french.json | 0 Mods/vcmi/config/{vcmi => }/german.json | 0 Mods/vcmi/config/{vcmi => }/polish.json | 0 Mods/vcmi/config/{vcmi => }/portuguese.json | 0 .../hdmod/aroundamarsh.json} | 0 .../balance.JSON => rmg/hdmod/balance.json} | 0 .../hdmod/blockbuster.json} | 0 .../{vcmi => }/rmg/hdmod/clashOfDragons.json | 0 .../rmg/hdmod/coldshadowsFantasy.json | 0 .../hdmod/cube.JSON => rmg/hdmod/cube.json} | 0 .../diamond.JSON => rmg/hdmod/diamond.json} | 0 .../extreme.JSON => rmg/hdmod/extreme.json} | 0 .../extreme2.JSON => rmg/hdmod/extreme2.json} | 0 .../hdmod/fear.JSON => rmg/hdmod/fear.json} | 0 .../hdmod/frozenDragons.json} | 0 .../hdmod/gimlisRevenge.json} | 0 .../guerilla.JSON => rmg/hdmod/guerilla.json} | 0 .../hdmod/headquarters.json} | 0 .../hdmod/hypercube.json} | 0 .../{vcmi => }/rmg/hdmod/jebusCross.json | 0 .../longRun.JSON => rmg/hdmod/longRun.json} | 0 .../marathon.JSON => rmg/hdmod/marathon.json} | 0 .../hdmod/miniNostalgia.json} | 0 .../hdmod/nostalgia.json} | 0 .../hdmod/oceansEleven.json} | 0 .../hdmod/panic.JSON => rmg/hdmod/panic.json} | 0 .../hdmod/poorJebus.json} | 0 .../reckless.JSON => rmg/hdmod/reckless.json} | 0 .../hdmod/roadrunner.json} | 0 .../hdmod/shaaafworld.json} | 0 .../skirmish.JSON => rmg/hdmod/skirmish.json} | 0 .../speed1.JSON => rmg/hdmod/speed1.json} | 0 .../speed2.JSON => rmg/hdmod/speed2.json} | 0 .../spider.JSON => rmg/hdmod/spider.json} | 0 .../hdmod/superslam.json} | 0 .../hdmod/triad.JSON => rmg/hdmod/triad.json} | 0 .../vortex.JSON => rmg/hdmod/vortex.json} | 0 .../hdmodUnused/anarchy.json} | 0 .../hdmodUnused/balance m+u 200%.json} | 0 .../hdmodUnused/midnightMix.json} | 0 .../hdmodUnused/skirmish m-u 200%.json} | 0 .../hdmodUnused/true random.json} | 0 .../heroes3/dwarvenTunnels.json} | 0 .../heroes3/golemsAplenty.json} | 0 .../heroes3/meetingInMuzgob.json} | 0 .../heroes3/monksRetreat.json} | 0 .../heroes3/newcomers.json} | 0 .../heroes3/readyOrNot.json} | 0 .../heroes3/smallRing.json} | 0 .../heroes3/southOfHell.json} | 0 .../heroes3/worldsAtWar.json} | 0 .../{vcmi => }/rmg/heroes3unused/dragon.json | 0 .../heroes3unused/gauntlet.json} | 0 .../ring.JSON => rmg/heroes3unused/ring.json} | 0 .../heroes3unused/riseOfPhoenix.json} | 0 .../2sm0k.JSON => rmg/symmetric/2sm0k.json} | 0 .../2sm2a.JSON => rmg/symmetric/2sm2a.json} | 0 .../symmetric/2sm2b(2).json} | 0 .../2sm2b.JSON => rmg/symmetric/2sm2b.json} | 0 .../2sm2c.JSON => rmg/symmetric/2sm2c.json} | 0 .../symmetric/2sm2f(2).json} | 0 .../2sm2f.JSON => rmg/symmetric/2sm2f.json} | 0 .../symmetric/2sm2h(2).json} | 0 .../2sm2h.JSON => rmg/symmetric/2sm2h.json} | 0 .../symmetric/2sm2i(2).json} | 0 .../2sm2i.JSON => rmg/symmetric/2sm2i.json} | 0 .../symmetric/2sm4d(2).json} | 0 .../symmetric/2sm4d(3).json} | 0 .../2sm4d.JSON => rmg/symmetric/2sm4d.json} | 0 .../3sb0b.JSON => rmg/symmetric/3sb0b.json} | 0 .../3sb0c.JSON => rmg/symmetric/3sb0c.json} | 0 .../3sm3d.JSON => rmg/symmetric/3sm3d.json} | 0 .../4sm0d.JSON => rmg/symmetric/4sm0d.json} | 0 .../4sm0f.JSON => rmg/symmetric/4sm0f.json} | 0 .../4sm0g.JSON => rmg/symmetric/4sm0g.json} | 0 .../4sm4e.JSON => rmg/symmetric/4sm4e.json} | 0 .../5sb0a.JSON => rmg/symmetric/5sb0a.json} | 0 .../5sb0b.JSON => rmg/symmetric/5sb0b.json} | 0 .../6lm10.JSON => rmg/symmetric/6lm10.json} | 0 .../6lm10a.JSON => rmg/symmetric/6lm10a.json} | 0 .../6sm0b.JSON => rmg/symmetric/6sm0b.json} | 0 .../6sm0d.JSON => rmg/symmetric/6sm0d.json} | 0 .../6sm0e.JSON => rmg/symmetric/6sm0e.json} | 0 .../7sb0b.JSON => rmg/symmetric/7sb0b.json} | 0 .../7sb0c.JSON => rmg/symmetric/7sb0c.json} | 0 .../8mm0e.JSON => rmg/symmetric/8mm0e.json} | 0 .../8mm6.JSON => rmg/symmetric/8mm6.json} | 0 .../8mm6a.JSON => rmg/symmetric/8mm6a.json} | 0 .../8sm0c.JSON => rmg/symmetric/8sm0c.json} | 0 .../8sm0f.JSON => rmg/symmetric/8sm0f.json} | 0 .../8xm12.JSON => rmg/symmetric/8xm12.json} | 0 .../8xm12a.JSON => rmg/symmetric/8xm12a.json} | 0 .../8xm8.JSON => rmg/symmetric/8xm8.json} | 0 .../unknownUnused/2mm2h.json} | 0 .../unknownUnused/2x2sm4d(3).json} | 0 .../unknownUnused/4mm2h.json} | 0 .../unknownUnused/4sm3i.json} | 0 .../unknownUnused/6lm10a.json} | 0 .../unknownUnused/8xm12 huge.json} | 0 .../unknownUnused/8xm8 huge.json} | 0 .../{vcmi => }/rmg/unknownUnused/analogy.json | 0 .../unknownUnused/cross.json} | 0 .../unknownUnused/cross2.json} | 0 .../unknownUnused/cross3.json} | 0 .../unknownUnused/deux paires.json} | 0 .../unknownUnused/doubled 8mm6.json} | 0 .../elka.JSON => rmg/unknownUnused/elka.json} | 0 .../rmg/unknownUnused/goldenRing.json | 0 .../unknownUnused/greatSands.json} | 0 .../kite.JSON => rmg/unknownUnused/kite.json} | 0 .../{vcmi => }/rmg/unknownUnused/upgrade.json | 0 .../unknownUnused/wheel.json} | 0 Mods/vcmi/config/{vcmi => }/russian.json | 0 Mods/vcmi/config/{vcmi => }/spanish.json | 0 Mods/vcmi/config/{vcmi => }/spells.json | 0 Mods/vcmi/config/{vcmi => }/swedish.json | 0 .../vcmi/config/{vcmi => }/towerCreature.json | 0 .../vcmi/config/{vcmi => }/towerFactions.json | 0 Mods/vcmi/config/{vcmi => }/ukrainian.json | 0 Mods/vcmi/config/{vcmi => }/vietnamese.json | 0 Mods/vcmi/mod.json | 190 +++++++++--------- docs/modders/Mod_File_Format.md | 30 +-- lib/CBonusTypeHandler.cpp | 10 +- lib/json/JsonNode.cpp | 8 +- lib/json/JsonNode.h | 2 +- lib/json/JsonUtils.cpp | 11 +- lib/json/JsonUtils.h | 2 +- lib/modding/CModHandler.cpp | 2 +- 131 files changed, 129 insertions(+), 126 deletions(-) rename Mods/vcmi/config/{vcmi => }/chinese.json (100%) rename Mods/vcmi/config/{vcmi => }/czech.json (100%) rename Mods/vcmi/config/{vcmi => }/english.json (100%) rename Mods/vcmi/config/{vcmi => }/french.json (100%) rename Mods/vcmi/config/{vcmi => }/german.json (100%) rename Mods/vcmi/config/{vcmi => }/polish.json (100%) rename Mods/vcmi/config/{vcmi => }/portuguese.json (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/aroundamarsh.JSON => rmg/hdmod/aroundamarsh.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/balance.JSON => rmg/hdmod/balance.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/blockbuster.JSON => rmg/hdmod/blockbuster.json} (100%) rename Mods/vcmi/config/{vcmi => }/rmg/hdmod/clashOfDragons.json (100%) rename Mods/vcmi/config/{vcmi => }/rmg/hdmod/coldshadowsFantasy.json (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/cube.JSON => rmg/hdmod/cube.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/diamond.JSON => rmg/hdmod/diamond.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/extreme.JSON => rmg/hdmod/extreme.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/extreme2.JSON => rmg/hdmod/extreme2.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/fear.JSON => rmg/hdmod/fear.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/frozenDragons.JSON => rmg/hdmod/frozenDragons.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/gimlisRevenge.JSON => rmg/hdmod/gimlisRevenge.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/guerilla.JSON => rmg/hdmod/guerilla.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/headquarters.JSON => rmg/hdmod/headquarters.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/hypercube.JSON => rmg/hdmod/hypercube.json} (100%) rename Mods/vcmi/config/{vcmi => }/rmg/hdmod/jebusCross.json (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/longRun.JSON => rmg/hdmod/longRun.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/marathon.JSON => rmg/hdmod/marathon.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/miniNostalgia.JSON => rmg/hdmod/miniNostalgia.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/nostalgia.JSON => rmg/hdmod/nostalgia.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/oceansEleven.JSON => rmg/hdmod/oceansEleven.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/panic.JSON => rmg/hdmod/panic.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/poorJebus.JSON => rmg/hdmod/poorJebus.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/reckless.JSON => rmg/hdmod/reckless.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/roadrunner.JSON => rmg/hdmod/roadrunner.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/shaaafworld.JSON => rmg/hdmod/shaaafworld.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/skirmish.JSON => rmg/hdmod/skirmish.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/speed1.JSON => rmg/hdmod/speed1.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/speed2.JSON => rmg/hdmod/speed2.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/spider.JSON => rmg/hdmod/spider.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/superslam.JSON => rmg/hdmod/superslam.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/triad.JSON => rmg/hdmod/triad.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmod/vortex.JSON => rmg/hdmod/vortex.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmodUnused/anarchy.JSON => rmg/hdmodUnused/anarchy.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmodUnused/balance m+u 200%.JSON => rmg/hdmodUnused/balance m+u 200%.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmodUnused/midnightMix.JSON => rmg/hdmodUnused/midnightMix.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmodUnused/skirmish m-u 200%.JSON => rmg/hdmodUnused/skirmish m-u 200%.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/hdmodUnused/true random.JSON => rmg/hdmodUnused/true random.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/dwarvenTunnels.JSON => rmg/heroes3/dwarvenTunnels.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/golemsAplenty.JSON => rmg/heroes3/golemsAplenty.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/meetingInMuzgob.JSON => rmg/heroes3/meetingInMuzgob.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/monksRetreat.JSON => rmg/heroes3/monksRetreat.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/newcomers.JSON => rmg/heroes3/newcomers.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/readyOrNot.JSON => rmg/heroes3/readyOrNot.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/smallRing.JSON => rmg/heroes3/smallRing.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/southOfHell.JSON => rmg/heroes3/southOfHell.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3/worldsAtWar.JSON => rmg/heroes3/worldsAtWar.json} (100%) rename Mods/vcmi/config/{vcmi => }/rmg/heroes3unused/dragon.json (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3unused/gauntlet.JSON => rmg/heroes3unused/gauntlet.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3unused/ring.JSON => rmg/heroes3unused/ring.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/heroes3unused/riseOfPhoenix.JSON => rmg/heroes3unused/riseOfPhoenix.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm0k.JSON => rmg/symmetric/2sm0k.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2a.JSON => rmg/symmetric/2sm2a.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2b(2).JSON => rmg/symmetric/2sm2b(2).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2b.JSON => rmg/symmetric/2sm2b.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2c.JSON => rmg/symmetric/2sm2c.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2f(2).JSON => rmg/symmetric/2sm2f(2).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2f.JSON => rmg/symmetric/2sm2f.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2h(2).JSON => rmg/symmetric/2sm2h(2).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2h.JSON => rmg/symmetric/2sm2h.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2i(2).JSON => rmg/symmetric/2sm2i(2).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm2i.JSON => rmg/symmetric/2sm2i.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm4d(2).JSON => rmg/symmetric/2sm4d(2).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm4d(3).JSON => rmg/symmetric/2sm4d(3).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/2sm4d.JSON => rmg/symmetric/2sm4d.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/3sb0b.JSON => rmg/symmetric/3sb0b.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/3sb0c.JSON => rmg/symmetric/3sb0c.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/3sm3d.JSON => rmg/symmetric/3sm3d.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/4sm0d.JSON => rmg/symmetric/4sm0d.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/4sm0f.JSON => rmg/symmetric/4sm0f.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/4sm0g.JSON => rmg/symmetric/4sm0g.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/4sm4e.JSON => rmg/symmetric/4sm4e.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/5sb0a.JSON => rmg/symmetric/5sb0a.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/5sb0b.JSON => rmg/symmetric/5sb0b.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/6lm10.JSON => rmg/symmetric/6lm10.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/6lm10a.JSON => rmg/symmetric/6lm10a.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/6sm0b.JSON => rmg/symmetric/6sm0b.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/6sm0d.JSON => rmg/symmetric/6sm0d.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/6sm0e.JSON => rmg/symmetric/6sm0e.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/7sb0b.JSON => rmg/symmetric/7sb0b.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/7sb0c.JSON => rmg/symmetric/7sb0c.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8mm0e.JSON => rmg/symmetric/8mm0e.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8mm6.JSON => rmg/symmetric/8mm6.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8mm6a.JSON => rmg/symmetric/8mm6a.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8sm0c.JSON => rmg/symmetric/8sm0c.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8sm0f.JSON => rmg/symmetric/8sm0f.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8xm12.JSON => rmg/symmetric/8xm12.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8xm12a.JSON => rmg/symmetric/8xm12a.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/symmetric/8xm8.JSON => rmg/symmetric/8xm8.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/2mm2h.JSON => rmg/unknownUnused/2mm2h.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/2x2sm4d(3).JSON => rmg/unknownUnused/2x2sm4d(3).json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/4mm2h.JSON => rmg/unknownUnused/4mm2h.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/4sm3i.JSON => rmg/unknownUnused/4sm3i.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/6lm10a.JSON => rmg/unknownUnused/6lm10a.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/8xm12 huge.JSON => rmg/unknownUnused/8xm12 huge.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/8xm8 huge.JSON => rmg/unknownUnused/8xm8 huge.json} (100%) rename Mods/vcmi/config/{vcmi => }/rmg/unknownUnused/analogy.json (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/cross.JSON => rmg/unknownUnused/cross.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/cross2.JSON => rmg/unknownUnused/cross2.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/cross3.JSON => rmg/unknownUnused/cross3.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/deux paires.JSON => rmg/unknownUnused/deux paires.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/doubled 8mm6.JSON => rmg/unknownUnused/doubled 8mm6.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/elka.JSON => rmg/unknownUnused/elka.json} (100%) rename Mods/vcmi/config/{vcmi => }/rmg/unknownUnused/goldenRing.json (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/greatSands.JSON => rmg/unknownUnused/greatSands.json} (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/kite.JSON => rmg/unknownUnused/kite.json} (100%) rename Mods/vcmi/config/{vcmi => }/rmg/unknownUnused/upgrade.json (100%) rename Mods/vcmi/config/{vcmi/rmg/unknownUnused/wheel.JSON => rmg/unknownUnused/wheel.json} (100%) rename Mods/vcmi/config/{vcmi => }/russian.json (100%) rename Mods/vcmi/config/{vcmi => }/spanish.json (100%) rename Mods/vcmi/config/{vcmi => }/spells.json (100%) rename Mods/vcmi/config/{vcmi => }/swedish.json (100%) rename Mods/vcmi/config/{vcmi => }/towerCreature.json (100%) rename Mods/vcmi/config/{vcmi => }/towerFactions.json (100%) rename Mods/vcmi/config/{vcmi => }/ukrainian.json (100%) rename Mods/vcmi/config/{vcmi => }/vietnamese.json (100%) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/chinese.json similarity index 100% rename from Mods/vcmi/config/vcmi/chinese.json rename to Mods/vcmi/config/chinese.json diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/czech.json similarity index 100% rename from Mods/vcmi/config/vcmi/czech.json rename to Mods/vcmi/config/czech.json diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/english.json similarity index 100% rename from Mods/vcmi/config/vcmi/english.json rename to Mods/vcmi/config/english.json diff --git a/Mods/vcmi/config/vcmi/french.json b/Mods/vcmi/config/french.json similarity index 100% rename from Mods/vcmi/config/vcmi/french.json rename to Mods/vcmi/config/french.json diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/german.json similarity index 100% rename from Mods/vcmi/config/vcmi/german.json rename to Mods/vcmi/config/german.json diff --git a/Mods/vcmi/config/vcmi/polish.json b/Mods/vcmi/config/polish.json similarity index 100% rename from Mods/vcmi/config/vcmi/polish.json rename to Mods/vcmi/config/polish.json diff --git a/Mods/vcmi/config/vcmi/portuguese.json b/Mods/vcmi/config/portuguese.json similarity index 100% rename from Mods/vcmi/config/vcmi/portuguese.json rename to Mods/vcmi/config/portuguese.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/aroundamarsh.JSON b/Mods/vcmi/config/rmg/hdmod/aroundamarsh.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/aroundamarsh.JSON rename to Mods/vcmi/config/rmg/hdmod/aroundamarsh.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/balance.JSON b/Mods/vcmi/config/rmg/hdmod/balance.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/balance.JSON rename to Mods/vcmi/config/rmg/hdmod/balance.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON b/Mods/vcmi/config/rmg/hdmod/blockbuster.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/blockbuster.JSON rename to Mods/vcmi/config/rmg/hdmod/blockbuster.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/clashOfDragons.json b/Mods/vcmi/config/rmg/hdmod/clashOfDragons.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/clashOfDragons.json rename to Mods/vcmi/config/rmg/hdmod/clashOfDragons.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/coldshadowsFantasy.json b/Mods/vcmi/config/rmg/hdmod/coldshadowsFantasy.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/coldshadowsFantasy.json rename to Mods/vcmi/config/rmg/hdmod/coldshadowsFantasy.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/cube.JSON b/Mods/vcmi/config/rmg/hdmod/cube.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/cube.JSON rename to Mods/vcmi/config/rmg/hdmod/cube.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/diamond.JSON b/Mods/vcmi/config/rmg/hdmod/diamond.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/diamond.JSON rename to Mods/vcmi/config/rmg/hdmod/diamond.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON b/Mods/vcmi/config/rmg/hdmod/extreme.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/extreme.JSON rename to Mods/vcmi/config/rmg/hdmod/extreme.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON b/Mods/vcmi/config/rmg/hdmod/extreme2.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/extreme2.JSON rename to Mods/vcmi/config/rmg/hdmod/extreme2.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/fear.JSON b/Mods/vcmi/config/rmg/hdmod/fear.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/fear.JSON rename to Mods/vcmi/config/rmg/hdmod/fear.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/frozenDragons.JSON b/Mods/vcmi/config/rmg/hdmod/frozenDragons.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/frozenDragons.JSON rename to Mods/vcmi/config/rmg/hdmod/frozenDragons.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/gimlisRevenge.JSON b/Mods/vcmi/config/rmg/hdmod/gimlisRevenge.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/gimlisRevenge.JSON rename to Mods/vcmi/config/rmg/hdmod/gimlisRevenge.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/guerilla.JSON b/Mods/vcmi/config/rmg/hdmod/guerilla.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/guerilla.JSON rename to Mods/vcmi/config/rmg/hdmod/guerilla.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/headquarters.JSON b/Mods/vcmi/config/rmg/hdmod/headquarters.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/headquarters.JSON rename to Mods/vcmi/config/rmg/hdmod/headquarters.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/hypercube.JSON b/Mods/vcmi/config/rmg/hdmod/hypercube.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/hypercube.JSON rename to Mods/vcmi/config/rmg/hdmod/hypercube.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/jebusCross.json b/Mods/vcmi/config/rmg/hdmod/jebusCross.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/jebusCross.json rename to Mods/vcmi/config/rmg/hdmod/jebusCross.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON b/Mods/vcmi/config/rmg/hdmod/longRun.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/longRun.JSON rename to Mods/vcmi/config/rmg/hdmod/longRun.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON b/Mods/vcmi/config/rmg/hdmod/marathon.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/marathon.JSON rename to Mods/vcmi/config/rmg/hdmod/marathon.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/miniNostalgia.JSON b/Mods/vcmi/config/rmg/hdmod/miniNostalgia.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/miniNostalgia.JSON rename to Mods/vcmi/config/rmg/hdmod/miniNostalgia.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/nostalgia.JSON b/Mods/vcmi/config/rmg/hdmod/nostalgia.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/nostalgia.JSON rename to Mods/vcmi/config/rmg/hdmod/nostalgia.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/oceansEleven.JSON b/Mods/vcmi/config/rmg/hdmod/oceansEleven.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/oceansEleven.JSON rename to Mods/vcmi/config/rmg/hdmod/oceansEleven.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON b/Mods/vcmi/config/rmg/hdmod/panic.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/panic.JSON rename to Mods/vcmi/config/rmg/hdmod/panic.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON b/Mods/vcmi/config/rmg/hdmod/poorJebus.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/poorJebus.JSON rename to Mods/vcmi/config/rmg/hdmod/poorJebus.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON b/Mods/vcmi/config/rmg/hdmod/reckless.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/reckless.JSON rename to Mods/vcmi/config/rmg/hdmod/reckless.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON b/Mods/vcmi/config/rmg/hdmod/roadrunner.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/roadrunner.JSON rename to Mods/vcmi/config/rmg/hdmod/roadrunner.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/shaaafworld.JSON b/Mods/vcmi/config/rmg/hdmod/shaaafworld.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/shaaafworld.JSON rename to Mods/vcmi/config/rmg/hdmod/shaaafworld.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON b/Mods/vcmi/config/rmg/hdmod/skirmish.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/skirmish.JSON rename to Mods/vcmi/config/rmg/hdmod/skirmish.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/speed1.JSON b/Mods/vcmi/config/rmg/hdmod/speed1.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/speed1.JSON rename to Mods/vcmi/config/rmg/hdmod/speed1.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/speed2.JSON b/Mods/vcmi/config/rmg/hdmod/speed2.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/speed2.JSON rename to Mods/vcmi/config/rmg/hdmod/speed2.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/spider.JSON b/Mods/vcmi/config/rmg/hdmod/spider.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/spider.JSON rename to Mods/vcmi/config/rmg/hdmod/spider.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON b/Mods/vcmi/config/rmg/hdmod/superslam.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/superslam.JSON rename to Mods/vcmi/config/rmg/hdmod/superslam.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON b/Mods/vcmi/config/rmg/hdmod/triad.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/triad.JSON rename to Mods/vcmi/config/rmg/hdmod/triad.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON b/Mods/vcmi/config/rmg/hdmod/vortex.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmod/vortex.JSON rename to Mods/vcmi/config/rmg/hdmod/vortex.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmodUnused/anarchy.JSON b/Mods/vcmi/config/rmg/hdmodUnused/anarchy.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmodUnused/anarchy.JSON rename to Mods/vcmi/config/rmg/hdmodUnused/anarchy.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmodUnused/balance m+u 200%.JSON b/Mods/vcmi/config/rmg/hdmodUnused/balance m+u 200%.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmodUnused/balance m+u 200%.JSON rename to Mods/vcmi/config/rmg/hdmodUnused/balance m+u 200%.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmodUnused/midnightMix.JSON b/Mods/vcmi/config/rmg/hdmodUnused/midnightMix.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmodUnused/midnightMix.JSON rename to Mods/vcmi/config/rmg/hdmodUnused/midnightMix.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmodUnused/skirmish m-u 200%.JSON b/Mods/vcmi/config/rmg/hdmodUnused/skirmish m-u 200%.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmodUnused/skirmish m-u 200%.JSON rename to Mods/vcmi/config/rmg/hdmodUnused/skirmish m-u 200%.json diff --git a/Mods/vcmi/config/vcmi/rmg/hdmodUnused/true random.JSON b/Mods/vcmi/config/rmg/hdmodUnused/true random.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/hdmodUnused/true random.JSON rename to Mods/vcmi/config/rmg/hdmodUnused/true random.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/dwarvenTunnels.JSON b/Mods/vcmi/config/rmg/heroes3/dwarvenTunnels.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/dwarvenTunnels.JSON rename to Mods/vcmi/config/rmg/heroes3/dwarvenTunnels.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/golemsAplenty.JSON b/Mods/vcmi/config/rmg/heroes3/golemsAplenty.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/golemsAplenty.JSON rename to Mods/vcmi/config/rmg/heroes3/golemsAplenty.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/meetingInMuzgob.JSON b/Mods/vcmi/config/rmg/heroes3/meetingInMuzgob.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/meetingInMuzgob.JSON rename to Mods/vcmi/config/rmg/heroes3/meetingInMuzgob.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/monksRetreat.JSON b/Mods/vcmi/config/rmg/heroes3/monksRetreat.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/monksRetreat.JSON rename to Mods/vcmi/config/rmg/heroes3/monksRetreat.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/newcomers.JSON b/Mods/vcmi/config/rmg/heroes3/newcomers.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/newcomers.JSON rename to Mods/vcmi/config/rmg/heroes3/newcomers.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/readyOrNot.JSON b/Mods/vcmi/config/rmg/heroes3/readyOrNot.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/readyOrNot.JSON rename to Mods/vcmi/config/rmg/heroes3/readyOrNot.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/smallRing.JSON b/Mods/vcmi/config/rmg/heroes3/smallRing.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/smallRing.JSON rename to Mods/vcmi/config/rmg/heroes3/smallRing.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/southOfHell.JSON b/Mods/vcmi/config/rmg/heroes3/southOfHell.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/southOfHell.JSON rename to Mods/vcmi/config/rmg/heroes3/southOfHell.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3/worldsAtWar.JSON b/Mods/vcmi/config/rmg/heroes3/worldsAtWar.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3/worldsAtWar.JSON rename to Mods/vcmi/config/rmg/heroes3/worldsAtWar.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3unused/dragon.json b/Mods/vcmi/config/rmg/heroes3unused/dragon.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3unused/dragon.json rename to Mods/vcmi/config/rmg/heroes3unused/dragon.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3unused/gauntlet.JSON b/Mods/vcmi/config/rmg/heroes3unused/gauntlet.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3unused/gauntlet.JSON rename to Mods/vcmi/config/rmg/heroes3unused/gauntlet.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3unused/ring.JSON b/Mods/vcmi/config/rmg/heroes3unused/ring.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3unused/ring.JSON rename to Mods/vcmi/config/rmg/heroes3unused/ring.json diff --git a/Mods/vcmi/config/vcmi/rmg/heroes3unused/riseOfPhoenix.JSON b/Mods/vcmi/config/rmg/heroes3unused/riseOfPhoenix.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/heroes3unused/riseOfPhoenix.JSON rename to Mods/vcmi/config/rmg/heroes3unused/riseOfPhoenix.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm0k.JSON b/Mods/vcmi/config/rmg/symmetric/2sm0k.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm0k.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm0k.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2a.JSON b/Mods/vcmi/config/rmg/symmetric/2sm2a.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2a.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2a.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2b(2).JSON b/Mods/vcmi/config/rmg/symmetric/2sm2b(2).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2b(2).JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2b(2).json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2b.JSON b/Mods/vcmi/config/rmg/symmetric/2sm2b.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2b.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2b.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2c.JSON b/Mods/vcmi/config/rmg/symmetric/2sm2c.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2c.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2c.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2f(2).JSON b/Mods/vcmi/config/rmg/symmetric/2sm2f(2).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2f(2).JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2f(2).json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2f.JSON b/Mods/vcmi/config/rmg/symmetric/2sm2f.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2f.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2f.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2h(2).JSON b/Mods/vcmi/config/rmg/symmetric/2sm2h(2).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2h(2).JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2h(2).json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2h.JSON b/Mods/vcmi/config/rmg/symmetric/2sm2h.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2h.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2h.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2i(2).JSON b/Mods/vcmi/config/rmg/symmetric/2sm2i(2).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2i(2).JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2i(2).json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm2i.JSON b/Mods/vcmi/config/rmg/symmetric/2sm2i.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm2i.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm2i.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm4d(2).JSON b/Mods/vcmi/config/rmg/symmetric/2sm4d(2).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm4d(2).JSON rename to Mods/vcmi/config/rmg/symmetric/2sm4d(2).json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm4d(3).JSON b/Mods/vcmi/config/rmg/symmetric/2sm4d(3).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm4d(3).JSON rename to Mods/vcmi/config/rmg/symmetric/2sm4d(3).json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/2sm4d.JSON b/Mods/vcmi/config/rmg/symmetric/2sm4d.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/2sm4d.JSON rename to Mods/vcmi/config/rmg/symmetric/2sm4d.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/3sb0b.JSON b/Mods/vcmi/config/rmg/symmetric/3sb0b.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/3sb0b.JSON rename to Mods/vcmi/config/rmg/symmetric/3sb0b.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/3sb0c.JSON b/Mods/vcmi/config/rmg/symmetric/3sb0c.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/3sb0c.JSON rename to Mods/vcmi/config/rmg/symmetric/3sb0c.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/3sm3d.JSON b/Mods/vcmi/config/rmg/symmetric/3sm3d.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/3sm3d.JSON rename to Mods/vcmi/config/rmg/symmetric/3sm3d.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/4sm0d.JSON b/Mods/vcmi/config/rmg/symmetric/4sm0d.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/4sm0d.JSON rename to Mods/vcmi/config/rmg/symmetric/4sm0d.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/4sm0f.JSON b/Mods/vcmi/config/rmg/symmetric/4sm0f.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/4sm0f.JSON rename to Mods/vcmi/config/rmg/symmetric/4sm0f.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/4sm0g.JSON b/Mods/vcmi/config/rmg/symmetric/4sm0g.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/4sm0g.JSON rename to Mods/vcmi/config/rmg/symmetric/4sm0g.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/4sm4e.JSON b/Mods/vcmi/config/rmg/symmetric/4sm4e.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/4sm4e.JSON rename to Mods/vcmi/config/rmg/symmetric/4sm4e.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/5sb0a.JSON b/Mods/vcmi/config/rmg/symmetric/5sb0a.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/5sb0a.JSON rename to Mods/vcmi/config/rmg/symmetric/5sb0a.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/5sb0b.JSON b/Mods/vcmi/config/rmg/symmetric/5sb0b.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/5sb0b.JSON rename to Mods/vcmi/config/rmg/symmetric/5sb0b.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/6lm10.JSON b/Mods/vcmi/config/rmg/symmetric/6lm10.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/6lm10.JSON rename to Mods/vcmi/config/rmg/symmetric/6lm10.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/6lm10a.JSON b/Mods/vcmi/config/rmg/symmetric/6lm10a.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/6lm10a.JSON rename to Mods/vcmi/config/rmg/symmetric/6lm10a.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/6sm0b.JSON b/Mods/vcmi/config/rmg/symmetric/6sm0b.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/6sm0b.JSON rename to Mods/vcmi/config/rmg/symmetric/6sm0b.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/6sm0d.JSON b/Mods/vcmi/config/rmg/symmetric/6sm0d.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/6sm0d.JSON rename to Mods/vcmi/config/rmg/symmetric/6sm0d.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/6sm0e.JSON b/Mods/vcmi/config/rmg/symmetric/6sm0e.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/6sm0e.JSON rename to Mods/vcmi/config/rmg/symmetric/6sm0e.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/7sb0b.JSON b/Mods/vcmi/config/rmg/symmetric/7sb0b.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/7sb0b.JSON rename to Mods/vcmi/config/rmg/symmetric/7sb0b.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/7sb0c.JSON b/Mods/vcmi/config/rmg/symmetric/7sb0c.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/7sb0c.JSON rename to Mods/vcmi/config/rmg/symmetric/7sb0c.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8mm0e.JSON b/Mods/vcmi/config/rmg/symmetric/8mm0e.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8mm0e.JSON rename to Mods/vcmi/config/rmg/symmetric/8mm0e.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8mm6.JSON b/Mods/vcmi/config/rmg/symmetric/8mm6.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8mm6.JSON rename to Mods/vcmi/config/rmg/symmetric/8mm6.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8mm6a.JSON b/Mods/vcmi/config/rmg/symmetric/8mm6a.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8mm6a.JSON rename to Mods/vcmi/config/rmg/symmetric/8mm6a.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8sm0c.JSON b/Mods/vcmi/config/rmg/symmetric/8sm0c.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8sm0c.JSON rename to Mods/vcmi/config/rmg/symmetric/8sm0c.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8sm0f.JSON b/Mods/vcmi/config/rmg/symmetric/8sm0f.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8sm0f.JSON rename to Mods/vcmi/config/rmg/symmetric/8sm0f.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8xm12.JSON b/Mods/vcmi/config/rmg/symmetric/8xm12.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8xm12.JSON rename to Mods/vcmi/config/rmg/symmetric/8xm12.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8xm12a.JSON b/Mods/vcmi/config/rmg/symmetric/8xm12a.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8xm12a.JSON rename to Mods/vcmi/config/rmg/symmetric/8xm12a.json diff --git a/Mods/vcmi/config/vcmi/rmg/symmetric/8xm8.JSON b/Mods/vcmi/config/rmg/symmetric/8xm8.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/symmetric/8xm8.JSON rename to Mods/vcmi/config/rmg/symmetric/8xm8.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/2mm2h.JSON b/Mods/vcmi/config/rmg/unknownUnused/2mm2h.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/2mm2h.JSON rename to Mods/vcmi/config/rmg/unknownUnused/2mm2h.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/2x2sm4d(3).JSON b/Mods/vcmi/config/rmg/unknownUnused/2x2sm4d(3).json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/2x2sm4d(3).JSON rename to Mods/vcmi/config/rmg/unknownUnused/2x2sm4d(3).json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/4mm2h.JSON b/Mods/vcmi/config/rmg/unknownUnused/4mm2h.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/4mm2h.JSON rename to Mods/vcmi/config/rmg/unknownUnused/4mm2h.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/4sm3i.JSON b/Mods/vcmi/config/rmg/unknownUnused/4sm3i.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/4sm3i.JSON rename to Mods/vcmi/config/rmg/unknownUnused/4sm3i.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/6lm10a.JSON b/Mods/vcmi/config/rmg/unknownUnused/6lm10a.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/6lm10a.JSON rename to Mods/vcmi/config/rmg/unknownUnused/6lm10a.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/8xm12 huge.JSON b/Mods/vcmi/config/rmg/unknownUnused/8xm12 huge.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/8xm12 huge.JSON rename to Mods/vcmi/config/rmg/unknownUnused/8xm12 huge.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/8xm8 huge.JSON b/Mods/vcmi/config/rmg/unknownUnused/8xm8 huge.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/8xm8 huge.JSON rename to Mods/vcmi/config/rmg/unknownUnused/8xm8 huge.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/analogy.json b/Mods/vcmi/config/rmg/unknownUnused/analogy.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/analogy.json rename to Mods/vcmi/config/rmg/unknownUnused/analogy.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/cross.JSON b/Mods/vcmi/config/rmg/unknownUnused/cross.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/cross.JSON rename to Mods/vcmi/config/rmg/unknownUnused/cross.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/cross2.JSON b/Mods/vcmi/config/rmg/unknownUnused/cross2.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/cross2.JSON rename to Mods/vcmi/config/rmg/unknownUnused/cross2.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/cross3.JSON b/Mods/vcmi/config/rmg/unknownUnused/cross3.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/cross3.JSON rename to Mods/vcmi/config/rmg/unknownUnused/cross3.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/deux paires.JSON b/Mods/vcmi/config/rmg/unknownUnused/deux paires.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/deux paires.JSON rename to Mods/vcmi/config/rmg/unknownUnused/deux paires.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/doubled 8mm6.JSON b/Mods/vcmi/config/rmg/unknownUnused/doubled 8mm6.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/doubled 8mm6.JSON rename to Mods/vcmi/config/rmg/unknownUnused/doubled 8mm6.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/elka.JSON b/Mods/vcmi/config/rmg/unknownUnused/elka.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/elka.JSON rename to Mods/vcmi/config/rmg/unknownUnused/elka.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/goldenRing.json b/Mods/vcmi/config/rmg/unknownUnused/goldenRing.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/goldenRing.json rename to Mods/vcmi/config/rmg/unknownUnused/goldenRing.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/greatSands.JSON b/Mods/vcmi/config/rmg/unknownUnused/greatSands.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/greatSands.JSON rename to Mods/vcmi/config/rmg/unknownUnused/greatSands.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/kite.JSON b/Mods/vcmi/config/rmg/unknownUnused/kite.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/kite.JSON rename to Mods/vcmi/config/rmg/unknownUnused/kite.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/upgrade.json b/Mods/vcmi/config/rmg/unknownUnused/upgrade.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/upgrade.json rename to Mods/vcmi/config/rmg/unknownUnused/upgrade.json diff --git a/Mods/vcmi/config/vcmi/rmg/unknownUnused/wheel.JSON b/Mods/vcmi/config/rmg/unknownUnused/wheel.json similarity index 100% rename from Mods/vcmi/config/vcmi/rmg/unknownUnused/wheel.JSON rename to Mods/vcmi/config/rmg/unknownUnused/wheel.json diff --git a/Mods/vcmi/config/vcmi/russian.json b/Mods/vcmi/config/russian.json similarity index 100% rename from Mods/vcmi/config/vcmi/russian.json rename to Mods/vcmi/config/russian.json diff --git a/Mods/vcmi/config/vcmi/spanish.json b/Mods/vcmi/config/spanish.json similarity index 100% rename from Mods/vcmi/config/vcmi/spanish.json rename to Mods/vcmi/config/spanish.json diff --git a/Mods/vcmi/config/vcmi/spells.json b/Mods/vcmi/config/spells.json similarity index 100% rename from Mods/vcmi/config/vcmi/spells.json rename to Mods/vcmi/config/spells.json diff --git a/Mods/vcmi/config/vcmi/swedish.json b/Mods/vcmi/config/swedish.json similarity index 100% rename from Mods/vcmi/config/vcmi/swedish.json rename to Mods/vcmi/config/swedish.json diff --git a/Mods/vcmi/config/vcmi/towerCreature.json b/Mods/vcmi/config/towerCreature.json similarity index 100% rename from Mods/vcmi/config/vcmi/towerCreature.json rename to Mods/vcmi/config/towerCreature.json diff --git a/Mods/vcmi/config/vcmi/towerFactions.json b/Mods/vcmi/config/towerFactions.json similarity index 100% rename from Mods/vcmi/config/vcmi/towerFactions.json rename to Mods/vcmi/config/towerFactions.json diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/ukrainian.json similarity index 100% rename from Mods/vcmi/config/vcmi/ukrainian.json rename to Mods/vcmi/config/ukrainian.json diff --git a/Mods/vcmi/config/vcmi/vietnamese.json b/Mods/vcmi/config/vietnamese.json similarity index 100% rename from Mods/vcmi/config/vcmi/vietnamese.json rename to Mods/vcmi/config/vietnamese.json diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index b1b6ba9dd..b561b666f 100644 --- a/Mods/vcmi/mod.json +++ b/Mods/vcmi/mod.json @@ -8,7 +8,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/chinese.json" + "config/chinese.json" ] }, @@ -18,7 +18,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/czech.json" + "config/czech.json" ] }, @@ -29,7 +29,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/french.json" + "config/french.json" ] }, @@ -40,7 +40,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/german.json" + "config/german.json" ] }, @@ -51,7 +51,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/polish.json" + "config/polish.json" ] }, @@ -62,7 +62,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/portuguese.json" + "config/portuguese.json" ] }, @@ -73,7 +73,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/russian.json" + "config/russian.json" ] }, @@ -84,7 +84,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/spanish.json" + "config/spanish.json" ] }, @@ -95,7 +95,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/swedish.json" + "config/swedish.json" ] }, @@ -106,7 +106,7 @@ "skipValidation" : true, "translations" : [ - "config/vcmi/ukrainian.json" + "config/ukrainian.json" ] }, @@ -116,7 +116,7 @@ "author": "Vũ Đắc Hoàng Ân", "skipValidation": true, "translations": [ - "config/vcmi/vietnamese.json" + "config/vietnamese.json" ] }, @@ -125,95 +125,95 @@ "contact" : "http://forum.vcmi.eu/index.php", "modType" : "Graphical", - "factions" : [ "config/vcmi/towerFactions" ], - "creatures" : [ "config/vcmi/towerCreature" ], - "spells" : [ "config/vcmi/spells" ], + "factions" : [ "config/towerFactions" ], + "creatures" : [ "config/towerCreature" ], + "spells" : [ "config/spells" ], "translations" : [ - "config/vcmi/english.json" + "config/english.json" ], "templates" : [ - "config/vcmi/rmg/hdmod/aroundamarsh.JSON", - "config/vcmi/rmg/hdmod/balance.JSON", - "config/vcmi/rmg/hdmod/blockbuster.JSON", - "config/vcmi/rmg/hdmod/clashOfDragons.json", - "config/vcmi/rmg/hdmod/coldshadowsFantasy.json", - "config/vcmi/rmg/hdmod/cube.JSON", - "config/vcmi/rmg/hdmod/diamond.JSON", - "config/vcmi/rmg/hdmod/extreme.JSON", - "config/vcmi/rmg/hdmod/extreme2.JSON", - "config/vcmi/rmg/hdmod/fear.JSON", - "config/vcmi/rmg/hdmod/frozenDragons.JSON", - "config/vcmi/rmg/hdmod/gimlisRevenge.JSON", - "config/vcmi/rmg/hdmod/guerilla.JSON", - "config/vcmi/rmg/hdmod/headquarters.JSON", - "config/vcmi/rmg/hdmod/hypercube.JSON", - "config/vcmi/rmg/hdmod/jebusCross.json", - "config/vcmi/rmg/hdmod/longRun.JSON", - "config/vcmi/rmg/hdmod/marathon.JSON", - "config/vcmi/rmg/hdmod/miniNostalgia.JSON", - "config/vcmi/rmg/hdmod/nostalgia.JSON", - "config/vcmi/rmg/hdmod/oceansEleven.JSON", - "config/vcmi/rmg/hdmod/panic.JSON", - "config/vcmi/rmg/hdmod/poorJebus.JSON", - "config/vcmi/rmg/hdmod/reckless.JSON", - "config/vcmi/rmg/hdmod/roadrunner.JSON", - "config/vcmi/rmg/hdmod/shaaafworld.JSON", - "config/vcmi/rmg/hdmod/skirmish.JSON", - "config/vcmi/rmg/hdmod/speed1.JSON", - "config/vcmi/rmg/hdmod/speed2.JSON", - "config/vcmi/rmg/hdmod/spider.JSON", - "config/vcmi/rmg/hdmod/superslam.JSON", - "config/vcmi/rmg/hdmod/triad.JSON", - "config/vcmi/rmg/hdmod/vortex.JSON", - "config/vcmi/rmg/heroes3/dwarvenTunnels.JSON", - "config/vcmi/rmg/heroes3/golemsAplenty.JSON", - "config/vcmi/rmg/heroes3/meetingInMuzgob.JSON", - "config/vcmi/rmg/heroes3/monksRetreat.JSON", - "config/vcmi/rmg/heroes3/newcomers.JSON", - "config/vcmi/rmg/heroes3/readyOrNot.JSON", - "config/vcmi/rmg/heroes3/smallRing.JSON", - "config/vcmi/rmg/heroes3/southOfHell.JSON", - "config/vcmi/rmg/heroes3/worldsAtWar.JSON", - "config/vcmi/rmg/symmetric/2sm0k.JSON", - "config/vcmi/rmg/symmetric/2sm2a.JSON", - "config/vcmi/rmg/symmetric/2sm2b.JSON", - "config/vcmi/rmg/symmetric/2sm2b(2).JSON", - "config/vcmi/rmg/symmetric/2sm2c.JSON", - "config/vcmi/rmg/symmetric/2sm2f.JSON", - "config/vcmi/rmg/symmetric/2sm2f(2).JSON", - "config/vcmi/rmg/symmetric/2sm2h.JSON", - "config/vcmi/rmg/symmetric/2sm2h(2).JSON", - "config/vcmi/rmg/symmetric/2sm2i.JSON", - "config/vcmi/rmg/symmetric/2sm2i(2).JSON", - "config/vcmi/rmg/symmetric/2sm4d.JSON", - "config/vcmi/rmg/symmetric/2sm4d(2).JSON", - "config/vcmi/rmg/symmetric/2sm4d(3).JSON", - "config/vcmi/rmg/symmetric/3sb0b.JSON", - "config/vcmi/rmg/symmetric/3sb0c.JSON", - "config/vcmi/rmg/symmetric/3sm3d.JSON", - "config/vcmi/rmg/symmetric/4sm0d.JSON", - "config/vcmi/rmg/symmetric/4sm0f.JSON", - "config/vcmi/rmg/symmetric/4sm0g.JSON", - "config/vcmi/rmg/symmetric/4sm4e.JSON", - "config/vcmi/rmg/symmetric/5sb0a.JSON", - "config/vcmi/rmg/symmetric/5sb0b.JSON", - "config/vcmi/rmg/symmetric/6lm10.JSON", - "config/vcmi/rmg/symmetric/6lm10a.JSON", - "config/vcmi/rmg/symmetric/6sm0b.JSON", - "config/vcmi/rmg/symmetric/6sm0d.JSON", - "config/vcmi/rmg/symmetric/6sm0e.JSON", - "config/vcmi/rmg/symmetric/7sb0b.JSON", - "config/vcmi/rmg/symmetric/7sb0c.JSON", - "config/vcmi/rmg/symmetric/8mm0e.JSON", - "config/vcmi/rmg/symmetric/8mm6.JSON", - "config/vcmi/rmg/symmetric/8mm6a.JSON", - "config/vcmi/rmg/symmetric/8sm0c.JSON", - "config/vcmi/rmg/symmetric/8sm0f.JSON", - "config/vcmi/rmg/symmetric/8xm12.JSON", - "config/vcmi/rmg/symmetric/8xm12a.JSON", - "config/vcmi/rmg/symmetric/8xm8.JSON" + "config/rmg/hdmod/aroundamarsh.JSON", + "config/rmg/hdmod/balance.JSON", + "config/rmg/hdmod/blockbuster.JSON", + "config/rmg/hdmod/clashOfDragons.json", + "config/rmg/hdmod/coldshadowsFantasy.json", + "config/rmg/hdmod/cube.JSON", + "config/rmg/hdmod/diamond.JSON", + "config/rmg/hdmod/extreme.JSON", + "config/rmg/hdmod/extreme2.JSON", + "config/rmg/hdmod/fear.JSON", + "config/rmg/hdmod/frozenDragons.JSON", + "config/rmg/hdmod/gimlisRevenge.JSON", + "config/rmg/hdmod/guerilla.JSON", + "config/rmg/hdmod/headquarters.JSON", + "config/rmg/hdmod/hypercube.JSON", + "config/rmg/hdmod/jebusCross.json", + "config/rmg/hdmod/longRun.JSON", + "config/rmg/hdmod/marathon.JSON", + "config/rmg/hdmod/miniNostalgia.JSON", + "config/rmg/hdmod/nostalgia.JSON", + "config/rmg/hdmod/oceansEleven.JSON", + "config/rmg/hdmod/panic.JSON", + "config/rmg/hdmod/poorJebus.JSON", + "config/rmg/hdmod/reckless.JSON", + "config/rmg/hdmod/roadrunner.JSON", + "config/rmg/hdmod/shaaafworld.JSON", + "config/rmg/hdmod/skirmish.JSON", + "config/rmg/hdmod/speed1.JSON", + "config/rmg/hdmod/speed2.JSON", + "config/rmg/hdmod/spider.JSON", + "config/rmg/hdmod/superslam.JSON", + "config/rmg/hdmod/triad.JSON", + "config/rmg/hdmod/vortex.JSON", + "config/rmg/heroes3/dwarvenTunnels.JSON", + "config/rmg/heroes3/golemsAplenty.JSON", + "config/rmg/heroes3/meetingInMuzgob.JSON", + "config/rmg/heroes3/monksRetreat.JSON", + "config/rmg/heroes3/newcomers.JSON", + "config/rmg/heroes3/readyOrNot.JSON", + "config/rmg/heroes3/smallRing.JSON", + "config/rmg/heroes3/southOfHell.JSON", + "config/rmg/heroes3/worldsAtWar.JSON", + "config/rmg/symmetric/2sm0k.JSON", + "config/rmg/symmetric/2sm2a.JSON", + "config/rmg/symmetric/2sm2b.JSON", + "config/rmg/symmetric/2sm2b(2).JSON", + "config/rmg/symmetric/2sm2c.JSON", + "config/rmg/symmetric/2sm2f.JSON", + "config/rmg/symmetric/2sm2f(2).JSON", + "config/rmg/symmetric/2sm2h.JSON", + "config/rmg/symmetric/2sm2h(2).JSON", + "config/rmg/symmetric/2sm2i.JSON", + "config/rmg/symmetric/2sm2i(2).JSON", + "config/rmg/symmetric/2sm4d.JSON", + "config/rmg/symmetric/2sm4d(2).JSON", + "config/rmg/symmetric/2sm4d(3).JSON", + "config/rmg/symmetric/3sb0b.JSON", + "config/rmg/symmetric/3sb0c.JSON", + "config/rmg/symmetric/3sm3d.JSON", + "config/rmg/symmetric/4sm0d.JSON", + "config/rmg/symmetric/4sm0f.JSON", + "config/rmg/symmetric/4sm0g.JSON", + "config/rmg/symmetric/4sm4e.JSON", + "config/rmg/symmetric/5sb0a.JSON", + "config/rmg/symmetric/5sb0b.JSON", + "config/rmg/symmetric/6lm10.JSON", + "config/rmg/symmetric/6lm10a.JSON", + "config/rmg/symmetric/6sm0b.JSON", + "config/rmg/symmetric/6sm0d.JSON", + "config/rmg/symmetric/6sm0e.JSON", + "config/rmg/symmetric/7sb0b.JSON", + "config/rmg/symmetric/7sb0c.JSON", + "config/rmg/symmetric/8mm0e.JSON", + "config/rmg/symmetric/8mm6.JSON", + "config/rmg/symmetric/8mm6a.JSON", + "config/rmg/symmetric/8sm0c.JSON", + "config/rmg/symmetric/8sm0f.JSON", + "config/rmg/symmetric/8xm12.JSON", + "config/rmg/symmetric/8xm12a.JSON", + "config/rmg/symmetric/8xm8.JSON" ], "filesystem": diff --git a/docs/modders/Mod_File_Format.md b/docs/modders/Mod_File_Format.md index 3379ba7f2..8a689ccf8 100644 --- a/docs/modders/Mod_File_Format.md +++ b/docs/modders/Mod_File_Format.md @@ -103,19 +103,19 @@ These are fields that are present only in local mod.json file // list of factions/towns configuration files "factions" : [ - "config/myMod/faction.json" + "config/faction.json" ] // List of hero classes configuration files "heroClasses" : [ - "config/myMod/heroClasses.json" + "config/heroClasses.json" ], // List of heroes configuration files "heroes" : [ - "config/myMod/heroes.json" + "config/heroes.json" ], // List of configuration files for skills @@ -124,68 +124,68 @@ These are fields that are present only in local mod.json file // list of creature configuration files "creatures" : [ - "config/myMod/creatures.json" + "config/creatures.json" ], // List of artifacts configuration files "artifacts" : [ - "config/myMod/artifacts.json" + "config/artifacts.json" ], // List of objects defined in this mod "objects" : [ - "config/myMod/objects.json" + "config/objects.json" ], // List of spells defined in this mod "spells" : [ - "config/myMod/spells.json" + "config/spells.json" ], // List of configuration files for terrains "terrains" : [ - "config/myMod/terrains.json" + "config/terrains.json" ], // List of configuration files for roads "roads" : [ - "config/myMod/roads.json" + "config/roads.json" ], // List of configuration files for rivers "rivers" : [ - "config/myMod/rivers.json" + "config/rivers.json" ], // List of configuration files for battlefields "battlefields" : [ - "config/myMod/battlefields.json" + "config/battlefields.json" ], // List of configuration files for obstacles "obstacles" : [ - "config/myMod/obstacles.json" + "config/obstacles.json" ], // List of RMG templates defined in this mod "templates" : [ - "config/myMod/templates.json" + "config/templates.json" ], // Optional, primaly used by translation mods // Defines strings that are translated by mod into base language specified in "language" field "translations" : [ - "config/myMod/englishStrings.json + "config/englishStrings.json ] } ``` @@ -201,7 +201,7 @@ See [Translations](Translations.md) for more information "description" : "", "author" : "", "translations" : [ - "config//.json" + "config/.json" ] }, ``` diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 52c5f4dbc..3c142b063 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -15,12 +15,13 @@ #include "filesystem/Filesystem.h" -#include "GameConstants.h" #include "CCreatureHandler.h" +#include "GameConstants.h" +#include "VCMI_Lib.h" +#include "modding/ModScope.h" +#include "spells/CSpellHandler.h" #include "texts/CGeneralTextHandler.h" #include "json/JsonUtils.h" -#include "spells/CSpellHandler.h" -#include "VCMI_Lib.h" template class std::vector; @@ -201,7 +202,8 @@ ImagePath CBonusTypeHandler::bonusToGraphics(const std::shared_ptr & bonu void CBonusTypeHandler::load() { JsonNode gameConf(JsonPath::builtin("config/gameConfig.json")); - JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"].convertTo>())); + gameConf.setModScope(ModScope::scopeBuiltin()); + JsonNode config(JsonUtils::assembleFromFiles(gameConf["bonuses"])); config.setModScope("vcmi"); load(config); } diff --git a/lib/json/JsonNode.cpp b/lib/json/JsonNode.cpp index 4d0e7ddd5..3fd979e00 100644 --- a/lib/json/JsonNode.cpp +++ b/lib/json/JsonNode.cpp @@ -110,17 +110,17 @@ JsonNode::JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserS *this = parser.parse(fileURI.getName()); } -JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx) +JsonNode::JsonNode(const JsonPath & fileURI, const std::string & modName) { - auto file = CResourceHandler::get(idx)->load(fileURI)->readAll(); + auto file = CResourceHandler::get(modName)->load(fileURI)->readAll(); JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); } -JsonNode::JsonNode(const JsonPath & fileURI, bool & isValidSyntax) +JsonNode::JsonNode(const JsonPath & fileURI, const std::string & modName, bool & isValidSyntax) { - auto file = CResourceHandler::get()->load(fileURI)->readAll(); + auto file = CResourceHandler::get(modName)->load(fileURI)->readAll(); JsonParser parser(reinterpret_cast(file.first.get()), file.second, JsonParsingSettings()); *this = parser.parse(fileURI.getName()); diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index f4338423a..7ac55f57e 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -79,7 +79,7 @@ public: explicit JsonNode(const JsonPath & fileURI); explicit JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings); explicit JsonNode(const JsonPath & fileURI, const std::string & modName); - explicit JsonNode(const JsonPath & fileURI, bool & isValidSyntax); + explicit JsonNode(const JsonPath & fileURI, const std::string & modName, bool & isValidSyntax); bool operator==(const JsonNode & other) const; bool operator!=(const JsonNode & other) const; diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 0388d03e3..b335e3e5d 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -234,8 +234,9 @@ JsonNode JsonUtils::assembleFromFiles(const JsonNode & files, bool & isValid) { if (files.isVector()) { + assert(!files.getModScope().empty()); auto configList = files.convertTo >(); - JsonNode result = JsonUtils::assembleFromFiles(configList, isValid); + JsonNode result = JsonUtils::assembleFromFiles(configList, files.getModScope(), isValid); return result; } @@ -255,10 +256,10 @@ JsonNode JsonUtils::assembleFromFiles(const JsonNode & files) JsonNode JsonUtils::assembleFromFiles(const std::vector & files) { bool isValid = false; - return assembleFromFiles(files, isValid); + return assembleFromFiles(files, "", isValid); } -JsonNode JsonUtils::assembleFromFiles(const std::vector & files, bool & isValid) +JsonNode JsonUtils::assembleFromFiles(const std::vector & files, std::string modName, bool & isValid) { isValid = true; JsonNode result; @@ -267,10 +268,10 @@ JsonNode JsonUtils::assembleFromFiles(const std::vector & files, bo { JsonPath path = JsonPath::builtinTODO(file); - if (CResourceHandler::get()->existsResource(path)) + if (CResourceHandler::get(modName)->existsResource(path)) { bool isValidFile = false; - JsonNode section(JsonPath::builtinTODO(file), isValidFile); + JsonNode section(JsonPath::builtinTODO(file), modName, isValidFile); merge(result, section); isValid |= isValidFile; } diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index 85c322bcb..17d8941a0 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -47,7 +47,7 @@ namespace JsonUtils DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files); DLL_LINKAGE JsonNode assembleFromFiles(const JsonNode & files, bool & isValid); DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files); - DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files, bool & isValid); + DLL_LINKAGE JsonNode assembleFromFiles(const std::vector & files, std::string modName, bool & isValid); /// This version loads all files with same name (overridden by mods) DLL_LINKAGE JsonNode assembleFromFiles(const std::string & filename); diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index f1534bc62..c68491222 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -385,7 +385,7 @@ void CModHandler::loadModFilesystems() if (getModSoftDependencies(leftModName).count(rightModName) || getModSoftDependencies(rightModName).count(leftModName)) continue; - const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY && path.getType() != EResType::JSON;}; std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); std::unordered_set rightResources = modFilesystems[rightModName]->getFilteredFiles(filter); From 1f0847660b32952bed8cdffc11b7e278abbe3505 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 31 Oct 2024 21:57:20 +0100 Subject: [PATCH 498/726] possibility to delete unsupported saves --- Mods/vcmi/config/english.json | 1 + client/lobby/SelectionTab.cpp | 34 ++++++++++++++++++++++++++++++- lib/networkPacks/NetPackVisitor.h | 1 + lib/networkPacks/NetPacksLib.cpp | 5 +++++ lib/networkPacks/PacksForLobby.h | 18 ++++++++++++++++ lib/serializer/RegisterTypes.h | 1 + server/LobbyNetPackVisitors.h | 2 ++ server/NetPacksLobbyServer.cpp | 18 +++++++++++++++- 8 files changed, 78 insertions(+), 2 deletions(-) diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/config/english.json index 498741306..c0d674f63 100644 --- a/Mods/vcmi/config/english.json +++ b/Mods/vcmi/config/english.json @@ -106,6 +106,7 @@ "vcmi.lobby.handicap.resource" : "Gives players appropriate resources to start with in addition to the normal starting resources. Negative values are allowed, but are limited to 0 in total (the player never starts with negative resources).", "vcmi.lobby.handicap.income" : "Changes the player's various incomes by the percentage. Is rounded up.", "vcmi.lobby.handicap.growth" : "Changes the growth rate of creatures in the towns owned by the player. Is rounded up.", + "vcmi.lobby.deleteUnsupportedSave" : "Unsupported saves found (e.g. from previous versions).\n\nDelete them?", "vcmi.lobby.login.title" : "VCMI Online Lobby", "vcmi.lobby.login.username" : "Username:", diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 8029dac48..1554d183e 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -42,9 +42,11 @@ #include "../../lib/mapping/CMapInfo.h" #include "../../lib/mapping/CMapHeader.h" #include "../../lib/mapping/MapFormat.h" +#include "../../lib/networkPacks/PacksForLobby.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/texts/TextOperations.h" #include "../../lib/TerrainHandler.h" +#include "../../lib/UnlockGuard.h" bool mapSorter::operator()(const std::shared_ptr aaa, const std::shared_ptr bbb) { @@ -823,6 +825,15 @@ void SelectionTab::parseMaps(const std::unordered_set & files) void SelectionTab::parseSaves(const std::unordered_set & files) { + enum Choice { NONE, REMAIN, DELETE }; + Choice deleteUnsupported = NONE; + auto doDeleteUnsupported = [](std::string file){ + LobbyDelete ld; + ld.type = LobbyDelete::SAVEGAME; + ld.name = file; + CSH->sendLobbyPack(ld); + }; + for(auto & file : files) { try @@ -863,7 +874,28 @@ void SelectionTab::parseSaves(const std::unordered_set & files) } catch(const std::exception & e) { - logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what()); + // asking for deletion of unsupported saves + if(CSH->isHost()) + { + if(deleteUnsupported == DELETE) + doDeleteUnsupported(file.getName()); + else if(deleteUnsupported == NONE) + { + CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteUnsupportedSave"), std::vector>(), [&deleteUnsupported, doDeleteUnsupported, file](){ + doDeleteUnsupported(file.getName()); + deleteUnsupported = DELETE; + }, [&deleteUnsupported](){ deleteUnsupported = REMAIN; }); + + while(deleteUnsupported == NONE) + { + auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); + boost::this_thread::sleep_for(boost::chrono::milliseconds(5)); + } + } + } + + if(deleteUnsupported != DELETE) + logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what()); } } } diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 09a67e354..335f7caa1 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -178,6 +178,7 @@ public: virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {} virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {} virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {} + virtual void visitLobbyDelete(LobbyDelete & pack) {} virtual void visitSaveLocalState(SaveLocalState & pack) {} }; diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 225fe9365..a4c6d2a96 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -842,6 +842,11 @@ void LobbyPvPAction::visitTyped(ICPackVisitor & visitor) visitor.visitLobbyPvPAction(*this); } +void LobbyDelete::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitLobbyDelete(*this); +} + void SetResources::applyGs(CGameState *gs) { assert(player.isValidPlayer()); diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index 1477bc98e..7fafbae9d 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -381,4 +381,22 @@ struct DLL_LINKAGE LobbyPvPAction : public CLobbyPackToServer } }; +struct DLL_LINKAGE LobbyDelete : public CLobbyPackToServer +{ + enum EType : ui8 { + SAVEGAME, SAVEGAME_FOLDER, RANDOMMAP + }; + + EType type = SAVEGAME; + std::string name; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler &h) + { + h & type; + h & name; + } +}; + VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 073b68ae9..c3dab0559 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -294,6 +294,7 @@ void registerTypes(Serializer &s) s.template registerType(241); s.template registerType(242); s.template registerType(243); + s.template registerType(244); } VCMI_LIB_NAMESPACE_END diff --git a/server/LobbyNetPackVisitors.h b/server/LobbyNetPackVisitors.h index 0905aed48..1e103890b 100644 --- a/server/LobbyNetPackVisitors.h +++ b/server/LobbyNetPackVisitors.h @@ -39,6 +39,7 @@ public: void visitLobbyChatMessage(LobbyChatMessage & pack) override; void visitLobbyGuiAction(LobbyGuiAction & pack) override; void visitLobbyPvPAction(LobbyPvPAction & pack) override; + void visitLobbyDelete(LobbyDelete & pack) override; }; class ApplyOnServerAfterAnnounceNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) @@ -96,4 +97,5 @@ public: void visitLobbySetDifficulty(LobbySetDifficulty & pack) override; void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) override; void visitLobbyPvPAction(LobbyPvPAction & pack) override; + void visitLobbyDelete(LobbyDelete & pack) override; }; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index d46799b12..7ed190236 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -22,6 +22,7 @@ #include "../lib/serializer/Connection.h" #include "../lib/mapping/CMapInfo.h" #include "../lib/mapping/CMapHeader.h" +#include "../lib/filesystem/Filesystem.h" void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack) { @@ -383,7 +384,6 @@ void ApplyOnServerNetPackVisitor::visitLobbyForceSetPlayer(LobbyForceSetPlayer & result = true; } - void ClientPermissionsCheckerNetPackVisitor::visitLobbyPvPAction(LobbyPvPAction & pack) { result = true; @@ -433,3 +433,19 @@ void ApplyOnServerNetPackVisitor::visitLobbyPvPAction(LobbyPvPAction & pack) } result = true; } + + +void ClientPermissionsCheckerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) +{ + result = srv.isClientHost(pack.c->connectionID); +} + +void ApplyOnServerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) +{ + if(pack.type == LobbyDelete::SAVEGAME) + { + auto res = ResourcePath(pack.name, EResType::SAVEGAME); + auto file = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); + boost::filesystem::remove(file); + } +} From 38c25cd13e038bc5ad2af581e120f8b12a1c2ae2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:11:55 +0100 Subject: [PATCH 499/726] Delete UI --- Mods/vcmi/Sprites/lobby/delete-normal.png | Bin 0 -> 997 bytes Mods/vcmi/Sprites/lobby/delete-pressed.png | Bin 0 -> 986 bytes Mods/vcmi/Sprites/lobby/deleteButton.json | 8 ++++ Mods/vcmi/config/english.json | 5 +++ client/lobby/SelectionTab.cpp | 50 ++++++++++++++++++--- client/lobby/SelectionTab.h | 3 ++ 6 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 Mods/vcmi/Sprites/lobby/delete-normal.png create mode 100644 Mods/vcmi/Sprites/lobby/delete-pressed.png create mode 100644 Mods/vcmi/Sprites/lobby/deleteButton.json diff --git a/Mods/vcmi/Sprites/lobby/delete-normal.png b/Mods/vcmi/Sprites/lobby/delete-normal.png new file mode 100644 index 0000000000000000000000000000000000000000..75913ed548f508e45afb71118a2a827f1934201b GIT binary patch literal 997 zcmVEX>4Tx04R}tkv&MmKpe$iQ$;Bi2a8A%%usc)i;6hbDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0Yan9G^;BHXnNI5 zCE{WxyDIj)B7hMLB8+jFS;m|sCE+=~?&0I>U6f~epZjz4sX2=QK9M-a46{nSK|Hfr zH8}4RhgeZoiO-2AOu8WPBi9v|-#8Z>7I0{s+IiwenMwZc;D~bidg4$0*RV3pDGt{e5iP%@e@?3|#4Lf29G;ev)2q zYmp zj1?$*-Q(T8oxS~grq$mM-NACvpqo0W00006VoOIv0RI600RN!9r;`8x010qNS#tmY z10VnZ0$2g9#|k|F00H$$L_t(I%cYb(ixW{4hM#k0Vv<6)a^q@eAr_)0TWBR%_ya8L z>|~eqgYCq>U?7cJ2nzmzZMR$ztQ9MT2$m9M7eg>)vu0eOOeE`;n{Q@+Bot=CK;P(C(ZpKks7cX2YNVnEtr`1ju zWTAQ3vDWFWwRxw(!XU#yJ=Mo%a|K#$?)?0eLf-;sj>ZL8x7Jt?#Q=?Bh+|v0+GcZQ zq0`Q=zZ8a7R|V8E=FLNQSFaTEWiU5DZ=1|*3j%b;2Zj8t$*|mqzAH7Wwnp}vWkO{m zHX|}nRR~m;!g_1%O7#oIHUsl*R>8WpMnR}NRQ&8TdE@}sqZzBygGx2?(DRdD+^{&& zWd4?uD?bYz+rcseh3X7ZIb_U>4P)EX>4Tx04R}tkv&MmKpe$iQ$;Bi2a8A%%usc)i;6hbDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0Yan9G^;BHXnNI5 zCE{WxyDIj)B7hMLB8+jFS;m|sCE+=~?&0I>U6f~epZjz4sX2=QK9M-a46{nSK|Hfr zH8}4RhgeZoiO-2AOu8WPBi9v|-#8Z>7I0{s+IiwenMwZc;D~bidg4$0*RV3pDGt{e5iP%@e@?3|#4Lf29G;ev)2q zYmp zj1?$*-Q(T8oxS~grq$mM-NACvpqo0W00006VoOIv0RI600RN!9r;`8x010qNS#tmY z10VnZ0$2g9#|k|F00HVrL_t(I%dM0>ixW{4hM#k1hD;Dm<;IWBLTtoMw$Mtj*dJhF zXD0?-MFb0ff&{h^I}887wp*?U){2!v7Az&oE{0&pW=ZBAi<^5hnaQNl)7^9KJiPb3 zXN~~Me~cL5`OZTkapcTGWWXQ*WU7d`bEb+a#70oSs$vz+B!-zPM&>|k;riw#A|N@* zs)QVXRR=L5h`2LAB<|j$ou>}S9P;AT-akLt0K`$b@MiL7?T0tjvU@sO%2}NQ1u(KG z91Q#1|M{7NVZRdg?&v!Y`k#1vG7NdwYd}N-@a1&E)}RN#)}U8{df27$4*>2SedCVJ zLf;HHS4AALo22YDTLF-V7GX0}WA|c<561P(`mxC}P+g2;r`~{Oi+jJm1n7In7>K5dUP0h7rg^^Ge%;-db4jB7Z1Gr`}+H<3=gJ3br&h0wsmMXYJ8(DZf1)tb}C? zQ-nB@wQkN#ZKY8w6{>>GNvv|S%KZ}1s)ql!3f4{1l2JuyFeNrZG@tBS*_4gRaiyC@ z=#}wrZmTW+DGQfh#}0B5pRIJ$LkG3^6=MVNSnBG7sK1Z8=d`kG&~ejgeneraltexth->arraytxt[229]; + tabTitle = "{" + CGI->generaltexth->arraytxt[229] + "}"; + tabTitleDelete = "{red|" + CGI->generaltexth->translate("vcmi.lobby.deleteMapTitle") + "}"; break; case ESelectionScreen::loadGame: - tabTitle = CGI->generaltexth->arraytxt[230]; + tabTitle = "{" + CGI->generaltexth->arraytxt[230] + "}"; + tabTitleDelete = "{red|" + CGI->generaltexth->translate("vcmi.lobby.deleteSaveGameTitle") + "}"; break; case ESelectionScreen::saveGame: positionsToShow = 16; - tabTitle = CGI->generaltexth->arraytxt[231]; + tabTitle = "{" + CGI->generaltexth->arraytxt[231] + "}"; + tabTitleDelete = "{red|" + CGI->generaltexth->translate("vcmi.lobby.deleteSaveGameTitle") + "}"; break; case ESelectionScreen::campaignList: - tabTitle = CGI->generaltexth->allTexts[726]; + tabTitle = "{" + CGI->generaltexth->allTexts[726] + "}"; setRedrawParent(true); // we use parent background so we need to make sure it's will be redrawn too pos.w = parent->pos.w; pos.h = parent->pos.h; @@ -227,12 +231,26 @@ SelectionTab::SelectionTab(ESelectionScreen Type) auto sortByDate = std::make_shared(Point(371, 85), AnimationPath::builtin("selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate), EShortcut::MAPS_SORT_CHANGEDATE); sortByDate->setOverlay(std::make_shared(ImagePath::builtin("lobby/selectionTabSortDate"))); buttonsSortBy.push_back(sortByDate); + + if(tabType == ESelectionScreen::loadGame || tabType == ESelectionScreen::saveGame || tabType == ESelectionScreen::newGame) + { + buttonDeleteMode = std::make_shared(Point(367, 18), AnimationPath::builtin("lobby/deleteButton"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.deleteMode")), [this, tabTitle, tabTitleDelete](){ + deleteMode = !deleteMode; + if(deleteMode) + labelTabTitle->setText(tabTitleDelete); + else + labelTabTitle->setText(tabTitle); + }); + + if(tabType == ESelectionScreen::newGame) + buttonDeleteMode->setEnabled(false); + } } for(int i = 0; i < positionsToShow; i++) listItems.push_back(std::make_shared(Point(30, 129 + i * 25))); - labelTabTitle = std::make_shared(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle); + labelTabTitle = std::make_shared(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, tabTitle); slider = std::make_shared(Point(372, 86 + (enableUiEnhancements ? 30 : 0)), (tabType != ESelectionScreen::saveGame ? 480 : 430) - (enableUiEnhancements ? 30 : 0), std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, Orientation::VERTICAL, CSlider::BLUE); slider->setPanningStep(24); @@ -311,7 +329,22 @@ void SelectionTab::clickReleased(const Point & cursorPosition) if(line != -1) { - select(line); + if(!deleteMode) + select(line); + else + { + int py = line + slider->getValue(); + vstd::amax(py, 0); + vstd::amin(py, curItems.size() - 1); + + if(curItems[py]->isFolder && boost::algorithm::starts_with(curItems[py]->folderName, "..")) + { + select(line); + return; + } + + std::cout << (curItems[py]->isFolder ? curItems[py]->folderName : curItems[py]->fullFileURI) << "\n"; + } } #ifdef VCMI_MOBILE // focus input field if clicked inside it @@ -477,6 +510,9 @@ void SelectionTab::filter(int size, bool selectFirst) curItems.clear(); + if(buttonDeleteMode) + buttonDeleteMode->setEnabled(tabType != ESelectionScreen::newGame || showRandom); + for(auto elem : allItems) { if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList) diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index 86c625a58..74dc43bd6 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -117,6 +117,9 @@ private: ESelectionScreen tabType; Rect inputNameRect; + std::shared_ptr buttonDeleteMode; + bool deleteMode; + auto checkSubfolder(std::string path); bool isMapSupported(const CMapInfo & info); From 164aac4db2f8a679791f190b0f83fd68bc1aa9ed Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:52:19 +0100 Subject: [PATCH 500/726] refresh list --- client/NetPacksLobbyClient.cpp | 2 +- client/lobby/SelectionTab.cpp | 12 ++++++++++-- lib/networkPacks/PacksForLobby.h | 3 +++ lib/serializer/ESerializationVersion.h | 3 ++- server/NetPacksLobbyServer.cpp | 9 +++++++-- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 4689d8d57..49c86f11d 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -226,7 +226,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & else lobby->updateAfterStateChange(); - if(pack.hostChanged) + if(pack.hostChanged || pack.refreshList) lobby->toggleMode(handler.isHost()); } diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 646753de2..a8e1a273d 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -262,10 +262,10 @@ SelectionTab::SelectionTab(ESelectionScreen Type) void SelectionTab::toggleMode() { + allItems.clear(); + curItems.clear(); if(CSH->isGuest()) { - allItems.clear(); - curItems.clear(); if(slider) slider->block(true); } @@ -344,6 +344,14 @@ void SelectionTab::clickReleased(const Point & cursorPosition) } std::cout << (curItems[py]->isFolder ? curItems[py]->folderName : curItems[py]->fullFileURI) << "\n"; + + if(!curItems[py]->isFolder) + CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteFile") + "\n\n" + curItems[py]->fullFileURI, std::vector>(), [this, py](){ + LobbyDelete ld; + ld.type = tabType == ESelectionScreen::newGame ? LobbyDelete::RANDOMMAP : LobbyDelete::SAVEGAME; + ld.name = curItems[py]->fileURI; + CSH->sendLobbyPack(ld); + }, nullptr); } } #ifdef VCMI_MOBILE diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index 7fafbae9d..8f16a80af 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -170,12 +170,15 @@ struct DLL_LINKAGE LobbyUpdateState : public CLobbyPackToPropagate { LobbyState state; bool hostChanged = false; // Used on client-side only + bool refreshList = false; void visitTyped(ICPackVisitor & visitor) override; template void serialize(Handler &h) { h & state; + if (h.version >= Handler::Version::LOBBY_DELETE) + h & refreshList; } }; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 7b4270bc9..7ef4a4f39 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -65,6 +65,7 @@ enum class ESerializationVersion : int32_t LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance + LOBBY_DELETE, // 869 - possibility to delete savegames and random maps - CURRENT = REMOVE_OBJECT_TYPENAME + CURRENT = LOBBY_DELETE }; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 7ed190236..c9458e50e 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -442,10 +442,15 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack void ApplyOnServerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) { - if(pack.type == LobbyDelete::SAVEGAME) + if(pack.type == LobbyDelete::SAVEGAME || pack.type == LobbyDelete::RANDOMMAP) { - auto res = ResourcePath(pack.name, EResType::SAVEGAME); + auto res = ResourcePath(pack.name, pack.type == LobbyDelete::SAVEGAME ? EResType::SAVEGAME : EResType::MAP); auto file = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); boost::filesystem::remove(file); } + + LobbyUpdateState lus; + lus.state = srv; + lus.refreshList = true; + srv.announcePack(lus); } From b06efa2d13de311ddd70e3322913f57bdd9ed5c2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 1 Nov 2024 01:16:01 +0100 Subject: [PATCH 501/726] delete folder --- client/lobby/SelectionTab.cpp | 12 ++++++++---- server/NetPacksLobbyServer.cpp | 8 ++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index a8e1a273d..b9a56242a 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -208,7 +208,6 @@ SelectionTab::SelectionTab(ESelectionScreen Type) case ESelectionScreen::saveGame: positionsToShow = 16; tabTitle = "{" + CGI->generaltexth->arraytxt[231] + "}"; - tabTitleDelete = "{red|" + CGI->generaltexth->translate("vcmi.lobby.deleteSaveGameTitle") + "}"; break; case ESelectionScreen::campaignList: tabTitle = "{" + CGI->generaltexth->allTexts[726] + "}"; @@ -232,7 +231,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type) sortByDate->setOverlay(std::make_shared(ImagePath::builtin("lobby/selectionTabSortDate"))); buttonsSortBy.push_back(sortByDate); - if(tabType == ESelectionScreen::loadGame || tabType == ESelectionScreen::saveGame || tabType == ESelectionScreen::newGame) + if(tabType == ESelectionScreen::loadGame || tabType == ESelectionScreen::newGame) { buttonDeleteMode = std::make_shared(Point(367, 18), AnimationPath::builtin("lobby/deleteButton"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.deleteMode")), [this, tabTitle, tabTitleDelete](){ deleteMode = !deleteMode; @@ -343,8 +342,6 @@ void SelectionTab::clickReleased(const Point & cursorPosition) return; } - std::cout << (curItems[py]->isFolder ? curItems[py]->folderName : curItems[py]->fullFileURI) << "\n"; - if(!curItems[py]->isFolder) CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteFile") + "\n\n" + curItems[py]->fullFileURI, std::vector>(), [this, py](){ LobbyDelete ld; @@ -352,6 +349,13 @@ void SelectionTab::clickReleased(const Point & cursorPosition) ld.name = curItems[py]->fileURI; CSH->sendLobbyPack(ld); }, nullptr); + else + CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteFolder") + "\n\n" + curFolder + curItems[py]->folderName, std::vector>(), [this, py](){ + LobbyDelete ld; + ld.type = LobbyDelete::SAVEGAME_FOLDER; + ld.name = curFolder + curItems[py]->folderName; + CSH->sendLobbyPack(ld); + }, nullptr); } } #ifdef VCMI_MOBILE diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index c9458e50e..2f645d9f7 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -447,6 +447,14 @@ void ApplyOnServerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) auto res = ResourcePath(pack.name, pack.type == LobbyDelete::SAVEGAME ? EResType::SAVEGAME : EResType::MAP); auto file = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); boost::filesystem::remove(file); + if(boost::filesystem::is_empty(file.parent_path())) + boost::filesystem::remove(file.parent_path()); + } + else if(pack.type == LobbyDelete::SAVEGAME_FOLDER) + { + auto res = ResourcePath("Saves/" + pack.name, EResType::DIRECTORY); + auto folder = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); + boost::filesystem::remove_all(folder); } LobbyUpdateState lus; From ee88cfa1506dc24c16b9884d0f39f9425d30fd25 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 1 Nov 2024 13:40:29 +0000 Subject: [PATCH 502/726] Fix selection of encoding from maps Fixes typo from previous PR --- lib/modding/CModHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index f1534bc62..4bf11bbc5 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -443,7 +443,7 @@ std::string CModHandler::findResourceEncoding(const ResourcePath & resource) con // however at the moment we have no way to detect that for sure - file can be either in English or in user-preferred language // but since all known H3 encodings (Win125X or GBK) are supersets of ASCII, we can safely load English data using encoding of user-preferred language std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); - std::string fileEncoding = Languages::getLanguageOptions(modLanguage).encoding; + std::string fileEncoding = Languages::getLanguageOptions(preferredLanguage).encoding; return fileEncoding; } else From 3038e5140a8eb51aeedf75c74549583eb5e9797d Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:02:51 +0100 Subject: [PATCH 503/726] add color frames; remove scrollbar --- .../stackWindow/bonus-effects-noscroll.png | Bin 0 -> 35463 bytes client/windows/CCreatureWindow.cpp | 37 +++++++++++++++--- client/windows/CCreatureWindow.h | 5 ++- 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 Mods/vcmi/Data/stackWindow/bonus-effects-noscroll.png diff --git a/Mods/vcmi/Data/stackWindow/bonus-effects-noscroll.png b/Mods/vcmi/Data/stackWindow/bonus-effects-noscroll.png new file mode 100644 index 0000000000000000000000000000000000000000..67fe2ce82f477afb855ddce32baf5db3f4db0c62 GIT binary patch literal 35463 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
v7=T$@uH5Lx(DpA& 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.imagePath, position.x, position.y); name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name); description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 17, 137, 30), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); + + std::map bonusColors = { + {BonusSource::ARTIFACT, ColorRGBA(255, 0, 0)}, + {BonusSource::ARTIFACT_INSTANCE, ColorRGBA(255, 0, 0)}, + {BonusSource::OBJECT_TYPE, ColorRGBA(255, 0, 0)}, + {BonusSource::OBJECT_INSTANCE, ColorRGBA(255, 0, 0)}, + {BonusSource::CREATURE_ABILITY, ColorRGBA(255, 0, 0)}, + {BonusSource::TERRAIN_NATIVE, ColorRGBA(255, 0, 0)}, + {BonusSource::TERRAIN_OVERLAY, ColorRGBA(255, 0, 0)}, + {BonusSource::SPELL_EFFECT, ColorRGBA(255, 0, 0)}, + {BonusSource::TOWN_STRUCTURE, ColorRGBA(255, 0, 0)}, + {BonusSource::HERO_BASE_SKILL, ColorRGBA(255, 0, 0)}, + {BonusSource::SECONDARY_SKILL, ColorRGBA(0, 0, 255)}, + {BonusSource::HERO_SPECIAL, ColorRGBA(0, 0, 255)}, + {BonusSource::ARMY, ColorRGBA(0, 255, 0)}, + {BonusSource::CAMPAIGN_BONUS, ColorRGBA(255, 0, 0)}, + {BonusSource::STACK_EXPERIENCE, ColorRGBA(255, 0, 0)}, + {BonusSource::COMMANDER, ColorRGBA(255, 0, 0)}, + {BonusSource::GLOBAL, ColorRGBA(255, 0, 0)}, + {BonusSource::OTHER, ColorRGBA(255, 0, 0)} + }; + + frame[leftRight] = std::make_shared(Rect(position.x, position.y, 50, 50)); + if(bonusColors.count(bi.bonus->source)) + frame[leftRight]->addRectangle(Point(0, 0), Point(50, 50), bonusColors[bi.bonus->source]); } } } @@ -281,10 +307,10 @@ CStackWindow::BonusesSection::BonusesSection(CStackWindow * owner, int yOffset, auto onCreate = [=](size_t index) -> std::shared_ptr { - return std::make_shared(owner, index); + return std::make_shared(owner, index, totalSize <= 3); }; - lines = std::make_shared(onCreate, Point(0, 0), Point(0, itemHeight), visibleSize, totalSize, 0, 1, Rect(pos.w - 15, 0, pos.h, pos.h)); + lines = std::make_shared(onCreate, Point(0, 0), Point(0, itemHeight), visibleSize, totalSize, 0, totalSize > 3 ? 1 : 0, Rect(pos.w - 15, 0, pos.h, pos.h)); } CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset) @@ -533,7 +559,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s animation->setAmount(parent->info->creatureCount); } - name = std::make_shared(215, 12, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName()); + name = std::make_shared(215, 13, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName()); const BattleInterface* battleInterface = LOCPLINT->battleInt.get(); const CStack* battleStack = parent->info->stack; @@ -801,6 +827,7 @@ void CStackWindow::initBonusesList() bonusInfo.name = info->stackNode->bonusToString(b, false); bonusInfo.description = info->stackNode->bonusToString(b, true); bonusInfo.imagePath = info->stackNode->bonusToGraphics(b); + bonusInfo.bonus = b; //if it's possible to give any description or image for this kind of bonus //TODO: figure out why half of bonuses don't have proper description diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h index 62ed20a74..bfa2468b0 100644 --- a/client/windows/CCreatureWindow.h +++ b/client/windows/CCreatureWindow.h @@ -31,6 +31,7 @@ class CListBox; class CArtPlace; class CCommanderArtPlace; class LRClickableArea; +class GraphicalPrimitiveCanvas; class CCommanderSkillIcon : public LRClickableAreaWText //TODO: maybe bring commander skill button initialization logic inside? { @@ -58,6 +59,7 @@ class CStackWindow : public CWindowObject std::string name; std::string description; ImagePath imagePath; + std::shared_ptr bonus; }; class CWindowSection : public CIntObject @@ -84,8 +86,9 @@ class CStackWindow : public CWindowObject std::array, 2> icon; std::array, 2> name; std::array, 2> description; + std::array, 2> frame; public: - BonusLineSection(CStackWindow * owner, size_t lineIndex); + BonusLineSection(CStackWindow * owner, size_t lineIndex, bool noScroll); }; class BonusesSection : public CWindowSection From e95d375a02401c294cbf67cd126f59c1dfbefedd Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:53:36 +0100 Subject: [PATCH 504/726] Updated Czech translation --- mapeditor/vcmieditor.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapeditor/vcmieditor.desktop b/mapeditor/vcmieditor.desktop index 115cf3145..468dd1c33 100644 --- a/mapeditor/vcmieditor.desktop +++ b/mapeditor/vcmieditor.desktop @@ -7,7 +7,7 @@ GenericName=Strategy Game Map Editor GenericName[cs]=Editor map strategické hry GenericName[de]=Karteneditor für Strategiespiel Comment=Map editor for the open-source recreation of Heroes of Might & Magic III -Comment[cs]=Editor map enginu s otevřeným kódem pro Heroes of Might and Magic III +Comment[cs]=Editor map pro open-source engine Heroes of Might and Magic III Comment[de]=Karteneditor für den Open-Source-Nachbau von Heroes of Might and Magic III Icon=vcmieditor Exec=vcmieditor From 63e6c98d81e6a2d80f56dad705205811fcb868be Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:54:06 +0100 Subject: [PATCH 505/726] Updated Czech translation --- launcher/vcmilauncher.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/vcmilauncher.desktop b/launcher/vcmilauncher.desktop index ad792e8bb..bb25584bf 100644 --- a/launcher/vcmilauncher.desktop +++ b/launcher/vcmilauncher.desktop @@ -5,7 +5,7 @@ GenericName=Strategy Game GenericName[cs]=Strategická hra GenericName[de]=Strategiespiel Comment=Open-source recreation of Heroes of Might & Magic III -Comment[cs]=Spouštěč enginu s otevřeným kódem pro Heroes of Might and Magic III +Comment[cs]=Open-source engine pro Heroes of Might and Magic III Comment[de]=Open-Source-Nachbau von Heroes of Might and Magic III Icon=vcmiclient Exec=vcmilauncher From 8d70c48f002cb958ace3557066a7b2d6c284e729 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:54:43 +0100 Subject: [PATCH 506/726] Updated Czech translation --- clientapp/icons/vcmiclient.desktop | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clientapp/icons/vcmiclient.desktop b/clientapp/icons/vcmiclient.desktop index 68d446c47..3a94d8deb 100644 --- a/clientapp/icons/vcmiclient.desktop +++ b/clientapp/icons/vcmiclient.desktop @@ -1,8 +1,11 @@ [Desktop Entry] Type=Application Name=VCMI Client +Name[cs]=VCMI Klient GenericName=Strategy Game Engine +GenericName[cs]=Engine strategické hry Comment=Open engine for Heroes of Might and Magic 3 +Comment[cs]=Open-source engine pro Heroes of Might and Magic III Icon=vcmiclient Exec=vcmiclient Categories=Game;StrategyGame; From 5f02ede31a7520374865bd7abc1b52b3a18a445b Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:58:06 +0100 Subject: [PATCH 507/726] Updated Czech translation --- launcher/translation/czech.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index 5434843d7..9c219d942 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -749,7 +749,7 @@ Nainstalovat úspěšně stažené? Online Lobby address - Adresa online předsíně + Adresa online lobby From 440e9a6575be183522afa5f41322e94493a4564a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:05:10 +0100 Subject: [PATCH 508/726] assign colors for bonuses --- client/windows/CCreatureWindow.cpp | 33 ++++++++++-------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index dae312dc1..429c9effa 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -264,29 +264,18 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 17, 137, 30), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); std::map bonusColors = { - {BonusSource::ARTIFACT, ColorRGBA(255, 0, 0)}, - {BonusSource::ARTIFACT_INSTANCE, ColorRGBA(255, 0, 0)}, - {BonusSource::OBJECT_TYPE, ColorRGBA(255, 0, 0)}, - {BonusSource::OBJECT_INSTANCE, ColorRGBA(255, 0, 0)}, - {BonusSource::CREATURE_ABILITY, ColorRGBA(255, 0, 0)}, - {BonusSource::TERRAIN_NATIVE, ColorRGBA(255, 0, 0)}, - {BonusSource::TERRAIN_OVERLAY, ColorRGBA(255, 0, 0)}, - {BonusSource::SPELL_EFFECT, ColorRGBA(255, 0, 0)}, - {BonusSource::TOWN_STRUCTURE, ColorRGBA(255, 0, 0)}, - {BonusSource::HERO_BASE_SKILL, ColorRGBA(255, 0, 0)}, - {BonusSource::SECONDARY_SKILL, ColorRGBA(0, 0, 255)}, - {BonusSource::HERO_SPECIAL, ColorRGBA(0, 0, 255)}, - {BonusSource::ARMY, ColorRGBA(0, 255, 0)}, - {BonusSource::CAMPAIGN_BONUS, ColorRGBA(255, 0, 0)}, - {BonusSource::STACK_EXPERIENCE, ColorRGBA(255, 0, 0)}, - {BonusSource::COMMANDER, ColorRGBA(255, 0, 0)}, - {BonusSource::GLOBAL, ColorRGBA(255, 0, 0)}, - {BonusSource::OTHER, ColorRGBA(255, 0, 0)} + {BonusSource::ARTIFACT, Colors::GREEN}, + {BonusSource::ARTIFACT_INSTANCE, Colors::GREEN}, + {BonusSource::CREATURE_ABILITY, Colors::YELLOW}, + {BonusSource::SPELL_EFFECT, Colors::ORANGE}, + {BonusSource::SECONDARY_SKILL, Colors::PURPLE}, + {BonusSource::HERO_SPECIAL, Colors::PURPLE}, + {BonusSource::STACK_EXPERIENCE, Colors::CYAN}, + {BonusSource::COMMANDER, Colors::CYAN}, }; - frame[leftRight] = std::make_shared(Rect(position.x, position.y, 50, 50)); - if(bonusColors.count(bi.bonus->source)) - frame[leftRight]->addRectangle(Point(0, 0), Point(50, 50), bonusColors[bi.bonus->source]); + frame[leftRight] = std::make_shared(Rect(position.x - 1, position.y - 1, 52, 52)); + frame[leftRight]->addRectangle(Point(0, 0), Point(52, 52), bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : Colors::BLACK); } } } @@ -827,7 +816,7 @@ void CStackWindow::initBonusesList() bonusInfo.name = info->stackNode->bonusToString(b, false); bonusInfo.description = info->stackNode->bonusToString(b, true); bonusInfo.imagePath = info->stackNode->bonusToGraphics(b); - bonusInfo.bonus = b; + bonusInfo.bonusSource = b->source; //if it's possible to give any description or image for this kind of bonus //TODO: figure out why half of bonuses don't have proper description From 5a72a65b31be8d923008e6e7d3dacf6c9b17c4bc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:59:09 +0100 Subject: [PATCH 509/726] add labels --- Mods/vcmi/config/english.json | 6 +++++ client/windows/CCreatureWindow.cpp | 39 +++++++++++++++++++----------- client/windows/CCreatureWindow.h | 3 ++- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/config/english.json index 498741306..7afc0f8ec 100644 --- a/Mods/vcmi/config/english.json +++ b/Mods/vcmi/config/english.json @@ -28,6 +28,12 @@ "vcmi.adventureMap.movementPointsHeroInfo" : "(Movement points: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sorry, replay opponent turn is not implemented yet!", + "vcmi.bonusSource.artifact" : "Art.", + "vcmi.bonusSource.creature" : "Crea.", + "vcmi.bonusSource.spell" : "Spell", + "vcmi.bonusSource.hero" : "Hero", + "vcmi.bonusSource.commander" : "Comm.", + "vcmi.capitalColors.0" : "Red", "vcmi.capitalColors.1" : "Blue", "vcmi.capitalColors.2" : "Tan", diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 429c9effa..4613e9e63 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -250,6 +250,28 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li Point(6, 4), Point(214, 4) }; + + std::map bonusColors = { + {BonusSource::ARTIFACT, Colors::GREEN}, + {BonusSource::ARTIFACT_INSTANCE, Colors::GREEN}, + {BonusSource::CREATURE_ABILITY, Colors::YELLOW}, + {BonusSource::SPELL_EFFECT, Colors::ORANGE}, + {BonusSource::SECONDARY_SKILL, Colors::PURPLE}, + {BonusSource::HERO_SPECIAL, Colors::PURPLE}, + {BonusSource::STACK_EXPERIENCE, Colors::CYAN}, + {BonusSource::COMMANDER, Colors::CYAN}, + }; + + std::map bonusNames = { + {BonusSource::ARTIFACT, CGI->generaltexth->translate("vcmi.bonusSource.artifact")}, + {BonusSource::ARTIFACT_INSTANCE, CGI->generaltexth->translate("vcmi.bonusSource.artifact")}, + {BonusSource::CREATURE_ABILITY, CGI->generaltexth->translate("vcmi.bonusSource.creature")}, + {BonusSource::SPELL_EFFECT, CGI->generaltexth->translate("vcmi.bonusSource.spell")}, + {BonusSource::SECONDARY_SKILL, CGI->generaltexth->translate("vcmi.bonusSource.hero")}, + {BonusSource::HERO_SPECIAL, CGI->generaltexth->translate("vcmi.bonusSource.hero")}, + {BonusSource::STACK_EXPERIENCE, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, + {BonusSource::COMMANDER, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, + }; for(size_t leftRight : {0, 1}) { @@ -260,20 +282,9 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li { BonusInfo & bi = parent->activeBonuses[bonusIndex]; icon[leftRight] = std::make_shared(bi.imagePath, position.x, position.y); - name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name); - description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 17, 137, 30), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); - - std::map bonusColors = { - {BonusSource::ARTIFACT, Colors::GREEN}, - {BonusSource::ARTIFACT_INSTANCE, Colors::GREEN}, - {BonusSource::CREATURE_ABILITY, Colors::YELLOW}, - {BonusSource::SPELL_EFFECT, Colors::ORANGE}, - {BonusSource::SECONDARY_SKILL, Colors::PURPLE}, - {BonusSource::HERO_SPECIAL, Colors::PURPLE}, - {BonusSource::STACK_EXPERIENCE, Colors::CYAN}, - {BonusSource::COMMANDER, Colors::CYAN}, - }; - + name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name, 110); + description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 17, 137, 30), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); + bonusSource[leftRight] = std::make_shared(position.x + 60 + 137, position.y + 2, FONT_TINY, ETextAlignment::TOPRIGHT, bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : Colors::BLACK, bonusNames.count(bi.bonusSource) ? bonusNames[bi.bonusSource] : "", 27); frame[leftRight] = std::make_shared(Rect(position.x - 1, position.y - 1, 52, 52)); frame[leftRight]->addRectangle(Point(0, 0), Point(52, 52), bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : Colors::BLACK); } diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h index bfa2468b0..3f69f711d 100644 --- a/client/windows/CCreatureWindow.h +++ b/client/windows/CCreatureWindow.h @@ -59,7 +59,7 @@ class CStackWindow : public CWindowObject std::string name; std::string description; ImagePath imagePath; - std::shared_ptr bonus; + BonusSource bonusSource; }; class CWindowSection : public CIntObject @@ -87,6 +87,7 @@ class CStackWindow : public CWindowObject std::array, 2> name; std::array, 2> description; std::array, 2> frame; + std::array, 2> bonusSource; public: BonusLineSection(CStackWindow * owner, size_t lineIndex, bool noScroll); }; From 17ee363d8d4b1eef1d5d193d1ccc8f208a9c38fa Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:37:21 +0100 Subject: [PATCH 510/726] changed bonus source text pos --- Mods/vcmi/config/english.json | 6 +++--- client/windows/CCreatureWindow.cpp | 24 +++++++++++++++++++++--- client/windows/CCreatureWindow.h | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/config/english.json index 7afc0f8ec..ac22082de 100644 --- a/Mods/vcmi/config/english.json +++ b/Mods/vcmi/config/english.json @@ -28,11 +28,11 @@ "vcmi.adventureMap.movementPointsHeroInfo" : "(Movement points: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sorry, replay opponent turn is not implemented yet!", - "vcmi.bonusSource.artifact" : "Art.", - "vcmi.bonusSource.creature" : "Crea.", + "vcmi.bonusSource.artifact" : "Artifact", + "vcmi.bonusSource.creature" : "Creature", "vcmi.bonusSource.spell" : "Spell", "vcmi.bonusSource.hero" : "Hero", - "vcmi.bonusSource.commander" : "Comm.", + "vcmi.bonusSource.commander" : "Commander", "vcmi.capitalColors.0" : "Red", "vcmi.capitalColors.1" : "Blue", diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index 4613e9e63..e44809e31 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -273,6 +273,24 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li {BonusSource::COMMANDER, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, }; + auto drawBonusSourceText = [this, &bonusColors, &bonusNames](int leftRight, Point p, BonusInfo & bi) + { + if(!bonusColors.count(bi.bonusSource) || !bonusNames.count(bi.bonusSource)) + return; + + auto c = bonusColors[bi.bonusSource]; + std::string t = bonusNames[bi.bonusSource]; + int maxLen = 50; + EFonts f = FONT_TINY; + + // 1px Black border + bonusSource[leftRight].push_back(std::make_shared(p.x - 1, p.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(p.x + 1, p.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(p.x, p.y - 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(p.x, p.y + 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(p.x, p.y, f, ETextAlignment::TOPLEFT, c, t, maxLen)); + }; + for(size_t leftRight : {0, 1}) { auto position = offset[leftRight]; @@ -282,9 +300,9 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li { BonusInfo & bi = parent->activeBonuses[bonusIndex]; icon[leftRight] = std::make_shared(bi.imagePath, position.x, position.y); - name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name, 110); - description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 17, 137, 30), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); - bonusSource[leftRight] = std::make_shared(position.x + 60 + 137, position.y + 2, FONT_TINY, ETextAlignment::TOPRIGHT, bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : Colors::BLACK, bonusNames.count(bi.bonusSource) ? bonusNames[bi.bonusSource] : "", 27); + name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name, 137); + description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 20, 137, 30), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); + drawBonusSourceText(leftRight, Point(position.x + 2, position.y + 39), bi); frame[leftRight] = std::make_shared(Rect(position.x - 1, position.y - 1, 52, 52)); frame[leftRight]->addRectangle(Point(0, 0), Point(52, 52), bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : Colors::BLACK); } diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h index 3f69f711d..a94196a26 100644 --- a/client/windows/CCreatureWindow.h +++ b/client/windows/CCreatureWindow.h @@ -87,7 +87,7 @@ class CStackWindow : public CWindowObject std::array, 2> name; std::array, 2> description; std::array, 2> frame; - std::array, 2> bonusSource; + std::array>, 2> bonusSource; public: BonusLineSection(CStackWindow * owner, size_t lineIndex, bool noScroll); }; From 6a1e69ae85cad2091d556cb9d285af573a8a9550 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:46:52 +0100 Subject: [PATCH 511/726] Add files via upload --- Mods/vcmi/config/czech.json | 1428 ++++++++++++++++++----------------- 1 file changed, 717 insertions(+), 711 deletions(-) diff --git a/Mods/vcmi/config/czech.json b/Mods/vcmi/config/czech.json index 4ccc83653..672c6c340 100644 --- a/Mods/vcmi/config/czech.json +++ b/Mods/vcmi/config/czech.json @@ -1,712 +1,718 @@ -{ - "vcmi.adventureMap.monsterThreat.title" : "\n\nHrozba: ", - "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", - "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", - "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", - "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", - "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", - "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", - "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", - "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", - "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", - "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", - "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", - "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", - "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", - "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", - "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", - "vcmi.adventureMap.search.hover" : "Prohledat objekt", - "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", - - "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", - "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", - "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", - "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", - "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", - "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", - "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", - "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", - "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", - - "vcmi.capitalColors.0" : "Červený", - "vcmi.capitalColors.1" : "Modrý", - "vcmi.capitalColors.2" : "Hnědý", - "vcmi.capitalColors.3" : "Zelený", - "vcmi.capitalColors.4" : "Oranžový", - "vcmi.capitalColors.5" : "Fialový", - "vcmi.capitalColors.6" : "Tyrkysový", - "vcmi.capitalColors.7" : "Růžový", - - "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", - "vcmi.heroOverview.warMachine" : "Bojové stroje", - "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", - "vcmi.heroOverview.spells" : "Kouzla", - - "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", - "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", - "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", - "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", - "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", - - "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", - "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", - "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", - "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", - "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", - "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", - - "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", - "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", - "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", - "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", - "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", - "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", - - "vcmi.radialWheel.moveTop" : "Přesunout nahoru", - "vcmi.radialWheel.moveUp" : "Posunout výše", - "vcmi.radialWheel.moveDown" : "Posunout níže", - "vcmi.radialWheel.moveBottom" : "Přesunout dolů", - - "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", - "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", - "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", - "vcmi.randomMap.description.water.none" : "žádná", - "vcmi.randomMap.description.water.normal" : "normální", - "vcmi.randomMap.description.water.islands" : "ostrovy", - "vcmi.randomMap.description.monster.weak" : "nízká", - "vcmi.randomMap.description.monster.normal" : "normální", - "vcmi.randomMap.description.monster.strong" : "vysoká", - - "vcmi.spellBook.search" : "Hledat", - - "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", - "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", - "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", - "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", - "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", - "vcmi.spellResearch.abort" : "Přerušit", - - "vcmi.mainMenu.serverConnecting" : "Připojování...", - "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", - "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", - "vcmi.mainMenu.serverClosing" : "Zavírání...", - "vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP", - "vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP", - - "vcmi.lobby.filepath" : "Název souboru", - "vcmi.lobby.creationDate" : "Datum vytvoření", - "vcmi.lobby.scenarioName" : "Název scénáře", - "vcmi.lobby.mapPreview" : "Náhled mapy", - "vcmi.lobby.noPreview" : "bez náhledu", - "vcmi.lobby.noUnderground" : "bez podzemí", - "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", - "vcmi.lobby.backToLobby" : "Vrátit se do lobby", - "vcmi.lobby.author" : "Autor", - "vcmi.lobby.handicap" : "Postih", - "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", - "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", - "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", - - "vcmi.lobby.login.title" : "Online lobby VCMI", - "vcmi.lobby.login.username" : "Uživatelské jméno:", - "vcmi.lobby.login.connecting" : "Připojování...", - "vcmi.lobby.login.error" : "Chyba při připojování: %s", - "vcmi.lobby.login.create" : "Nový účet", - "vcmi.lobby.login.login" : "Přihlásit se", - "vcmi.lobby.login.as" : "Přilásit se jako %s", - "vcmi.lobby.header.rooms" : "Herní místnosti - %d", - "vcmi.lobby.header.channels" : "Kanály konverzace", - "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name - "vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time - "vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player - "vcmi.lobby.header.history" : "Vaše předchozí hry", - "vcmi.lobby.header.players" : "Online hráči - %d", - "vcmi.lobby.match.solo" : "Hra jednoho hráče", - "vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player - "vcmi.lobby.match.multi" : "%d hráčů", - "vcmi.lobby.room.create" : "Vytvořit novou místnost", - "vcmi.lobby.room.players.limit" : "Omezení počtu hráčů", - "vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.", - "vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.", - "vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.", - "vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", - "vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).", - "vcmi.lobby.invite.header" : "Pozvat hráče", - "vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.", - "vcmi.lobby.preview.title" : "Připojit se do herní místnosti", - "vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host - "vcmi.lobby.preview.version" : "Verze hry:", - "vcmi.lobby.preview.players" : "Hráči:", - "vcmi.lobby.preview.mods" : "Použité modifikace:", - "vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?", - "vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.", - "vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.", - "vcmi.lobby.preview.error.full" : "Místnost je již plná.", - "vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.", - "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", - "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", - "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", - "vcmi.lobby.room.new" : "Nová hra", - "vcmi.lobby.room.load" : "Načíst hru", - "vcmi.lobby.room.type" : "Druh místnosti", - "vcmi.lobby.room.mode" : "Herní režim", - "vcmi.lobby.room.state.public" : "Veřejná", - "vcmi.lobby.room.state.private" : "Soukromá", - "vcmi.lobby.room.state.busy" : "Ve hře", - "vcmi.lobby.room.state.invited" : "Pozvaný", - "vcmi.lobby.mod.state.compatible" : "Kompatibilní", - "vcmi.lobby.mod.state.disabled" : "Musí být povolena", - "vcmi.lobby.mod.state.version" : "Neshoda verze", - "vcmi.lobby.mod.state.excessive" : "Musí být zakázána", - "vcmi.lobby.mod.state.missing" : "Není nainstalována", - "vcmi.lobby.pvp.coin.hover" : "Mince", - "vcmi.lobby.pvp.coin.help" : "Hodí mincí", - "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", - "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", - "vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", - "vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace", - "vcmi.lobby.pvp.versus" : "vs.", - - "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", - "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", - "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", - "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color - "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", - "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", - "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", - "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", - "vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.", - "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", - "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", - - "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", - - "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", - "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", - "vcmi.settingsMainWindow.battleTab.hover" : "Bitva", - "vcmi.settingsMainWindow.battleTab.help" : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.", - "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa", - "vcmi.settingsMainWindow.adventureTab.help" : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).", - - "vcmi.systemOptions.videoGroup" : "Nastavení obrazu", - "vcmi.systemOptions.audioGroup" : "Nastavení zvuku", - "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now - "vcmi.systemOptions.townsGroup" : "Obrazovka města", - - "vcmi.statisticWindow.statistics" : "Statistiky", - "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", - "vcmi.statisticWindow.selectView" : "Vybrat pohled", - "vcmi.statisticWindow.value" : "Hodnota", - "vcmi.statisticWindow.title.overview" : "Přehled", - "vcmi.statisticWindow.title.resources" : "Zdroje", - "vcmi.statisticWindow.title.income" : "Příjem", - "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", - "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", - "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", - "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", - "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", - "vcmi.statisticWindow.title.armyStrength" : "Síla armády", - "vcmi.statisticWindow.title.experience" : "Zkušenosti", - "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", - "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", - "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", - "vcmi.statisticWindow.param.playerName" : "Jméno hráče", - "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", - "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", - "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", - "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", - "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", - "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", - "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", - "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", - "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", - "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", - "vcmi.statisticWindow.icon.defeated" : "Porážka", - - "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", - "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", - "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", - "vcmi.systemOptions.fullscreenExclusive.help" : "{Celá obrazovka}\n\nPokud je vybráno, VCMI poběží v režimu exkluzivní celé obrazovky. V tomto režimu hra změní rozlišení na vybrané.", - "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", - "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", - "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", - "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", - "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", - "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", - "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", - "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", - "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", - "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", - "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", - "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", - "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", - "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", - "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", - "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", - "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", - "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", - "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", - "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", - "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", - - "vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací", - "vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.", - "vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek", - "vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.", - "vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu", - "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", - "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", - "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", - "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", - "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", - "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", - "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", - "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", - "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", - "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", - "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", - "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", - "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", - "vcmi.adventureOptions.mapScrollSpeed1.hover": "", - "vcmi.adventureOptions.mapScrollSpeed5.hover": "", - "vcmi.adventureOptions.mapScrollSpeed6.hover": "", - "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", - "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", - "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", - "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", - "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", - - "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", - "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", - "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", - "vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ", - "vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ", - "vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.", - "vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", - "vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.", - "vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", - "vcmi.battleOptions.animationsSpeed1.hover": "", - "vcmi.battleOptions.animationsSpeed5.hover": "", - "vcmi.battleOptions.animationsSpeed6.hover": "", - "vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.", - "vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.", - "vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.", - "vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí", - "vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.", - "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců", - "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Zobrazit omezení dostřelu střelců při najetí}\n\nZobrazit dostřel střelce při najetí na něj.", - "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Zobrazit okno statistik hrdinů", - "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", - "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", - "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", - "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", - "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", - "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", - "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", - - "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", - "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", - - "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", - "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", - "vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.ranged" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE).", - "vcmi.battleWindow.damageEstimation.rangedKills" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.shots" : "%d střel zbývá", - "vcmi.battleWindow.damageEstimation.shots.1" : "%d střela zbývá", - "vcmi.battleWindow.damageEstimation.damage" : "%d poškození", - "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", - "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", - "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", - - "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", - "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", - "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", - "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", - "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", - - "vcmi.battleWindow.killed" : "Zabito", - "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", - "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", - - "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", - - "vcmi.tutorialWindow.title" : "Úvod ovládání dotykem", - "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", - "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", - "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", - "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", - "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", - "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", - "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", - - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky", - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Zobrazit týdenní přírůstek jednotek}\n\nZobrazit týdenní přírůstek jednotek místo dostupného počtu ke koupení v přehledu města (levý spodní okraj obrazovky města).", - "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", - "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", - - "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", - "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", - - "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", - "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", - - "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", - "vcmi.logicalExpressions.allOf" : "Všechny následující:", - "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", - - "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", - "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", - "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", - "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", - "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", - "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", - "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", - "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", - "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", - "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", - "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", - - "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", - - "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", - - "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", - "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", - "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", - "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", - "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", - "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", - - "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", - "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", - - "vcmi.randomMapTab.widgets.randomTemplate" : "(Náhodná)", - "vcmi.randomMapTab.widgets.templateLabel" : "Šablona", - "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...", - "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Přiřazení týmů", - "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", - - "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", - "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", - - "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", - "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", - "vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy", - "vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky", - "vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.", - "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.", - "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.", - "vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.", - - "vcmi.optionsTab.accumulate" : "Akumulovat", - - "vcmi.optionsTab.simturnsTitle" : "Souběžné tahy", - "vcmi.optionsTab.simturnsMin.hover" : "Alespoň po", - "vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po", - "vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI", - "vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno", - "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", - "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", - - "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", - "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", - "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", - "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", - "vcmi.optionsTab.turnTime.classic.5" : "Klasický časovač: 5 minut", - "vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut", - "vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut", - "vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut", - "vcmi.optionsTab.turnTime.chess.20" : "Šachová: 20:00 + 10:00 + 02:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.16" : "Šachová: 16:00 + 08:00 + 01:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.8" : "Šachová: 08:00 + 04:00 + 01:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.4" : "Šachová: 04:00 + 02:00 + 00:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", - "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", - - "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", - "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", - "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", - "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", - "vcmi.optionsTab.simturns.tillContact2" : "Souběžně: 2 týdny, přerušit při setkání", - "vcmi.optionsTab.simturns.tillContact4" : "Souběžně: 1 mšsíc, přerušit při setkání", - "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", - "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", - "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", - - // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language - // Using this information, VCMI will automatically select correct plural form for every possible amount - "vcmi.optionsTab.simturns.days.0" : " %d dní", - "vcmi.optionsTab.simturns.days.1" : " %d den", - "vcmi.optionsTab.simturns.days.2" : " %d dny", - "vcmi.optionsTab.simturns.weeks.0" : " %d týdnů", - "vcmi.optionsTab.simturns.weeks.1" : " %d týden", - "vcmi.optionsTab.simturns.weeks.2" : " %d týdny", - "vcmi.optionsTab.simturns.months.0" : " %d měsíců", - "vcmi.optionsTab.simturns.months.1" : " %d měsíc", - "vcmi.optionsTab.simturns.months.2" : " %d měsíce", - - "vcmi.optionsTab.extraOptions.hover" : "Další možnosti", - "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", - - "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", - "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", - "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", - "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", - - // Custom victory conditions for H3 campaigns and HotA maps - "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", - "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", - "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", - "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", - "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", - "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", - "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", - "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", - - // few strings from WoG used by vcmi - "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", - "vcmi.stackExperience.rank.0" : "Začátečník", - "vcmi.stackExperience.rank.1" : "Učeň", - "vcmi.stackExperience.rank.2" : "Trénovaný", - "vcmi.stackExperience.rank.3" : "Zručný", - "vcmi.stackExperience.rank.4" : "Prověřený", - "vcmi.stackExperience.rank.5" : "Veterán", - "vcmi.stackExperience.rank.6" : "Adept", - "vcmi.stackExperience.rank.7" : "Expert", - "vcmi.stackExperience.rank.8" : "Elitní", - "vcmi.stackExperience.rank.9" : "Mistr", - "vcmi.stackExperience.rank.10" : "Eso", - - // Strings for HotA Seer Hut / Quest Guards - "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", - "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", - "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", - - "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", - - "mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.", - - "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", - "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", - "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", - "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", - "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", - "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", - "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", - "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", - "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", - "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", - "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", - "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", - "core.bonus.CATAPULT.name": "Katapult", - "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", - "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", - "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", - "core.bonus.DARKNESS.name": "Závoj temnoty", - "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", - "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", - "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", - "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", - "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", - "core.bonus.DESTRUCTION.name": "Zničení", - "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", - "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", - "core.bonus.DRAGON_NATURE.name": "Dračí povaha", - "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", - "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", - "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", - "core.bonus.ENCHANTER.name": "Zaklínač", - "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", - "core.bonus.ENCHANTED.name": "Očarovaný", - "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", - "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", - "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", - "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", - "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", - "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", - "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", - "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", - "core.bonus.FIRST_STRIKE.name": "První úder", - "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", - "core.bonus.FEAR.name": "Strach", - "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", - "core.bonus.FEARLESS.name": "Nebojácnost", - "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", - "core.bonus.FEROCITY.name": "Zuřivost", - "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", - "core.bonus.FLYING.name": "Létání", - "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", - "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", - "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", - "core.bonus.GARGOYLE.name": "Chrlič", - "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", - "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", - "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", - "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", - "core.bonus.HEALER.name": "Léčitel", - "core.bonus.HEALER.description": "Léčí spojenecké jednotky", - "core.bonus.HP_REGENERATION.name": "Regenerace", - "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", - "core.bonus.JOUSTING.name": "Nájezd šampionů", - "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", - "core.bonus.KING.name": "Král", - "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", - "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", - "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", - "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", - "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", - "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", - "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", - "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", - "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", - "core.bonus.MANA_DRAIN.name": "Vysávání many", - "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", - "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", - "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", - "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", - "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", - "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", - "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", - "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", - "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", - "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", - "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", - "core.bonus.NO_MORALE.name": "Neutrální morálka", - "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", - "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", - "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", - "core.bonus.NON_LIVING.name": "Neživý", - "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", - "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", - "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", - "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", - "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", - "core.bonus.RECEPTIVE.name": "Vnímavý", - "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", - "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", - "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", - "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", - "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", - "core.bonus.REVENGE.name": "Pomsta", - "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", - "core.bonus.SHOOTER.name": "Střelec", - "core.bonus.SHOOTER.description": "Jednotka může střílet", - "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", - "core.bonus.SOUL_STEAL.name": "Zloděj duší", - "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", - "core.bonus.SPELLCASTER.name": "Kouzelník", - "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", - "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", - "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", - "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", - "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", - "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", - "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", - "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", - "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", - "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", - "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", - "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", - "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", - "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", - "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", - "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", - "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", - "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", - "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", - "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", - "core.bonus.TRANSMUTATION.name": "Transmutace", - "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", - "core.bonus.UNDEAD.name": "Nemrtvý", - "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", - "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", - "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", - "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", - "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", - "core.bonus.WIDE_BREATH.name": "Široký dech", - "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", - "core.bonus.DISINTEGRATE.name": "Rozpad", - "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", - "core.bonus.INVINCIBLE.name": "Neporazitelný", - "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem", - "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", - "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" +{ + "vcmi.adventureMap.monsterThreat.title" : "\n\nHrozba: ", + "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", + "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", + "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", + "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", + "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", + "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", + "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", + "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", + "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", + "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", + "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", + "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", + "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", + "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", + "vcmi.adventureMap.search.hover" : "Prohledat objekt", + "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", + + "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", + "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", + "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", + "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", + "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", + "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", + + "vcmi.capitalColors.0" : "Červený", + "vcmi.capitalColors.1" : "Modrý", + "vcmi.capitalColors.2" : "Hnědý", + "vcmi.capitalColors.3" : "Zelený", + "vcmi.capitalColors.4" : "Oranžový", + "vcmi.capitalColors.5" : "Fialový", + "vcmi.capitalColors.6" : "Tyrkysový", + "vcmi.capitalColors.7" : "Růžový", + + "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", + "vcmi.heroOverview.warMachine" : "Bojové stroje", + "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", + "vcmi.heroOverview.spells" : "Kouzla", + + "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", + "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", + "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", + "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", + "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", + + "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", + "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", + "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", + "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", + "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", + "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", + + "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", + "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", + "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", + "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", + "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", + "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", + + "vcmi.radialWheel.moveTop" : "Přesunout nahoru", + "vcmi.radialWheel.moveUp" : "Posunout výše", + "vcmi.radialWheel.moveDown" : "Posunout níže", + "vcmi.radialWheel.moveBottom" : "Přesunout dolů", + + "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", + "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", + "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", + "vcmi.randomMap.description.water.none" : "žádná", + "vcmi.randomMap.description.water.normal" : "normální", + "vcmi.randomMap.description.water.islands" : "ostrovy", + "vcmi.randomMap.description.monster.weak" : "nízká", + "vcmi.randomMap.description.monster.normal" : "normální", + "vcmi.randomMap.description.monster.strong" : "vysoká", + + "vcmi.spellBook.search" : "Hledat", + + "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", + "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", + "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", + "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", + "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", + "vcmi.spellResearch.abort" : "Přerušit", + + "vcmi.mainMenu.serverConnecting" : "Připojování...", + "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", + "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", + "vcmi.mainMenu.serverClosing" : "Zavírání...", + "vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP", + "vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP", + + "vcmi.lobby.filepath" : "Název souboru", + "vcmi.lobby.creationDate" : "Datum vytvoření", + "vcmi.lobby.scenarioName" : "Název scénáře", + "vcmi.lobby.mapPreview" : "Náhled mapy", + "vcmi.lobby.noPreview" : "bez náhledu", + "vcmi.lobby.noUnderground" : "bez podzemí", + "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", + "vcmi.lobby.backToLobby" : "Vrátit se do lobby", + "vcmi.lobby.author" : "Autor", + "vcmi.lobby.handicap" : "Postih", + "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", + "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", + "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", + "vcmi.lobby.deleteUnsupportedSave" : "Nalezeny nepodporované uložené hry (např. z předchozích verzí).\n\nChcete je odstranit?", + "vcmi.lobby.deleteSaveGameTitle" : "Vyberte uloženou hru k odstranění", + "vcmi.lobby.deleteMapTitle" : "Vyberte scénář k odstranění", + "vcmi.lobby.deleteFile" : "Chcete smazat následující soubor?", + "vcmi.lobby.deleteFolder" : "Chcete smazat následující složku?", + "vcmi.lobby.deleteMode" : "Přepnout do režimu mazání a zpět", + + "vcmi.lobby.login.title" : "Online lobby VCMI", + "vcmi.lobby.login.username" : "Uživatelské jméno:", + "vcmi.lobby.login.connecting" : "Připojování...", + "vcmi.lobby.login.error" : "Chyba při připojování: %s", + "vcmi.lobby.login.create" : "Nový účet", + "vcmi.lobby.login.login" : "Přihlásit se", + "vcmi.lobby.login.as" : "Přilásit se jako %s", + "vcmi.lobby.header.rooms" : "Herní místnosti - %d", + "vcmi.lobby.header.channels" : "Kanály konverzace", + "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name + "vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time + "vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player + "vcmi.lobby.header.history" : "Vaše předchozí hry", + "vcmi.lobby.header.players" : "Online hráči - %d", + "vcmi.lobby.match.solo" : "Hra jednoho hráče", + "vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player + "vcmi.lobby.match.multi" : "%d hráčů", + "vcmi.lobby.room.create" : "Vytvořit novou místnost", + "vcmi.lobby.room.players.limit" : "Omezení počtu hráčů", + "vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.", + "vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.", + "vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.", + "vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", + "vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).", + "vcmi.lobby.invite.header" : "Pozvat hráče", + "vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.", + "vcmi.lobby.preview.title" : "Připojit se do herní místnosti", + "vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host + "vcmi.lobby.preview.version" : "Verze hry:", + "vcmi.lobby.preview.players" : "Hráči:", + "vcmi.lobby.preview.mods" : "Použité modifikace:", + "vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?", + "vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.", + "vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.", + "vcmi.lobby.preview.error.full" : "Místnost je již plná.", + "vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.", + "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", + "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", + "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", + "vcmi.lobby.room.new" : "Nová hra", + "vcmi.lobby.room.load" : "Načíst hru", + "vcmi.lobby.room.type" : "Druh místnosti", + "vcmi.lobby.room.mode" : "Herní režim", + "vcmi.lobby.room.state.public" : "Veřejná", + "vcmi.lobby.room.state.private" : "Soukromá", + "vcmi.lobby.room.state.busy" : "Ve hře", + "vcmi.lobby.room.state.invited" : "Pozvaný", + "vcmi.lobby.mod.state.compatible" : "Kompatibilní", + "vcmi.lobby.mod.state.disabled" : "Musí být povolena", + "vcmi.lobby.mod.state.version" : "Neshoda verze", + "vcmi.lobby.mod.state.excessive" : "Musí být zakázána", + "vcmi.lobby.mod.state.missing" : "Není nainstalována", + "vcmi.lobby.pvp.coin.hover" : "Mince", + "vcmi.lobby.pvp.coin.help" : "Hodí mincí", + "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", + "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", + "vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", + "vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace", + "vcmi.lobby.pvp.versus" : "vs.", + + "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", + "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", + "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", + "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color + "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", + "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", + "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", + "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", + "vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.", + "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", + "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", + + "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", + + "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", + "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", + "vcmi.settingsMainWindow.battleTab.hover" : "Bitva", + "vcmi.settingsMainWindow.battleTab.help" : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.", + "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa", + "vcmi.settingsMainWindow.adventureTab.help" : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).", + + "vcmi.systemOptions.videoGroup" : "Nastavení obrazu", + "vcmi.systemOptions.audioGroup" : "Nastavení zvuku", + "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now + "vcmi.systemOptions.townsGroup" : "Obrazovka města", + + "vcmi.statisticWindow.statistics" : "Statistiky", + "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", + "vcmi.statisticWindow.selectView" : "Vybrat pohled", + "vcmi.statisticWindow.value" : "Hodnota", + "vcmi.statisticWindow.title.overview" : "Přehled", + "vcmi.statisticWindow.title.resources" : "Zdroje", + "vcmi.statisticWindow.title.income" : "Příjem", + "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", + "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", + "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", + "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", + "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", + "vcmi.statisticWindow.title.armyStrength" : "Síla armády", + "vcmi.statisticWindow.title.experience" : "Zkušenosti", + "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", + "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", + "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", + "vcmi.statisticWindow.param.playerName" : "Jméno hráče", + "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", + "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", + "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", + "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", + "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", + "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", + "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", + "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", + "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", + "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", + "vcmi.statisticWindow.icon.defeated" : "Porážka", + + "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", + "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", + "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", + "vcmi.systemOptions.fullscreenExclusive.help" : "{Celá obrazovka}\n\nPokud je vybráno, VCMI poběží v režimu exkluzivní celé obrazovky. V tomto režimu hra změní rozlišení na vybrané.", + "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", + "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", + "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", + "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", + "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", + "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", + "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", + "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" + "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", + "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", + "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", + "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", + "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", + "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", + "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", + "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", + "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", + "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", + "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", + + "vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací", + "vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.", + "vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek", + "vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.", + "vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu", + "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", + "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", + "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", + "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", + "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", + "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", + "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", + "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", + "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", + "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", + "vcmi.adventureOptions.mapScrollSpeed1.hover": "", + "vcmi.adventureOptions.mapScrollSpeed5.hover": "", + "vcmi.adventureOptions.mapScrollSpeed6.hover": "", + "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", + "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", + "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", + "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", + "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", + + "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", + "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", + "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", + "vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ", + "vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ", + "vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.", + "vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", + "vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.", + "vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", + "vcmi.battleOptions.animationsSpeed1.hover": "", + "vcmi.battleOptions.animationsSpeed5.hover": "", + "vcmi.battleOptions.animationsSpeed6.hover": "", + "vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.", + "vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.", + "vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.", + "vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí", + "vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Zobrazit omezení dostřelu střelců při najetí}\n\nZobrazit dostřel střelce při najetí na něj.", + "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Zobrazit okno statistik hrdinů", + "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", + "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", + "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", + "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", + "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", + "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", + "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", + + "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", + "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", + + "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", + "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", + "vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.ranged" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d střel zbývá", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d střela zbývá", + "vcmi.battleWindow.damageEstimation.damage" : "%d poškození", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", + "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", + + "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", + "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", + "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", + "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", + "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", + + "vcmi.battleWindow.killed" : "Zabito", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", + + "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", + + "vcmi.tutorialWindow.title" : "Úvod ovládání dotykem", + "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", + "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", + "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", + "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", + "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", + "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", + "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", + + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Zobrazit týdenní přírůstek jednotek}\n\nZobrazit týdenní přírůstek jednotek místo dostupného počtu ke koupení v přehledu města (levý spodní okraj obrazovky města).", + "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", + "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", + + "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", + "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", + + "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", + "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", + + "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", + "vcmi.logicalExpressions.allOf" : "Všechny následující:", + "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", + + "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", + "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", + "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", + "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", + "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", + "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", + "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", + + "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", + + "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", + + "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", + "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", + "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", + "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", + "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", + "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", + + "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", + "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", + + "vcmi.randomMapTab.widgets.randomTemplate" : "(Náhodná)", + "vcmi.randomMapTab.widgets.templateLabel" : "Šablona", + "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...", + "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Přiřazení týmů", + "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", + + "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", + "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", + + "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", + "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", + "vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy", + "vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky", + "vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.", + "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.", + "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.", + "vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.", + + "vcmi.optionsTab.accumulate" : "Akumulovat", + + "vcmi.optionsTab.simturnsTitle" : "Souběžné tahy", + "vcmi.optionsTab.simturnsMin.hover" : "Alespoň po", + "vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po", + "vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI", + "vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno", + "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", + "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", + + "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", + "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", + "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", + "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", + "vcmi.optionsTab.turnTime.classic.5" : "Klasický časovač: 5 minut", + "vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut", + "vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut", + "vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut", + "vcmi.optionsTab.turnTime.chess.20" : "Šachová: 20:00 + 10:00 + 02:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.16" : "Šachová: 16:00 + 08:00 + 01:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.8" : "Šachová: 08:00 + 04:00 + 01:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.4" : "Šachová: 04:00 + 02:00 + 00:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", + "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", + + "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", + "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", + "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", + "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", + "vcmi.optionsTab.simturns.tillContact2" : "Souběžně: 2 týdny, přerušit při setkání", + "vcmi.optionsTab.simturns.tillContact4" : "Souběžně: 1 mšsíc, přerušit při setkání", + "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", + "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", + "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d dní", + "vcmi.optionsTab.simturns.days.1" : " %d den", + "vcmi.optionsTab.simturns.days.2" : " %d dny", + "vcmi.optionsTab.simturns.weeks.0" : " %d týdnů", + "vcmi.optionsTab.simturns.weeks.1" : " %d týden", + "vcmi.optionsTab.simturns.weeks.2" : " %d týdny", + "vcmi.optionsTab.simturns.months.0" : " %d měsíců", + "vcmi.optionsTab.simturns.months.1" : " %d měsíc", + "vcmi.optionsTab.simturns.months.2" : " %d měsíce", + + "vcmi.optionsTab.extraOptions.hover" : "Další možnosti", + "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", + + "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", + "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", + "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", + "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", + + // Custom victory conditions for H3 campaigns and HotA maps + "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", + "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", + "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", + "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", + "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", + "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", + + // few strings from WoG used by vcmi + "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", + "vcmi.stackExperience.rank.0" : "Začátečník", + "vcmi.stackExperience.rank.1" : "Učeň", + "vcmi.stackExperience.rank.2" : "Trénovaný", + "vcmi.stackExperience.rank.3" : "Zručný", + "vcmi.stackExperience.rank.4" : "Prověřený", + "vcmi.stackExperience.rank.5" : "Veterán", + "vcmi.stackExperience.rank.6" : "Adept", + "vcmi.stackExperience.rank.7" : "Expert", + "vcmi.stackExperience.rank.8" : "Elitní", + "vcmi.stackExperience.rank.9" : "Mistr", + "vcmi.stackExperience.rank.10" : "Eso", + + // Strings for HotA Seer Hut / Quest Guards + "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", + + "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", + + "mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.", + + "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", + "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", + "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", + "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", + "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", + "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", + "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", + "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", + "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", + "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", + "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", + "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", + "core.bonus.CATAPULT.name": "Katapult", + "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", + "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", + "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", + "core.bonus.DARKNESS.name": "Závoj temnoty", + "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", + "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", + "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", + "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", + "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", + "core.bonus.DESTRUCTION.name": "Zničení", + "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", + "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", + "core.bonus.DRAGON_NATURE.name": "Dračí povaha", + "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", + "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", + "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", + "core.bonus.ENCHANTER.name": "Zaklínač", + "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", + "core.bonus.ENCHANTED.name": "Očarovaný", + "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", + "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", + "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", + "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", + "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", + "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", + "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", + "core.bonus.FIRST_STRIKE.name": "První úder", + "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", + "core.bonus.FEAR.name": "Strach", + "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", + "core.bonus.FEARLESS.name": "Nebojácnost", + "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", + "core.bonus.FEROCITY.name": "Zuřivost", + "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", + "core.bonus.FLYING.name": "Létání", + "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", + "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", + "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", + "core.bonus.GARGOYLE.name": "Chrlič", + "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", + "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", + "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", + "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", + "core.bonus.HEALER.name": "Léčitel", + "core.bonus.HEALER.description": "Léčí spojenecké jednotky", + "core.bonus.HP_REGENERATION.name": "Regenerace", + "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", + "core.bonus.JOUSTING.name": "Nájezd šampionů", + "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", + "core.bonus.KING.name": "Král", + "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", + "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", + "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", + "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", + "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", + "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", + "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", + "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", + "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", + "core.bonus.MANA_DRAIN.name": "Vysávání many", + "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", + "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", + "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", + "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", + "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", + "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", + "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", + "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", + "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", + "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", + "core.bonus.NO_MORALE.name": "Neutrální morálka", + "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", + "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", + "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", + "core.bonus.NON_LIVING.name": "Neživý", + "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", + "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", + "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", + "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", + "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", + "core.bonus.RECEPTIVE.name": "Vnímavý", + "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", + "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", + "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", + "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", + "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", + "core.bonus.REVENGE.name": "Pomsta", + "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", + "core.bonus.SHOOTER.name": "Střelec", + "core.bonus.SHOOTER.description": "Jednotka může střílet", + "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", + "core.bonus.SOUL_STEAL.name": "Zloděj duší", + "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", + "core.bonus.SPELLCASTER.name": "Kouzelník", + "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", + "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", + "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", + "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", + "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", + "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", + "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", + "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", + "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", + "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", + "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", + "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", + "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", + "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", + "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", + "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", + "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", + "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", + "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", + "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", + "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", + "core.bonus.TRANSMUTATION.name": "Transmutace", + "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", + "core.bonus.UNDEAD.name": "Nemrtvý", + "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", + "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", + "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", + "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", + "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", + "core.bonus.WIDE_BREATH.name": "Široký dech", + "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", + "core.bonus.DISINTEGRATE.name": "Rozpad", + "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", + "core.bonus.INVINCIBLE.name": "Neporazitelný", + "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" } \ No newline at end of file From 9009dffebdda158a424e3dcf1202dadb37fd9291 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:51:33 +0100 Subject: [PATCH 512/726] add "other" for bonus display --- Mods/vcmi/config/english.json | 1 + client/windows/CCreatureWindow.cpp | 71 +++++++++++++++--------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/config/english.json index ac22082de..b0288519d 100644 --- a/Mods/vcmi/config/english.json +++ b/Mods/vcmi/config/english.json @@ -33,6 +33,7 @@ "vcmi.bonusSource.spell" : "Spell", "vcmi.bonusSource.hero" : "Hero", "vcmi.bonusSource.commander" : "Commander", + "vcmi.bonusSource.other" : "Other", "vcmi.capitalColors.0" : "Red", "vcmi.capitalColors.1" : "Blue", diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index e44809e31..f4f6d6f3c 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -250,45 +250,46 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li Point(6, 4), Point(214, 4) }; - - std::map bonusColors = { - {BonusSource::ARTIFACT, Colors::GREEN}, - {BonusSource::ARTIFACT_INSTANCE, Colors::GREEN}, - {BonusSource::CREATURE_ABILITY, Colors::YELLOW}, - {BonusSource::SPELL_EFFECT, Colors::ORANGE}, - {BonusSource::SECONDARY_SKILL, Colors::PURPLE}, - {BonusSource::HERO_SPECIAL, Colors::PURPLE}, - {BonusSource::STACK_EXPERIENCE, Colors::CYAN}, - {BonusSource::COMMANDER, Colors::CYAN}, - }; - - std::map bonusNames = { - {BonusSource::ARTIFACT, CGI->generaltexth->translate("vcmi.bonusSource.artifact")}, - {BonusSource::ARTIFACT_INSTANCE, CGI->generaltexth->translate("vcmi.bonusSource.artifact")}, - {BonusSource::CREATURE_ABILITY, CGI->generaltexth->translate("vcmi.bonusSource.creature")}, - {BonusSource::SPELL_EFFECT, CGI->generaltexth->translate("vcmi.bonusSource.spell")}, - {BonusSource::SECONDARY_SKILL, CGI->generaltexth->translate("vcmi.bonusSource.hero")}, - {BonusSource::HERO_SPECIAL, CGI->generaltexth->translate("vcmi.bonusSource.hero")}, - {BonusSource::STACK_EXPERIENCE, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, - {BonusSource::COMMANDER, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, - }; - auto drawBonusSourceText = [this, &bonusColors, &bonusNames](int leftRight, Point p, BonusInfo & bi) - { - if(!bonusColors.count(bi.bonusSource) || !bonusNames.count(bi.bonusSource)) - return; + auto drawBonusSource = [this](int leftRight, Point p, BonusInfo & bi) + { + std::map bonusColors = { + {BonusSource::ARTIFACT, Colors::GREEN}, + {BonusSource::ARTIFACT_INSTANCE, Colors::GREEN}, + {BonusSource::CREATURE_ABILITY, Colors::YELLOW}, + {BonusSource::SPELL_EFFECT, Colors::ORANGE}, + {BonusSource::SECONDARY_SKILL, Colors::PURPLE}, + {BonusSource::HERO_SPECIAL, Colors::PURPLE}, + {BonusSource::STACK_EXPERIENCE, Colors::CYAN}, + {BonusSource::COMMANDER, Colors::CYAN}, + }; + + std::map bonusNames = { + {BonusSource::ARTIFACT, CGI->generaltexth->translate("vcmi.bonusSource.artifact")}, + {BonusSource::ARTIFACT_INSTANCE, CGI->generaltexth->translate("vcmi.bonusSource.artifact")}, + {BonusSource::CREATURE_ABILITY, CGI->generaltexth->translate("vcmi.bonusSource.creature")}, + {BonusSource::SPELL_EFFECT, CGI->generaltexth->translate("vcmi.bonusSource.spell")}, + {BonusSource::SECONDARY_SKILL, CGI->generaltexth->translate("vcmi.bonusSource.hero")}, + {BonusSource::HERO_SPECIAL, CGI->generaltexth->translate("vcmi.bonusSource.hero")}, + {BonusSource::STACK_EXPERIENCE, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, + {BonusSource::COMMANDER, CGI->generaltexth->translate("vcmi.bonusSource.commander")}, + }; - auto c = bonusColors[bi.bonusSource]; - std::string t = bonusNames[bi.bonusSource]; + auto c = bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : ColorRGBA(192, 192, 192); + std::string t = bonusNames.count(bi.bonusSource) ? bonusNames[bi.bonusSource] : CGI->generaltexth->translate("vcmi.bonusSource.other"); int maxLen = 50; EFonts f = FONT_TINY; + Point pText = p + Point(3, 40); // 1px Black border - bonusSource[leftRight].push_back(std::make_shared(p.x - 1, p.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); - bonusSource[leftRight].push_back(std::make_shared(p.x + 1, p.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); - bonusSource[leftRight].push_back(std::make_shared(p.x, p.y - 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); - bonusSource[leftRight].push_back(std::make_shared(p.x, p.y + 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); - bonusSource[leftRight].push_back(std::make_shared(p.x, p.y, f, ETextAlignment::TOPLEFT, c, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(pText.x - 1, pText.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(pText.x + 1, pText.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(pText.x, pText.y - 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(pText.x, pText.y + 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen)); + bonusSource[leftRight].push_back(std::make_shared(pText.x, pText.y, f, ETextAlignment::TOPLEFT, c, t, maxLen)); + + frame[leftRight] = std::make_shared(Rect(p.x, p.y, 52, 52)); + frame[leftRight]->addRectangle(Point(0, 0), Point(52, 52), c); }; for(size_t leftRight : {0, 1}) @@ -302,9 +303,7 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li icon[leftRight] = std::make_shared(bi.imagePath, position.x, position.y); name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name, 137); description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 20, 137, 30), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); - drawBonusSourceText(leftRight, Point(position.x + 2, position.y + 39), bi); - frame[leftRight] = std::make_shared(Rect(position.x - 1, position.y - 1, 52, 52)); - frame[leftRight]->addRectangle(Point(0, 0), Point(52, 52), bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : Colors::BLACK); + drawBonusSource(leftRight, Point(position.x - 1, position.y - 1), bi); } } } From 0ab4b66026d1ffcb4e5e7eaa58969954e7ac6127 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:16:38 +0100 Subject: [PATCH 513/726] better approach for scrollbar --- .../stackWindow/bonus-effects-noscroll.png | Bin 35463 -> 0 bytes Mods/vcmi/Data/stackWindow/bonus-effects.png | Bin 29969 -> 35463 bytes client/widgets/ObjectLists.cpp | 11 ++++------- client/windows/CCreatureWindow.cpp | 6 +++--- client/windows/CCreatureWindow.h | 2 +- 5 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 Mods/vcmi/Data/stackWindow/bonus-effects-noscroll.png diff --git a/Mods/vcmi/Data/stackWindow/bonus-effects-noscroll.png b/Mods/vcmi/Data/stackWindow/bonus-effects-noscroll.png deleted file mode 100644 index 67fe2ce82f477afb855ddce32baf5db3f4db0c62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35463 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
v7=T$@uH5Lx(DpA& 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
v7=T$@uH5Lx(DpA& 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 diff --git a/client/widgets/ObjectLists.cpp b/client/widgets/ObjectLists.cpp index d9a257923..6470df4a5 100644 --- a/client/widgets/ObjectLists.cpp +++ b/client/widgets/ObjectLists.cpp @@ -116,12 +116,12 @@ void CListBox::updatePositions() (elem)->moveTo(itemPos); itemPos += itemOffset; } - if (isActive()) + if(slider) { - redraw(); - if (slider) - slider->scrollTo((int)first); + slider->scrollTo((int)first); + moveChildForeground(slider.get()); } + redraw(); } void CListBox::reset() @@ -185,9 +185,6 @@ void CListBox::scrollTo(size_t which) //scroll down else if (first + items.size() <= which && which < totalSize) moveToPos(which - items.size() + 1); - - if(slider) - slider->scrollTo(which); } void CListBox::moveToPos(size_t which) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index f4f6d6f3c..ca50be8e6 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -240,8 +240,8 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int } } -CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t lineIndex, bool noScroll) - : CWindowSection(owner, ImagePath::builtin(noScroll ? "stackWindow/bonus-effects-noscroll" : "stackWindow/bonus-effects"), 0) +CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t lineIndex) + : CWindowSection(owner, ImagePath::builtin("stackWindow/bonus-effects"), 0) { OBJECT_CONSTRUCTION; @@ -324,7 +324,7 @@ CStackWindow::BonusesSection::BonusesSection(CStackWindow * owner, int yOffset, auto onCreate = [=](size_t index) -> std::shared_ptr { - return std::make_shared(owner, index, totalSize <= 3); + return std::make_shared(owner, index); }; lines = std::make_shared(onCreate, Point(0, 0), Point(0, itemHeight), visibleSize, totalSize, 0, totalSize > 3 ? 1 : 0, Rect(pos.w - 15, 0, pos.h, pos.h)); diff --git a/client/windows/CCreatureWindow.h b/client/windows/CCreatureWindow.h index a94196a26..acf43029a 100644 --- a/client/windows/CCreatureWindow.h +++ b/client/windows/CCreatureWindow.h @@ -89,7 +89,7 @@ class CStackWindow : public CWindowObject std::array, 2> frame; std::array>, 2> bonusSource; public: - BonusLineSection(CStackWindow * owner, size_t lineIndex, bool noScroll); + BonusLineSection(CStackWindow * owner, size_t lineIndex); }; class BonusesSection : public CWindowSection From 74b4fbfcebeb72729e2eb66aa6796b258ee00334 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:19:39 +0100 Subject: [PATCH 514/726] small fix --- client/windows/CCreatureWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index ca50be8e6..ce8ae3343 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -302,7 +302,7 @@ CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t li BonusInfo & bi = parent->activeBonuses[bonusIndex]; icon[leftRight] = std::make_shared(bi.imagePath, position.x, position.y); name[leftRight] = std::make_shared(position.x + 60, position.y + 2, FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.name, 137); - description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 20, 137, 30), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); + description[leftRight] = std::make_shared(Rect(position.x + 60, position.y + 20, 137, 26), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description); drawBonusSource(leftRight, Point(position.x - 1, position.y - 1), bi); } } From 6ae3ad4615afd3955778a6ae2cce06d81bad7201 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:39:55 +0100 Subject: [PATCH 515/726] Add files via upload --- Mods/vcmi/config/czech.json | 47 +++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/Mods/vcmi/config/czech.json b/Mods/vcmi/config/czech.json index 672c6c340..fe229ffa5 100644 --- a/Mods/vcmi/config/czech.json +++ b/Mods/vcmi/config/czech.json @@ -28,6 +28,13 @@ "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", + "vcmi.bonusSource.artifact" : "Artefakt", + "vcmi.bonusSource.creature" : "Jednotka", + "vcmi.bonusSource.spell" : "Kouzlo", + "vcmi.bonusSource.hero" : "Hrdina", + "vcmi.bonusSource.commander" : "Velitel", + "vcmi.bonusSource.other" : "Ostatní", + "vcmi.capitalColors.0" : "Červený", "vcmi.capitalColors.1" : "Modrý", "vcmi.capitalColors.2" : "Hnědý", @@ -36,25 +43,25 @@ "vcmi.capitalColors.5" : "Fialový", "vcmi.capitalColors.6" : "Tyrkysový", "vcmi.capitalColors.7" : "Růžový", - + "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", "vcmi.heroOverview.warMachine" : "Bojové stroje", "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", "vcmi.heroOverview.spells" : "Kouzla", - + "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", - + "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", - + "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", @@ -66,7 +73,7 @@ "vcmi.radialWheel.moveUp" : "Posunout výše", "vcmi.radialWheel.moveDown" : "Posunout níže", "vcmi.radialWheel.moveBottom" : "Přesunout dolů", - + "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", @@ -106,13 +113,13 @@ "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", - "vcmi.lobby.deleteUnsupportedSave" : "Nalezeny nepodporované uložené hry (např. z předchozích verzí).\n\nChcete je odstranit?", - "vcmi.lobby.deleteSaveGameTitle" : "Vyberte uloženou hru k odstranění", - "vcmi.lobby.deleteMapTitle" : "Vyberte scénář k odstranění", - "vcmi.lobby.deleteFile" : "Chcete smazat následující soubor?", - "vcmi.lobby.deleteFolder" : "Chcete smazat následující složku?", - "vcmi.lobby.deleteMode" : "Přepnout do režimu mazání a zpět", - + "vcmi.lobby.deleteUnsupportedSave" : "Nalezeny nepodporované uložené hry (např. z předchozích verzí).\n\nChcete je odstranit?", + "vcmi.lobby.deleteSaveGameTitle" : "Vyberte uloženou hru k odstranění", + "vcmi.lobby.deleteMapTitle" : "Vyberte scénář k odstranění", + "vcmi.lobby.deleteFile" : "Chcete smazat následující soubor?", + "vcmi.lobby.deleteFolder" : "Chcete smazat následující složku?", + "vcmi.lobby.deleteMode" : "Přepnout do režimu mazání a zpět", + "vcmi.lobby.login.title" : "Online lobby VCMI", "vcmi.lobby.login.username" : "Uživatelské jméno:", "vcmi.lobby.login.connecting" : "Připojování...", @@ -184,7 +191,7 @@ "vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.", "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", - + "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", @@ -330,13 +337,13 @@ "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", - + "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", - + "vcmi.battleWindow.killed" : "Zabito", "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", @@ -451,7 +458,7 @@ "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", - + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language // Using this information, VCMI will automatically select correct plural form for every possible amount "vcmi.optionsTab.simturns.days.0" : " %d dní", @@ -495,7 +502,7 @@ "vcmi.stackExperience.rank.8" : "Elitní", "vcmi.stackExperience.rank.9" : "Mistr", "vcmi.stackExperience.rank.10" : "Eso", - + // Strings for HotA Seer Hut / Quest Guards "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", @@ -527,7 +534,7 @@ "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", - + "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", @@ -558,9 +565,9 @@ "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", - + "mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.", - + "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", From 988d36bd139e0a2c31f639a74444db4ac68bf28c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:46:20 +0100 Subject: [PATCH 516/726] sorted --- Mods/vcmi/config/english.json | 2 +- client/windows/CCreatureWindow.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/config/english.json index b0288519d..526d60906 100644 --- a/Mods/vcmi/config/english.json +++ b/Mods/vcmi/config/english.json @@ -29,7 +29,7 @@ "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sorry, replay opponent turn is not implemented yet!", "vcmi.bonusSource.artifact" : "Artifact", - "vcmi.bonusSource.creature" : "Creature", + "vcmi.bonusSource.creature" : "Ability", "vcmi.bonusSource.spell" : "Spell", "vcmi.bonusSource.hero" : "Hero", "vcmi.bonusSource.commander" : "Commander", diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index ce8ae3343..ee3a6bf24 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -829,6 +829,9 @@ void CStackWindow::initBonusesList() BonusList output; BonusList input; input = *(info->stackNode->getBonuses(CSelector(Bonus::Permanent), Selector::all)); + std::sort(input.begin(), input.end(), [this](std::shared_ptr v1, std::shared_ptr & v2){ + return v1->source == v2->source ? info->stackNode->bonusToString(v1, false) < info->stackNode->bonusToString(v2, false) : v1->source == BonusSource::CREATURE_ABILITY || (v1->source < v2->source); + }); while(!input.empty()) { From 1367348da92f37ebf05ee706ef847a751b243fe1 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 2 Nov 2024 16:09:29 +0100 Subject: [PATCH 517/726] allow scrolling in touch popups --- client/eventsSDL/InputSourceTouch.cpp | 13 +++++++++---- client/eventsSDL/InputSourceTouch.h | 10 ++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/client/eventsSDL/InputSourceTouch.cpp b/client/eventsSDL/InputSourceTouch.cpp index 0b5d48954..1135a0b52 100644 --- a/client/eventsSDL/InputSourceTouch.cpp +++ b/client/eventsSDL/InputSourceTouch.cpp @@ -83,16 +83,18 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin break; } case TouchState::TAP_DOWN_SHORT: + case TouchState::TAP_DOWN_LONG_AWAIT: { Point distance = convertTouchToMouse(tfinger) - lastTapPosition; if ( std::abs(distance.x) > params.panningSensitivityThreshold || std::abs(distance.y) > params.panningSensitivityThreshold) { - state = TouchState::TAP_DOWN_PANNING; + state = state == TouchState::TAP_DOWN_SHORT ? TouchState::TAP_DOWN_PANNING : TouchState::TAP_DOWN_PANNING_POPUP; GH.events().dispatchGesturePanningStarted(lastTapPosition); } break; } case TouchState::TAP_DOWN_PANNING: + case TouchState::TAP_DOWN_PANNING_POPUP: { emitPanningEvent(tfinger); break; @@ -103,7 +105,6 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin break; } case TouchState::TAP_DOWN_LONG: - case TouchState::TAP_DOWN_LONG_AWAIT: { // no-op break; @@ -157,8 +158,11 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge CSH->getGlobalLobby().activateInterface(); break; } - case TouchState::TAP_DOWN_LONG: case TouchState::TAP_DOWN_LONG_AWAIT: + lastTapPosition = convertTouchToMouse(tfinger); + break; + case TouchState::TAP_DOWN_LONG: + case TouchState::TAP_DOWN_PANNING_POPUP: { // no-op break; @@ -205,9 +209,10 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger) break; } case TouchState::TAP_DOWN_PANNING: + case TouchState::TAP_DOWN_PANNING_POPUP: { GH.events().dispatchGesturePanningEnded(lastTapPosition, convertTouchToMouse(tfinger)); - state = TouchState::IDLE; + state = state == TouchState::TAP_DOWN_PANNING ? TouchState::IDLE : TouchState::TAP_DOWN_LONG_AWAIT; break; } case TouchState::TAP_DOWN_DOUBLE: diff --git a/client/eventsSDL/InputSourceTouch.h b/client/eventsSDL/InputSourceTouch.h index 19a8cca50..2c1d8490c 100644 --- a/client/eventsSDL/InputSourceTouch.h +++ b/client/eventsSDL/InputSourceTouch.h @@ -45,6 +45,12 @@ enum class TouchState // UP -> transition to IDLE TAP_DOWN_PANNING, + // single finger is moving across screen + // DOWN -> ignored + // MOTION -> emit panning event + // UP -> transition to TAP_DOWN_LONG_AWAIT + TAP_DOWN_PANNING_POPUP, + // two fingers are touching the screen // DOWN -> ??? how to handle 3rd finger? Ignore? // MOTION -> emit pinch event @@ -59,7 +65,7 @@ enum class TouchState // right-click popup is active, waiting for new tap to hide popup // DOWN -> ignored - // MOTION -> ignored + // MOTION -> transition to TAP_DOWN_PANNING_POPUP // UP -> transition to IDLE, generate closePopup() event TAP_DOWN_LONG_AWAIT, }; @@ -79,7 +85,7 @@ struct TouchInputParameters uint32_t doubleTouchToleranceDistance = 50; /// moving finger for distance larger than specified will be qualified as panning gesture instead of long press - uint32_t panningSensitivityThreshold = 10; + uint32_t panningSensitivityThreshold = 15; /// gesture will be qualified as pinch if distance between fingers is at least specified here uint32_t pinchSensitivityThreshold = 10; From ca037aae33a3e9d685bff98b52b25353e1b6bfdc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 3 Nov 2024 01:13:14 +0100 Subject: [PATCH 518/726] notFocusedClick refactoring --- client/lobby/OptionsTab.cpp | 20 ++++---------------- client/lobby/OptionsTab.h | 5 ++--- client/windows/GUIClasses.cpp | 4 ++-- client/windows/GUIClasses.h | 2 +- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 3aa94542e..4d5aa771c 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -765,9 +765,9 @@ void OptionsTab::SelectionWindow::sliderMove(int slidPos) redraw(); } -bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int eventType) const +void OptionsTab::SelectionWindow::notFocusedClick() { - return true; // capture click also outside of window + close(); } void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition) @@ -775,12 +775,6 @@ void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition) if(slider && slider->pos.isInside(cursorPosition)) return; - if(!pos.isInside(cursorPosition)) - { - close(); - return; - } - int elem = getElement(cursorPosition); setElement(elem, true); @@ -898,15 +892,9 @@ OptionsTab::HandicapWindow::HandicapWindow() center(); } -bool OptionsTab::HandicapWindow::receiveEvent(const Point & position, int eventType) const +void OptionsTab::HandicapWindow::notFocusedClick() { - return true; // capture click also outside of window -} - -void OptionsTab::HandicapWindow::clickReleased(const Point & cursorPosition) -{ - if(!pos.isInside(cursorPosition)) // make it possible to close window by touching/clicking outside of window - close(); + close(); } OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & playerSettings, SelType type) diff --git a/client/lobby/OptionsTab.h b/client/lobby/OptionsTab.h index 8bef7c9c2..e74666e6d 100644 --- a/client/lobby/OptionsTab.h +++ b/client/lobby/OptionsTab.h @@ -63,8 +63,7 @@ public: std::map>> textinputs; std::vector> buttons; - bool receiveEvent(const Point & position, int eventType) const override; - void clickReleased(const Point & cursorPosition) override; + void notFocusedClick() override; public: HandicapWindow(); }; @@ -167,7 +166,7 @@ private: void sliderMove(int slidPos); - bool receiveEvent(const Point & position, int eventType) const override; + void notFocusedClick() override; void clickReleased(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override; diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 344b18deb..5178c332c 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1703,7 +1703,7 @@ void VideoWindow::keyPressed(EShortcut key) exit(true); } -bool VideoWindow::receiveEvent(const Point & position, int eventType) const +void VideoWindow::notFocusedClick() { - return true; // capture click also outside of window + exit(true); } diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 06e070bfe..f3c8a044c 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -523,5 +523,5 @@ public: void clickPressed(const Point & cursorPosition) override; void keyPressed(EShortcut key) override; - bool receiveEvent(const Point & position, int eventType) const override; + void notFocusedClick() override; }; From e4f9bcb8877660d8f01ab8088593a5deea46d368 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sun, 3 Nov 2024 14:45:49 +0100 Subject: [PATCH 519/726] Fixed typo --- Mods/vcmi/config/czech.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/czech.json b/Mods/vcmi/config/czech.json index fe229ffa5..35463c388 100644 --- a/Mods/vcmi/config/czech.json +++ b/Mods/vcmi/config/czech.json @@ -126,7 +126,7 @@ "vcmi.lobby.login.error" : "Chyba při připojování: %s", "vcmi.lobby.login.create" : "Nový účet", "vcmi.lobby.login.login" : "Přihlásit se", - "vcmi.lobby.login.as" : "Přilásit se jako %s", + "vcmi.lobby.login.as" : "Přihlásit se jako %s", "vcmi.lobby.header.rooms" : "Herní místnosti - %d", "vcmi.lobby.header.channels" : "Kanály konverzace", "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name From f60813d86fe27c138b003f718fa63e4187065a85 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:24:08 +0200 Subject: [PATCH 520/726] CHeroOverview secondary skill icons fixed --- client/widgets/CComponentHolder.cpp | 6 +++--- client/windows/CExchangeWindow.cpp | 2 +- client/windows/CHeroOverview.cpp | 5 +++-- client/windows/CHeroOverview.h | 3 ++- client/windows/CHeroWindow.cpp | 2 +- client/windows/GUIClasses.cpp | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/widgets/CComponentHolder.cpp b/client/widgets/CComponentHolder.cpp index 825d4ec67..e0f4196a3 100644 --- a/client/widgets/CComponentHolder.cpp +++ b/client/widgets/CComponentHolder.cpp @@ -266,11 +266,11 @@ CSecSkillPlace::CSecSkillPlace(const Point & position, const ImageSize & imageSi { OBJECT_CONSTRUCTION; - auto imagePath = AnimationPath::builtin("SECSKILL"); + auto imagePath = AnimationPath::builtin("SECSK82"); if(imageSize == ImageSize::MEDIUM) - imagePath = AnimationPath::builtin("SECSK32"); + imagePath = AnimationPath::builtin("SECSKILL"); if(imageSize == ImageSize::SMALL) - imagePath = AnimationPath::builtin("SECSK82"); + imagePath = AnimationPath::builtin("SECSK32"); image = std::make_shared(imagePath, 0); component.type = ComponentType::SEC_SKILL; diff --git a/client/windows/CExchangeWindow.cpp b/client/windows/CExchangeWindow.cpp index 81fd27888..b97696446 100644 --- a/client/windows/CExchangeWindow.cpp +++ b/client/windows/CExchangeWindow.cpp @@ -82,7 +82,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, for(int m=0; m < hero->secSkills.size(); ++m) - secSkills[leftRight].push_back(std::make_shared(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::MEDIUM, + secSkills[leftRight].push_back(std::make_shared(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::SMALL, hero->secSkills[m].first, hero->secSkills[m].second)); specImages[leftRight] = std::make_shared(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45); diff --git a/client/windows/CHeroOverview.cpp b/client/windows/CHeroOverview.cpp index ac1c9dd00..ae6c81409 100644 --- a/client/windows/CHeroOverview.cpp +++ b/client/windows/CHeroOverview.cpp @@ -18,7 +18,7 @@ #include "../render/Colors.h" #include "../render/IImage.h" #include "../renderSDL/RenderHandler.h" -#include "../widgets/CComponent.h" +#include"../widgets/CComponentHolder.h" #include "../widgets/Images.h" #include "../widgets/TextControls.h" #include "../widgets/GraphicalPrimitiveCanvas.h" @@ -206,7 +206,8 @@ void CHeroOverview::genControls() i = 0; for(auto & skill : (*CGI->heroh)[heroIdx]->secSkillsInit) { - imageSecSkills.push_back(std::make_shared(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex(skill.second + 2), 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset))); + imageSecSkills.push_back(std::make_shared(Point(302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)), + CSecSkillPlace::ImageSize::SMALL, skill.first, skill.second)); labelSecSkillsNames.push_back(std::make_shared(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->levels[skill.second - 1])); labelSecSkillsNames.push_back(std::make_shared(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->skillh)[skill.first]->getNameTranslated())); i++; diff --git a/client/windows/CHeroOverview.h b/client/windows/CHeroOverview.h index 64c9b16e9..d2ac4aa2e 100644 --- a/client/windows/CHeroOverview.h +++ b/client/windows/CHeroOverview.h @@ -19,6 +19,7 @@ class CComponentBox; class CTextBox; class TransparentFilledRectangle; class SimpleLine; +class CSecSkillPlace; class CHeroOverview : public CWindowObject { @@ -60,7 +61,7 @@ class CHeroOverview : public CWindowObject std::vector> labelSpellsNames; std::shared_ptr labelSecSkillTitle; - std::vector> imageSecSkills; + std::vector> imageSecSkills; std::vector> labelSecSkillsNames; void genBackground(); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 26a9e35f9..b0b495009 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -152,7 +152,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero) for(int i = 0; i < std::min(hero->secSkills.size(), 8u); ++i) { Rect r = Rect(i%2 == 0 ? 18 : 162, 276 + 48 * (i/2), 136, 42); - secSkills.emplace_back(std::make_shared(r.topLeft(), CSecSkillPlace::ImageSize::LARGE)); + secSkills.emplace_back(std::make_shared(r.topLeft(), CSecSkillPlace::ImageSize::MEDIUM)); int x = (i % 2) ? 212 : 68; int y = 280 + 48 * (i/2); diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 344b18deb..a15027290 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -896,7 +896,7 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int pos.x += X; pos.y += Y; - skill = std::make_shared(Point(), CSecSkillPlace::ImageSize::LARGE, _ID, 1); + skill = std::make_shared(Point(), CSecSkillPlace::ImageSize::MEDIUM, _ID, 1); skill->setClickPressedCallback([this](const CComponentHolder&, const Point& cursorPosition) { bool skillKnown = parent->hero->getSecSkillLevel(ID); From 529f6b8a829d0bd6735a5122844baba27e78495c Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:28:45 +0100 Subject: [PATCH 521/726] Add files via upload --- Mods/vcmi/config/czech.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/czech.json b/Mods/vcmi/config/czech.json index 35463c388..eaf0950a8 100644 --- a/Mods/vcmi/config/czech.json +++ b/Mods/vcmi/config/czech.json @@ -29,7 +29,7 @@ "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", "vcmi.bonusSource.artifact" : "Artefakt", - "vcmi.bonusSource.creature" : "Jednotka", + "vcmi.bonusSource.creature" : "Schopnost", "vcmi.bonusSource.spell" : "Kouzlo", "vcmi.bonusSource.hero" : "Hrdina", "vcmi.bonusSource.commander" : "Velitel", From 11b437db62af8371fad482a473d23bd349ad3e7b Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 3 Nov 2024 13:38:15 +0100 Subject: [PATCH 522/726] prescaled image support --- client/render/ImageLocator.cpp | 1 + client/render/ImageLocator.h | 1 + client/renderSDL/RenderHandler.cpp | 75 +++++++++++++++++++++++++++--- client/renderSDL/RenderHandler.h | 2 + client/renderSDL/SDLImage.cpp | 27 +++++++---- client/renderSDL/SDLImage.h | 7 ++- 6 files changed, 96 insertions(+), 17 deletions(-) diff --git a/client/render/ImageLocator.cpp b/client/render/ImageLocator.cpp index a9f732923..27c377d5b 100644 --- a/client/render/ImageLocator.cpp +++ b/client/render/ImageLocator.cpp @@ -71,6 +71,7 @@ ImageLocator ImageLocator::copyFile() const { ImageLocator result; result.scalingFactor = 1; + result.preScaledFactor = preScaledFactor; result.image = image; result.defFile = defFile; result.defFrame = defFrame; diff --git a/client/render/ImageLocator.h b/client/render/ImageLocator.h index e53679be0..f03150981 100644 --- a/client/render/ImageLocator.h +++ b/client/render/ImageLocator.h @@ -33,6 +33,7 @@ struct ImageLocator bool verticalFlip = false; bool horizontalFlip = false; int8_t scalingFactor = 0; // 0 = auto / use default scaling + int8_t preScaledFactor = 1; EImageLayer layer = EImageLayer::ALL; ImageLocator() = default; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index f1afc8255..837d8035c 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -55,6 +55,58 @@ std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & return result; } +std::optional RenderHandler::getPath(ResourcePath path) +{ + if(CResourceHandler::get()->existsResource(path)) + return path; + if(path.getType() == EResType::IMAGE) + { + auto p = ImagePath::builtin(path.getName()); + if(CResourceHandler::get()->existsResource(p.addPrefix("DATA/"))) + return std::optional(p.addPrefix("DATA/")); + if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES/"))) + return std::optional(p.addPrefix("SPRITES/")); + } + else + { + auto p = AnimationPath::builtin(path.getName()); + auto pJson = p.toType(); + if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES/"))) + return std::optional(p.addPrefix("SPRITES/")); + if(CResourceHandler::get()->existsResource(pJson)) + return std::optional(p); + if(CResourceHandler::get()->existsResource(pJson.addPrefix("SPRITES/"))) + return std::optional(p.addPrefix("SPRITES/")); + } + + return std::nullopt; +} + +std::pair RenderHandler::getScalePath(ResourcePath p) +{ + auto path = p; + auto name = p.getName(); + int scaleFactor = 1; + if(getScalingFactor() > 1) + { + std::vector factorsToCheck = {getScalingFactor(), 4, 3, 2}; + for(auto factorToCheck : factorsToCheck) + { + ResourcePath scaledPath = ImagePath::builtin(name + "$" + std::to_string(factorToCheck)); + if(p.getType() != EResType::IMAGE) + scaledPath = AnimationPath::builtin(name + "$" + std::to_string(factorToCheck)); + if(getPath(scaledPath)) + { + path = scaledPath; + scaleFactor = factorToCheck; + break; + } + } + } + + return std::pair(path, scaleFactor); +}; + void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config) { std::string basepath; @@ -177,13 +229,13 @@ std::shared_ptr RenderHandler::loadImageFromFileUncached(const Ima if (locator.image) { // TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load - return std::make_shared(*locator.image); + return std::make_shared(*locator.image, locator.preScaledFactor); } if (locator.defFile) { auto defFile = getAnimationFile(*locator.defFile); - return std::make_shared(defFile.get(), locator.defFrame, locator.defGroup); + return std::make_shared(defFile.get(), locator.defFrame, locator.defGroup, locator.preScaledFactor); } throw std::runtime_error("Invalid image locator received!"); @@ -247,7 +299,7 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator & loc if (locator.layer == EImageLayer::ALL && locator.playerColored != PlayerColor::CANNOT_DETERMINE) handle->playerColored(locator.playerColored); - handle->scaleInteger(locator.scalingFactor); + handle->scaleInteger(locator.scalingFactor); // TODO: try to optimize image size (possibly even before scaling?) - trim image borders if they are completely transparent auto result = handle->getSharedImage(); @@ -284,13 +336,17 @@ std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, E std::shared_ptr RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) { - ImageLocator locator = getLocatorForAnimationFrame(path, frame, group); + auto tmp = getScalePath(path); + ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(tmp.first.getName()), frame, group); + locator.preScaledFactor = tmp.second; return loadImage(locator, mode); } std::shared_ptr RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode) { - ImageLocator locator(path); + auto tmp = getScalePath(path); + ImageLocator locator(ImagePath::builtin(tmp.first.getName())); + locator.preScaledFactor = tmp.second; return loadImage(locator, mode); } @@ -301,7 +357,14 @@ std::shared_ptr RenderHandler::createImage(SDL_Surface * source) std::shared_ptr RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode) { - return std::make_shared(path, getAnimationLayout(path), mode); + auto tmp = getScalePath(path); + auto animPath = AnimationPath::builtin(tmp.first.getName()); + auto layout = getAnimationLayout(animPath); + for(auto & g : layout) + for(auto & i : g.second) + i.preScaledFactor = tmp.second; + + return std::make_shared(animPath, layout, mode); } void RenderHandler::addImageListEntries(const EntityService * service) diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index 20b13306a..e30ccbf4d 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -29,6 +29,8 @@ class RenderHandler : public IRenderHandler std::map> fonts; std::shared_ptr getAnimationFile(const AnimationPath & path); + std::optional getPath(ResourcePath path); + std::pair getScalePath(ResourcePath p); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path); void initFromJson(AnimationLayoutMap & layout, const JsonNode & config); diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 83067b8a2..61b1b2b52 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -89,11 +89,12 @@ int IImage::height() const return dimensions().y; } -SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group) +SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int scaleFactor) : surf(nullptr), margins(0, 0), fullSize(0, 0), - originalPalette(nullptr) + originalPalette(nullptr), + scaleFactor(scaleFactor) { SDLImageLoader loader(this); data->loadFrame(frame, group, loader); @@ -105,7 +106,8 @@ SDLImageShared::SDLImageShared(SDL_Surface * from) : surf(nullptr), margins(0, 0), fullSize(0, 0), - originalPalette(nullptr) + originalPalette(nullptr), + scaleFactor(1) { surf = from; if (surf == nullptr) @@ -118,11 +120,12 @@ SDLImageShared::SDLImageShared(SDL_Surface * from) fullSize.y = surf->h; } -SDLImageShared::SDLImageShared(const ImagePath & filename) +SDLImageShared::SDLImageShared(const ImagePath & filename, int scaleFactor) : surf(nullptr), margins(0, 0), fullSize(0, 0), - originalPalette(nullptr) + originalPalette(nullptr), + scaleFactor(scaleFactor) { surf = BitmapHandler::loadBitmap(filename); @@ -274,7 +277,13 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet if (palette && surf && surf->format->palette) SDL_SetSurfacePalette(surf, palette); - SDL_Surface * scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ); + SDL_Surface * scaled = nullptr; + if(scaleFactor == factor) + scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, 1, EScalingAlgorithm::NEAREST); // keep size + else if(scaleFactor == 1) + scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ); + else + scaled = CSDL_Ext::scaleSurface(surf, (surf->w / scaleFactor) * factor, (surf->h / scaleFactor) * factor); auto ret = std::make_shared(scaled); @@ -296,8 +305,8 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const { - float scaleX = float(size.x) / dimensions().x; - float scaleY = float(size.y) / dimensions().y; + float scaleX = float(size.x) / fullSize.x; + float scaleY = float(size.y) / fullSize.y; if (palette && surf->format->palette) SDL_SetSurfacePalette(surf, palette); @@ -355,7 +364,7 @@ bool SDLImageShared::isTransparent(const Point & coords) const Point SDLImageShared::dimensions() const { - return fullSize; + return fullSize / scaleFactor; } std::shared_ptr SDLImageShared::createImageReference(EImageBlitMode mode) diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index e9465eda4..64e72703b 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -35,6 +35,9 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from //total size including borders Point fullSize; + //pre scaled image + int scaleFactor; + // Keep the original palette, in order to do color switching operation void savePalette(); @@ -42,9 +45,9 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from public: //Load image from def file - SDLImageShared(const CDefFile *data, size_t frame, size_t group=0); + SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int scaleFactor=1); //Load from bitmap file - SDLImageShared(const ImagePath & filename); + SDLImageShared(const ImagePath & filename, int scaleFactor=1); //Create using existing surface, extraRef will increase refcount on SDL_Surface SDLImageShared(SDL_Surface * from); ~SDLImageShared(); From 52aa4aeb827d9084aacab32b06f0eb25aae248cc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:17:29 +0100 Subject: [PATCH 523/726] fix for not loaded images --- client/renderSDL/RenderHandler.cpp | 32 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 837d8035c..f4b8c1c68 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -299,7 +299,7 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator & loc if (locator.layer == EImageLayer::ALL && locator.playerColored != PlayerColor::CANNOT_DETERMINE) handle->playerColored(locator.playerColored); - handle->scaleInteger(locator.scalingFactor); + handle->scaleInteger(locator.scalingFactor); // TODO: try to optimize image size (possibly even before scaling?) - trim image borders if they are completely transparent auto result = handle->getSharedImage(); @@ -309,10 +309,24 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator & loc std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode) { - if (locator.scalingFactor == 0 && getScalingFactor() != 1 ) + ImageLocator loc = locator; + if(loc.defFile && loc.scalingFactor == 0 && loc.preScaledFactor == 1) { - auto unscaledLocator = locator; - auto scaledLocator = locator; + auto tmp = getScalePath(*loc.defFile); + loc.defFile = AnimationPath::builtin(tmp.first.getName()); + loc.preScaledFactor = tmp.second; + } + if(loc.image && loc.scalingFactor == 0 && loc.preScaledFactor == 1) + { + auto tmp = getScalePath(*loc.image); + loc.image = ImagePath::builtin(tmp.first.getName()); + loc.preScaledFactor = tmp.second; + } + + if (loc.scalingFactor == 0 && getScalingFactor() != 1 ) + { + auto unscaledLocator = loc; + auto scaledLocator = loc; unscaledLocator.scalingFactor = 1; scaledLocator.scalingFactor = getScalingFactor(); @@ -321,16 +335,16 @@ std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, E return std::make_shared(scaledLocator, unscaledImage, mode); } - if (locator.scalingFactor == 0) + if (loc.scalingFactor == 0) { - auto scaledLocator = locator; + auto scaledLocator = loc; scaledLocator.scalingFactor = getScalingFactor(); return loadImageImpl(scaledLocator)->createImageReference(mode); } else { - return loadImageImpl(locator)->createImageReference(mode); + return loadImageImpl(loc)->createImageReference(mode); } } @@ -344,9 +358,7 @@ std::shared_ptr RenderHandler::loadImage(const AnimationPath & path, int std::shared_ptr RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode) { - auto tmp = getScalePath(path); - ImageLocator locator(ImagePath::builtin(tmp.first.getName())); - locator.preScaledFactor = tmp.second; + ImageLocator locator(path); return loadImage(locator, mode); } From 7a52d1d53394454da6b502c9ca2d90c9ee09aa28 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:51:48 +0100 Subject: [PATCH 524/726] 8th creature fix --- client/windows/CCastleInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 28984167b..ded97d2d3 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -238,7 +238,8 @@ std::string CBuildingRect::getSubtitle()//hover text for building return town->getTown()->buildings.at(getBuilding()->bid)->getNameTranslated(); else//dwellings - recruit %creature% { - auto & availableCreatures = town->creatures[(bid-30)%town->getTown()->creatures.size()].second; + int level = BuildingID::getLevelFromDwelling(getBuilding()->bid); + auto & availableCreatures = town->creatures[level].second; if(availableCreatures.size()) { int creaID = availableCreatures.back();//taking last of available creatures From fa9201831b50c5666056f078463c5799430a6d47 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:45:44 +0200 Subject: [PATCH 525/726] QuickBackpack window position on shift+click slot fixed --- client/windows/CExchangeWindow.cpp | 4 ++-- client/windows/CHeroBackpackWindow.cpp | 2 +- client/windows/CHeroOverview.cpp | 4 ++-- client/windows/CHeroOverview.h | 2 +- client/windows/CHeroWindow.cpp | 2 +- client/windows/CKingdomInterface.cpp | 2 +- client/windows/CMarketWindow.cpp | 2 +- client/windows/CWindowWithArtifacts.cpp | 8 +++++--- client/windows/CWindowWithArtifacts.h | 2 +- 9 files changed, 15 insertions(+), 13 deletions(-) diff --git a/client/windows/CExchangeWindow.cpp b/client/windows/CExchangeWindow.cpp index b97696446..35c5d8c7f 100644 --- a/client/windows/CExchangeWindow.cpp +++ b/client/windows/CExchangeWindow.cpp @@ -95,12 +95,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2, } artifs[0] = std::make_shared(Point(-334, 151)); - artifs[0]->clickPressedCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false);}; + artifs[0]->clickPressedCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false, cursorPosition);}; artifs[0]->showPopupCallback = [this, heroArts = artifs[0]](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*heroArts, artPlace, cursorPosition);}; artifs[0]->gestureCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(hero, artPlace.slot, cursorPosition);}; artifs[0]->setHero(heroInst[0]); artifs[1] = std::make_shared(Point(98, 151)); - artifs[1]->clickPressedCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false);}; + artifs[1]->clickPressedCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false, cursorPosition);}; artifs[1]->showPopupCallback = [this, heroArts = artifs[1]](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*heroArts, artPlace, cursorPosition);}; artifs[1]->gestureCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(hero, artPlace.slot, cursorPosition);}; artifs[1]->setHero(heroInst[1]); diff --git a/client/windows/CHeroBackpackWindow.cpp b/client/windows/CHeroBackpackWindow.cpp index ce8b90e46..94b7a622f 100644 --- a/client/windows/CHeroBackpackWindow.cpp +++ b/client/windows/CHeroBackpackWindow.cpp @@ -35,7 +35,7 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero, const std: arts->moveBy(Point(windowMargin, windowMargin)); arts->clickPressedCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition) { - clickPressedOnArtPlace(arts->getHero(), artPlace.slot, true, false, true); + clickPressedOnArtPlace(arts->getHero(), artPlace.slot, true, false, true, cursorPosition); }; arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition) { diff --git a/client/windows/CHeroOverview.cpp b/client/windows/CHeroOverview.cpp index ae6c81409..1c1f0bacf 100644 --- a/client/windows/CHeroOverview.cpp +++ b/client/windows/CHeroOverview.cpp @@ -18,7 +18,7 @@ #include "../render/Colors.h" #include "../render/IImage.h" #include "../renderSDL/RenderHandler.h" -#include"../widgets/CComponentHolder.h" +#include "../widgets/CComponentHolder.h" #include "../widgets/Images.h" #include "../widgets/TextControls.h" #include "../widgets/GraphicalPrimitiveCanvas.h" @@ -206,7 +206,7 @@ void CHeroOverview::genControls() i = 0; for(auto & skill : (*CGI->heroh)[heroIdx]->secSkillsInit) { - imageSecSkills.push_back(std::make_shared(Point(302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)), + secSkills.push_back(std::make_shared(Point(302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)), CSecSkillPlace::ImageSize::SMALL, skill.first, skill.second)); labelSecSkillsNames.push_back(std::make_shared(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->levels[skill.second - 1])); labelSecSkillsNames.push_back(std::make_shared(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->skillh)[skill.first]->getNameTranslated())); diff --git a/client/windows/CHeroOverview.h b/client/windows/CHeroOverview.h index d2ac4aa2e..38e5fa683 100644 --- a/client/windows/CHeroOverview.h +++ b/client/windows/CHeroOverview.h @@ -61,7 +61,7 @@ class CHeroOverview : public CWindowObject std::vector> labelSpellsNames; std::shared_ptr labelSecSkillTitle; - std::vector> imageSecSkills; + std::vector> secSkills; std::vector> labelSecSkillsNames; void genBackground(); diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index b0b495009..5698d8328 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -209,7 +209,7 @@ void CHeroWindow::update() if(!arts) { arts = std::make_shared(Point(-65, -8)); - arts->clickPressedCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(curHero, artPlace.slot, true, false, false);}; + arts->clickPressedCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(curHero, artPlace.slot, true, false, false, cursorPosition);}; arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*arts, artPlace, cursorPosition);}; arts->gestureCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(curHero, artPlace.slot, cursorPosition);}; arts->setHero(curHero); diff --git a/client/windows/CKingdomInterface.cpp b/client/windows/CKingdomInterface.cpp index 136f7317b..61254a60a 100644 --- a/client/windows/CKingdomInterface.cpp +++ b/client/windows/CKingdomInterface.cpp @@ -552,7 +552,7 @@ std::shared_ptr CKingdomInterface::createMainTab(size_t index) { newHeroSet->clickPressedCallback = [this, newHeroSet](const CArtPlace & artPlace, const Point & cursorPosition) { - clickPressedOnArtPlace(newHeroSet->getHero(), artPlace.slot, false, false, false); + clickPressedOnArtPlace(newHeroSet->getHero(), artPlace.slot, false, false, false, cursorPosition); }; newHeroSet->showPopupCallback = [this, newHeroSet](CArtPlace & artPlace, const Point & cursorPosition) { diff --git a/client/windows/CMarketWindow.cpp b/client/windows/CMarketWindow.cpp index 059e1f9f0..1fbd750fd 100644 --- a/client/windows/CMarketWindow.cpp +++ b/client/windows/CMarketWindow.cpp @@ -262,7 +262,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns const auto heroArts = altarArtifactsStorage->getAOHset(); heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition) { - clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false); + clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false, cursorPosition); }; heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition) { diff --git a/client/windows/CWindowWithArtifacts.cpp b/client/windows/CWindowWithArtifacts.cpp index b8d20b8f4..4356f89d8 100644 --- a/client/windows/CWindowWithArtifacts.cpp +++ b/client/windows/CWindowWithArtifacts.cpp @@ -71,7 +71,7 @@ const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact() const } void CWindowWithArtifacts::clickPressedOnArtPlace(const CGHeroInstance * hero, const ArtifactPosition & slot, - bool allowExchange, bool altarTrading, bool closeWindow) + bool allowExchange, bool altarTrading, bool closeWindow, const Point & cursorPosition) { if(!LOCPLINT->makingTurn) return; @@ -85,8 +85,7 @@ void CWindowWithArtifacts::clickPressedOnArtPlace(const CGHeroInstance * hero, c } else if(GH.isKeyboardShiftDown()) { - if(ArtifactUtils::isSlotEquipment(slot)) - GH.windows().createAndPushWindow(hero, slot); + showQuickBackpackWindow(hero, slot, cursorPosition); } else if(auto art = hero->getArt(slot)) { @@ -134,6 +133,9 @@ void CWindowWithArtifacts::showQuickBackpackWindow(const CGHeroInstance * hero, if(!settings["general"]["enableUiEnhancements"].Bool()) return; + if(!ArtifactUtils::isSlotEquipment(slot)) + return; + GH.windows().createAndPushWindow(hero, slot); auto backpackWindow = GH.windows().topWindow(); backpackWindow->moveTo(cursorPosition - Point(1, 1)); diff --git a/client/windows/CWindowWithArtifacts.h b/client/windows/CWindowWithArtifacts.h index 9b83ec991..cd6a42484 100644 --- a/client/windows/CWindowWithArtifacts.h +++ b/client/windows/CWindowWithArtifacts.h @@ -28,7 +28,7 @@ public: const CGHeroInstance * getHeroPickedArtifact() const; const CArtifactInstance * getPickedArtifact() const; void clickPressedOnArtPlace(const CGHeroInstance * hero, const ArtifactPosition & slot, - bool allowExchange, bool altarTrading, bool closeWindow); + bool allowExchange, bool altarTrading, bool closeWindow, const Point & cursorPosition); void swapArtifactAndClose(const CArtifactsOfHeroBase & artsInst, const ArtifactPosition & slot, const ArtifactLocation & dstLoc); void showArtifactAssembling(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const; void showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot, const Point & cursorPosition) const; From 8b15c9744d8621f195ab0a22a56ac9bba75329ce Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:07:13 +0100 Subject: [PATCH 526/726] Improve Shrine spell readability --- config/objects/shrine.json | 416 ++++++++++++++++++------------------- 1 file changed, 208 insertions(+), 208 deletions(-) diff --git a/config/objects/shrine.json b/config/objects/shrine.json index 725bc45cd..2a6ea73ac 100644 --- a/config/objects/shrine.json +++ b/config/objects/shrine.json @@ -1,209 +1,209 @@ -{ - "shrineOfMagicLevel1" : {//incantation - "index" :88, - "handler" : "configurable", - "base" : { - "sounds" : { - "ambient" : ["LOOPSHRIN"], - "visit" : ["TEMPLE"] - } - }, - "types" : { - "shrineOfMagicLevel1" : { - "index" : 0, - "aiValue" : 500, - "rmg" : { - "value" : 500, - "rarity" : 100 - }, - "compatibilityIdentifiers" : [ "object" ], - - "visitMode" : "limiter", - "visitedTooltip" : 354, - "description" : "@core.xtrainfo.19", - "showScoutedPreview" : true, - - "variables" : { - "spell" : { - "gainedSpell" : { // Note: this variable name is used by engine for H3M loading - "level": 1 - } - } - }, - "visitLimiter" : { - "spells" : [ - "@gainedSpell" - ] - }, - "rewards" : [ - { - "limiter" : { - "canLearnSpells" : [ - "@gainedSpell" - ] - }, - "spells" : [ - "@gainedSpell" - ], - "description" : "@core.genrltxt.355", - "message" : [ 127, "%s." ] // You learn new spell - } - ], - "onVisitedMessage" : [ 127, "%s. ", 174 ], // You already known this spell - "onEmpty" : [ - { - "limiter" : { - "artifacts" : [ - { - "type" : "spellBook" - } - ] - }, - "message" : [ 127, "%s. ", 130 ] // No Wisdom - }, - { - "message" : [ 127, "%s. ", 131 ] // No spellbook - } - ] - } - } - }, - "shrineOfMagicLevel2" : {//gesture - "index" :89, - "handler" : "configurable", - "base" : { - "sounds" : { - "ambient" : ["LOOPSHRIN"], - "visit" : ["TEMPLE"] - } - }, - "types" : { - "shrineOfMagicLevel2" : { - "index" : 0, - "aiValue" : 2000, - "rmg" : { - "value" : 2000, - "rarity" : 100 - }, - "compatibilityIdentifiers" : [ "object" ], - - "visitMode" : "limiter", - "visitedTooltip" : 354, - "description" : "@core.xtrainfo.20", - "showScoutedPreview" : true, - - "variables" : { - "spell" : { - "gainedSpell" : { // Note: this variable name is used by engine for H3M loading - "level": 2 - } - } - }, - "visitLimiter" : { - "spells" : [ - "@gainedSpell" - ] - }, - "rewards" : [ - { - "limiter" : { - "canLearnSpells" : [ - "@gainedSpell" - ] - }, - "spells" : [ - "@gainedSpell" - ], - "description" : "@core.genrltxt.355", - "message" : [ 128, "%s." ] // You learn new spell - } - ], - "onVisitedMessage" : [ 128, "%s. ", 174 ], // You already known this spell - "onEmpty" : [ - { - "limiter" : { - "artifacts" : [ - { - "type" : "spellBook" - } - ] - }, - "message" : [ 128, "%s. ", 130 ] // No Wisdom - }, - { - "message" : [ 128, "%s. ", 131 ] // No spellbook - } - ] - } - } - }, - "shrineOfMagicLevel3" : {//thinking - "index" :90, - "handler" : "configurable", - "base" : { - "sounds" : { - "ambient" : ["LOOPSHRIN"], - "visit" : ["TEMPLE"] - } - }, - "types" : { - "shrineOfMagicLevel3" : { - "index" : 0, - "aiValue" : 3000, - "rmg" : { - "value" : 3000, - "rarity" : 100 - }, - "compatibilityIdentifiers" : [ "object" ], - - "visitMode" : "limiter", - "visitedTooltip" : 354, - "description" : "@core.xtrainfo.21", - "showScoutedPreview" : true, - - "variables" : { - "spell" : { - "gainedSpell" : { // Note: this variable name is used by engine for H3M loading - "level": 3 - } - } - }, - "visitLimiter" : { - "spells" : [ - "@gainedSpell" - ] - }, - "rewards" : [ - { - "limiter" : { - "canLearnSpells" : [ - "@gainedSpell" - ] - }, - "spells" : [ - "@gainedSpell" - ], - "description" : "@core.genrltxt.355", - "message" : [ 129, "%s." ] // You learn new spell - } - ], - "onVisitedMessage" : [ 129, "%s. ", 174 ], // You already known this spell - "onEmpty" : [ - { - "limiter" : { - "artifacts" : [ - { - "type" : "spellBook" - } - ] - }, - "message" : [ 129, "%s. ", 130 ] // No Wisdom - }, - { - "message" : [ 129, "%s. ", 131 ] // No spellbook - } - ] - } - } - } +{ + "shrineOfMagicLevel1" : {//incantation + "index" :88, + "handler" : "configurable", + "base" : { + "sounds" : { + "ambient" : ["LOOPSHRIN"], + "visit" : ["TEMPLE"] + } + }, + "types" : { + "shrineOfMagicLevel1" : { + "index" : 0, + "aiValue" : 500, + "rmg" : { + "value" : 500, + "rarity" : 100 + }, + "compatibilityIdentifiers" : [ "object" ], + + "visitMode" : "limiter", + "visitedTooltip" : 354, + "description" : "@core.xtrainfo.19", + "showScoutedPreview" : true, + + "variables" : { + "spell" : { + "gainedSpell" : { // Note: this variable name is used by engine for H3M loading + "level": 1 + } + } + }, + "visitLimiter" : { + "spells" : [ + "@gainedSpell" + ] + }, + "rewards" : [ + { + "limiter" : { + "canLearnSpells" : [ + "@gainedSpell" + ] + }, + "spells" : [ + "@gainedSpell" + ], + "description" : "@core.genrltxt.355", + "message" : [ 127, "{%s}." ] // You learn new spell + } + ], + "onVisitedMessage" : [ 127, "{%s}. ", 174 ], // You already known this spell + "onEmpty" : [ + { + "limiter" : { + "artifacts" : [ + { + "type" : "spellBook" + } + ] + }, + "message" : [ 127, "{%s}. ", 130 ] // No Wisdom + }, + { + "message" : [ 127, "{%s}. ", 131 ] // No spellbook + } + ] + } + } + }, + "shrineOfMagicLevel2" : {//gesture + "index" :89, + "handler" : "configurable", + "base" : { + "sounds" : { + "ambient" : ["LOOPSHRIN"], + "visit" : ["TEMPLE"] + } + }, + "types" : { + "shrineOfMagicLevel2" : { + "index" : 0, + "aiValue" : 2000, + "rmg" : { + "value" : 2000, + "rarity" : 100 + }, + "compatibilityIdentifiers" : [ "object" ], + + "visitMode" : "limiter", + "visitedTooltip" : 354, + "description" : "@core.xtrainfo.20", + "showScoutedPreview" : true, + + "variables" : { + "spell" : { + "gainedSpell" : { // Note: this variable name is used by engine for H3M loading + "level": 2 + } + } + }, + "visitLimiter" : { + "spells" : [ + "@gainedSpell" + ] + }, + "rewards" : [ + { + "limiter" : { + "canLearnSpells" : [ + "@gainedSpell" + ] + }, + "spells" : [ + "@gainedSpell" + ], + "description" : "@core.genrltxt.355", + "message" : [ 128, "{%s}." ] // You learn new spell + } + ], + "onVisitedMessage" : [ 128, "{%s}. ", 174 ], // You already known this spell + "onEmpty" : [ + { + "limiter" : { + "artifacts" : [ + { + "type" : "spellBook" + } + ] + }, + "message" : [ 128, "{%s}. ", 130 ] // No Wisdom + }, + { + "message" : [ 128, "{%s}. ", 131 ] // No spellbook + } + ] + } + } + }, + "shrineOfMagicLevel3" : {//thinking + "index" :90, + "handler" : "configurable", + "base" : { + "sounds" : { + "ambient" : ["LOOPSHRIN"], + "visit" : ["TEMPLE"] + } + }, + "types" : { + "shrineOfMagicLevel3" : { + "index" : 0, + "aiValue" : 3000, + "rmg" : { + "value" : 3000, + "rarity" : 100 + }, + "compatibilityIdentifiers" : [ "object" ], + + "visitMode" : "limiter", + "visitedTooltip" : 354, + "description" : "@core.xtrainfo.21", + "showScoutedPreview" : true, + + "variables" : { + "spell" : { + "gainedSpell" : { // Note: this variable name is used by engine for H3M loading + "level": 3 + } + } + }, + "visitLimiter" : { + "spells" : [ + "@gainedSpell" + ] + }, + "rewards" : [ + { + "limiter" : { + "canLearnSpells" : [ + "@gainedSpell" + ] + }, + "spells" : [ + "@gainedSpell" + ], + "description" : "@core.genrltxt.355", + "message" : [ 129, "{%s}." ] // You learn new spell + } + ], + "onVisitedMessage" : [ 129, "{%s}. ", 174 ], // You already known this spell + "onEmpty" : [ + { + "limiter" : { + "artifacts" : [ + { + "type" : "spellBook" + } + ] + }, + "message" : [ 129, "{%s}. ", 130 ] // No Wisdom + }, + { + "message" : [ 129, "{%s}. ", 131 ] // No spellbook + } + ] + } + } + } } \ No newline at end of file From 3150376b3652a915334950d84c3772dde4bf3805 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:07:51 +0100 Subject: [PATCH 527/726] Improve Shrine spell readability --- config/objects/shrine.json | 416 ++++++++++++++++++------------------- 1 file changed, 208 insertions(+), 208 deletions(-) diff --git a/config/objects/shrine.json b/config/objects/shrine.json index 2a6ea73ac..8216e9874 100644 --- a/config/objects/shrine.json +++ b/config/objects/shrine.json @@ -1,209 +1,209 @@ -{ - "shrineOfMagicLevel1" : {//incantation - "index" :88, - "handler" : "configurable", - "base" : { - "sounds" : { - "ambient" : ["LOOPSHRIN"], - "visit" : ["TEMPLE"] - } - }, - "types" : { - "shrineOfMagicLevel1" : { - "index" : 0, - "aiValue" : 500, - "rmg" : { - "value" : 500, - "rarity" : 100 - }, - "compatibilityIdentifiers" : [ "object" ], - - "visitMode" : "limiter", - "visitedTooltip" : 354, - "description" : "@core.xtrainfo.19", - "showScoutedPreview" : true, - - "variables" : { - "spell" : { - "gainedSpell" : { // Note: this variable name is used by engine for H3M loading - "level": 1 - } - } - }, - "visitLimiter" : { - "spells" : [ - "@gainedSpell" - ] - }, - "rewards" : [ - { - "limiter" : { - "canLearnSpells" : [ - "@gainedSpell" - ] - }, - "spells" : [ - "@gainedSpell" - ], - "description" : "@core.genrltxt.355", - "message" : [ 127, "{%s}." ] // You learn new spell - } - ], - "onVisitedMessage" : [ 127, "{%s}. ", 174 ], // You already known this spell - "onEmpty" : [ - { - "limiter" : { - "artifacts" : [ - { - "type" : "spellBook" - } - ] - }, - "message" : [ 127, "{%s}. ", 130 ] // No Wisdom - }, - { - "message" : [ 127, "{%s}. ", 131 ] // No spellbook - } - ] - } - } - }, - "shrineOfMagicLevel2" : {//gesture - "index" :89, - "handler" : "configurable", - "base" : { - "sounds" : { - "ambient" : ["LOOPSHRIN"], - "visit" : ["TEMPLE"] - } - }, - "types" : { - "shrineOfMagicLevel2" : { - "index" : 0, - "aiValue" : 2000, - "rmg" : { - "value" : 2000, - "rarity" : 100 - }, - "compatibilityIdentifiers" : [ "object" ], - - "visitMode" : "limiter", - "visitedTooltip" : 354, - "description" : "@core.xtrainfo.20", - "showScoutedPreview" : true, - - "variables" : { - "spell" : { - "gainedSpell" : { // Note: this variable name is used by engine for H3M loading - "level": 2 - } - } - }, - "visitLimiter" : { - "spells" : [ - "@gainedSpell" - ] - }, - "rewards" : [ - { - "limiter" : { - "canLearnSpells" : [ - "@gainedSpell" - ] - }, - "spells" : [ - "@gainedSpell" - ], - "description" : "@core.genrltxt.355", - "message" : [ 128, "{%s}." ] // You learn new spell - } - ], - "onVisitedMessage" : [ 128, "{%s}. ", 174 ], // You already known this spell - "onEmpty" : [ - { - "limiter" : { - "artifacts" : [ - { - "type" : "spellBook" - } - ] - }, - "message" : [ 128, "{%s}. ", 130 ] // No Wisdom - }, - { - "message" : [ 128, "{%s}. ", 131 ] // No spellbook - } - ] - } - } - }, - "shrineOfMagicLevel3" : {//thinking - "index" :90, - "handler" : "configurable", - "base" : { - "sounds" : { - "ambient" : ["LOOPSHRIN"], - "visit" : ["TEMPLE"] - } - }, - "types" : { - "shrineOfMagicLevel3" : { - "index" : 0, - "aiValue" : 3000, - "rmg" : { - "value" : 3000, - "rarity" : 100 - }, - "compatibilityIdentifiers" : [ "object" ], - - "visitMode" : "limiter", - "visitedTooltip" : 354, - "description" : "@core.xtrainfo.21", - "showScoutedPreview" : true, - - "variables" : { - "spell" : { - "gainedSpell" : { // Note: this variable name is used by engine for H3M loading - "level": 3 - } - } - }, - "visitLimiter" : { - "spells" : [ - "@gainedSpell" - ] - }, - "rewards" : [ - { - "limiter" : { - "canLearnSpells" : [ - "@gainedSpell" - ] - }, - "spells" : [ - "@gainedSpell" - ], - "description" : "@core.genrltxt.355", - "message" : [ 129, "{%s}." ] // You learn new spell - } - ], - "onVisitedMessage" : [ 129, "{%s}. ", 174 ], // You already known this spell - "onEmpty" : [ - { - "limiter" : { - "artifacts" : [ - { - "type" : "spellBook" - } - ] - }, - "message" : [ 129, "{%s}. ", 130 ] // No Wisdom - }, - { - "message" : [ 129, "{%s}. ", 131 ] // No spellbook - } - ] - } - } - } +{ + "shrineOfMagicLevel1" : {//incantation + "index" :88, + "handler" : "configurable", + "base" : { + "sounds" : { + "ambient" : ["LOOPSHRIN"], + "visit" : ["TEMPLE"] + } + }, + "types" : { + "shrineOfMagicLevel1" : { + "index" : 0, + "aiValue" : 500, + "rmg" : { + "value" : 500, + "rarity" : 100 + }, + "compatibilityIdentifiers" : [ "object" ], + + "visitMode" : "limiter", + "visitedTooltip" : 354, + "description" : "@core.xtrainfo.19", + "showScoutedPreview" : true, + + "variables" : { + "spell" : { + "gainedSpell" : { // Note: this variable name is used by engine for H3M loading + "level": 1 + } + } + }, + "visitLimiter" : { + "spells" : [ + "@gainedSpell" + ] + }, + "rewards" : [ + { + "limiter" : { + "canLearnSpells" : [ + "@gainedSpell" + ] + }, + "spells" : [ + "@gainedSpell" + ], + "description" : "@core.genrltxt.355", + "message" : [ 127, "{%s}." ] // You learn new spell + } + ], + "onVisitedMessage" : [ 127, "{%s}. ", 174 ], // You already known this spell + "onEmpty" : [ + { + "limiter" : { + "artifacts" : [ + { + "type" : "spellBook" + } + ] + }, + "message" : [ 127, "{%s}. ", 130 ] // No Wisdom + }, + { + "message" : [ 127, "{%s}. ", 131 ] // No spellbook + } + ] + } + } + }, + "shrineOfMagicLevel2" : {//gesture + "index" :89, + "handler" : "configurable", + "base" : { + "sounds" : { + "ambient" : ["LOOPSHRIN"], + "visit" : ["TEMPLE"] + } + }, + "types" : { + "shrineOfMagicLevel2" : { + "index" : 0, + "aiValue" : 2000, + "rmg" : { + "value" : 2000, + "rarity" : 100 + }, + "compatibilityIdentifiers" : [ "object" ], + + "visitMode" : "limiter", + "visitedTooltip" : 354, + "description" : "@core.xtrainfo.20", + "showScoutedPreview" : true, + + "variables" : { + "spell" : { + "gainedSpell" : { // Note: this variable name is used by engine for H3M loading + "level": 2 + } + } + }, + "visitLimiter" : { + "spells" : [ + "@gainedSpell" + ] + }, + "rewards" : [ + { + "limiter" : { + "canLearnSpells" : [ + "@gainedSpell" + ] + }, + "spells" : [ + "@gainedSpell" + ], + "description" : "@core.genrltxt.355", + "message" : [ 128, "{%s}." ] // You learn new spell + } + ], + "onVisitedMessage" : [ 128, "{%s}. ", 174 ], // You already known this spell + "onEmpty" : [ + { + "limiter" : { + "artifacts" : [ + { + "type" : "spellBook" + } + ] + }, + "message" : [ 128, "{%s}. ", 130 ] // No Wisdom + }, + { + "message" : [ 128, "{%s}. ", 131 ] // No spellbook + } + ] + } + } + }, + "shrineOfMagicLevel3" : {//thinking + "index" :90, + "handler" : "configurable", + "base" : { + "sounds" : { + "ambient" : ["LOOPSHRIN"], + "visit" : ["TEMPLE"] + } + }, + "types" : { + "shrineOfMagicLevel3" : { + "index" : 0, + "aiValue" : 3000, + "rmg" : { + "value" : 3000, + "rarity" : 100 + }, + "compatibilityIdentifiers" : [ "object" ], + + "visitMode" : "limiter", + "visitedTooltip" : 354, + "description" : "@core.xtrainfo.21", + "showScoutedPreview" : true, + + "variables" : { + "spell" : { + "gainedSpell" : { // Note: this variable name is used by engine for H3M loading + "level": 3 + } + } + }, + "visitLimiter" : { + "spells" : [ + "@gainedSpell" + ] + }, + "rewards" : [ + { + "limiter" : { + "canLearnSpells" : [ + "@gainedSpell" + ] + }, + "spells" : [ + "@gainedSpell" + ], + "description" : "@core.genrltxt.355", + "message" : [ 129, "{%s}." ] // You learn new spell + } + ], + "onVisitedMessage" : [ 129, "{%s}. ", 174 ], // You already known this spell + "onEmpty" : [ + { + "limiter" : { + "artifacts" : [ + { + "type" : "spellBook" + } + ] + }, + "message" : [ 129, "{%s}. ", 130 ] // No Wisdom + }, + { + "message" : [ 129, "{%s}. ", 131 ] // No spellbook + } + ] + } + } + } } \ No newline at end of file From b5d92e78522e82ed18d887a3743161b201622d53 Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:29:50 +0100 Subject: [PATCH 528/726] Fixed file encoding --- Mods/vcmi/config/czech.json | 1448 +++++++++++++++++------------------ 1 file changed, 724 insertions(+), 724 deletions(-) diff --git a/Mods/vcmi/config/czech.json b/Mods/vcmi/config/czech.json index eaf0950a8..8d4911f58 100644 --- a/Mods/vcmi/config/czech.json +++ b/Mods/vcmi/config/czech.json @@ -1,725 +1,725 @@ -{ - "vcmi.adventureMap.monsterThreat.title" : "\n\nHrozba: ", - "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", - "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", - "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", - "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", - "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", - "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", - "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", - "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", - "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", - "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", - "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", - "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", - "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", - "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", - "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", - "vcmi.adventureMap.search.hover" : "Prohledat objekt", - "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", - - "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", - "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", - "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", - "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", - "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", - "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", - "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", - "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", - "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", - - "vcmi.bonusSource.artifact" : "Artefakt", - "vcmi.bonusSource.creature" : "Schopnost", - "vcmi.bonusSource.spell" : "Kouzlo", - "vcmi.bonusSource.hero" : "Hrdina", - "vcmi.bonusSource.commander" : "Velitel", - "vcmi.bonusSource.other" : "Ostatní", - - "vcmi.capitalColors.0" : "Červený", - "vcmi.capitalColors.1" : "Modrý", - "vcmi.capitalColors.2" : "Hnědý", - "vcmi.capitalColors.3" : "Zelený", - "vcmi.capitalColors.4" : "Oranžový", - "vcmi.capitalColors.5" : "Fialový", - "vcmi.capitalColors.6" : "Tyrkysový", - "vcmi.capitalColors.7" : "Růžový", - - "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", - "vcmi.heroOverview.warMachine" : "Bojové stroje", - "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", - "vcmi.heroOverview.spells" : "Kouzla", - - "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", - "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", - "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", - "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", - "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", - - "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", - "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", - "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", - "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", - "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", - "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", - - "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", - "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", - "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", - "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", - "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", - "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", - - "vcmi.radialWheel.moveTop" : "Přesunout nahoru", - "vcmi.radialWheel.moveUp" : "Posunout výše", - "vcmi.radialWheel.moveDown" : "Posunout níže", - "vcmi.radialWheel.moveBottom" : "Přesunout dolů", - - "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", - "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", - "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", - "vcmi.randomMap.description.water.none" : "žádná", - "vcmi.randomMap.description.water.normal" : "normální", - "vcmi.randomMap.description.water.islands" : "ostrovy", - "vcmi.randomMap.description.monster.weak" : "nízká", - "vcmi.randomMap.description.monster.normal" : "normální", - "vcmi.randomMap.description.monster.strong" : "vysoká", - - "vcmi.spellBook.search" : "Hledat", - - "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", - "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", - "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", - "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", - "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", - "vcmi.spellResearch.abort" : "Přerušit", - - "vcmi.mainMenu.serverConnecting" : "Připojování...", - "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", - "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", - "vcmi.mainMenu.serverClosing" : "Zavírání...", - "vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP", - "vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP", - - "vcmi.lobby.filepath" : "Název souboru", - "vcmi.lobby.creationDate" : "Datum vytvoření", - "vcmi.lobby.scenarioName" : "Název scénáře", - "vcmi.lobby.mapPreview" : "Náhled mapy", - "vcmi.lobby.noPreview" : "bez náhledu", - "vcmi.lobby.noUnderground" : "bez podzemí", - "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", - "vcmi.lobby.backToLobby" : "Vrátit se do lobby", - "vcmi.lobby.author" : "Autor", - "vcmi.lobby.handicap" : "Postih", - "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", - "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", - "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", - "vcmi.lobby.deleteUnsupportedSave" : "Nalezeny nepodporované uložené hry (např. z předchozích verzí).\n\nChcete je odstranit?", - "vcmi.lobby.deleteSaveGameTitle" : "Vyberte uloženou hru k odstranění", - "vcmi.lobby.deleteMapTitle" : "Vyberte scénář k odstranění", - "vcmi.lobby.deleteFile" : "Chcete smazat následující soubor?", - "vcmi.lobby.deleteFolder" : "Chcete smazat následující složku?", - "vcmi.lobby.deleteMode" : "Přepnout do režimu mazání a zpět", - - "vcmi.lobby.login.title" : "Online lobby VCMI", - "vcmi.lobby.login.username" : "Uživatelské jméno:", - "vcmi.lobby.login.connecting" : "Připojování...", - "vcmi.lobby.login.error" : "Chyba při připojování: %s", - "vcmi.lobby.login.create" : "Nový účet", - "vcmi.lobby.login.login" : "Přihlásit se", - "vcmi.lobby.login.as" : "Přihlásit se jako %s", - "vcmi.lobby.header.rooms" : "Herní místnosti - %d", - "vcmi.lobby.header.channels" : "Kanály konverzace", - "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name - "vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time - "vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player - "vcmi.lobby.header.history" : "Vaše předchozí hry", - "vcmi.lobby.header.players" : "Online hráči - %d", - "vcmi.lobby.match.solo" : "Hra jednoho hráče", - "vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player - "vcmi.lobby.match.multi" : "%d hráčů", - "vcmi.lobby.room.create" : "Vytvořit novou místnost", - "vcmi.lobby.room.players.limit" : "Omezení počtu hráčů", - "vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.", - "vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.", - "vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.", - "vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", - "vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).", - "vcmi.lobby.invite.header" : "Pozvat hráče", - "vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.", - "vcmi.lobby.preview.title" : "Připojit se do herní místnosti", - "vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host - "vcmi.lobby.preview.version" : "Verze hry:", - "vcmi.lobby.preview.players" : "Hráči:", - "vcmi.lobby.preview.mods" : "Použité modifikace:", - "vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?", - "vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.", - "vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.", - "vcmi.lobby.preview.error.full" : "Místnost je již plná.", - "vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.", - "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", - "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", - "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", - "vcmi.lobby.room.new" : "Nová hra", - "vcmi.lobby.room.load" : "Načíst hru", - "vcmi.lobby.room.type" : "Druh místnosti", - "vcmi.lobby.room.mode" : "Herní režim", - "vcmi.lobby.room.state.public" : "Veřejná", - "vcmi.lobby.room.state.private" : "Soukromá", - "vcmi.lobby.room.state.busy" : "Ve hře", - "vcmi.lobby.room.state.invited" : "Pozvaný", - "vcmi.lobby.mod.state.compatible" : "Kompatibilní", - "vcmi.lobby.mod.state.disabled" : "Musí být povolena", - "vcmi.lobby.mod.state.version" : "Neshoda verze", - "vcmi.lobby.mod.state.excessive" : "Musí být zakázána", - "vcmi.lobby.mod.state.missing" : "Není nainstalována", - "vcmi.lobby.pvp.coin.hover" : "Mince", - "vcmi.lobby.pvp.coin.help" : "Hodí mincí", - "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", - "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", - "vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", - "vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace", - "vcmi.lobby.pvp.versus" : "vs.", - - "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", - "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", - "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", - "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color - "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", - "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", - "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", - "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", - "vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.", - "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", - "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", - - "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", - - "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", - "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", - "vcmi.settingsMainWindow.battleTab.hover" : "Bitva", - "vcmi.settingsMainWindow.battleTab.help" : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.", - "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa", - "vcmi.settingsMainWindow.adventureTab.help" : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).", - - "vcmi.systemOptions.videoGroup" : "Nastavení obrazu", - "vcmi.systemOptions.audioGroup" : "Nastavení zvuku", - "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now - "vcmi.systemOptions.townsGroup" : "Obrazovka města", - - "vcmi.statisticWindow.statistics" : "Statistiky", - "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", - "vcmi.statisticWindow.selectView" : "Vybrat pohled", - "vcmi.statisticWindow.value" : "Hodnota", - "vcmi.statisticWindow.title.overview" : "Přehled", - "vcmi.statisticWindow.title.resources" : "Zdroje", - "vcmi.statisticWindow.title.income" : "Příjem", - "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", - "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", - "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", - "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", - "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", - "vcmi.statisticWindow.title.armyStrength" : "Síla armády", - "vcmi.statisticWindow.title.experience" : "Zkušenosti", - "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", - "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", - "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", - "vcmi.statisticWindow.param.playerName" : "Jméno hráče", - "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", - "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", - "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", - "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", - "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", - "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", - "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", - "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", - "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", - "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", - "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", - "vcmi.statisticWindow.icon.defeated" : "Porážka", - - "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", - "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", - "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", - "vcmi.systemOptions.fullscreenExclusive.help" : "{Celá obrazovka}\n\nPokud je vybráno, VCMI poběží v režimu exkluzivní celé obrazovky. V tomto režimu hra změní rozlišení na vybrané.", - "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", - "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", - "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", - "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", - "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", - "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", - "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", - "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", - "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" - "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", - "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", - "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", - "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", - "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", - "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", - "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", - "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", - "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", - "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", - "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", - "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", - "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", - "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", - - "vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací", - "vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.", - "vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek", - "vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.", - "vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu", - "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", - "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", - "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", - "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", - "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", - "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", - "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", - "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", - "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", - "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", - "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", - "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", - "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", - "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", - "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", - "vcmi.adventureOptions.mapScrollSpeed1.hover": "", - "vcmi.adventureOptions.mapScrollSpeed5.hover": "", - "vcmi.adventureOptions.mapScrollSpeed6.hover": "", - "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", - "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", - "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", - "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", - "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", - - "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", - "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", - "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", - "vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ", - "vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ", - "vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.", - "vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", - "vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.", - "vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", - "vcmi.battleOptions.animationsSpeed1.hover": "", - "vcmi.battleOptions.animationsSpeed5.hover": "", - "vcmi.battleOptions.animationsSpeed6.hover": "", - "vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.", - "vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.", - "vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.", - "vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí", - "vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.", - "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců", - "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Zobrazit omezení dostřelu střelců při najetí}\n\nZobrazit dostřel střelce při najetí na něj.", - "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Zobrazit okno statistik hrdinů", - "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", - "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", - "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", - "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", - "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", - "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", - "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", - - "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", - "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", - - "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", - "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", - "vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.ranged" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE).", - "vcmi.battleWindow.damageEstimation.rangedKills" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE, %KILLS).", - "vcmi.battleWindow.damageEstimation.shots" : "%d střel zbývá", - "vcmi.battleWindow.damageEstimation.shots.1" : "%d střela zbývá", - "vcmi.battleWindow.damageEstimation.damage" : "%d poškození", - "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", - "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", - "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", - - "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", - "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", - "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", - "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", - "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", - - "vcmi.battleWindow.killed" : "Zabito", - "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", - "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", - "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", - - "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", - - "vcmi.tutorialWindow.title" : "Úvod ovládání dotykem", - "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", - "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", - "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", - "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", - "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", - "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", - "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", - - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky", - "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek", - "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Zobrazit týdenní přírůstek jednotek}\n\nZobrazit týdenní přírůstek jednotek místo dostupného počtu ke koupení v přehledu města (levý spodní okraj obrazovky města).", - "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", - "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", - - "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", - "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", - - "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", - "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", - - "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", - "vcmi.logicalExpressions.allOf" : "Všechny následující:", - "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", - - "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", - "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", - "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", - "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", - "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", - "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", - "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", - "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", - "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", - "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", - "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", - - "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", - - "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", - - "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", - "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", - "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", - "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", - "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", - "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", - - "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", - "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", - - "vcmi.randomMapTab.widgets.randomTemplate" : "(Náhodná)", - "vcmi.randomMapTab.widgets.templateLabel" : "Šablona", - "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...", - "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Přiřazení týmů", - "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", - - "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", - "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", - - "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", - "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", - "vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy", - "vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky", - "vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.", - "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.", - "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.", - "vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.", - "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.", - "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.", - - "vcmi.optionsTab.accumulate" : "Akumulovat", - - "vcmi.optionsTab.simturnsTitle" : "Souběžné tahy", - "vcmi.optionsTab.simturnsMin.hover" : "Alespoň po", - "vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po", - "vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI", - "vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno", - "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", - "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", - - "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", - "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", - "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", - "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", - "vcmi.optionsTab.turnTime.classic.5" : "Klasický časovač: 5 minut", - "vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut", - "vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut", - "vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut", - "vcmi.optionsTab.turnTime.chess.20" : "Šachová: 20:00 + 10:00 + 02:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.16" : "Šachová: 16:00 + 08:00 + 01:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.8" : "Šachová: 08:00 + 04:00 + 01:00 + 00:00", - "vcmi.optionsTab.turnTime.chess.4" : "Šachová: 04:00 + 02:00 + 00:30 + 00:00", - "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", - "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", - - "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", - "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", - "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", - "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", - "vcmi.optionsTab.simturns.tillContact2" : "Souběžně: 2 týdny, přerušit při setkání", - "vcmi.optionsTab.simturns.tillContact4" : "Souběžně: 1 mšsíc, přerušit při setkání", - "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", - "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", - "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", - - // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language - // Using this information, VCMI will automatically select correct plural form for every possible amount - "vcmi.optionsTab.simturns.days.0" : " %d dní", - "vcmi.optionsTab.simturns.days.1" : " %d den", - "vcmi.optionsTab.simturns.days.2" : " %d dny", - "vcmi.optionsTab.simturns.weeks.0" : " %d týdnů", - "vcmi.optionsTab.simturns.weeks.1" : " %d týden", - "vcmi.optionsTab.simturns.weeks.2" : " %d týdny", - "vcmi.optionsTab.simturns.months.0" : " %d měsíců", - "vcmi.optionsTab.simturns.months.1" : " %d měsíc", - "vcmi.optionsTab.simturns.months.2" : " %d měsíce", - - "vcmi.optionsTab.extraOptions.hover" : "Další možnosti", - "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", - - "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", - "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", - "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", - "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", - - // Custom victory conditions for H3 campaigns and HotA maps - "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", - "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", - "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", - "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", - "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", - "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", - "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", - "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", - - // few strings from WoG used by vcmi - "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", - "vcmi.stackExperience.rank.0" : "Začátečník", - "vcmi.stackExperience.rank.1" : "Učeň", - "vcmi.stackExperience.rank.2" : "Trénovaný", - "vcmi.stackExperience.rank.3" : "Zručný", - "vcmi.stackExperience.rank.4" : "Prověřený", - "vcmi.stackExperience.rank.5" : "Veterán", - "vcmi.stackExperience.rank.6" : "Adept", - "vcmi.stackExperience.rank.7" : "Expert", - "vcmi.stackExperience.rank.8" : "Elitní", - "vcmi.stackExperience.rank.9" : "Mistr", - "vcmi.stackExperience.rank.10" : "Eso", - - // Strings for HotA Seer Hut / Quest Guards - "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", - "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", - "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", - "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", - "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", - "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", - "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", - "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", - "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", - "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", - "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", - - "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", - "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", - "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", - "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", - "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", - "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", - "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", - "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", - "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", - - "mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.", - - "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", - "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", - "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", - "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", - "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", - "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", - "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", - "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", - "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", - "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", - "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", - "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", - "core.bonus.CATAPULT.name": "Katapult", - "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", - "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", - "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", - "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", - "core.bonus.DARKNESS.name": "Závoj temnoty", - "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", - "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", - "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", - "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", - "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", - "core.bonus.DESTRUCTION.name": "Zničení", - "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", - "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", - "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", - "core.bonus.DRAGON_NATURE.name": "Dračí povaha", - "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", - "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", - "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", - "core.bonus.ENCHANTER.name": "Zaklínač", - "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", - "core.bonus.ENCHANTED.name": "Očarovaný", - "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", - "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", - "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", - "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", - "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", - "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", - "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", - "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", - "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", - "core.bonus.FIRST_STRIKE.name": "První úder", - "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", - "core.bonus.FEAR.name": "Strach", - "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", - "core.bonus.FEARLESS.name": "Nebojácnost", - "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", - "core.bonus.FEROCITY.name": "Zuřivost", - "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", - "core.bonus.FLYING.name": "Létání", - "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", - "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", - "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", - "core.bonus.GARGOYLE.name": "Chrlič", - "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", - "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", - "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", - "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", - "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", - "core.bonus.HEALER.name": "Léčitel", - "core.bonus.HEALER.description": "Léčí spojenecké jednotky", - "core.bonus.HP_REGENERATION.name": "Regenerace", - "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", - "core.bonus.JOUSTING.name": "Nájezd šampionů", - "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", - "core.bonus.KING.name": "Král", - "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", - "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", - "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", - "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", - "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", - "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", - "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", - "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", - "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", - "core.bonus.MANA_DRAIN.name": "Vysávání many", - "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", - "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", - "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", - "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", - "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", - "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", - "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", - "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", - "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", - "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", - "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", - "core.bonus.NO_MORALE.name": "Neutrální morálka", - "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", - "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", - "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", - "core.bonus.NON_LIVING.name": "Neživý", - "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", - "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", - "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", - "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", - "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", - "core.bonus.RECEPTIVE.name": "Vnímavý", - "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", - "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", - "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", - "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", - "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", - "core.bonus.REVENGE.name": "Pomsta", - "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", - "core.bonus.SHOOTER.name": "Střelec", - "core.bonus.SHOOTER.description": "Jednotka může střílet", - "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", - "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", - "core.bonus.SOUL_STEAL.name": "Zloděj duší", - "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", - "core.bonus.SPELLCASTER.name": "Kouzelník", - "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", - "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", - "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", - "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", - "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", - "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", - "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", - "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", - "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", - "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", - "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", - "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", - "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", - "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", - "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", - "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", - "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", - "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", - "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", - "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", - "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", - "core.bonus.TRANSMUTATION.name": "Transmutace", - "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", - "core.bonus.UNDEAD.name": "Nemrtvý", - "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", - "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", - "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", - "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", - "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", - "core.bonus.WIDE_BREATH.name": "Široký dech", - "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", - "core.bonus.DISINTEGRATE.name": "Rozpad", - "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", - "core.bonus.INVINCIBLE.name": "Neporazitelný", - "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem", - "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", - "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" +{ + "vcmi.adventureMap.monsterThreat.title" : "\n\nHrozba: ", + "vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy", + "vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá", + "vcmi.adventureMap.monsterThreat.levels.2" : "Slabá", + "vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší", + "vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná", + "vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější", + "vcmi.adventureMap.monsterThreat.levels.6" : "Silná", + "vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná", + "vcmi.adventureMap.monsterThreat.levels.8" : "Výzva", + "vcmi.adventureMap.monsterThreat.levels.9" : "Převaha", + "vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící", + "vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná", + "vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE", + "vcmi.adventureMap.monsterMeleeType" : "útočí zblízka", + "vcmi.adventureMap.monsterRangedType" : "útočí na dálku", + "vcmi.adventureMap.search.hover" : "Prohledat objekt", + "vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.", + + "vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?", + "vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!", + "vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", + "vcmi.adventureMap.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.", + "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", + "vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", + "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!", + + "vcmi.bonusSource.artifact" : "Artefakt", + "vcmi.bonusSource.creature" : "Schopnost", + "vcmi.bonusSource.spell" : "Kouzlo", + "vcmi.bonusSource.hero" : "Hrdina", + "vcmi.bonusSource.commander" : "Velitel", + "vcmi.bonusSource.other" : "Ostatní", + + "vcmi.capitalColors.0" : "Červený", + "vcmi.capitalColors.1" : "Modrý", + "vcmi.capitalColors.2" : "Hnědý", + "vcmi.capitalColors.3" : "Zelený", + "vcmi.capitalColors.4" : "Oranžový", + "vcmi.capitalColors.5" : "Fialový", + "vcmi.capitalColors.6" : "Tyrkysový", + "vcmi.capitalColors.7" : "Růžový", + + "vcmi.heroOverview.startingArmy" : "Počáteční jednotky", + "vcmi.heroOverview.warMachine" : "Bojové stroje", + "vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti", + "vcmi.heroOverview.spells" : "Kouzla", + + "vcmi.quickExchange.moveUnit" : "Přesunout jednotku", + "vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky", + "vcmi.quickExchange.swapAllUnits" : "Vyměnit armády", + "vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty", + "vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt", + + "vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky", + "vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou", + "vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku", + "vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně", + "vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu", + "vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice", + + "vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny", + "vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou", + "vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů", + "vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiného hrdiny", + "vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou", + "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", + + "vcmi.radialWheel.moveTop" : "Přesunout nahoru", + "vcmi.radialWheel.moveUp" : "Posunout výše", + "vcmi.radialWheel.moveDown" : "Posunout níže", + "vcmi.radialWheel.moveBottom" : "Přesunout dolů", + + "vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa", + "vcmi.randomMap.description.isHuman" : ", %s je lidský hráč", + "vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s", + "vcmi.randomMap.description.water.none" : "žádná", + "vcmi.randomMap.description.water.normal" : "normální", + "vcmi.randomMap.description.water.islands" : "ostrovy", + "vcmi.randomMap.description.monster.weak" : "nízká", + "vcmi.randomMap.description.monster.normal" : "normální", + "vcmi.randomMap.description.monster.strong" : "vysoká", + + "vcmi.spellBook.search" : "Hledat", + + "vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.", + "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", + "vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu kouzel?", + "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", + "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", + "vcmi.spellResearch.abort" : "Přerušit", + + "vcmi.mainMenu.serverConnecting" : "Připojování...", + "vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:", + "vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo", + "vcmi.mainMenu.serverClosing" : "Zavírání...", + "vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP", + "vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP", + + "vcmi.lobby.filepath" : "Název souboru", + "vcmi.lobby.creationDate" : "Datum vytvoření", + "vcmi.lobby.scenarioName" : "Název scénáře", + "vcmi.lobby.mapPreview" : "Náhled mapy", + "vcmi.lobby.noPreview" : "bez náhledu", + "vcmi.lobby.noUnderground" : "bez podzemí", + "vcmi.lobby.sortDate" : "Řadit mapy dle data změny", + "vcmi.lobby.backToLobby" : "Vrátit se do lobby", + "vcmi.lobby.author" : "Autor", + "vcmi.lobby.handicap" : "Postih", + "vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).", + "vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.", + "vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.", + "vcmi.lobby.deleteUnsupportedSave" : "Nalezeny nepodporované uložené hry (např. z předchozích verzí).\n\nChcete je odstranit?", + "vcmi.lobby.deleteSaveGameTitle" : "Vyberte uloženou hru k odstranění", + "vcmi.lobby.deleteMapTitle" : "Vyberte scénář k odstranění", + "vcmi.lobby.deleteFile" : "Chcete smazat následující soubor?", + "vcmi.lobby.deleteFolder" : "Chcete smazat následující složku?", + "vcmi.lobby.deleteMode" : "Přepnout do režimu mazání a zpět", + + "vcmi.lobby.login.title" : "Online lobby VCMI", + "vcmi.lobby.login.username" : "Uživatelské jméno:", + "vcmi.lobby.login.connecting" : "Připojování...", + "vcmi.lobby.login.error" : "Chyba při připojování: %s", + "vcmi.lobby.login.create" : "Nový účet", + "vcmi.lobby.login.login" : "Přihlásit se", + "vcmi.lobby.login.as" : "Přihlásit se jako %s", + "vcmi.lobby.header.rooms" : "Herní místnosti - %d", + "vcmi.lobby.header.channels" : "Kanály konverzace", + "vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name + "vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time + "vcmi.lobby.header.chat.player" : "Soukromá konverzace s %s", // %s -> nickname of another player + "vcmi.lobby.header.history" : "Vaše předchozí hry", + "vcmi.lobby.header.players" : "Online hráči - %d", + "vcmi.lobby.match.solo" : "Hra jednoho hráče", + "vcmi.lobby.match.duel" : "Hra s %s", // %s -> nickname of another player + "vcmi.lobby.match.multi" : "%d hráčů", + "vcmi.lobby.room.create" : "Vytvořit novou místnost", + "vcmi.lobby.room.players.limit" : "Omezení počtu hráčů", + "vcmi.lobby.room.description.public" : "Jakýkoliv hráč se může připojit do veřejné místnosti.", + "vcmi.lobby.room.description.private" : "Pouze pozvaní hráči se mohou připojit do soukromé místnosti.", + "vcmi.lobby.room.description.new" : "Pro start hry vyberte scénář, nebo nastavte náhodnou mapu.", + "vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", + "vcmi.lobby.room.description.limit" : "Až %d hráčů se může připojit do vaší místnosti (včetně vás).", + "vcmi.lobby.invite.header" : "Pozvat hráče", + "vcmi.lobby.invite.notification" : "Pozval vás hráč do jejich soukromé místnosti. Nyní se do ní můžete připojit.", + "vcmi.lobby.preview.title" : "Připojit se do herní místnosti", + "vcmi.lobby.preview.subtitle" : "Hra na %s, pořádána %s", //TL Note: 1) name of map or RMG template 2) nickname of game host + "vcmi.lobby.preview.version" : "Verze hry:", + "vcmi.lobby.preview.players" : "Hráči:", + "vcmi.lobby.preview.mods" : "Použité modifikace:", + "vcmi.lobby.preview.allowed" : "Připojit se do herní místnosti?", + "vcmi.lobby.preview.error.header" : "Nelze se připojit do této herní místnosti.", + "vcmi.lobby.preview.error.playing" : "Nejdříve musíte opustit vaši současnou hru.", + "vcmi.lobby.preview.error.full" : "Místnost je již plná.", + "vcmi.lobby.preview.error.busy" : "Místnost již nepřijímá nové hráče.", + "vcmi.lobby.preview.error.invite" : "Nebyl jste pozván do této mísnosti.", + "vcmi.lobby.preview.error.mods" : "Použváte jinou sadu modifikací.", + "vcmi.lobby.preview.error.version" : "Používáte jinou verzi VCMI.", + "vcmi.lobby.room.new" : "Nová hra", + "vcmi.lobby.room.load" : "Načíst hru", + "vcmi.lobby.room.type" : "Druh místnosti", + "vcmi.lobby.room.mode" : "Herní režim", + "vcmi.lobby.room.state.public" : "Veřejná", + "vcmi.lobby.room.state.private" : "Soukromá", + "vcmi.lobby.room.state.busy" : "Ve hře", + "vcmi.lobby.room.state.invited" : "Pozvaný", + "vcmi.lobby.mod.state.compatible" : "Kompatibilní", + "vcmi.lobby.mod.state.disabled" : "Musí být povolena", + "vcmi.lobby.mod.state.version" : "Neshoda verze", + "vcmi.lobby.mod.state.excessive" : "Musí být zakázána", + "vcmi.lobby.mod.state.missing" : "Není nainstalována", + "vcmi.lobby.pvp.coin.hover" : "Mince", + "vcmi.lobby.pvp.coin.help" : "Hodí mincí", + "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", + "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", + "vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", + "vcmi.lobby.pvp.randomTownVs.help" : "Napsat 2 náhodná města do konvezace", + "vcmi.lobby.pvp.versus" : "vs.", + + "vcmi.client.errors.invalidMap" : "{Neplatná mapa nebo kampaň}\n\nChyba při startu hry! Vybraná mapa nebo kampaň může být neplatná nebo poškozená. Důvod:\n%s", + "vcmi.client.errors.missingCampaigns" : "{Chybějící datové soubory}\n\nDatové soubory kampaně nebyly nalezeny! Možná máte nekompletní nebo poškozené datové soubory Heroes 3. Prosíme, přeinstalujte hru.", + "vcmi.server.errors.disconnected" : "{Chyba sítě}\n\nPřipojení k hernímu serveru bylo ztraceno!", + "vcmi.server.errors.playerLeft" : "{Hráč opustil hru}\n\nHráč %s se odpojil ze hry!", //%s -> player color + "vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.", + "vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}", + "vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}", + "vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n", + "vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.", + "vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n", + "vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!", + + "vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.", + + "vcmi.settingsMainWindow.generalTab.hover" : "Obecné", + "vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry.", + "vcmi.settingsMainWindow.battleTab.hover" : "Bitva", + "vcmi.settingsMainWindow.battleTab.help" : "Přepne na kartu nastavení bitvy, která umožňuje konfiguraci chování hry v bitvách.", + "vcmi.settingsMainWindow.adventureTab.hover" : "Mapa světa", + "vcmi.settingsMainWindow.adventureTab.help" : "Přepne na kartu nastavení mapy světa (mapa světa je sekce hry, ve které hráči mohou ovládat pohyb hrdinů).", + + "vcmi.systemOptions.videoGroup" : "Nastavení obrazu", + "vcmi.systemOptions.audioGroup" : "Nastavení zvuku", + "vcmi.systemOptions.otherGroup" : "Ostatní nastavení", // unused right now + "vcmi.systemOptions.townsGroup" : "Obrazovka města", + + "vcmi.statisticWindow.statistics" : "Statistiky", + "vcmi.statisticWindow.tsvCopy" : "Zkopírovat data do schránky", + "vcmi.statisticWindow.selectView" : "Vybrat pohled", + "vcmi.statisticWindow.value" : "Hodnota", + "vcmi.statisticWindow.title.overview" : "Přehled", + "vcmi.statisticWindow.title.resources" : "Zdroje", + "vcmi.statisticWindow.title.income" : "Příjem", + "vcmi.statisticWindow.title.numberOfHeroes" : "Počet hrdinů", + "vcmi.statisticWindow.title.numberOfTowns" : "Počet měst", + "vcmi.statisticWindow.title.numberOfArtifacts" : "Počet artefaktů", + "vcmi.statisticWindow.title.numberOfDwellings" : "Počet obydlí", + "vcmi.statisticWindow.title.numberOfMines" : "Počet dolů", + "vcmi.statisticWindow.title.armyStrength" : "Síla armády", + "vcmi.statisticWindow.title.experience" : "Zkušenosti", + "vcmi.statisticWindow.title.resourcesSpentArmy" : "Náklady na armádu", + "vcmi.statisticWindow.title.resourcesSpentBuildings" : "Náklady na budovy", + "vcmi.statisticWindow.title.mapExplored" : "Prozkoumaná část mapy", + "vcmi.statisticWindow.param.playerName" : "Jméno hráče", + "vcmi.statisticWindow.param.daysSurvived" : "Počet přežitých dní", + "vcmi.statisticWindow.param.maxHeroLevel" : "Maximální úroveň hrdiny", + "vcmi.statisticWindow.param.battleWinRatioHero" : "Poměr výher (proti hrdinům)", + "vcmi.statisticWindow.param.battleWinRatioNeutral" : "Poměr výher (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.battlesHero" : "Bitev (proti hrdinům)", + "vcmi.statisticWindow.param.battlesNeutral" : "Bitej (proti neutrálním jednotkám)", + "vcmi.statisticWindow.param.maxArmyStrength" : "Maximální síla armády", + "vcmi.statisticWindow.param.tradeVolume" : "Objem obchodu", + "vcmi.statisticWindow.param.obeliskVisited" : "Navštívené obelisky", + "vcmi.statisticWindow.icon.townCaptured" : "Dobyto měst", + "vcmi.statisticWindow.icon.strongestHeroDefeated" : "Nejsilnější hrdina protivníka poražen", + "vcmi.statisticWindow.icon.grailFound" : "Nalezen Svatý grál", + "vcmi.statisticWindow.icon.defeated" : "Porážka", + + "vcmi.systemOptions.fullscreenBorderless.hover" : "Celá obrazovka (bez okrajů)", + "vcmi.systemOptions.fullscreenBorderless.help" : "{Celá obrazovka bez okrajů}\n\nPokud je vybráno, VCMI poběží v režimu celé obrazovky bez okrajů. V tomto režimu bude hra respektovat systémové rozlišení a ignorovat vybrané rozlišení ve hře.", + "vcmi.systemOptions.fullscreenExclusive.hover" : "Celá obrazovka (exkluzivní)", + "vcmi.systemOptions.fullscreenExclusive.help" : "{Celá obrazovka}\n\nPokud je vybráno, VCMI poběží v režimu exkluzivní celé obrazovky. V tomto režimu hra změní rozlišení na vybrané.", + "vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h", + "vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.", + "vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení", + "vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.", + "vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%", + "vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní", + "vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní", + "vcmi.systemOptions.scalingMenu.help" : "Změní škálování herního rozhraní.", + "vcmi.systemOptions.longTouchButton.hover" : "Doba dlouhého podržení: %d ms", // Translation note: "ms" = "milliseconds" + "vcmi.systemOptions.longTouchButton.help" : "{Doba dlouhého podržení}\n\nPři používání dotykové obrazovky bude zobrazeno vyskakovací okno při podržení prstu na obrazovce, v milisekundách", + "vcmi.systemOptions.longTouchMenu.hover" : "Vybrat dobu dlouhého podržení", + "vcmi.systemOptions.longTouchMenu.help" : "Změnit dobu dlouhého podržení.", + "vcmi.systemOptions.longTouchMenu.entry" : "%d milisekund", + "vcmi.systemOptions.framerateButton.hover" : "Zobrazit FPS", + "vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počítadla snímků za sekundu v rohu obrazovky hry.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání.", + "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.", + "vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel", + "vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se vejde více kouzel na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.", + "vcmi.systemOptions.audioMuteFocus.hover" : "Ztlumit při neaktivitě", + "vcmi.systemOptions.audioMuteFocus.help" : "{Ztlumit při neaktivitě}\n\nZtlumit zvuk, pokud je okno hry v pozadí. Výjimkou jsou zprávy ve hře a zvuk nového tahu.", + + "vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací", + "vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.", + "vcmi.adventureOptions.numericQuantities.hover" : "Číselné množství jednotek", + "vcmi.adventureOptions.numericQuantities.help" : "{Číselné množství jednotek}\n\nZobrazit přibližné množství nepřátelských jednotek ve formátu A-B.", + "vcmi.adventureOptions.forceMovementInfo.hover" : "Vždy zobrazit cenu pohybu", + "vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).", + "vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku", + "vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.", + "vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky", + "vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.", + "vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu", + "vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.", + "vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.", + "vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.", + "vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.", + "vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.", + "vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy", + "vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.", + "vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení", + "vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Přeskočit efekty mizení}\n\nKdyž je povoleno, přeskočí se efekty mizení objektů a podobné efekty (sběr surovin, nalodění atd.). V některých případech zrychlí uživatelské rozhraní na úkor estetiky. Obzvláště užitečné v PvP hrách. Pro maximální rychlost pohybu je toto nastavení aktivní bez ohledu na další volby.", + "vcmi.adventureOptions.mapScrollSpeed1.hover": "", + "vcmi.adventureOptions.mapScrollSpeed5.hover": "", + "vcmi.adventureOptions.mapScrollSpeed6.hover": "", + "vcmi.adventureOptions.mapScrollSpeed1.help": "Nastavit posouvání mapy na velmi pomalé", + "vcmi.adventureOptions.mapScrollSpeed5.help": "Nastavit posouvání mapy na velmi rychlé", + "vcmi.adventureOptions.mapScrollSpeed6.help": "Nastavit posouvání mapy na okamžité", + "vcmi.adventureOptions.hideBackground.hover" : "Skrýt pozadí", + "vcmi.adventureOptions.hideBackground.help" : "{Skrýt pozadí}\n\nSkryje mapu dobrodružství na pozadí a místo ní zobrazí texturu.", + + "vcmi.battleOptions.queueSizeLabel.hover": "Zobrazit frontu pořadí tahů", + "vcmi.battleOptions.queueSizeNoneButton.hover": "VYPNUTO", + "vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO", + "vcmi.battleOptions.queueSizeSmallButton.hover": "MALÁ", + "vcmi.battleOptions.queueSizeBigButton.hover": "VELKÁ", + "vcmi.battleOptions.queueSizeNoneButton.help": "Nezobrazovat frontu pořadí tahů.", + "vcmi.battleOptions.queueSizeAutoButton.help": "Nastavit automaticky velikost fronty pořadí tahů podle rozlišení obrazovky hry (Při výšce herního rozlišení menší než 700 pixelů je použita velikost MALÁ, jinak velikost VELKÁ)", + "vcmi.battleOptions.queueSizeSmallButton.help": "Zobrazit MALOU frontu pořadí tahů.", + "vcmi.battleOptions.queueSizeBigButton.help": "Zobrazit VELKOU frontu pořadí tahů (není podporováno, pokud výška rozlišení hry není alespoň 700 pixelů).", + "vcmi.battleOptions.animationsSpeed1.hover": "", + "vcmi.battleOptions.animationsSpeed5.hover": "", + "vcmi.battleOptions.animationsSpeed6.hover": "", + "vcmi.battleOptions.animationsSpeed1.help": "Nastavit rychlost animací na velmi pomalé.", + "vcmi.battleOptions.animationsSpeed5.help": "Nastavit rychlost animací na velmi rychlé.", + "vcmi.battleOptions.animationsSpeed6.help": "Nastavit rychlost animací na okamžité.", + "vcmi.battleOptions.movementHighlightOnHover.hover": "Zvýraznění pohybu při najetí", + "vcmi.battleOptions.movementHighlightOnHover.help": "{Zvýraznění pohybu při najetí}\n\nZvýraznit rozsah pohybu jednotky při najetí na něj.", + "vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Zobrazit omezení dostřelu střelců", + "vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Zobrazit omezení dostřelu střelců při najetí}\n\nZobrazit dostřel střelce při najetí na něj.", + "vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Zobrazit okno statistik hrdinů", + "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.", + "vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu", + "vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.", + "vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu", + "vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", + "vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel", + "vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", + + "vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", + "vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit objekt}\n\nPokud hrdina právě stojí na objektu na mapě, může toto místo znovu navštívit.", + + "vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy", + "vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).", + "vcmi.battleWindow.damageEstimation.meleeKills" : "Zaútočit na %CREATURE (%DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.ranged" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE).", + "vcmi.battleWindow.damageEstimation.rangedKills" : "Vystřelit na %CREATURE (%SHOTS, %DAMAGE, %KILLS).", + "vcmi.battleWindow.damageEstimation.shots" : "%d střel zbývá", + "vcmi.battleWindow.damageEstimation.shots.1" : "%d střela zbývá", + "vcmi.battleWindow.damageEstimation.damage" : "%d poškození", + "vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození", + "vcmi.battleWindow.damageEstimation.kills" : "%d zahyne", + "vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne", + + "vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ", + "vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu", + "vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.", + "vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).", + "vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).", + + "vcmi.battleWindow.killed" : "Zabito", + "vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!", + "vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!", + "vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?", + + "vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy", + + "vcmi.tutorialWindow.title" : "Úvod ovládání dotykem", + "vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.", + "vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.", + "vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.", + "vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/jednotek a příkazy měst.", + "vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.", + "vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.", + "vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.", + + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky", + "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek", + "vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Zobrazit týdenní přírůstek jednotek}\n\nZobrazit týdenní přírůstek jednotek místo dostupného počtu ke koupení v přehledu města (levý spodní okraj obrazovky města).", + "vcmi.otherOptions.compactTownCreatureInfo.hover": "Kompaktní informace o jednotkách", + "vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).", + + "vcmi.townHall.missingBase" : "Nejdříve musí být postavena základní budova %s", + "vcmi.townHall.noCreaturesToRecruit" : "Nejsou k dispozici žádné jednotky k najmutí!", + + "vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"", + "vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"", + + "vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:", + "vcmi.logicalExpressions.allOf" : "Všechny následující:", + "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", + + "vcmi.heroWindow.openCommander.hover" : "Otevřít okno s informacemi o veliteli", + "vcmi.heroWindow.openCommander.help" : "Zobrazí podrobnosti o veliteli tohoto hrdiny.", + "vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", + "vcmi.heroWindow.openBackpack.help" : "Otevře okno, které umožňuje snadnější správu artefaktů v batohu.", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Seřadit podle ceny", + "vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Seřadí artefakty v batohu podle přiřazeného slotu.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", + "vcmi.heroWindow.sortBackpackByClass.help" : "Seřadí artefakty v batohu podle třídy artefaktu. Poklad, Menší, Větší, Relikvie.", + "vcmi.heroWindow.fusingArtifact.fusing" : "Máte všechny potřebné části k vytvoření %s. Chcete provést sloučení? {Při sloučení budou použity všechny části.}", + + "vcmi.tavernWindow.inviteHero" : "Pozvat hrdinu", + + "vcmi.commanderWindow.artifactMessage" : "Chcete tento artefakt vrátit hrdinovi?", + + "vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů", + "vcmi.creatureWindow.showBonuses.help" : "Zobrazí všechny aktivní bonusy velitele.", + "vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení dovedností", + "vcmi.creatureWindow.showSkills.help" : "Zobrazí všechny naučené dovednosti velitele.", + "vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt", + "vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro vrácení artefaktu do batohu hrdiny.", + + "vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly", + "vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly.", + + "vcmi.randomMapTab.widgets.randomTemplate" : "(Náhodná)", + "vcmi.randomMapTab.widgets.templateLabel" : "Šablona", + "vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Nastavit...", + "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Přiřazení týmů", + "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", + + "vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu", + "vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů", + + "vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač", + "vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu", + "vcmi.optionsTab.chessFieldBattle.hover" : "Časovač bitvy", + "vcmi.optionsTab.chessFieldUnit.hover" : "Časovač jednotky", + "vcmi.optionsTab.chessFieldBase.help" : "Použit při poklesnutí {Časovače bitvy} na 0. Nastaveno jednou při začátku hry. Při poklesu na nulu skončí tah. Jákákoliv trvající bitva skončí prohrou.", + "vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Přebytečný čas je přidán do {Základního časovače} na konci tahu.", + "vcmi.optionsTab.chessFieldTurnDiscard.help" : "Použit mimo bitvu nebo když {Časovač bitvy} vyprší. Resetuje se každý tah. Jakýkoliv přebytečný čas je ztracen.", + "vcmi.optionsTab.chessFieldBattle.help" : "Použit v bitvách s AI nebo v pvp soubojích při vypršení {Časovače jednotky}. Resetuje se startu každé bitvy.", + "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Použit při vybírání úkonu jednotky. Přebytečný čas je přidán do {Časovače bitvy} na konci tahu jednotky.", + "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Použit při vybírání úkonu jednotky. Resetuje se na začátku tahu každé jednotky. Jakýkoliv přebytečný čas je ztracen.", + + "vcmi.optionsTab.accumulate" : "Akumulovat", + + "vcmi.optionsTab.simturnsTitle" : "Souběžné tahy", + "vcmi.optionsTab.simturnsMin.hover" : "Alespoň po", + "vcmi.optionsTab.simturnsMax.hover" : "Nejvíce po", + "vcmi.optionsTab.simturnsAI.hover" : "(Experimentální) Souběžné tahy AI", + "vcmi.optionsTab.simturnsMin.help" : "Hrát souběžně po určený počet dní. Setkání mezi hráči je v této době zablokováno", + "vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem", + "vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.", + + "vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače", + "vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu", + "vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta", + "vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty", + "vcmi.optionsTab.turnTime.classic.5" : "Klasický časovač: 5 minut", + "vcmi.optionsTab.turnTime.classic.10" : "Klasický časovač: 10 minut", + "vcmi.optionsTab.turnTime.classic.20" : "Klasický časovač: 20 minut", + "vcmi.optionsTab.turnTime.classic.30" : "Klasický časovač: 30 minut", + "vcmi.optionsTab.turnTime.chess.20" : "Šachová: 20:00 + 10:00 + 02:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.16" : "Šachová: 16:00 + 08:00 + 01:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.8" : "Šachová: 08:00 + 04:00 + 01:00 + 00:00", + "vcmi.optionsTab.turnTime.chess.4" : "Šachová: 04:00 + 02:00 + 00:30 + 00:00", + "vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00", + "vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00", + + "vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů", + "vcmi.optionsTab.simturns.none" : "Bez souběžných tahů", + "vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání", + "vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání", + "vcmi.optionsTab.simturns.tillContact2" : "Souběžně: 2 týdny, přerušit při setkání", + "vcmi.optionsTab.simturns.tillContact4" : "Souběžně: 1 mšsíc, přerušit při setkání", + "vcmi.optionsTab.simturns.blocked1" : "Souběžně: 1 týden, setkání zablokována", + "vcmi.optionsTab.simturns.blocked2" : "Souběžně: 2 týdny, setkání zablokována", + "vcmi.optionsTab.simturns.blocked4" : "Souběžně: 1 měsíc, setkání zablokována", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d dní", + "vcmi.optionsTab.simturns.days.1" : " %d den", + "vcmi.optionsTab.simturns.days.2" : " %d dny", + "vcmi.optionsTab.simturns.weeks.0" : " %d týdnů", + "vcmi.optionsTab.simturns.weeks.1" : " %d týden", + "vcmi.optionsTab.simturns.weeks.2" : " %d týdny", + "vcmi.optionsTab.simturns.months.0" : " %d měsíců", + "vcmi.optionsTab.simturns.months.1" : " %d měsíc", + "vcmi.optionsTab.simturns.months.2" : " %d měsíce", + + "vcmi.optionsTab.extraOptions.hover" : "Další možnosti", + "vcmi.optionsTab.extraOptions.help" : "Další herní možnosti", + + "vcmi.optionsTab.cheatAllowed.hover" : "Povolit cheaty", + "vcmi.optionsTab.unlimitedReplay.hover" : "Neomezené opakování bitvy", + "vcmi.optionsTab.cheatAllowed.help" : "{Povolit cheaty}\nPovolí zadávání cheatů během hry.", + "vcmi.optionsTab.unlimitedReplay.help" : "{Neomezené opakování bitvy}\nŽádné omezení pro opakování bitev.", + + // Custom victory conditions for H3 campaigns and HotA maps + "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", + "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", + "vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Nepřítel porazil všechny jednotky zamořující tuto zemi a nárokuje si vítězství!", + "vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!", + "vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty", + "vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!", + "vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci", + "vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Bohužel, ztratili jste část Andělské aliance. Vše je ztraceno.", + + // few strings from WoG used by vcmi + "vcmi.stackExperience.description" : "» P o d r o b n o s t i z k u š e n o s t í o d d í l u «\n\nDruh jednotky ................... : %s\nÚroveň hodnosti ................. : %s (%i)\nBody zkušeností ............... : %i\nZkušenostních bodů do další úrovně hodnosti .. : %i\nMaximum zkušeností na bitvu ... : %i%% (%i)\nPočet jednotek v oddílu .... : %i\nMaximum nových rekrutů\n bez ztráty současné hodnosti .... : %i\nNásobič zkušeností ........... : %.2f\nNásobič vylepšení .............. : %.2f\nZkušnosti po 10. úrovně hodnosti ........ : %i\nMaximální počet nových rekrutů pro zachování\n 10. úrovně hodnosti s maximálními zkušenostmi: %i", + "vcmi.stackExperience.rank.0" : "Začátečník", + "vcmi.stackExperience.rank.1" : "Učeň", + "vcmi.stackExperience.rank.2" : "Trénovaný", + "vcmi.stackExperience.rank.3" : "Zručný", + "vcmi.stackExperience.rank.4" : "Prověřený", + "vcmi.stackExperience.rank.5" : "Veterán", + "vcmi.stackExperience.rank.6" : "Adept", + "vcmi.stackExperience.rank.7" : "Expert", + "vcmi.stackExperience.rank.8" : "Elitní", + "vcmi.stackExperience.rank.9" : "Mistr", + "vcmi.stackExperience.rank.10" : "Eso", + + // Strings for HotA Seer Hut / Quest Guards + "core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.2" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?", + "core.seerhut.quest.heroClass.complete.3" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.4" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.complete.5" : "Stráže si všimly, že jste %s a nabízejí vám průchod. Přijmete to?", + "core.seerhut.quest.heroClass.description.0" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.1" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.2" : "Pošlete %s do %s", + "core.seerhut.quest.heroClass.description.3" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.4" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.description.5" : "Pošlete %s, aby otevřel bránu", + "core.seerhut.quest.heroClass.hover.0" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.1" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.2" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.3" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.4" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.hover.5" : "(hledá hrdinu třídy %s)", + "core.seerhut.quest.heroClass.receive.0" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.1" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.2" : "Mám dárek pro %s.", + "core.seerhut.quest.heroClass.receive.3" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.4" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.receive.5" : "Stráže říkají, že průchod povolí pouze %s.", + "core.seerhut.quest.heroClass.visit.0" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.1" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.2" : "Nejste %s. Nemám pro vás nic. Zmizte!", + "core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.", + "core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.", + + "core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?", + "core.seerhut.quest.reachDate.complete.3" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.4" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.complete.5" : "Nyní můžete projít. Chcete pokračovat?", + "core.seerhut.quest.reachDate.description.0" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.1" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.2" : "Čekejte do %s pro %s", + "core.seerhut.quest.reachDate.description.3" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.4" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.description.5" : "Čekejte do %s, abyste otevřeli bránu", + "core.seerhut.quest.reachDate.hover.0" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.1" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.2" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.3" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.4" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.hover.5" : "(Vraťte se nejdříve %s)", + "core.seerhut.quest.reachDate.receive.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s", + "core.seerhut.quest.reachDate.receive.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.receive.5" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.0" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.1" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.2" : "Jsem zaneprázdněný. Vraťte se nejdříve %s.", + "core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.", + "core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.", + + "mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.", + + "core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok", + "core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát", + "core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky", + "core.bonus.ADDITIONAL_RETALIATION.description": "Může odvetně zaútočit ${val} krát navíc", + "core.bonus.AIR_IMMUNITY.name": "Odolnost vůči vzdušné magii", + "core.bonus.AIR_IMMUNITY.description": "Imunní vůči všem kouzlům školy vzdušné magie", + "core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok na všechny kolem", + "core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední nepřátele", + "core.bonus.BLOCKS_RETALIATION.name": "Žádná odveta", + "core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit", + "core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná střelecká odveta", + "core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže odvetně zaútočit střeleckým útokem", + "core.bonus.CATAPULT.name": "Katapult", + "core.bonus.CATAPULT.description": "Útočí na ochranné hradby", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje náklady na kouzla pro hrdinu o ${val}", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})", + "core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje náklady na kouzla nepřítele o ${val}", + "core.bonus.CHARGE_IMMUNITY.name": "Odolnost vůči Nájezdu", + "core.bonus.CHARGE_IMMUNITY.description": "Imunní vůči Nájezdu Jezdců a Šampionů", + "core.bonus.DARKNESS.name": "Závoj temnoty", + "core.bonus.DARKNESS.description": "Vytváří závoj temnoty s poloměrem ${val}", + "core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)", + "core.bonus.DEATH_STARE.description": "Má ${val}% šanci zabít jednu jednotku", + "core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus", + "core.bonus.DEFENSIVE_STANCE.description": "+${val} k obraně při bránění", + "core.bonus.DESTRUCTION.name": "Zničení", + "core.bonus.DESTRUCTION.description": "Má ${val}% šanci zabít další jednotky po útoku", + "core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtelný úder", + "core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Má ${val}% šanci způsobit dvojnásobné základní poškození při útoku", + "core.bonus.DRAGON_NATURE.name": "Dračí povaha", + "core.bonus.DRAGON_NATURE.description": "Jednotka má Dračí povahu", + "core.bonus.EARTH_IMMUNITY.name": "Odolnost vůči zemské magii", + "core.bonus.EARTH_IMMUNITY.description": "Imunní vůči všem kouzlům školy zemské magie", + "core.bonus.ENCHANTER.name": "Zaklínač", + "core.bonus.ENCHANTER.description": "Může každé kolo sesílat masové kouzlo ${subtype.spell}", + "core.bonus.ENCHANTED.name": "Očarovaný", + "core.bonus.ENCHANTED.description": "Je pod trvalým účinkem kouzla ${subtype.spell}", + "core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignorování útoku (${val}%)", + "core.bonus.ENEMY_ATTACK_REDUCTION.description": "Při útoku je ignorováno ${val}% útočníkovy síly", + "core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignorování obrany (${val}%)", + "core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude bráno v potaz ${val}% bodů obrany obránce", + "core.bonus.FIRE_IMMUNITY.name": "Odolnost vůči ohnivé magii", + "core.bonus.FIRE_IMMUNITY.description": "Imunní vůči všem kouzlům školy ohnivé magie", + "core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)", + "core.bonus.FIRE_SHIELD.description": "Odrazí část zranění při útoku z blízka", + "core.bonus.FIRST_STRIKE.name": "První úder", + "core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí dříve, než je napadena", + "core.bonus.FEAR.name": "Strach", + "core.bonus.FEAR.description": "Vyvolává strach u nepřátelské jednotky", + "core.bonus.FEARLESS.name": "Nebojácnost", + "core.bonus.FEARLESS.description": "Imunní vůči schopnosti Strach", + "core.bonus.FEROCITY.name": "Zuřivost", + "core.bonus.FEROCITY.description": "Útočí ${val} krát navíc, pokud někoho zabije", + "core.bonus.FLYING.name": "Létání", + "core.bonus.FLYING.description": "Při pohybu létá (ignoruje překážky)", + "core.bonus.FREE_SHOOTING.name": "Střelba zblízka", + "core.bonus.FREE_SHOOTING.description": "Může použít výstřely i při útoku zblízka", + "core.bonus.GARGOYLE.name": "Chrlič", + "core.bonus.GARGOYLE.description": "Nemůže být oživen ani vyléčen", + "core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)", + "core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka", + "core.bonus.HATE.name": "Nenávidí ${subtype.creature}", + "core.bonus.HATE.description": "Způsobuje ${val}% více poškození vůči ${subtype.creature}", + "core.bonus.HEALER.name": "Léčitel", + "core.bonus.HEALER.description": "Léčí spojenecké jednotky", + "core.bonus.HP_REGENERATION.name": "Regenerace", + "core.bonus.HP_REGENERATION.description": "Každé kolo regeneruje ${val} bodů zdraví", + "core.bonus.JOUSTING.name": "Nájezd šampionů", + "core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole", + "core.bonus.KING.name": "Král", + "core.bonus.KING.description": "Zranitelný proti zabijákovi úrovně ${val} a vyšší", + "core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}", + "core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}", + "core.bonus.LIMITED_SHOOTING_RANGE.name": "Omezený dostřel", + "core.bonus.LIMITED_SHOOTING_RANGE.description": "Není schopen zasáhnout jednotky vzdálenější než ${val} polí", + "core.bonus.LIFE_DRAIN.name": "Vysávání života (${val}%)", + "core.bonus.LIFE_DRAIN.description": "Vysává ${val}% způsobeného poškození", + "core.bonus.MANA_CHANNELING.name": "Kanál magie ${val}%", + "core.bonus.MANA_CHANNELING.description": "Poskytuje vašemu hrdinovi ${val}% many použité nepřítelem", + "core.bonus.MANA_DRAIN.name": "Vysávání many", + "core.bonus.MANA_DRAIN.description": "Vysává ${val} many každý tah", + "core.bonus.MAGIC_MIRROR.name": "Magické zrcadlo (${val}%)", + "core.bonus.MAGIC_MIRROR.description": "Má ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku", + "core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)", + "core.bonus.MAGIC_RESISTANCE.description": "Má ${val}% šanci odolat nepřátelskému kouzlu", + "core.bonus.MIND_IMMUNITY.name": "Imunita vůči kouzlům mysli", + "core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům mysli", + "core.bonus.NO_DISTANCE_PENALTY.name": "Žádná penalizace vzdálenosti", + "core.bonus.NO_DISTANCE_PENALTY.description": "Způsobuje plné poškození na jakoukoliv vzdálenost", + "core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka", + "core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka", + "core.bonus.NO_MORALE.name": "Neutrální morálka", + "core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektům morálky", + "core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami", + "core.bonus.NO_WALL_PENALTY.description": "Plné poškození během obléhání", + "core.bonus.NON_LIVING.name": "Neživý", + "core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům", + "core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník", + "core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo", + "core.bonus.RANGED_RETALIATION.name": "Střelecká odveta", + "core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku", + "core.bonus.RECEPTIVE.name": "Vnímavý", + "core.bonus.RECEPTIVE.description": "Nemá imunitu na přátelská kouzla", + "core.bonus.REBIRTH.name": "Znovuzrození (${val}%)", + "core.bonus.REBIRTH.description": "${val}% jednotek povstane po smrti", + "core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat", + "core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na zblízka", + "core.bonus.REVENGE.name": "Pomsta", + "core.bonus.REVENGE.description": "Způsobuje extra poškození na základě ztrát útočníka v bitvě", + "core.bonus.SHOOTER.name": "Střelec", + "core.bonus.SHOOTER.description": "Jednotka může střílet", + "core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí všude kolem", + "core.bonus.SHOOTS_ALL_ADJACENT.description": "Střelecký útok této jednotky zasáhne všechny cíle v malé oblasti", + "core.bonus.SOUL_STEAL.name": "Zloděj duší", + "core.bonus.SOUL_STEAL.description": "Získává ${val} nové jednotky za každého zabitého nepřítele", + "core.bonus.SPELLCASTER.name": "Kouzelník", + "core.bonus.SPELLCASTER.description": "Může seslat kouzlo ${subtype.spell}", + "core.bonus.SPELL_AFTER_ATTACK.name": "Sesílá po útoku", + "core.bonus.SPELL_AFTER_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} po útoku", + "core.bonus.SPELL_BEFORE_ATTACK.name": "Sesílá před útokem", + "core.bonus.SPELL_BEFORE_ATTACK.description": "Má ${val}% šanci seslat ${subtype.spell} před útokem", + "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost", + "core.bonus.SPELL_DAMAGE_REDUCTION.description": "Poškození kouzly sníženo o ${val}%.", + "core.bonus.SPELL_IMMUNITY.name": "Imunita vůči kouzlům", + "core.bonus.SPELL_IMMUNITY.description": "Imunní vůči ${subtype.spell}", + "core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem", + "core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}", + "core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odporu", + "core.bonus.SPELL_RESISTANCE_AURA.description": "Jednotky poblíž získají ${val}% magickou odolnost", + "core.bonus.SUMMON_GUARDIANS.name": "Přivolání ochránců", + "core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy přivolá ${subtype.creature} (${val}%)", + "core.bonus.SYNERGY_TARGET.name": "Synergizovatelný", + "core.bonus.SYNERGY_TARGET.description": "Tato jednotka je náchylná k synergickým efektům", + "core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech", + "core.bonus.TWO_HEX_ATTACK_BREATH.description": "Útok dechem (dosah 2 polí)", + "core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok", + "core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky", + "core.bonus.TRANSMUTATION.name": "Transmutace", + "core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu napadené jednotky na jiný typ", + "core.bonus.UNDEAD.name": "Nemrtvý", + "core.bonus.UNDEAD.description": "Jednotka je nemrtvá", + "core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvetné útoky", + "core.bonus.UNLIMITED_RETALIATIONS.description": "Může provést neomezený počet odvetných útoků", + "core.bonus.WATER_IMMUNITY.name": "Odolnost vůči vodní magii", + "core.bonus.WATER_IMMUNITY.description": "Imunní vůči všem kouzlům školy vodní magie", + "core.bonus.WIDE_BREATH.name": "Široký dech", + "core.bonus.WIDE_BREATH.description": "Široký útok dechem (více polí)", + "core.bonus.DISINTEGRATE.name": "Rozpad", + "core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo", + "core.bonus.INVINCIBLE.name": "Neporazitelný", + "core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)" } \ No newline at end of file From 498f78de8700e74b345c78ce4edff9be19f365ee Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:39:22 +0100 Subject: [PATCH 529/726] Add files via upload --- config/objects/pyramid.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/objects/pyramid.json b/config/objects/pyramid.json index e39380a30..e1efd7b85 100644 --- a/config/objects/pyramid.json +++ b/config/objects/pyramid.json @@ -46,7 +46,7 @@ "spells" : [ "@gainedSpell" ], - "message" : [ 106, "%s." ], // Upon defeating monsters, you learn new spell + "message" : [ 106, "{%s}." ], // Upon defeating monsters, you learn new spell "guards" : [ { "amount" : 40, "type" : "goldGolem" }, { "amount" : 10, "type" : "diamondGolem" }, @@ -63,10 +63,10 @@ } ] }, - "message" : [ 106, "%s. ", 108 ] // No Wisdom + "message" : [ 106, "{%s}. ", 108 ] // No Wisdom }, { - "message" : [ 106, "%s. ", 109 ] // No spellbook + "message" : [ 106, "{%s}. ", 109 ] // No spellbook } ] From 42059d18c32cbff015adabac1c948cc31384def2 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:30:56 +0100 Subject: [PATCH 530/726] fix chronicles --- launcher/modManager/chroniclesextractor.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/launcher/modManager/chroniclesextractor.cpp b/launcher/modManager/chroniclesextractor.cpp index 57759e2df..022c46fd2 100644 --- a/launcher/modManager/chroniclesextractor.cpp +++ b/launcher/modManager/chroniclesextractor.cpp @@ -106,7 +106,7 @@ void ChroniclesExtractor::createBaseMod() const { "author", "3DO" }, { "version", "1.0" }, { "contact", "vcmi.eu" }, - { "heroes", QJsonArray({"config/heroes/portraitsChronicles.json"}) }, + { "heroes", QJsonArray({"config/portraitsChronicles.json"}) }, { "settings", QJsonObject({{"mapFormat", QJsonObject({{"chronicles", QJsonObject({{ {"supported", true}, {"portraits", QJsonObject({ @@ -123,6 +123,17 @@ void ChroniclesExtractor::createBaseMod() const QFile jsonFile(dir.filePath("mod.json")); jsonFile.open(QFile::WriteOnly); jsonFile.write(QJsonDocument(mod).toJson()); + + for(auto & dataPath : VCMIDirs::get().dataPaths()) + { + auto file = dataPath / "config" / "heroes" / "portraitsChronicles.json"; + auto destFolder = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "config"; + if(boost::filesystem::exists(file)) + { + boost::filesystem::create_directories(destFolder); + boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_options::overwrite_existing); + } + } } void ChroniclesExtractor::createChronicleMod(int no) From 10da13729cefa589138c9ca6d733bc833868e3c4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 5 Nov 2024 02:00:26 +0100 Subject: [PATCH 531/726] fix boost --- launcher/modManager/chroniclesextractor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/modManager/chroniclesextractor.cpp b/launcher/modManager/chroniclesextractor.cpp index 022c46fd2..6f58bdd06 100644 --- a/launcher/modManager/chroniclesextractor.cpp +++ b/launcher/modManager/chroniclesextractor.cpp @@ -131,7 +131,11 @@ void ChroniclesExtractor::createBaseMod() const if(boost::filesystem::exists(file)) { boost::filesystem::create_directories(destFolder); +#if BOOST_VERSION >= 107400 boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_options::overwrite_existing); +#else + boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_option::overwrite_if_exists); +#endif } } } From 7c3afde7eee38aab91a5f5d899f323d14e5ecac9 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 5 Nov 2024 23:12:02 +0100 Subject: [PATCH 532/726] fix --- client/renderSDL/RenderHandler.cpp | 31 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index f4b8c1c68..efc2c89e0 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -148,7 +148,9 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path) { - AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/"); + auto tmp = getScalePath(path); + auto animPath = AnimationPath::builtin(tmp.first.getName()); + AnimationPath actualPath = boost::starts_with(animPath.getName(), "SPRITES") ? animPath : animPath.addPrefix("SPRITES/"); auto it = animationLayouts.find(actualPath); @@ -175,11 +177,15 @@ RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const Anim std::unique_ptr textData(new ui8[stream->getSize()]); stream->read(textData.get(), stream->getSize()); - const JsonNode config(reinterpret_cast(textData.get()), stream->getSize(), path.getOriginalName()); + const JsonNode config(reinterpret_cast(textData.get()), stream->getSize(), animPath.getOriginalName()); initFromJson(result, config); } + for(auto & g : result) + for(auto & i : g.second) + i.preScaledFactor = tmp.second; + animationLayouts[actualPath] = result; return animationLayouts[actualPath]; } @@ -235,7 +241,17 @@ std::shared_ptr RenderHandler::loadImageFromFileUncached(const Ima if (locator.defFile) { auto defFile = getAnimationFile(*locator.defFile); - return std::make_shared(defFile.get(), locator.defFrame, locator.defGroup, locator.preScaledFactor); + int preScaledFactor = locator.preScaledFactor; + if(!defFile) // no presscale for this frame + { + auto tmpPath = (*locator.defFile).getName(); + boost::algorithm::replace_all(tmpPath, "$2", ""); + boost::algorithm::replace_all(tmpPath, "$3", ""); + boost::algorithm::replace_all(tmpPath, "$4", ""); + preScaledFactor = 1; + defFile = getAnimationFile(AnimationPath::builtin(tmpPath)); + } + return std::make_shared(defFile.get(), locator.defFrame, locator.defGroup, preScaledFactor); } throw std::runtime_error("Invalid image locator received!"); @@ -369,14 +385,7 @@ std::shared_ptr RenderHandler::createImage(SDL_Surface * source) std::shared_ptr RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode) { - auto tmp = getScalePath(path); - auto animPath = AnimationPath::builtin(tmp.first.getName()); - auto layout = getAnimationLayout(animPath); - for(auto & g : layout) - for(auto & i : g.second) - i.preScaledFactor = tmp.second; - - return std::make_shared(animPath, layout, mode); + return std::make_shared(path, getAnimationLayout(path), mode); } void RenderHandler::addImageListEntries(const EntityService * service) From bcdef11093f7fa52565c96d96f6c262be9130493 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 7 Nov 2024 02:32:06 +0100 Subject: [PATCH 533/726] rename variable --- client/renderSDL/SDLImage.cpp | 18 +++++++++--------- client/renderSDL/SDLImage.h | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 61b1b2b52..194c9d561 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -89,12 +89,12 @@ int IImage::height() const return dimensions().y; } -SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int scaleFactor) +SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int preScaleFactor) : surf(nullptr), margins(0, 0), fullSize(0, 0), originalPalette(nullptr), - scaleFactor(scaleFactor) + preScaleFactor(preScaleFactor) { SDLImageLoader loader(this); data->loadFrame(frame, group, loader); @@ -107,7 +107,7 @@ SDLImageShared::SDLImageShared(SDL_Surface * from) margins(0, 0), fullSize(0, 0), originalPalette(nullptr), - scaleFactor(1) + preScaleFactor(1) { surf = from; if (surf == nullptr) @@ -120,12 +120,12 @@ SDLImageShared::SDLImageShared(SDL_Surface * from) fullSize.y = surf->h; } -SDLImageShared::SDLImageShared(const ImagePath & filename, int scaleFactor) +SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor) : surf(nullptr), margins(0, 0), fullSize(0, 0), originalPalette(nullptr), - scaleFactor(scaleFactor) + preScaleFactor(preScaleFactor) { surf = BitmapHandler::loadBitmap(filename); @@ -278,12 +278,12 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet SDL_SetSurfacePalette(surf, palette); SDL_Surface * scaled = nullptr; - if(scaleFactor == factor) + if(preScaleFactor == factor) scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, 1, EScalingAlgorithm::NEAREST); // keep size - else if(scaleFactor == 1) + else if(preScaleFactor == 1) scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ); else - scaled = CSDL_Ext::scaleSurface(surf, (surf->w / scaleFactor) * factor, (surf->h / scaleFactor) * factor); + scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor); auto ret = std::make_shared(scaled); @@ -364,7 +364,7 @@ bool SDLImageShared::isTransparent(const Point & coords) const Point SDLImageShared::dimensions() const { - return fullSize / scaleFactor; + return fullSize / preScaleFactor; } std::shared_ptr SDLImageShared::createImageReference(EImageBlitMode mode) diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index 64e72703b..c18c01b10 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -36,7 +36,7 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from Point fullSize; //pre scaled image - int scaleFactor; + int preScaleFactor; // Keep the original palette, in order to do color switching operation void savePalette(); @@ -45,9 +45,9 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from public: //Load image from def file - SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int scaleFactor=1); + SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int preScaleFactor=1); //Load from bitmap file - SDLImageShared(const ImagePath & filename, int scaleFactor=1); + SDLImageShared(const ImagePath & filename, int preScaleFactor=1); //Create using existing surface, extraRef will increase refcount on SDL_Surface SDLImageShared(SDL_Surface * from); ~SDLImageShared(); From d3f6ec50889157ee07677ad17b26cb36f17ba4f5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 14:36:54 +0000 Subject: [PATCH 534/726] Fixed schema to account for lighthouse -> flaggable change --- config/schemas/object.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/schemas/object.json b/config/schemas/object.json index f74dffce3..59d913440 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -21,7 +21,7 @@ "enum" : [ "configurable", "dwelling", "hero", "town", "boat", "market", "hillFort", "shipyard", "monster", "resource", "static", "randomArtifact", "randomHero", "randomResource", "randomTown", "randomMonster", "randomDwelling", "generic", "artifact", "borderGate", "borderGuard", "denOfThieves", - "event", "garrison", "heroPlaceholder", "keymaster", "lighthouse", "magi", "mine", "obelisk", "pandora", "prison", "questGuard", "seerHut", "sign", + "event", "garrison", "heroPlaceholder", "keymaster", "flaggable", "magi", "mine", "obelisk", "pandora", "prison", "questGuard", "seerHut", "sign", "siren", "monolith", "subterraneanGate", "whirlpool", "terrain" ] }, From 8cd19f639f761fc1ff2f8770ba188886ced10e97 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 14:47:13 +0000 Subject: [PATCH 535/726] Added validation of flaggable objects --- config/schemas/flaggable.json | 64 +++++++++++++++++++ .../FlaggableInstanceConstructor.cpp | 9 ++- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 config/schemas/flaggable.json diff --git a/config/schemas/flaggable.json b/config/schemas/flaggable.json new file mode 100644 index 000000000..534c9297f --- /dev/null +++ b/config/schemas/flaggable.json @@ -0,0 +1,64 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-04/schema", + "title" : "VCMI map object format", + "description" : "Description of map object class", + "required" : [ "message" ], + "anyOf" : [ //NOTE: strictly speaking, not required - buidling can function without it, but won't do anythin + { + "required" : [ "bonuses" ] + }, + { + "required" : [ "dailyIncome" ] + } + ], + "additionalProperties" : false, + + "properties" : { + "bonuses" : { + "type" : "object", + "description" : "List of bonuses provided by this map object. See bonus format for more details", + "additionalProperties" : { "$ref" : "bonus.json" } + }, + + "message" : { + "description" : "Message that will be shown to player on capturing this object", + "anyOf" : [ + { + "type" : "string", + }, + { + "type" : "number", + } + ] + }, + + "dailyIncome" : { + "type" : "object", + "additionalProperties" : false, + "description" : "Daily income that this building provides to owner, if any", + "properties" : { + "gold" : { "type" : "number"}, + "wood" : { "type" : "number"}, + "ore" : { "type" : "number"}, + "mercury" : { "type" : "number"}, + "sulfur" : { "type" : "number"}, + "crystal" : { "type" : "number"}, + "gems" : { "type" : "number"} + } + }, + + // Properties that might appear since this node is shared with object config + "compatibilityIdentifiers" : { }, + "blockedVisitable" : { }, + "removable" : { }, + "aiValue" : { }, + "index" : { }, + "base" : { }, + "name" : { }, + "rmg" : { }, + "templates" : { }, + "battleground" : { }, + "sounds" : { } + } +} diff --git a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp index eeac5ee13..ed5eac661 100644 --- a/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp @@ -10,14 +10,19 @@ #include "StdInc.h" #include "FlaggableInstanceConstructor.h" -#include "../json/JsonBonus.h" -#include "../texts/CGeneralTextHandler.h" +#include "../CConfigHandler.h" #include "../VCMI_Lib.h" +#include "../json/JsonBonus.h" +#include "../json/JsonUtils.h" +#include "../texts/CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN void FlaggableInstanceConstructor::initTypeData(const JsonNode & config) { + if (settings["mods"]["validation"].String() != "off") + JsonUtils::validate(config, "vcmi:flaggable", getJsonKey()); + for (const auto & bonusJson : config["bonuses"].Struct()) providedBonuses.push_back(JsonUtils::parseBonus(bonusJson.second)); From 697d63d2b83a9353788870ced82ff1281548a83d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 3 Nov 2024 18:50:47 +0000 Subject: [PATCH 536/726] Reworked and fixed gendered hero sprites on adventure map --- config/schemas/hero.json | 2 +- config/schemas/heroClass.json | 4 +- .../AObjectTypeHandler.h | 2 +- .../CommonConstructors.cpp | 71 ++++++++++++++----- .../CommonConstructors.h | 18 +++-- lib/mapObjects/CGHeroInstance.cpp | 25 +++++-- lib/mapObjects/CGHeroInstance.h | 2 + lib/networkPacks/NetPacksLib.cpp | 3 +- 8 files changed, 93 insertions(+), 34 deletions(-) diff --git a/config/schemas/hero.json b/config/schemas/hero.json index 8d533387b..c1ccc548a 100644 --- a/config/schemas/hero.json +++ b/config/schemas/hero.json @@ -4,7 +4,7 @@ "title" : "VCMI hero format", "description" : "Format used to define new heroes in VCMI", "required" : [ "class", "army", "skills", "texts" ], - "oneOf" : [ + "anyOf" : [ { "required" : [ "images" ] }, diff --git a/config/schemas/heroClass.json b/config/schemas/heroClass.json index b6f5db342..93a8607a8 100644 --- a/config/schemas/heroClass.json +++ b/config/schemas/heroClass.json @@ -60,9 +60,7 @@ "properties" : { "filters" : { "type" : "object", - "additionalProperties" : { - "type" : "array" - } + "additionalProperties" : true } } }, diff --git a/lib/mapObjectConstructors/AObjectTypeHandler.h b/lib/mapObjectConstructors/AObjectTypeHandler.h index 402aa2aec..52a2c7c1d 100644 --- a/lib/mapObjectConstructors/AObjectTypeHandler.h +++ b/lib/mapObjectConstructors/AObjectTypeHandler.h @@ -90,7 +90,7 @@ public: /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) - std::shared_ptr getOverride(TerrainId terrainType, const CGObjectInstance * object) const; + virtual std::shared_ptr getOverride(TerrainId terrainType, const CGObjectInstance * object) const; BattleField getBattlefield() const; diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index f0434d415..57b52bd43 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -123,34 +123,71 @@ void CHeroInstanceConstructor::initTypeData(const JsonNode & input) input["heroClass"], [&](si32 index) { heroClass = HeroClassID(index).toHeroClass(); }); - filtersJson = input["filters"]; -} - -void CHeroInstanceConstructor::afterLoadFinalization() -{ - for(const auto & entry : filtersJson.Struct()) + for (const auto & [name, config] : input["filters"].Struct()) { - filters[entry.first] = LogicalExpression(entry.second, [](const JsonNode & node) + HeroFilter filter; + filter.allowFemale = config["female"].Bool(); + filter.allowMale = config["male"].Bool(); + filters[name] = filter; + + if (!config["hero"].isNull()) { - return HeroTypeID(VLC->identifiers()->getIdentifier("hero", node.Vector()[0]).value_or(-1)); - }); + VLC->identifiers()->requestIdentifier( "hero", config["hero"], [this, templateName = name](si32 index) { + filters.at(templateName).fixedHero = HeroTypeID(index); + }); + } } } -bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std::shared_ptr templ) const +std::shared_ptr CHeroInstanceConstructor::getOverride(TerrainId terrainType, const CGObjectInstance * object) const { const auto * hero = dynamic_cast(object); - auto heroTest = [&](const HeroTypeID & id) - { - return hero->getHeroTypeID() == id; - }; + std::vector> allTemplates = getTemplates(); + std::shared_ptr candidateFullMatch; + std::shared_ptr candidateGenderMatch; + std::shared_ptr candidateBase; - if(filters.count(templ->stringID)) + assert(hero->gender != EHeroGender::DEFAULT); + + for (const auto & templ : allTemplates) { - return filters.at(templ->stringID).test(heroTest); + if (filters.count(templ->stringID)) + { + const auto & filter = filters.at(templ->stringID); + if (filter.fixedHero.hasValue()) + { + if (filter.fixedHero == hero->getHeroTypeID()) + candidateFullMatch = templ; + } + else if (filter.allowMale) + { + if (hero->gender == EHeroGender::MALE) + candidateGenderMatch = templ; + } + else if (filter.allowFemale) + { + if (hero->gender == EHeroGender::FEMALE) + candidateGenderMatch = templ; + } + else + { + candidateBase = templ; + } + } + else + { + candidateBase = templ; + } } - return false; + + if (candidateFullMatch) + return candidateFullMatch; + + if (candidateGenderMatch) + return candidateGenderMatch; + + return candidateBase; } void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index 1a048df20..8d4abcc67 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -72,17 +72,21 @@ public: class CHeroInstanceConstructor : public CDefaultObjectTypeHandler { - JsonNode filtersJson; -protected: - bool objectFilter(const CGObjectInstance * obj, std::shared_ptr tmpl) const override; + struct HeroFilter + { + HeroTypeID fixedHero; + bool allowMale; + bool allowFemale; + }; + + std::map filters; + const CHeroClass * heroClass = nullptr; + + std::shared_ptr getOverride(TerrainId terrainType, const CGObjectInstance * object) const override; void initTypeData(const JsonNode & input) override; public: - const CHeroClass * heroClass = nullptr; - std::map> filters; - void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override; - void afterLoadFinalization() override; bool hasNameTextID() const override; std::string getNameTextID() const override; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 539da4345..be35ae4b4 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -336,6 +336,11 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType) subID = heroType; } +void CGHeroInstance::initObj(vstd::RNG & rand) +{ + updateAppearance(); +} + void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) { subID = SUBID.getNum(); @@ -350,12 +355,27 @@ TObjectTypeHandler CGHeroInstance::getObjectHandler() const return VLC->objtypeh->getHandlerFor(ID, 0); } +void CGHeroInstance::updateAppearance() +{ + auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex());; + auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); + auto app = handler->getOverride(terrain, this); + if (app) + appearance = app; +} + void CGHeroInstance::initHero(vstd::RNG & rand) { assert(validTypes(true)); + if (gender == EHeroGender::DEFAULT) + gender = getHeroType()->gender; + if (ID == Obj::HERO) - appearance = getObjectHandler()->getTemplates().front(); + { + auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex());; + appearance = handler->getTemplates().front(); + } if(!vstd::contains(spells, SpellID::PRESET)) { @@ -394,9 +414,6 @@ void CGHeroInstance::initHero(vstd::RNG & rand) if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::NONE, -1)) //set secondary skills to default secSkills = getHeroType()->secSkillsInit; - if (gender == EHeroGender::DEFAULT) - gender = getHeroType()->gender; - setFormation(EArmyFormation::LOOSE); if (!stacksCount()) //standard army//initial army { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 53a59ec13..c673b1727 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -241,6 +241,7 @@ public: HeroTypeID getHeroTypeID() const; void setHeroType(HeroTypeID type); + void initObj(vstd::RNG & rand) override; void initHero(vstd::RNG & rand); void initHero(vstd::RNG & rand, const HeroTypeID & SUBID); @@ -300,6 +301,7 @@ public: void attachToBoat(CGBoat* newBoat); void boatDeserializationFix(); void deserializationFix(); + void updateAppearance(); void pickRandomObject(vstd::RNG & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index f8f8e49d6..5317855ed 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1430,6 +1430,7 @@ void HeroRecruited::applyGs(CGameState *gs) h->setOwner(player); h->pos = tile; + h->updateAppearance(); if(h->id == ObjectInstanceID()) { @@ -1469,7 +1470,7 @@ void GiveHero::applyGs(CGameState *gs) auto oldVisitablePos = h->visitablePos(); gs->map->removeBlockVisTiles(h,true); - h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->getHeroClassID().getNum())->getTemplates().front(); + h->updateAppearance(); h->setOwner(player); h->setMovementPoints(h->movementPointsLimit(true)); From b9ff192a9111b2863169aa9759b66c163cbc6d6a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Nov 2024 12:07:45 +0000 Subject: [PATCH 537/726] Fix regressions from previous PR --- lib/CArtifactInstance.cpp | 2 +- lib/CCreatureSet.cpp | 4 ++-- lib/CCreatureSet.h | 13 ++++++++++--- lib/mapObjects/CGHeroInstance.cpp | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/CArtifactInstance.cpp b/lib/CArtifactInstance.cpp index 24e7f21db..e1be208dd 100644 --- a/lib/CArtifactInstance.cpp +++ b/lib/CArtifactInstance.cpp @@ -141,7 +141,7 @@ ArtifactID CArtifactInstance::getTypeId() const const CArtifact * CArtifactInstance::getType() const { - return artTypeID.toArtifact(); + return artTypeID.hasValue() ? artTypeID.toArtifact() : nullptr; } ArtifactInstanceID CArtifactInstance::getId() const diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 401611094..d96f4e79d 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -1005,12 +1005,12 @@ CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count const CCreature * CStackBasicDescriptor::getCreature() const { - return typeID.toCreature(); + return typeID.hasValue() ? typeID.toCreature() : nullptr; } const Creature * CStackBasicDescriptor::getType() const { - return typeID.toEntity(VLC); + return typeID.hasValue() ? typeID.toEntity(VLC) : nullptr; } CreatureID CStackBasicDescriptor::getId() const diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index e40c800a1..97065b597 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -51,9 +51,16 @@ public: template void serialize(Handler &h) { - h & typeID; - if(!h.saving) - setType(typeID.toCreature()); + if(h.saving) + { + h & typeID; + } + else + { + CreatureID creatureID; + h & creatureID; + setType(creatureID.toCreature()); + } h & count; } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index be35ae4b4..182494206 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -358,7 +358,7 @@ TObjectTypeHandler CGHeroInstance::getObjectHandler() const void CGHeroInstance::updateAppearance() { auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex());; - auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId(); + auto terrain = cb->gameState()->getTile(visitablePos())->getTerrainID(); auto app = handler->getOverride(terrain, this); if (app) appearance = app; From c3c5f73a63f485eb4fc445ab2bbb99829445b9a4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Nov 2024 12:08:08 +0000 Subject: [PATCH 538/726] Restore save compatibility with 1.5 --- lib/bonuses/Limiters.h | 11 ++++++++++- lib/mapObjects/CGMarket.h | 19 ++++++++++++++++++- lib/mapping/CMap.h | 20 +++++++++++++++++++- lib/mapping/CMapDefines.h | 7 +++---- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/bonuses/Limiters.h b/lib/bonuses/Limiters.h index 7a1813bab..9ad0e56d2 100644 --- a/lib/bonuses/Limiters.h +++ b/lib/bonuses/Limiters.h @@ -108,7 +108,16 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - h & creatureID; + + if (h.version < Handler::Version::REMOVE_TOWN_PTR) + { + bool isNull = false; + h & isNull; + if(!isNull) + h & creatureID; + } + else + h & creatureID; h & includeUpgrades; } }; diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index 9f2651028..b28a386bc 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -78,7 +78,24 @@ public: template void serialize(Handler &h) { h & static_cast(*this); - h & artifacts; + if (h.version < Handler::Version::REMOVE_VLC_POINTERS) + { + int32_t size = 0; + h & size; + for (int32_t i = 0; i < size; ++i) + { + bool isNull = false; + ArtifactID artifact; + h & isNull; + if (!isNull) + h & artifact; + artifacts.push_back(artifact); + } + } + else + { + h & artifacts; + } } }; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 7d48dc931..5192f7c1a 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -222,7 +222,25 @@ public: // static members h & obeliskCount; h & obelisksVisited; - h & townMerchantArtifacts; + + if (h.version < Handler::Version::REMOVE_VLC_POINTERS) + { + int32_t size = 0; + h & size; + for (int32_t i = 0; i < size; ++i) + { + bool isNull = false; + ArtifactID artifact; + h & isNull; + if (!isNull) + h & artifact; + townMerchantArtifacts.push_back(artifact); + } + } + else + { + h & townMerchantArtifacts; + } h & townUniversitySkills; h & instanceNames; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 49f27fa33..b37fcc692 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -146,10 +146,9 @@ struct DLL_LINKAGE TerrainTile { bool isNull = false; h & isNull; - if (isNull) + if (!isNull) h & terrainType; } - h & terrainType; h & terView; if (h.version >= Handler::Version::REMOVE_VLC_POINTERS) { @@ -159,7 +158,7 @@ struct DLL_LINKAGE TerrainTile { bool isNull = false; h & isNull; - if (isNull) + if (!isNull) h & riverType; } h & riverDir; @@ -171,7 +170,7 @@ struct DLL_LINKAGE TerrainTile { bool isNull = false; h & isNull; - if (isNull) + if (!isNull) h & roadType; } h & roadDir; From 733b1b339c4f0d42f9882fefbb68b304669eeb6f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 7 Nov 2024 12:08:28 +0000 Subject: [PATCH 539/726] Remove broken automatic addition of dependency for wog maps --- lib/mapping/MapFormatH3M.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 5340538f7..969e168b2 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -215,10 +215,6 @@ void CMapLoaderH3M::readHeader() reader->setIdentifierRemapper(identifierMapper); - // include basic mod - if(mapHeader->version == EMapFormat::WOG) - mapHeader->mods["wake-of-gods"]; - // Read map name, description, dimensions,... mapHeader->areAnyPlayers = reader->readBool(); mapHeader->height = mapHeader->width = reader->readInt32(); From 2786797a4eddff3a5245cb013d927ab33c86f4b0 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Thu, 7 Nov 2024 17:37:18 +0100 Subject: [PATCH 540/726] Fixed incompatibility with latest merge Incompatibility fix --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 2 +- lib/CCreatureSet.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index c740ecef1..a71d236db 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -903,7 +903,7 @@ public: break; } } - if(evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType && evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType->getId() != RoadId::NO_ROAD) + if(evaluationContext.evaluator.ai->cb->getTile(task->tile)->roadType != RoadId::NO_ROAD) evaluationContext.explorePriority = 1; if (evaluationContext.explorePriority == 0) evaluationContext.explorePriority = 3; diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 3316e9faf..590002df9 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -863,8 +863,8 @@ ui64 CStackInstance::getPower() const ui64 CStackInstance::getMarketValue() const { - assert(type); - return type->getFullRecruitCost().marketValue() * count; + assert(getType()); + return getType()->getFullRecruitCost().marketValue() * count; } ArtBearer::ArtBearer CStackInstance::bearerType() const From 79f57b36f365ba95126b8bccabc7ab936798dd40 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Thu, 7 Nov 2024 20:54:19 +0300 Subject: [PATCH 541/726] add Homebrew install option --- docs/players/Installation_macOS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/players/Installation_macOS.md b/docs/players/Installation_macOS.md index 8d767b079..23c3def95 100644 --- a/docs/players/Installation_macOS.md +++ b/docs/players/Installation_macOS.md @@ -1,15 +1,15 @@ # Installation macOS -For iOS installation look here: (Installation on iOS)[Installation_iOS.md] - ## Step 1: Download and install VCMI -- The latest release (recommended): +- The latest release (recommended): + - manually: + - via Homebrew: `brew install --cask --no-quarantine vcmi/vcmi/vcmi` - Daily builds (might be unstable) - - Intel (x86_64) builds: - - Apple Silicon (arm64) builds: + - Intel (x86_64) builds: + - Apple Silicon (arm64) builds: -If the app doesn't open, right-click the app bundle - select *Open* menu item - press *Open* button. +If the app doesn't open, right-click the app bundle - select *Open* menu item - press *Open* button. You may also need to allow running it in System Settings - Privacy & Security. Please report about gameplay problem on forums: [Help & Bugs](https://forum.vcmi.eu/c/international-board/help-bugs) Make sure to specify what hardware and macOS version you use. @@ -21,5 +21,5 @@ If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_mag ### Step 2.b: Installing by the classic way -1. Find a way to unpack Windows Heroes III or GOG installer. For example, use `vcmibuilder` script inside app bundle or install the game with [CrossOver](https://www.codeweavers.com/crossover) or [Wineskin](https://github.com/Gcenx/WineskinServer). +1. Find a way to unpack Windows Heroes III or GOG installer. For example, use `vcmibuilder` script inside app bundle or install the game with [CrossOver](https://www.codeweavers.com/crossover) or [Kegworks](https://github.com/Kegworks-App/Kegworks). 2. Place or symlink **Data**, **Maps** and **Mp3** directories from Heroes III to:`~/Library/Application\ Support/vcmi/` From 1f7cec3ae382e3a31d77b7af4263469f3b31cae7 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 8 Nov 2024 00:09:17 +0100 Subject: [PATCH 542/726] missing prescalefactor --- client/renderSDL/SDLImage.cpp | 12 ++++++------ client/renderSDL/SDLImage.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 194c9d561..d4236436f 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -102,12 +102,12 @@ SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group savePalette(); } -SDLImageShared::SDLImageShared(SDL_Surface * from) +SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor) : surf(nullptr), margins(0, 0), fullSize(0, 0), originalPalette(nullptr), - preScaleFactor(1) + preScaleFactor(preScaleFactor) { surf = from; if (surf == nullptr) @@ -285,7 +285,7 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet else scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor); - auto ret = std::make_shared(scaled); + auto ret = std::make_shared(scaled, preScaleFactor); ret->fullSize.x = fullSize.x * factor; ret->fullSize.y = fullSize.y * factor; @@ -320,7 +320,7 @@ std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Pa else CSDL_Ext::setDefaultColorKey(scaled);//just in case - auto ret = std::make_shared(scaled); + auto ret = std::make_shared(scaled, preScaleFactor); ret->fullSize.x = (int) round((float)fullSize.x * scaleX); ret->fullSize.y = (int) round((float)fullSize.y * scaleY); @@ -378,7 +378,7 @@ std::shared_ptr SDLImageShared::createImageReference(EImageBlitMode mode std::shared_ptr SDLImageShared::horizontalFlip() const { SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf); - auto ret = std::make_shared(flipped); + auto ret = std::make_shared(flipped, preScaleFactor); ret->fullSize = fullSize; ret->margins.x = margins.x; ret->margins.y = fullSize.y - surf->h - margins.y; @@ -390,7 +390,7 @@ std::shared_ptr SDLImageShared::horizontalFlip() const std::shared_ptr SDLImageShared::verticalFlip() const { SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf); - auto ret = std::make_shared(flipped); + auto ret = std::make_shared(flipped, preScaleFactor); ret->fullSize = fullSize; ret->margins.x = fullSize.x - surf->w - margins.x; ret->margins.y = margins.y; diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index c18c01b10..b5dcfb57a 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -49,7 +49,7 @@ public: //Load from bitmap file SDLImageShared(const ImagePath & filename, int preScaleFactor=1); //Create using existing surface, extraRef will increase refcount on SDL_Surface - SDLImageShared(SDL_Surface * from); + SDLImageShared(SDL_Surface * from, int preScaleFactor=1); ~SDLImageShared(); void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override; From 17dd8ffb48849befdf3cbdf38ed09a5130f79f3e Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sat, 9 Nov 2024 16:54:42 +0800 Subject: [PATCH 543/726] fix artifact sort by slot crash --- server/CGameHandler.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 373988702..7c47d88eb 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2772,7 +2772,28 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj { makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t { - return inf.getArt()->artType->getPossibleSlots().at(ArtBearer::HERO).front().num; + auto possibleSlots = inf.getArt()->artType->getPossibleSlots(); + if (possibleSlots.find(ArtBearer::ALTAR) != possibleSlots.end() && !possibleSlots.at(ArtBearer::ALTAR).empty()) + { + return -3; + } + else if (possibleSlots.find(ArtBearer::CREATURE) != possibleSlots.end() && !possibleSlots.at(ArtBearer::CREATURE).empty()) + { + return -2; + } + else if (possibleSlots.find(ArtBearer::COMMANDER) != possibleSlots.end() && !possibleSlots.at(ArtBearer::COMMANDER).empty()) + { + return -1; + } + else if (possibleSlots.find(ArtBearer::HERO) != possibleSlots.end() && !possibleSlots.at(ArtBearer::HERO).empty()) + { + return inf.getArt()->artType->getPossibleSlots().at(ArtBearer::HERO).front().num; + } + else + { + logGlobal->error("Unable to get artifact %s slot.", inf.getArt()->artType->getNameTextID()); + return -4; + } }); } else if(sortType == ManageBackpackArtifacts::ManageCmd::SORT_BY_COST) From 8d58066f6900361289a0d0c813279bdd562f190d Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sat, 9 Nov 2024 17:24:14 +0800 Subject: [PATCH 544/726] fix artifact sort by slot crash --- server/CGameHandler.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 7c47d88eb..696fd283b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2773,11 +2773,7 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t { auto possibleSlots = inf.getArt()->artType->getPossibleSlots(); - if (possibleSlots.find(ArtBearer::ALTAR) != possibleSlots.end() && !possibleSlots.at(ArtBearer::ALTAR).empty()) - { - return -3; - } - else if (possibleSlots.find(ArtBearer::CREATURE) != possibleSlots.end() && !possibleSlots.at(ArtBearer::CREATURE).empty()) + if (possibleSlots.find(ArtBearer::CREATURE) != possibleSlots.end() && !possibleSlots.at(ArtBearer::CREATURE).empty()) { return -2; } @@ -2792,7 +2788,7 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj else { logGlobal->error("Unable to get artifact %s slot.", inf.getArt()->artType->getNameTextID()); - return -4; + return -3; } }); } From e12b72a83919460576a2323cc6bd98a4ebc90ed1 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sat, 9 Nov 2024 17:37:06 +0800 Subject: [PATCH 545/726] merge changes --- server/CGameHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 65dc5b129..e035853e3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2771,7 +2771,7 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj { makeSortBackpackRequest([](const ArtSlotInfo & inf) -> int32_t { - auto possibleSlots = inf.getArt()->artType->getPossibleSlots(); + auto possibleSlots = inf.getArt()->getType()->getPossibleSlots(); if (possibleSlots.find(ArtBearer::CREATURE) != possibleSlots.end() && !possibleSlots.at(ArtBearer::CREATURE).empty()) { return -2; @@ -2786,7 +2786,7 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj } else { - logGlobal->error("Unable to get artifact %s slot.", inf.getArt()->artType->getNameTextID()); + logGlobal->error("Unable to get artifact %s slot.", inf.getArt()->getType()->getNameTextID()); return -3; } }); From ac31a946e66f9ab68e19ce641377a5290a4a87dc Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 9 Nov 2024 13:07:15 +0100 Subject: [PATCH 546/726] use path instead of different filename --- client/renderSDL/RenderHandler.cpp | 39 ++++++++++++++++-------------- client/renderSDL/RenderHandler.h | 2 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index efc2c89e0..f67264745 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -55,28 +55,28 @@ std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & return result; } -std::optional RenderHandler::getPath(ResourcePath path) +std::optional RenderHandler::getPath(ResourcePath path, std::string factor) { if(CResourceHandler::get()->existsResource(path)) return path; if(path.getType() == EResType::IMAGE) { auto p = ImagePath::builtin(path.getName()); - if(CResourceHandler::get()->existsResource(p.addPrefix("DATA/"))) - return std::optional(p.addPrefix("DATA/")); - if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES/"))) - return std::optional(p.addPrefix("SPRITES/")); + if(CResourceHandler::get()->existsResource(p.addPrefix("DATA" + factor + "X/"))) + return std::optional(p.addPrefix("DATA" + factor + "X/")); + if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/"))) + return std::optional(p.addPrefix("SPRITES" + factor + "X/")); } else { auto p = AnimationPath::builtin(path.getName()); auto pJson = p.toType(); - if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES/"))) - return std::optional(p.addPrefix("SPRITES/")); + if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/"))) + return std::optional(p.addPrefix("SPRITES" + factor + "X/")); if(CResourceHandler::get()->existsResource(pJson)) return std::optional(p); - if(CResourceHandler::get()->existsResource(pJson.addPrefix("SPRITES/"))) - return std::optional(p.addPrefix("SPRITES/")); + if(CResourceHandler::get()->existsResource(pJson.addPrefix("SPRITES" + factor + "X/"))) + return std::optional(p.addPrefix("SPRITES" + factor + "X/")); } return std::nullopt; @@ -85,19 +85,22 @@ std::optional RenderHandler::getPath(ResourcePath path) std::pair RenderHandler::getScalePath(ResourcePath p) { auto path = p; - auto name = p.getName(); int scaleFactor = 1; if(getScalingFactor() > 1) { std::vector factorsToCheck = {getScalingFactor(), 4, 3, 2}; for(auto factorToCheck : factorsToCheck) { - ResourcePath scaledPath = ImagePath::builtin(name + "$" + std::to_string(factorToCheck)); + std::string name = boost::algorithm::to_upper_copy(p.getName()); + boost::replace_all(name, "SPRITES/", std::string("SPRITES") + std::to_string(factorToCheck) + std::string("X/")); + boost::replace_all(name, "DATA/", std::string("DATA") + std::to_string(factorToCheck) + std::string("X/")); + ResourcePath scaledPath = ImagePath::builtin(name); if(p.getType() != EResType::IMAGE) - scaledPath = AnimationPath::builtin(name + "$" + std::to_string(factorToCheck)); - if(getPath(scaledPath)) + scaledPath = AnimationPath::builtin(name); + auto tmpPath = getPath(scaledPath, std::to_string(factorToCheck)); + if(tmpPath) { - path = scaledPath; + path = *tmpPath; scaleFactor = factorToCheck; break; } @@ -242,12 +245,12 @@ std::shared_ptr RenderHandler::loadImageFromFileUncached(const Ima { auto defFile = getAnimationFile(*locator.defFile); int preScaledFactor = locator.preScaledFactor; - if(!defFile) // no presscale for this frame + if(!defFile) // no prescale for this frame { auto tmpPath = (*locator.defFile).getName(); - boost::algorithm::replace_all(tmpPath, "$2", ""); - boost::algorithm::replace_all(tmpPath, "$3", ""); - boost::algorithm::replace_all(tmpPath, "$4", ""); + boost::algorithm::replace_all(tmpPath, "2X/", "/"); + boost::algorithm::replace_all(tmpPath, "3X/", "/"); + boost::algorithm::replace_all(tmpPath, "4X/", "/"); preScaledFactor = 1; defFile = getAnimationFile(AnimationPath::builtin(tmpPath)); } diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index e30ccbf4d..59f70561b 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -29,7 +29,7 @@ class RenderHandler : public IRenderHandler std::map> fonts; std::shared_ptr getAnimationFile(const AnimationPath & path); - std::optional getPath(ResourcePath path); + std::optional getPath(ResourcePath path, std::string factor); std::pair getScalePath(ResourcePath p); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path); void initFromJson(AnimationLayoutMap & layout, const JsonNode & config); From 35e198078b146cbf7f45a4627cbe2a980ba3411e Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 9 Nov 2024 14:02:09 +0100 Subject: [PATCH 547/726] cleanup logic --- client/renderSDL/RenderHandler.cpp | 10 ++++------ client/renderSDL/RenderHandler.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index f67264745..d28bf7cbf 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -55,17 +55,15 @@ std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & return result; } -std::optional RenderHandler::getPath(ResourcePath path, std::string factor) +std::optional RenderHandler::getPathForScaleFactor(ResourcePath path, std::string factor) { - if(CResourceHandler::get()->existsResource(path)) - return path; if(path.getType() == EResType::IMAGE) { auto p = ImagePath::builtin(path.getName()); - if(CResourceHandler::get()->existsResource(p.addPrefix("DATA" + factor + "X/"))) - return std::optional(p.addPrefix("DATA" + factor + "X/")); if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/"))) return std::optional(p.addPrefix("SPRITES" + factor + "X/")); + if(CResourceHandler::get()->existsResource(p.addPrefix("DATA" + factor + "X/"))) + return std::optional(p.addPrefix("DATA" + factor + "X/")); } else { @@ -97,7 +95,7 @@ std::pair RenderHandler::getScalePath(ResourcePath p) ResourcePath scaledPath = ImagePath::builtin(name); if(p.getType() != EResType::IMAGE) scaledPath = AnimationPath::builtin(name); - auto tmpPath = getPath(scaledPath, std::to_string(factorToCheck)); + auto tmpPath = getPathForScaleFactor(scaledPath, std::to_string(factorToCheck)); if(tmpPath) { path = *tmpPath; diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index 59f70561b..7014251ed 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -29,7 +29,7 @@ class RenderHandler : public IRenderHandler std::map> fonts; std::shared_ptr getAnimationFile(const AnimationPath & path); - std::optional getPath(ResourcePath path, std::string factor); + std::optional getPathForScaleFactor(ResourcePath path, std::string factor); std::pair getScalePath(ResourcePath p); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path); void initFromJson(AnimationLayoutMap & layout, const JsonNode & config); From 3872a3ea891d26ce807b2a1a4ccd238148f7b041 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 9 Nov 2024 17:58:23 +0100 Subject: [PATCH 548/726] fix fallback scaling --- client/renderSDL/RenderHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index d28bf7cbf..4c5e8477e 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -327,13 +327,13 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator & loc std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode) { ImageLocator loc = locator; - if(loc.defFile && loc.scalingFactor == 0 && loc.preScaledFactor == 1) + if(loc.defFile && loc.scalingFactor == 0) { auto tmp = getScalePath(*loc.defFile); loc.defFile = AnimationPath::builtin(tmp.first.getName()); loc.preScaledFactor = tmp.second; } - if(loc.image && loc.scalingFactor == 0 && loc.preScaledFactor == 1) + if(loc.image && loc.scalingFactor == 0) { auto tmp = getScalePath(*loc.image); loc.image = ImagePath::builtin(tmp.first.getName()); From 324fd5716901cdf34259fb8463e49a6ec2e44d8c Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 3 Nov 2024 18:42:02 +0200 Subject: [PATCH 549/726] Reorder creature abilities to be more logical --- config/creatures/castle.json | 28 +- config/creatures/conflux.json | 1220 +++++++++++++++--------------- config/creatures/fortress.json | 16 +- config/creatures/inferno.json | 126 +-- config/creatures/necropolis.json | 12 +- config/creatures/neutral.json | 77 +- config/creatures/rampart.json | 20 +- config/creatures/stronghold.json | 12 +- config/creatures/tower.json | 48 +- 9 files changed, 779 insertions(+), 780 deletions(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index e45c185db..f610c93c7 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -295,6 +295,10 @@ "faction": "castle", "abilities": { + "const_raises_morale" : + { + "stacking" : "Angels" + }, "hateDevils" : { "type" : "HATE", @@ -306,10 +310,6 @@ "type" : "HATE", "subtype" : "creature.archDevil", "val" : 50 - }, - "const_raises_morale" : - { - "stacking" : "Angels" } }, "upgrades": ["archangel"], @@ -333,23 +333,27 @@ "faction": "castle", "abilities": { - "resurrection100hp" : - { - "type" : "SPECIFIC_SPELL_POWER", - "subtype" : "spell.resurrection", - "val" : 100 - }, "resurrects" : { "type" : "SPELLCASTER", "subtype" : "spell.resurrection", "val" : 3 }, + "resurrection100hp" : + { + "type" : "SPECIFIC_SPELL_POWER", + "subtype" : "spell.resurrection", + "val" : 100 + }, "spellpoints" : { "type" : "CASTS", "val" : 1 }, + "const_raises_morale" : + { + "stacking" : "Angels" + }, "hateDevils" : { "type" : "HATE", @@ -361,10 +365,6 @@ "type" : "HATE", "subtype" : "creature.archDevil", "val" : 50 - }, - "const_raises_morale" : - { - "stacking" : "Angels" } }, "graphics" : diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index b7cc45ffe..37d85c0e3 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -1,277 +1,4 @@ { - "airElemental" : - { - "index": 112, - "level": 2, - "extraNames": [ "airElementals" ], - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" - }, - "meteorShowerImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.meteorShower" - }, - "lightingVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.lightningBolt", - "val" : 100 - }, - "chainLightingVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.chainLightning", - "val" : 100 - }, - "armageddonVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.armageddon", - "val" : 100 - }, - "oppositeEarth" : - { - "type" : "HATE", - "subtype" : "creature.earthElemental", - "val" : 100 - }, - "oppositeMagma" : - { - "type" : "HATE", - "subtype" : "creature.magmaElemental", - "val" : 100 - } - - }, - "upgrades": ["stormElemental"], - "graphics" : - { - "animation": "CAELEM.DEF" - }, - "sound" : - { - "attack": "AELMATTK.wav", - "defend": "AELMDFND.wav", - "killed": "AELMKILL.wav", - "move": "AELMMOVE.wav", - "wince": "AELMWNCE.wav" - } - }, - "earthElemental" : - { - "index": 113, - "level": 5, - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" - }, - "meteorShowerVulnerability" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.meteorShower", - "val" : 100 - }, - "lightingImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.lightningBolt" - }, - "chainLightingImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.chainLightning" - }, - "armageddonImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.armageddon" - }, - "oppositeAir" : - { - "type" : "HATE", - "subtype" : "creature.airElemental", - "val" : 100 - }, - "oppositeStorm" : - { - "type" : "HATE", - "subtype" : "creature.stormElemental", - "val" : 100 - } - }, - "upgrades": ["magmaElemental"], - "graphics" : - { - "animation": "CEELEM.DEF" - }, - "sound" : - { - "attack": "EELMATTK.wav", - "defend": "EELMDFND.wav", - "killed": "EELMKILL.wav", - "move": "EELMMOVE.wav", - "wince": "EELMWNCE.wav" - } - }, - "fireElemental" : - { - "index": 114, - "level": 4, - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" - }, - "immuneToFire" : - { - "type" : "SPELL_SCHOOL_IMMUNITY", - "subtype" : "spellSchool.fire" - }, - "frostRingVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.frostRing", - "val" : 100 - }, - "iceBoltVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.iceBolt", - "val" : 100 - }, - "oppositeWater" : - { - "type" : "HATE", - "subtype" : "creature.waterElemental", - "val" : 100 - }, - "oppositeIce" : - { - "type" : "HATE", - "subtype" : "creature.iceElemental", - "val" : 100 - } - }, - "upgrades": ["energyElemental"], - "graphics" : - { - "animation": "CFELEM.DEF" - }, - "sound" : - { - "attack": "FELMATTK.wav", - "defend": "FELMDFND.wav", - "killed": "FELMKILL.wav", - "move": "FELMMOVE.wav", - "wince": "FELMWNCE.wav" - } - }, - "waterElemental" : - { - "index": 115, - "level": 3, - "extraNames": [ "waterElementals" ], - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" - }, - "fireShieldVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.fireShield", - "val" : 100 - }, - "infernoVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.inferno", - "val" : 100 - }, - "fireballVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.fireball", - "val" : 100 - }, - "fireWallVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.fireWallTrigger", - "val" : 100 - }, - "armageddonVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.armageddon", - "val" : 100 - }, - "immuneToIceBolt" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.iceBolt" - }, - "immuneToFrostRing" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.frostRing" - }, - "oppositeFire" : - { - "type" : "HATE", - "subtype" : "creature.fireElemental", - "val" : 100 - }, - "oppositeEnergy" : - { - "type" : "HATE", - "subtype" : "creature.energyElemental", - "val" : 100 - } - }, - "doubleWide" : true, - "upgrades": ["iceElemental"], - "graphics" : - { - "animation": "CWELEM.DEF" - }, - "sound" : - { - "attack": "WELMATTK.wav", - "defend": "WELMDFND.wav", - "killed": "WELMKILL.wav", - "move": "WELMMOVE.wav", - "wince": "WELMWNCE.wav" - } - }, "pixie" : { "index": 118, @@ -310,6 +37,611 @@ "wince": "SPRTWNCE.wav" } }, + "airElemental" : + { + "index": 112, + "level": 2, + "extraNames": [ "airElementals" ], + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "lightingVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.lightningBolt", + "val" : 100 + }, + "chainLightingVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.chainLightning", + "val" : 100 + }, + "meteorShowerImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.meteorShower" + }, + "armageddonVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.armageddon", + "val" : 100 + }, + "oppositeEarth" : + { + "type" : "HATE", + "subtype" : "creature.earthElemental", + "val" : 100 + }, + "oppositeMagma" : + { + "type" : "HATE", + "subtype" : "creature.magmaElemental", + "val" : 100 + } + + }, + "upgrades": ["stormElemental"], + "graphics" : + { + "animation": "CAELEM.DEF" + }, + "sound" : + { + "attack": "AELMATTK.wav", + "defend": "AELMDFND.wav", + "killed": "AELMKILL.wav", + "move": "AELMMOVE.wav", + "wince": "AELMWNCE.wav" + } + }, + "stormElemental" : + { + "index": 127, + "level": 2, + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "spellcaster": + { + "type" : "SPELLCASTER", + "subtype" : "spell.protectAir", + "val" : 2 + }, + "spellPower" : + { + "type" : "CREATURE_ENCHANT_POWER", + "val" : 6 + }, + "spellPoints" : + { + "type" : "CASTS", + "val" : 3 + }, + "lightingVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.lightningBolt", + "val" : 100 + }, + "chainLightingVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.chainLightning", + "val" : 100 + }, + "meteorShowerImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.meteorShower" + }, + "armageddonVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.armageddon", + "val" : 100 + }, + "oppositeEarth" : + { + "type" : "HATE", + "subtype" : "creature.earthElemental", + "val" : 100 + }, + "oppositeMagma" : + { + "type" : "HATE", + "subtype" : "creature.magmaElemental", + "val" : 100 + } + }, + "graphics" : + { + "animation": "CSTORM.DEF", + "missile" : + { + "projectile": "CPRGTIX.DEF" + } + }, + "sound" : + { + "attack": "STORATTK.wav", + "defend": "STORDFND.wav", + "killed": "STORKILL.wav", + "move": "STORMOVE.wav", + "shoot": "STORSHOT.wav", + "wince": "STORWNCE.wav" + } + }, + "waterElemental" : + { + "index": 115, + "level": 3, + "extraNames": [ "waterElementals" ], + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "immuneToIceBolt" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.iceBolt" + }, + "immuneToFrostRing" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.frostRing" + }, + "fireballVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.fireball", + "val" : 100 + }, + "infernoVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.inferno", + "val" : 100 + }, + "armageddonVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.armageddon", + "val" : 100 + }, + "fireShieldVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.fireShield", + "val" : 100 + }, + "fireWallVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.fireWallTrigger", + "val" : 100 + }, + "oppositeFire" : + { + "type" : "HATE", + "subtype" : "creature.fireElemental", + "val" : 100 + }, + "oppositeEnergy" : + { + "type" : "HATE", + "subtype" : "creature.energyElemental", + "val" : 100 + } + }, + "doubleWide" : true, + "upgrades": ["iceElemental"], + "graphics" : + { + "animation": "CWELEM.DEF" + }, + "sound" : + { + "attack": "WELMATTK.wav", + "defend": "WELMDFND.wav", + "killed": "WELMKILL.wav", + "move": "WELMMOVE.wav", + "wince": "WELMWNCE.wav" + } + }, + "iceElemental" : + { + "index": 123, + "level": 3, + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "spellcaster": + { + "type" : "SPELLCASTER", + "subtype" : "spell.protectWater", + "val" : 2 + }, + "spellPower" : + { + "type" : "CREATURE_ENCHANT_POWER", + "val" : 6 + }, + "spellPoints" : + { + "type" : "CASTS", + "val" : 3 + }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "immuneToIceBolt" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.iceBolt" + }, + "immuneToFrostRing" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.frostRing" + }, + "fireballVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.fireball", + "val" : 100 + }, + "infernoVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.inferno", + "val" : 100 + }, + "armageddonVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.armageddon", + "val" : 100 + }, + "fireShieldVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.fireShield", + "val" : 100 + }, + "fireWallVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.fireWallTrigger", + "val" : 100 + }, + "oppositeFire" : + { + "type" : "HATE", + "subtype" : "creature.fireElemental", + "val" : 100 + }, + "oppositeEnergy" : + { + "type" : "HATE", + "subtype" : "creature.energyElemental", + "val" : 100 + } + }, + "doubleWide" : true, + "graphics" : + { + "animation": "CICEE.DEF", + "missile" : + { + "projectile": "PICEE.DEF" + } + }, + "sound" : + { + "attack": "ICELATTK.wav", + "defend": "ICELDFND.wav", + "killed": "ICELKILL.wav", + "move": "ICELMOVE.wav", + "shoot": "ICELSHOT.wav", + "wince": "ICELWNCE.wav" + } + }, + "fireElemental" : + { + "index": 114, + "level": 4, + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "immuneToFire" : + { + "type" : "SPELL_SCHOOL_IMMUNITY", + "subtype" : "spellSchool.fire" + }, + "iceBoltVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.iceBolt", + "val" : 100 + }, + "frostRingVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.frostRing", + "val" : 100 + }, + "oppositeWater" : + { + "type" : "HATE", + "subtype" : "creature.waterElemental", + "val" : 100 + }, + "oppositeIce" : + { + "type" : "HATE", + "subtype" : "creature.iceElemental", + "val" : 100 + } + }, + "upgrades": ["energyElemental"], + "graphics" : + { + "animation": "CFELEM.DEF" + }, + "sound" : + { + "attack": "FELMATTK.wav", + "defend": "FELMDFND.wav", + "killed": "FELMKILL.wav", + "move": "FELMMOVE.wav", + "wince": "FELMWNCE.wav" + } + }, + "energyElemental" : + { + "index": 129, + "level": 4, + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "spellcaster": + { + "type" : "SPELLCASTER", + "subtype" : "spell.protectFire", + "val" : 2 + }, + "spellPower" : + { + "type" : "CREATURE_ENCHANT_POWER", + "val" : 6 + }, + "spellPoints" : + { + "type" : "CASTS", + "val" : 3 + }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "immuneToFire" : + { + "type" : "SPELL_SCHOOL_IMMUNITY", + "subtype" : "spellSchool.fire" + }, + "iceBoltVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.iceBolt", + "val" : 100 + }, + "frostRingVulnerablity" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.frostRing", + "val" : 100 + }, + "oppositeWater" : + { + "type" : "HATE", + "subtype" : "creature.waterElemental", + "val" : 100 + }, + "oppositeIce" : + { + "type" : "HATE", + "subtype" : "creature.iceElemental", + "val" : 100 + } + }, + "graphics" : + { + "animation": "CNRG.DEF" + }, + "sound" : + { + "attack": "ENERATTK.wav", + "defend": "ENERDFND.wav", + "killed": "ENERKILL.wav", + "move": "ENERMOVE.wav", + "wince": "ENERWNCE.wav" + } + }, + "earthElemental" : + { + "index": 113, + "level": 5, + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "lightingImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.lightningBolt" + }, + "chainLightingImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.chainLightning" + }, + "armageddonImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.armageddon" + }, + "meteorShowerVulnerability" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.meteorShower", + "val" : 100 + }, + "oppositeAir" : + { + "type" : "HATE", + "subtype" : "creature.airElemental", + "val" : 100 + }, + "oppositeStorm" : + { + "type" : "HATE", + "subtype" : "creature.stormElemental", + "val" : 100 + } + }, + "upgrades": ["magmaElemental"], + "graphics" : + { + "animation": "CEELEM.DEF" + }, + "sound" : + { + "attack": "EELMATTK.wav", + "defend": "EELMDFND.wav", + "killed": "EELMKILL.wav", + "move": "EELMMOVE.wav", + "wince": "EELMWNCE.wav" + } + }, + "magmaElemental" : + { + "index": 125, + "level": 5, + "faction": "conflux", + "abilities": + { + "nonLiving" : + { + "type" : "NON_LIVING" + }, + "spellcaster": + { + "type" : "SPELLCASTER", + "subtype" : "spell.protectEarth", + "val" : 2 + }, + "spellPower" : + { + "type" : "CREATURE_ENCHANT_POWER", + "val" : 6 + }, + "spellPoints" : + { + "type" : "CASTS", + "val" : 3 + }, + "lightingImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.lightningBolt" + }, + "chainLightingImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.chainLightning" + }, + "armageddonImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.armageddon" + }, + "meteorShowerVulnerability" : + { + "type" : "MORE_DAMAGE_FROM_SPELL", + "subtype" : "spell.meteorShower", + "val" : 100 + }, + "oppositeAir" : + { + "type" : "HATE", + "subtype" : "creature.airElemental", + "val" : 100 + }, + "oppositeStorm" : + { + "type" : "HATE", + "subtype" : "creature.stormElemental", + "val" : 100 + } + }, + "graphics" : + { + "animation": "CSTONE.DEF" + }, + "sound" : + { + "attack": "MAGMATTK.wav", + "defend": "MAGMDFND.wav", + "killed": "MAGMKILL.wav", + "move": "MAGMMOVE.wav", + "wince": "MAGMWNCE.wav" + } + }, "psychicElemental" : { "index": 120, @@ -368,338 +700,6 @@ "wince": "MGELWNCE.wav" } }, - "iceElemental" : - { - "index": 123, - "level": 3, - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "spellPower" : - { - "type" : "CREATURE_ENCHANT_POWER", - "val" : 6 - }, - "spellPoints" : - { - "type" : "CASTS", - "val" : 3 - }, - "spellcaster": - { - "type" : "SPELLCASTER", - "subtype" : "spell.protectWater", - "val" : 2 - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" - }, - "fireShieldVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.fireShield", - "val" : 100 - }, - "infernoVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.inferno", - "val" : 100 - }, - "fireballVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.fireball", - "val" : 100 - }, - "fireWallVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.fireWallTrigger", - "val" : 100 - }, - "armageddonVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.armageddon", - "val" : 100 - }, - "immuneToIceBolt" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.iceBolt" - }, - "immuneToFrostRing" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.frostRing" - }, - "oppositeFire" : - { - "type" : "HATE", - "subtype" : "creature.fireElemental", - "val" : 100 - }, - "oppositeEnergy" : - { - "type" : "HATE", - "subtype" : "creature.energyElemental", - "val" : 100 - } - }, - "doubleWide" : true, - "graphics" : - { - "animation": "CICEE.DEF", - "missile" : - { - "projectile": "PICEE.DEF" - } - }, - "sound" : - { - "attack": "ICELATTK.wav", - "defend": "ICELDFND.wav", - "killed": "ICELKILL.wav", - "move": "ICELMOVE.wav", - "shoot": "ICELSHOT.wav", - "wince": "ICELWNCE.wav" - } - }, - "magmaElemental" : - { - "index": 125, - "level": 5, - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "spellPower" : - { - "type" : "CREATURE_ENCHANT_POWER", - "val" : 6 - }, - "spellPoints" : - { - "type" : "CASTS", - "val" : 3 - }, - "spellcaster": - { - "type" : "SPELLCASTER", - "subtype" : "spell.protectEarth", - "val" : 2 - }, - "meteorShowerVulnerability" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.meteorShower", - "val" : 100 - }, - "lightingImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.lightningBolt" - }, - "chainLightingImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.chainLightning" - }, - "armageddonImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.armageddon" - }, - "oppositeAir" : - { - "type" : "HATE", - "subtype" : "creature.airElemental", - "val" : 100 - }, - "oppositeStorm" : - { - "type" : "HATE", - "subtype" : "creature.stormElemental", - "val" : 100 - } - }, - "graphics" : - { - "animation": "CSTONE.DEF" - }, - "sound" : - { - "attack": "MAGMATTK.wav", - "defend": "MAGMDFND.wav", - "killed": "MAGMKILL.wav", - "move": "MAGMMOVE.wav", - "wince": "MAGMWNCE.wav" - } - }, - "stormElemental" : - { - "index": 127, - "level": 2, - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "spellPower" : - { - "type" : "CREATURE_ENCHANT_POWER", - "val" : 6 - }, - "spellPoints" : - { - "type" : "CASTS", - "val" : 3 - }, - "spellcaster": - { - "type" : "SPELLCASTER", - "subtype" : "spell.protectAir", - "val" : 2 - }, - "meteorShowerImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.meteorShower" - }, - "lightingVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.lightningBolt", - "val" : 100 - }, - "chainLightingVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.chainLightning", - "val" : 100 - }, - "armageddonVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.armageddon", - "val" : 100 - }, - "oppositeEarth" : - { - "type" : "HATE", - "subtype" : "creature.earthElemental", - "val" : 100 - }, - "oppositeMagma" : - { - "type" : "HATE", - "subtype" : "creature.magmaElemental", - "val" : 100 - } - }, - "graphics" : - { - "animation": "CSTORM.DEF", - "missile" : - { - "projectile": "CPRGTIX.DEF" - } - }, - "sound" : - { - "attack": "STORATTK.wav", - "defend": "STORDFND.wav", - "killed": "STORKILL.wav", - "move": "STORMOVE.wav", - "shoot": "STORSHOT.wav", - "wince": "STORWNCE.wav" - } - }, - "energyElemental" : - { - "index": 129, - "level": 4, - "faction": "conflux", - "abilities": - { - "nonLiving" : - { - "type" : "NON_LIVING" - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" - }, - "immuneToFire" : - { - "type" : "SPELL_SCHOOL_IMMUNITY", - "subtype" : "spellSchool.fire" - }, - "frostRingVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.frostRing", - "val" : 100 - }, - "iceBoltVulnerablity" : - { - "type" : "MORE_DAMAGE_FROM_SPELL", - "subtype" : "spell.iceBolt", - "val" : 100 - }, - "spellPower" : - { - "type" : "CREATURE_ENCHANT_POWER", - "val" : 6 - }, - "spellPoints" : - { - "type" : "CASTS", - "val" : 3 - }, - "spellcaster": - { - "type" : "SPELLCASTER", - "subtype" : "spell.protectFire", - "val" : 2 - }, - "oppositeWater" : - { - "type" : "HATE", - "subtype" : "creature.waterElemental", - "val" : 100 - }, - "oppositeIce" : - { - "type" : "HATE", - "subtype" : "creature.iceElemental", - "val" : 100 - } - }, - "graphics" : - { - "animation": "CNRG.DEF" - }, - "sound" : - { - "attack": "ENERATTK.wav", - "defend": "ENERDFND.wav", - "killed": "ENERKILL.wav", - "move": "ENERMOVE.wav", - "wince": "ENERWNCE.wav" - } - }, "firebird" : { "index": 130, @@ -734,16 +734,16 @@ "faction": "conflux", "abilities": { - "rebirthOnce" : - { - "type" : "CASTS", - "val" : 1 - }, "immuneToFire" : { "type" : "SPELL_SCHOOL_IMMUNITY", "subtype" : "spellSchool.fire" }, + "rebirthOnce" : + { + "type" : "CASTS", + "val" : 1 + }, "rebirth" : { "type" : "REBIRTH", diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index 1b3169052..aa17655f8 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -303,13 +303,13 @@ "faction": "fortress", "abilities": { - "noRetaliate" : - { - "type" : "BLOCKS_RETALIATION" - }, "attackAll" : { "type" : "ATTACKS_ALL_ADJACENT" + }, + "noRetaliate" : + { + "type" : "BLOCKS_RETALIATION" } }, "upgrades": ["chaosHydra"], @@ -333,13 +333,13 @@ "faction": "fortress", "abilities": { - "noRetaliate" : - { - "type" : "BLOCKS_RETALIATION" - }, "attackAll" : { "type" : "ATTACKS_ALL_ADJACENT" + }, + "noRetaliate" : + { + "type" : "BLOCKS_RETALIATION" } }, "graphics" : diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index 204432246..37189d570 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -130,14 +130,14 @@ "faction": "inferno", "abilities": { - "threeHeads" : - { - "type" : "THREE_HEADED_ATTACK" - }, "noRetaliation" : { "type" : "BLOCKS_RETALIATION" }, + "threeHeads" : + { + "type" : "THREE_HEADED_ATTACK" + }, "FLYING_ARMY" : null //cerberus doesn't fly }, "graphics" : @@ -254,6 +254,15 @@ "faction": "inferno", "abilities": { + "canFly" : + { + "type" : "FLYING" + }, + "immuneToFire" : + { + "type" : "SPELL_SCHOOL_IMMUNITY", + "subtype" : "spellSchool.fire" + }, "hateGenies" : { "type" : "HATE", @@ -265,15 +274,6 @@ "type" : "HATE", "subtype" : "creature.masterGenie", "val" : 50 - }, - "canFly" : - { - "type" : "FLYING" - }, - "immuneToFire" : - { - "type" : "SPELL_SCHOOL_IMMUNITY", - "subtype" : "spellSchool.fire" } }, "upgrades": ["efreetSultan"], @@ -297,6 +297,20 @@ "faction": "inferno", "abilities": { + "canFly" : + { + "type" : "FLYING" + }, + "fireShield" : + { + "type" : "FIRE_SHIELD", + "val" : 20 + }, + "immuneToFire" : + { + "type" : "SPELL_SCHOOL_IMMUNITY", + "subtype" : "spellSchool.fire" + }, "hateGenies" : { "type" : "HATE", @@ -308,20 +322,6 @@ "type" : "HATE", "subtype" : "creature.masterGenie", "val" : 50 - }, - "canFly" : - { - "type" : "FLYING" - }, - "immuneToFire" : - { - "type" : "SPELL_SCHOOL_IMMUNITY", - "subtype" : "spellSchool.fire" - }, - "fireShield" : - { - "type" : "FIRE_SHIELD", - "val" : 20 } }, "graphics" : @@ -345,6 +345,24 @@ "faction": "inferno", "abilities": { + "FLYING_ARMY" : + { + // type loaded from crtraits + "subtype" : "movementTeleporting" + }, + "blockRetaliation" : + { + "type" : "BLOCKS_RETALIATION" + }, + "descreaseLuck" : + { + "type" : "LUCK", + "val" : -1, + "stacking" : "Devils", + "propagator": "BATTLE_WIDE", + "propagationUpdater" : "BONUS_OWNER_UPDATER", + "limiters" : [ "OPPOSITE_SIDE" ] + }, "hateAngels" : { "type" : "HATE", @@ -357,24 +375,6 @@ "subtype" : "creature.archangel", "val" : 50, "description" : "Devil -1" - }, - "FLYING_ARMY" : - { - // type loaded from crtraits - "subtype" : "movementTeleporting" - }, - "descreaseLuck" : - { - "type" : "LUCK", - "val" : -1, - "stacking" : "Devils", - "propagator": "BATTLE_WIDE", - "propagationUpdater" : "BONUS_OWNER_UPDATER", - "limiters" : [ "OPPOSITE_SIDE" ] - }, - "blockRetaliation" : - { - "type" : "BLOCKS_RETALIATION" } }, "upgrades": ["archDevil"], @@ -400,6 +400,24 @@ "faction": "inferno", "abilities" : { + "FLYING_ARMY" : + { + // type loaded from crtraits + "subtype" : "movementTeleporting" + }, + "blockRetaliation" : + { + "type" : "BLOCKS_RETALIATION" + }, + "descreaseLuck" : + { + "type" : "LUCK", + "val" : -1, + "stacking" : "Devils", + "propagator": "BATTLE_WIDE", + "propagationUpdater" : "BONUS_OWNER_UPDATER", + "limiters" : [ "OPPOSITE_SIDE" ] + }, "hateAngels" : { "type" : "HATE", @@ -411,24 +429,6 @@ "type" : "HATE", "subtype" : "creature.archangel", "val" : 50 - }, - "FLYING_ARMY" : - { - // type loaded from crtraits - "subtype" : "movementTeleporting" - }, - "descreaseLuck" : - { - "type" : "LUCK", - "val" : -1, - "stacking" : "Devils", - "propagator": "BATTLE_WIDE", - "propagationUpdater" : "BONUS_OWNER_UPDATER", - "limiters" : [ "OPPOSITE_SIDE" ] - }, - "blockRetaliation" : - { - "type" : "BLOCKS_RETALIATION" } }, "graphics" : diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index 54f1e8cec..5202f6d87 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -372,6 +372,12 @@ { "type" : "DRAGON_NATURE" }, + "age" : + { + "type" : "SPELL_AFTER_ATTACK", + "subtype" : "spell.age", + "val" : 20 + }, "decreaseMorale" : { "type" : "MORALE", @@ -380,12 +386,6 @@ "propagator": "BATTLE_WIDE", "propagationUpdater" : "BONUS_OWNER_UPDATER", "limiters" : [ "OPPOSITE_SIDE" ] - }, - "age" : - { - "type" : "SPELL_AFTER_ATTACK", - "subtype" : "spell.age", - "val" : 20 } }, "graphics" : diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 8977afa7d..06c639bc8 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -7,15 +7,15 @@ "faction": "neutral", "abilities": { + "nonliving" : + { + "type" : "NON_LIVING" + }, "magicResistance" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : "spellSchool.any", "val" : 85 - }, - "nonliving" : - { - "type" : "NON_LIVING" } }, "graphics" : @@ -38,15 +38,15 @@ "faction": "neutral", "abilities": { + "nonliving" : + { + "type" : "NON_LIVING" + }, "magicResistance" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : "spellSchool.any", "val" : 95 - }, - "nonliving" : - { - "type" : "NON_LIVING" } }, "graphics" : @@ -78,6 +78,10 @@ { "type" : "TWO_HEX_ATTACK_BREATH" }, + "fear" : + { + "type" : "FEAR" + }, "spellImmunity" : { "type" : "LEVEL_SPELL_IMMUNITY", @@ -86,10 +90,6 @@ "fearless" : { "type" : "FEARLESS" - }, - "fear" : - { - "type" : "FEAR" } }, "graphics" : @@ -251,16 +251,16 @@ { "type" : "DRAGON_NATURE" }, + "fireBreath" : + { + "type" : "TWO_HEX_ATTACK_BREATH" + }, "acidBreath" : { "type" : "ACID_BREATH", "val" : 25, "addInfo" : 30 }, - "fireBreath" : - { - "type" : "TWO_HEX_ATTACK_BREATH" - }, "reduceDefence" : { "type" : "SPELL_AFTER_ATTACK", @@ -299,10 +299,24 @@ "type" : "CASTS", "val" : 5 }, - "castsAirShield" : + "castsHaste" : { "type" : "ENCHANTER", - "subtype" : "spell.airShield", + "subtype" : "spell.haste", + "val" : 3, + "addInfo" : 3 + }, + "castsSlow" : + { + "type" : "ENCHANTER", + "subtype" : "spell.slow", + "val" : 3, + "addInfo" : 3 + }, + "castsStoneSkin" : + { + "type" : "ENCHANTER", + "subtype" : "spell.stoneSkin", "val" : 3, "addInfo" : 3 }, @@ -320,28 +334,13 @@ "val" : 3, "addInfo" : 3 }, - "castsStoneSkin" : + "castsAirShield" : { "type" : "ENCHANTER", - "subtype" : "spell.stoneSkin", - "val" : 3, - "addInfo" : 3 - }, - "castsSlow" : - { - "type" : "ENCHANTER", - "subtype" : "spell.slow", - "val" : 3, - "addInfo" : 3 - }, - "castsHaste" : - { - "type" : "ENCHANTER", - "subtype" : "spell.haste", + "subtype" : "spell.airShield", "val" : 3, "addInfo" : 3 } - }, "graphics" : { @@ -370,13 +369,13 @@ "excludeFromRandomization" : true, "abilities": { - "noPenalty" : - { - "type" : "NO_WALL_PENALTY" - }, "noDistancePenalty" : { "type" : "NO_DISTANCE_PENALTY" + }, + "noPenalty" : + { + "type" : "NO_WALL_PENALTY" } }, "graphics" : diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index 157fe24ae..8fead5c52 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -265,17 +265,17 @@ "faction": "rampart", "abilities": { - "spellResistAura" : - { - "type" : "SPELL_RESISTANCE_AURA", - "val" : 20 - }, "blinds" : { "type" : "SPELL_AFTER_ATTACK", "subtype" : "spell.blind", "val" : 20, "addInfo" : [3,0] + }, + "spellResistAura" : + { + "type" : "SPELL_RESISTANCE_AURA", + "val" : 20 } }, "upgrades": ["warUnicorn"], @@ -299,17 +299,17 @@ "faction": "rampart", "abilities": { - "spellResistAura" : - { - "type" : "SPELL_RESISTANCE_AURA", - "val" : 20 - }, "blinds" : { "type" : "SPELL_AFTER_ATTACK", "subtype" : "spell.blind", "val" : 20, "addInfo" : [3,0] + }, + "spellResistAura" : + { + "type" : "SPELL_RESISTANCE_AURA", + "val" : 20 } }, "graphics" : diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index bbdb2e110..b8b8614ca 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -214,17 +214,17 @@ "faction": "stronghold", "abilities": { - "thunderStrength" : - { - "type" : "SPECIFIC_SPELL_POWER", - "subtype" : "spell.thunderbolt", - "val" : 10 - }, "thunderOnAttack" : { "type" : "SPELL_AFTER_ATTACK", "subtype" : "spell.thunderbolt", "val" : 20 + }, + "thunderStrength" : + { + "type" : "SPECIFIC_SPELL_POWER", + "subtype" : "spell.thunderbolt", + "val" : 10 } }, "graphics" : diff --git a/config/creatures/tower.json b/config/creatures/tower.json index 2c5c10f7c..3863187d4 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -102,15 +102,15 @@ "faction": "tower", "abilities": { + "nonliving" : + { + "type" : "NON_LIVING" + }, "magicResistance" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : "spellSchool.any", "val" : 50 - }, - "nonliving" : - { - "type" : "NON_LIVING" } }, "upgrades": ["stoneGolem"], @@ -134,15 +134,15 @@ "faction": "tower", "abilities" : { + "nonliving" : + { + "type" : "NON_LIVING" + }, "magicResistance" : { "type" : "SPELL_DAMAGE_REDUCTION", "subtype" : "spellSchool.any", "val" : 75 - }, - "nonliving" : - { - "type" : "NON_LIVING" } }, "graphics" : @@ -236,13 +236,13 @@ "faction": "tower", "abilities": { - "hateAngels" : + "hateEfreet" : { "type" : "HATE", "subtype" : "creature.efreet", "val" : 50 }, - "hateArchAngels" : + "hateEfreetSultans" : { "type" : "HATE", "subtype" : "creature.efreetSultan", @@ -270,17 +270,10 @@ "faction": "tower", "abilities": { - "hateAngels" : + "casts" : { - "type" : "HATE", - "subtype" : "creature.efreet", - "val" : 50 - }, - "hateArchAngels" : - { - "type" : "HATE", - "subtype" : "creature.efreetSultan", - "val" : 50 + "type" : "CASTS", + "val" : 3 }, "spellsLength" : { @@ -292,10 +285,17 @@ "type" : "RANDOM_SPELLCASTER", "val" : 2 }, - "casts" : + "hateEfreet" : { - "type" : "CASTS", - "val" : 3 + "type" : "HATE", + "subtype" : "creature.efreet", + "val" : 50 + }, + "hateEfreetSultans" : + { + "type" : "HATE", + "subtype" : "creature.efreetSultan", + "val" : 50 } }, "graphics" : @@ -400,7 +400,7 @@ { "type" : "MIND_IMMUNITY" }, - "hateArchAngels" : + "hateBlackDragons" : { "type" : "HATE", "subtype" : "creature.blackDragon", From 620ffe68f553e501460cb7a81b73764c896e3cd4 Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 3 Nov 2024 19:22:07 +0200 Subject: [PATCH 550/726] Add in creatures json configs all abilities that were implicit --- config/creatures/castle.json | 72 ++++++++++++++++++++- config/creatures/conflux.json | 101 ++++++++++++++++++++++++++--- config/creatures/dungeon.json | 75 ++++++++++++++++++++++ config/creatures/fortress.json | 45 ++++++++++++- config/creatures/inferno.json | 21 ++++++ config/creatures/necropolis.json | 107 +++++++++++++++++++++++++++++++ config/creatures/neutral.json | 58 +++++++++++++++-- config/creatures/rampart.json | 37 +++++++++++ config/creatures/stronghold.json | 43 +++++++++++++ config/creatures/tower.json | 61 ++++++++++++++++++ 10 files changed, 599 insertions(+), 21 deletions(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index f610c93c7..2bb5e8c07 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -57,6 +57,12 @@ "extraNames": [ "lightCrossbowman" ], "faction": "castle", "upgrades": ["marksman"], + "abilities" : + { + "SHOOTING_ARMY" : { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CLCBOW.DEF", @@ -80,7 +86,11 @@ "index": 3, "level": 2, "faction": "castle", - "abilities": { + "abilities": + { + "SHOOTING_ARMY" : { + "type" : "SHOOTER" + }, "extraAttack" : { "type": "ADDITIONAL_ATTACK", @@ -113,6 +123,9 @@ "faction": "castle", "abilities": { + "FLYING_ARMY" : { + "type" : "FLYING" + }, "extraRetaliation" : { "type" : "ADDITIONAL_RETALIATION", @@ -141,6 +154,9 @@ "faction": "castle", "abilities": { + "FLYING_ARMY" : { + "type" : "FLYING" + }, "unlimitedRetaliation" : { "type" : "UNLIMITED_RETALIATIONS" @@ -210,6 +226,12 @@ "level": 5, "faction": "castle", "upgrades": ["zealot"], + "abilities" : + { + "SHOOTING_ARMY" : { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CMONKK.DEF", @@ -233,6 +255,15 @@ "index": 9, "level": 5, "faction": "castle", + "abilities" : + { + "SHOOTING_ARMY" : { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : { + "type" : "NO_MELEE_PENALTY" + } + }, "graphics" : { "animation": "CZEALT.DEF", @@ -257,6 +288,12 @@ "level": 6, "faction": "castle", "upgrades": ["champion"], + "abilities" : + { + "const_jousting" : { + "type" : "JOUSTING" + } + }, "graphics" : { "animation": "CCAVLR.DEF" @@ -275,6 +312,12 @@ "index": 11, "level": 6, "faction": "castle", + "abilities" : + { + "const_jousting" : { + "type" : "JOUSTING" + } + }, "graphics" : { "animation": "CCHAMP.DEF" @@ -295,8 +338,20 @@ "faction": "castle", "abilities": { + "KING_2" : + { + "type" : "KING", + "val" : 2 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "const_raises_morale" : { + "type" : "MORALE", + "val" : 1, + "propagator" : "HERO", "stacking" : "Angels" }, "hateDevils" : @@ -333,6 +388,15 @@ "faction": "castle", "abilities": { + "KING_2" : + { + "type" : "KING", + "val" : 2 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "resurrects" : { "type" : "SPELLCASTER", @@ -350,8 +414,10 @@ "type" : "CASTS", "val" : 1 }, - "const_raises_morale" : - { + "const_raises_morale" : { + "type" : "MORALE", + "val" : 1, + "propagator" : "HERO", "stacking" : "Angels" }, "hateDevils" : diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index 37d85c0e3..59808d7fb 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -6,6 +6,12 @@ "extraNames": [ "pixies" ], "faction": "conflux", "upgrades": ["sprite"], + "abilities" : + { + "FLYING_ARMY" : { + "type" : "FLYING" + } + }, "graphics" : { "animation": "CPIXIE.DEF" @@ -24,6 +30,15 @@ "index": 119, "level": 1, "faction": "conflux", + "abilities" : + { + "FLYING_ARMY" : { + "type" : "FLYING" + }, + "const_free_attack" : { + "type" : "BLOCKS_RETALIATION" + } + }, "graphics" : { "animation": "CSPRITE.DEF" @@ -53,6 +68,11 @@ { "type" : "MIND_IMMUNITY" }, + "meteorShowerImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.meteorShower" + }, "lightingVulnerablity" : { "type" : "MORE_DAMAGE_FROM_SPELL", @@ -65,11 +85,6 @@ "subtype" : "spell.chainLightning", "val" : 100 }, - "meteorShowerImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.meteorShower" - }, "armageddonVulnerablity" : { "type" : "MORE_DAMAGE_FROM_SPELL", @@ -115,6 +130,10 @@ { "type" : "NON_LIVING" }, + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "spellcaster": { "type" : "SPELLCASTER", @@ -131,6 +150,15 @@ "type" : "CASTS", "val" : 3 }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, + "meteorShowerImmunity" : + { + "type" : "SPELL_IMMUNITY", + "subtype" : "spell.meteorShower" + }, "lightingVulnerablity" : { "type" : "MORE_DAMAGE_FROM_SPELL", @@ -143,11 +171,6 @@ "subtype" : "spell.chainLightning", "val" : 100 }, - "meteorShowerImmunity" : - { - "type" : "SPELL_IMMUNITY", - "subtype" : "spell.meteorShower" - }, "armageddonVulnerablity" : { "type" : "MORE_DAMAGE_FROM_SPELL", @@ -280,6 +303,10 @@ { "type" : "NON_LIVING" }, + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "spellcaster": { "type" : "SPELLCASTER", @@ -442,6 +469,10 @@ { "type" : "NON_LIVING" }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "spellcaster": { "type" : "SPELLCASTER", @@ -595,6 +626,10 @@ "type" : "CASTS", "val" : 3 }, + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, "lightingImmunity" : { "type" : "SPELL_IMMUNITY", @@ -652,6 +687,18 @@ "nonLiving" : { "type" : "NON_LIVING" + }, + "MULTI_HEADED" : + { + "type" : "ATTACKS_ALL_ADJACENT" + }, + "const_free_attack" : + { + "type" : "BLOCKS_RETALIATION" + }, + "IMMUNE_TO_MIND_SPELLS" : + { + "type" : "MIND_IMMUNITY" } }, "doubleWide" : false, @@ -680,6 +727,14 @@ { "type" : "NON_LIVING" }, + "MULTI_HEADED" : + { + "type" : "ATTACKS_ALL_ADJACENT" + }, + "const_free_attack" : + { + "type" : "BLOCKS_RETALIATION" + }, "magicImmunity" : { "type" : "LEVEL_SPELL_IMMUNITY", @@ -708,6 +763,19 @@ "upgrades": ["phoenix"], "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, + "HAS_EXTENDED_ATTACK" : + { + "type" : "TWO_HEX_ATTACK_BREATH" + }, "immuneToFire" : { "type" : "SPELL_SCHOOL_IMMUNITY", @@ -734,6 +802,19 @@ "faction": "conflux", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, + "HAS_EXTENDED_ATTACK" : + { + "type" : "TWO_HEX_ATTACK_BREATH" + }, "immuneToFire" : { "type" : "SPELL_SCHOOL_IMMUNITY", diff --git a/config/creatures/dungeon.json b/config/creatures/dungeon.json index e3e03e9b1..40064ac3f 100644 --- a/config/creatures/dungeon.json +++ b/config/creatures/dungeon.json @@ -74,6 +74,10 @@ "faction": "dungeon", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "strikeAndReturn" : { "type" : "RETURN_AFTER_STRIKE" @@ -101,6 +105,10 @@ "faction": "dungeon", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "strikeAndReturn" : { "type" : "RETURN_AFTER_STRIKE" @@ -130,6 +138,17 @@ "level": 3, "faction": "dungeon", "upgrades": ["evilEye"], + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + } + }, "graphics" : { "animation": "CBEHOL.DEF", @@ -160,6 +179,17 @@ "index": 75, "level": 3, "faction": "dungeon", + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + } + }, "graphics" : { "animation": "CEVEYE.DEF", @@ -192,6 +222,14 @@ "faction": "dungeon", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + }, "petrification" : { "type" : "SPELL_AFTER_ATTACK", @@ -226,6 +264,14 @@ "faction": "dungeon", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + }, "petrification" : { "type" : "SPELL_AFTER_ATTACK", @@ -314,6 +360,13 @@ "level": 6, "faction": "dungeon", "upgrades": ["scorpicore"], + "abilities" : + { + "FLYING_ARMY" : + { + "type" : "FLYING" + } + }, "graphics" : { "animation": "CMCORE.DEF" @@ -335,6 +388,10 @@ "faction": "dungeon", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "paralize" : { "type" : "SPELL_AFTER_ATTACK", @@ -363,6 +420,15 @@ "faction": "dungeon", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -398,6 +464,15 @@ "faction": "dungeon", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index aa17655f8..7dca0f38d 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -44,6 +44,13 @@ "faction": "fortress", "upgrades": ["lizardWarrior"], "hasDoubleWeek": true, + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CPLIZA.DEF", @@ -67,6 +74,13 @@ "index": 101, "level": 2, "faction": "fortress", + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CALIZA.DEF", @@ -139,6 +153,10 @@ "faction": "fortress", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dispellHelpful" : { "type" : "SPELL_AFTER_ATTACK", @@ -168,6 +186,10 @@ "faction": "fortress", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dispellHelpful" : { "type" : "SPELL_AFTER_ATTACK", @@ -256,6 +278,13 @@ "level": 6, "faction": "fortress", "upgrades": ["wyvernMonarch"], + "abilities" : + { + "FLYING_ARMY" : + { + "type" : "FLYING" + } + }, "graphics" : { "animation": "CWYVER.DEF" @@ -276,7 +305,11 @@ "faction": "fortress", "abilities": { - "petrify" : + "FLYING_ARMY" : + { + "type" : "FLYING" + }, + "poison" : { "type" : "SPELL_AFTER_ATTACK", "subtype" : "spell.poison", @@ -303,6 +336,11 @@ "faction": "fortress", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, "attackAll" : { "type" : "ATTACKS_ALL_ADJACENT" @@ -333,6 +371,11 @@ "faction": "fortress", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, "attackAll" : { "type" : "ATTACKS_ALL_ADJACENT" diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index 37189d570..b44ba8a6b 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -51,6 +51,13 @@ "faction": "inferno", "upgrades": ["magog"], "hasDoubleWeek": true, + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CGOG.DEF", @@ -76,6 +83,10 @@ "faction": "inferno", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "fireball" : { "type" : "SPELL_LIKE_ATTACK", @@ -345,6 +356,11 @@ "faction": "inferno", "abilities": { + "KING_2" : + { + "type" : "KING", + "val" : 2 + }, "FLYING_ARMY" : { // type loaded from crtraits @@ -400,6 +416,11 @@ "faction": "inferno", "abilities" : { + "KING_2" : + { + "type" : "KING", + "val" : 2 + }, "FLYING_ARMY" : { // type loaded from crtraits diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index 5202f6d87..a1d473394 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -5,6 +5,13 @@ "level": 1, "faction": "necropolis", "upgrades": ["skeletonWarrior"], + "abilities" : + { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + } + }, "graphics" : { "animation": "CSKELE.DEF" @@ -23,6 +30,13 @@ "index": 57, "level": 1, "faction": "necropolis", + "abilities" : + { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + } + }, "graphics" : { "animation": "CWSKEL.DEF" @@ -43,6 +57,13 @@ "extraNames": [ "zombie" ], //FIXME: zombie is a name of upgrade but not in HOTRAITS "faction" : "necropolis", "upgrades": ["zombieLord"], + "abilities" : + { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + } + }, "graphics" : { "animation": "CZOMBI.DEF" @@ -75,6 +96,10 @@ }, "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, "castDisease" : { "type" : "SPELL_AFTER_ATTACK", @@ -90,6 +115,14 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "regenerate" : { "type" : "HP_REGENERATION", @@ -118,6 +151,14 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "regenerate" : { "type" : "HP_REGENERATION", @@ -149,6 +190,14 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "noRetalitation" : { "type" : "BLOCKS_RETALIATION" @@ -177,6 +226,14 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "noRetalitation" : { "type" : "BLOCKS_RETALIATION" @@ -210,6 +267,14 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "deathCloud" : { "type" : "SPELL_LIKE_ATTACK", @@ -242,6 +307,14 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "deathCloud" : { "type" : "SPELL_LIKE_ATTACK", @@ -273,6 +346,10 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, "curses" : { "type" : "SPELL_AFTER_ATTACK", @@ -301,6 +378,10 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, "curses" : { "type" : "SPELL_AFTER_ATTACK", @@ -333,6 +414,19 @@ "faction": "necropolis", "abilities" : { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -368,6 +462,19 @@ "faction": "necropolis", "abilities": { + "IS_UNDEAD" : + { + "type" : "UNDEAD" + }, + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 06c639bc8..70431e8f3 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -70,6 +70,15 @@ "excludeFromRandomization" : true, "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -82,14 +91,14 @@ { "type" : "FEAR" }, + "fearless" : + { + "type" : "FEARLESS" + }, "spellImmunity" : { "type" : "LEVEL_SPELL_IMMUNITY", "val" : 3 - }, - "fearless" : - { - "type" : "FEARLESS" } }, "graphics" : @@ -113,6 +122,11 @@ "excludeFromRandomization" : true, "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -149,6 +163,11 @@ "excludeFromRandomization" : true, "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -247,6 +266,15 @@ "excludeFromRandomization" : true, "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -290,6 +318,14 @@ "excludeFromRandomization" : true, "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + }, "noPenalty" : { "type" : "NO_WALL_PENALTY" @@ -369,6 +405,10 @@ "excludeFromRandomization" : true, "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "noDistancePenalty" : { "type" : "NO_DISTANCE_PENALTY" @@ -403,6 +443,10 @@ "faction": "neutral", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "lucky" : { "type" : "LUCK", @@ -481,7 +525,7 @@ "type" : "SPELL_AFTER_ATTACK", "subtype" : "spell.curse", "val" : 50 - } + } }, "graphics" : { @@ -545,7 +589,7 @@ "subtype" : "visionsHeroes", "val" : 3, "valueType" : "INDEPENDENT_MAX", - "propagator" : "HERO" + "propagator" : "HERO" }, "visionsTowns" : { @@ -553,7 +597,7 @@ "subtype" : "visionsTowns", "val" : 3, "valueType" : "INDEPENDENT_MAX", - "propagator" : "HERO" + "propagator" : "HERO" } }, "graphics" : diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index 8fead5c52..a1146cd55 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -99,6 +99,13 @@ "level": 3, "faction": "rampart", "upgrades": ["grandElf"], + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CELF.DEF", @@ -124,6 +131,10 @@ "faction": "rampart", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "doubleShot" : { "type": "ADDITIONAL_ATTACK", @@ -156,6 +167,10 @@ "faction": "rampart", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "increaseManaCost" : { "type" : "CHANGES_SPELL_COST_FOR_ENEMY", @@ -184,6 +199,10 @@ "faction": "rampart", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "increaseManaCost" : { "type" : "CHANGES_SPELL_COST_FOR_ENEMY", @@ -333,6 +352,15 @@ "faction": "rampart", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" @@ -368,6 +396,15 @@ "faction": "rampart", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "dragon" : { "type" : "DRAGON_NATURE" diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index b8b8614ca..623ac2f77 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -90,6 +90,13 @@ "level": 3, "faction": "stronghold", "upgrades": ["orcChieftain"], + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CORC.DEF", @@ -113,6 +120,13 @@ "index": 89, "level": 3, "faction": "stronghold", + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CORCCH.DEF", @@ -194,6 +208,13 @@ "level": 5, "faction": "stronghold", "upgrades": ["thunderbird"], + "abilities" : + { + "FLYING_ARMY" : + { + "type" : "FLYING" + } + }, "graphics" : { "animation": "CROC.DEF" @@ -214,6 +235,10 @@ "faction": "stronghold", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "thunderOnAttack" : { "type" : "SPELL_AFTER_ATTACK", @@ -247,6 +272,10 @@ "faction": "stronghold", "abilities" : { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "siege" : { "subtype" : "spell.cyclopsShot", "type" : "CATAPULT" @@ -278,6 +307,10 @@ "faction": "stronghold", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, "siege" : { "subtype" : "spell.cyclopsShot", "type" : "CATAPULT" @@ -315,6 +348,11 @@ "faction": "stronghold", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, "reduceDefence" : { "type" : "ENEMY_DEFENCE_REDUCTION", @@ -342,6 +380,11 @@ "faction": "stronghold", "abilities": { + "KING_1" : + { + "type" : "KING", + "val" : 0 + }, "reduceDefence" : { "type" : "ENEMY_DEFENCE_REDUCTION", diff --git a/config/creatures/tower.json b/config/creatures/tower.json index 3863187d4..1609377e7 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -26,6 +26,13 @@ "index": 29, "level": 1, "faction": "tower", + "abilities" : + { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "CGREMM.DEF", @@ -54,6 +61,10 @@ "gargoyle" : { "type" : "GARGOYLE" + }, + "FLYING_ARMY" : + { + "type" : "FLYING" } }, "upgrades": ["obsidianGargoyle"], @@ -80,6 +91,10 @@ "gargoyle" : { "type" : "GARGOYLE" + }, + "FLYING_ARMY" : + { + "type" : "FLYING" } }, "graphics" : @@ -165,6 +180,14 @@ "faction": "tower", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + }, "reduceSpellCost" : { "type" : "CHANGES_SPELL_COST_FOR_ALLY", @@ -197,6 +220,18 @@ "faction": "tower", "abilities": { + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + }, + "const_no_wall_penalty" : + { + "type" : "NO_WALL_PENALTY" + }, "reduceSpellCost" : { "type" : "CHANGES_SPELL_COST_FOR_ALLY", @@ -236,6 +271,10 @@ "faction": "tower", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "hateEfreet" : { "type" : "HATE", @@ -270,6 +309,10 @@ "faction": "tower", "abilities": { + "FLYING_ARMY" : + { + "type" : "FLYING" + }, "casts" : { "type" : "CASTS", @@ -370,6 +413,11 @@ "faction": "tower", "abilities" : { + "KING_3" : + { + "type" : "KING", + "val" : 3 + }, "immuneToMind" : { "type" : "MIND_IMMUNITY" @@ -396,6 +444,19 @@ "faction": "tower", "abilities" : { + "KING_3" : + { + "type" : "KING", + "val" : 3 + }, + "SHOOTING_ARMY" : + { + "type" : "SHOOTER" + }, + "const_no_melee_penalty" : + { + "type" : "NO_MELEE_PENALTY" + }, "immuneToMind" : { "type" : "MIND_IMMUNITY" From 1a5e99724a81c907e81079b469f6ac2d33c37a0f Mon Sep 17 00:00:00 2001 From: krs Date: Sat, 9 Nov 2024 20:52:37 +0200 Subject: [PATCH 551/726] Add Description for KING bonuses --- config/creatures/castle.json | 4 ++-- config/creatures/conflux.json | 4 ++-- config/creatures/dungeon.json | 4 ++-- config/creatures/fortress.json | 4 ++-- config/creatures/inferno.json | 4 ++-- config/creatures/necropolis.json | 4 ++-- config/creatures/neutral.json | 8 ++++---- config/creatures/rampart.json | 4 ++-- config/creatures/stronghold.json | 4 ++-- config/creatures/tower.json | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 2bb5e8c07..94abe0d4a 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -338,7 +338,7 @@ "faction": "castle", "abilities": { - "KING_2" : + "KING_2" : // Will be affected by Advanced Slayer or better { "type" : "KING", "val" : 2 @@ -388,7 +388,7 @@ "faction": "castle", "abilities": { - "KING_2" : + "KING_2" : // Will be affected by Advanced Slayer or better { "type" : "KING", "val" : 2 diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index 59808d7fb..7aaf3ae65 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -763,7 +763,7 @@ "upgrades": ["phoenix"], "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -802,7 +802,7 @@ "faction": "conflux", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/dungeon.json b/config/creatures/dungeon.json index 40064ac3f..c38877f26 100644 --- a/config/creatures/dungeon.json +++ b/config/creatures/dungeon.json @@ -420,7 +420,7 @@ "faction": "dungeon", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -464,7 +464,7 @@ "faction": "dungeon", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index 7dca0f38d..549db72df 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -336,7 +336,7 @@ "faction": "fortress", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -371,7 +371,7 @@ "faction": "fortress", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index b44ba8a6b..ca304471c 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -356,7 +356,7 @@ "faction": "inferno", "abilities": { - "KING_2" : + "KING_2" : // Will be affected by Advanced Slayer or better { "type" : "KING", "val" : 2 @@ -416,7 +416,7 @@ "faction": "inferno", "abilities" : { - "KING_2" : + "KING_2" : // Will be affected by Advanced Slayer or better { "type" : "KING", "val" : 2 diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index a1d473394..fa06ffa69 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -418,7 +418,7 @@ { "type" : "UNDEAD" }, - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -466,7 +466,7 @@ { "type" : "UNDEAD" }, - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 70431e8f3..41556845d 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -70,7 +70,7 @@ "excludeFromRandomization" : true, "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -122,7 +122,7 @@ "excludeFromRandomization" : true, "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -163,7 +163,7 @@ "excludeFromRandomization" : true, "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -266,7 +266,7 @@ "excludeFromRandomization" : true, "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index a1146cd55..e81ded910 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -352,7 +352,7 @@ "faction": "rampart", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -396,7 +396,7 @@ "faction": "rampart", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index 623ac2f77..03234afcc 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -348,7 +348,7 @@ "faction": "stronghold", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 @@ -380,7 +380,7 @@ "faction": "stronghold", "abilities": { - "KING_1" : + "KING_1" : // Will be affected by Slayer with no expertise { "type" : "KING", "val" : 0 diff --git a/config/creatures/tower.json b/config/creatures/tower.json index 1609377e7..a44be1dfe 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -413,7 +413,7 @@ "faction": "tower", "abilities" : { - "KING_3" : + "KING_3" : // Will be affected by Expert Slayer only { "type" : "KING", "val" : 3 @@ -444,7 +444,7 @@ "faction": "tower", "abilities" : { - "KING_3" : + "KING_3" : // Will be affected by Expert Slayer only { "type" : "KING", "val" : 3 From 23709ab217d1c07eb9bbb7c6744ee88834d66344 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:04:20 +0100 Subject: [PATCH 552/726] support loading layer images --- client/renderSDL/RenderHandler.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 4c5e8477e..72076ab97 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -361,6 +361,18 @@ std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, E } else { + if(loc.image) + { + std::string imgPath = (*loc.image).getName(); + if(loc.layer == EImageLayer::OVERLAY) + imgPath += "-overlay"; + if(loc.layer == EImageLayer::SHADOW) + imgPath += "-shadow"; + + if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath))) + loc.image = ImagePath::builtin(imgPath); + } + return loadImageImpl(loc)->createImageReference(mode); } } From 77d14d095f5fd094e894aa78b0eab4029fcf7e7a Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sun, 10 Nov 2024 10:52:00 +0800 Subject: [PATCH 553/726] delete error message since grail will go to last branch --- server/CGameHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e035853e3..d7bacfc10 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2786,7 +2786,7 @@ bool CGameHandler::manageBackpackArtifacts(const PlayerColor & player, const Obj } else { - logGlobal->error("Unable to get artifact %s slot.", inf.getArt()->getType()->getNameTextID()); + // for grail return -3; } }); From 54f8f2c636008ed9d2332174e52f0777e9566301 Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 10 Nov 2024 14:18:16 +0200 Subject: [PATCH 554/726] Remove CCreatureHandler::loadBonuses --- config/creatures/castle.json | 40 +++++++++++++++--------- config/creatures/conflux.json | 39 +++++++++++++----------- config/creatures/dungeon.json | 38 +++++++++++++---------- config/creatures/fortress.json | 28 +++++++++++------ config/creatures/inferno.json | 25 +++++++-------- config/creatures/necropolis.json | 48 +++++++++++++++-------------- config/creatures/neutral.json | 29 ++++++++++-------- config/creatures/rampart.json | 24 ++++++++++----- config/creatures/special.json | 12 ++++++-- config/creatures/stronghold.json | 34 +++++++++++++-------- config/creatures/tower.json | 26 ++++++++-------- lib/CCreatureHandler.cpp | 52 +------------------------------- lib/CCreatureHandler.h | 2 -- 13 files changed, 202 insertions(+), 195 deletions(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 94abe0d4a..23e9f840d 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -59,7 +59,7 @@ "upgrades": ["marksman"], "abilities" : { - "SHOOTING_ARMY" : { + "shooter" : { "type" : "SHOOTER" } }, @@ -88,7 +88,7 @@ "faction": "castle", "abilities": { - "SHOOTING_ARMY" : { + "shooter" : { "type" : "SHOOTER" }, "extraAttack" : @@ -121,9 +121,10 @@ "index": 4, "level": 3, "faction": "castle", + "doubleWide": true, "abilities": { - "FLYING_ARMY" : { + "canFly" : { "type" : "FLYING" }, "extraRetaliation" : @@ -152,9 +153,10 @@ "index": 5, "level": 3, "faction": "castle", + "doubleWide": true, "abilities": { - "FLYING_ARMY" : { + "canFly" : { "type" : "FLYING" }, "unlimitedRetaliation" : @@ -228,7 +230,7 @@ "upgrades": ["zealot"], "abilities" : { - "SHOOTING_ARMY" : { + "shooter" : { "type" : "SHOOTER" } }, @@ -257,10 +259,10 @@ "faction": "castle", "abilities" : { - "SHOOTING_ARMY" : { + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : { + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" } }, @@ -287,11 +289,14 @@ "index": 10, "level": 6, "faction": "castle", + "doubleWide": true, "upgrades": ["champion"], "abilities" : { - "const_jousting" : { - "type" : "JOUSTING" + "jousting": + { + "type": "JOUSTING", + "val": 5 } }, "graphics" : @@ -312,10 +317,13 @@ "index": 11, "level": 6, "faction": "castle", + "doubleWide": true, "abilities" : { - "const_jousting" : { - "type" : "JOUSTING" + "jousting": + { + "type": "JOUSTING", + "val": 5 } }, "graphics" : @@ -336,6 +344,7 @@ "index": 12, "level": 7, "faction": "castle", + "doubleWide" : true, "abilities": { "KING_2" : // Will be affected by Advanced Slayer or better @@ -343,11 +352,11 @@ "type" : "KING", "val" : 2 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, - "const_raises_morale" : + "raisesMorale" : { "type" : "MORALE", "val" : 1, @@ -386,6 +395,7 @@ "index": 13, "level": 7, "faction": "castle", + "doubleWide" : true, "abilities": { "KING_2" : // Will be affected by Advanced Slayer or better @@ -393,7 +403,7 @@ "type" : "KING", "val" : 2 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -414,7 +424,7 @@ "type" : "CASTS", "val" : 1 }, - "const_raises_morale" : { + "raisesMorale" : { "type" : "MORALE", "val" : 1, "propagator" : "HERO", diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index 7aaf3ae65..c16d6024e 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -8,7 +8,8 @@ "upgrades": ["sprite"], "abilities" : { - "FLYING_ARMY" : { + "canFly" : + { "type" : "FLYING" } }, @@ -32,10 +33,12 @@ "faction": "conflux", "abilities" : { - "FLYING_ARMY" : { + "canFly" : + { "type" : "FLYING" }, - "const_free_attack" : { + "noRetaliation" : + { "type" : "BLOCKS_RETALIATION" } }, @@ -130,7 +133,7 @@ { "type" : "NON_LIVING" }, - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -214,6 +217,7 @@ "level": 3, "extraNames": [ "waterElementals" ], "faction": "conflux", + "doubleWide" : true, "abilities": { "nonLiving" : @@ -277,7 +281,6 @@ "val" : 100 } }, - "doubleWide" : true, "upgrades": ["iceElemental"], "graphics" : { @@ -297,13 +300,14 @@ "index": 123, "level": 3, "faction": "conflux", + "doubleWide" : true, "abilities": { "nonLiving" : { "type" : "NON_LIVING" }, - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -380,7 +384,6 @@ "val" : 100 } }, - "doubleWide" : true, "graphics" : { "animation": "CICEE.DEF", @@ -469,7 +472,7 @@ { "type" : "NON_LIVING" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -688,15 +691,15 @@ { "type" : "NON_LIVING" }, - "MULTI_HEADED" : + "attackAllAdjacent" : { "type" : "ATTACKS_ALL_ADJACENT" }, - "const_free_attack" : + "noRetaliation" : { "type" : "BLOCKS_RETALIATION" }, - "IMMUNE_TO_MIND_SPELLS" : + "immuneToMind" : { "type" : "MIND_IMMUNITY" } @@ -727,11 +730,11 @@ { "type" : "NON_LIVING" }, - "MULTI_HEADED" : + "attackAllAdjacent" : { "type" : "ATTACKS_ALL_ADJACENT" }, - "const_free_attack" : + "noRetaliation" : { "type" : "BLOCKS_RETALIATION" }, @@ -761,6 +764,7 @@ "level": 7, "faction": "conflux", "upgrades": ["phoenix"], + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -768,11 +772,11 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, - "HAS_EXTENDED_ATTACK" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, @@ -800,6 +804,7 @@ "index": 131, "level": 7, "faction": "conflux", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -807,11 +812,11 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, - "HAS_EXTENDED_ATTACK" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, diff --git a/config/creatures/dungeon.json b/config/creatures/dungeon.json index c38877f26..41791fb96 100644 --- a/config/creatures/dungeon.json +++ b/config/creatures/dungeon.json @@ -74,7 +74,7 @@ "faction": "dungeon", "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -105,7 +105,7 @@ "faction": "dungeon", "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -140,11 +140,11 @@ "upgrades": ["evilEye"], "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" } @@ -181,11 +181,11 @@ "faction": "dungeon", "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" } @@ -220,13 +220,14 @@ "index": 76, "level": 4, "faction": "dungeon", + "doubleWide" : true, "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" }, @@ -262,13 +263,14 @@ "index": 77, "level": 4, "faction": "dungeon", + "doubleWide" : true, "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" }, @@ -360,9 +362,10 @@ "level": 6, "faction": "dungeon", "upgrades": ["scorpicore"], + "doubleWide" : true, "abilities" : { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" } @@ -386,9 +389,10 @@ "index": 81, "level": 6, "faction": "dungeon", + "doubleWide" : true, "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -418,6 +422,7 @@ "index": 82, "level": 7, "faction": "dungeon", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -425,7 +430,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -433,7 +438,7 @@ { "type" : "DRAGON_NATURE" }, - "fireBreath" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, @@ -462,6 +467,7 @@ "index": 83, "level": 7, "faction": "dungeon", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -469,7 +475,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -477,7 +483,7 @@ { "type" : "DRAGON_NATURE" }, - "fireBreath" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index 549db72df..de7fa6ca7 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -46,7 +46,7 @@ "hasDoubleWeek": true, "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -76,7 +76,7 @@ "faction": "fortress", "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -105,6 +105,7 @@ "level": 5, "faction": "fortress", "upgrades": ["mightyGorgon"], + "doubleWide" : true, "graphics" : { "animation": "CCGORG.DEF" @@ -123,6 +124,7 @@ "index": 103, "level": 5, "faction": "fortress", + "doubleWide" : true, "abilities": { "deathStare" : @@ -153,7 +155,7 @@ "faction": "fortress", "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -186,7 +188,7 @@ "faction": "fortress", "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -222,6 +224,7 @@ "index": 106, "level": 4, "faction": "fortress", + "doubleWide" : true, "abilities": { "petrify" : @@ -250,6 +253,7 @@ "index": 107, "level": 4, "faction": "fortress", + "doubleWide" : true, "abilities": { "petrify" : @@ -278,9 +282,10 @@ "level": 6, "faction": "fortress", "upgrades": ["wyvernMonarch"], + "doubleWide" : true, "abilities" : { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" } @@ -303,9 +308,10 @@ "index": 109, "level": 6, "faction": "fortress", + "doubleWide" : true, "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -334,6 +340,7 @@ "index": 110, "level": 7, "faction": "fortress", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -341,11 +348,11 @@ "type" : "KING", "val" : 0 }, - "attackAll" : + "attackAllAdjacent" : { "type" : "ATTACKS_ALL_ADJACENT" }, - "noRetaliate" : + "noRetaliation" : { "type" : "BLOCKS_RETALIATION" } @@ -369,6 +376,7 @@ "index": 111, "level": 7, "faction": "fortress", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -376,11 +384,11 @@ "type" : "KING", "val" : 0 }, - "attackAll" : + "attackAllAdjacent" : { "type" : "ATTACKS_ALL_ADJACENT" }, - "noRetaliate" : + "noRetaliation" : { "type" : "BLOCKS_RETALIATION" } diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index ca304471c..4faeca5ae 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -53,7 +53,7 @@ "hasDoubleWeek": true, "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -83,7 +83,7 @@ "faction": "inferno", "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -117,10 +117,7 @@ "level": 3, "faction": "inferno", "upgrades": ["cerberus"], - "abilities": - { - "FLYING_ARMY" : null //hell hound doesn't fly - }, + "doubleWide" : true, "graphics" : { "animation": "CHHOUN.DEF" @@ -139,6 +136,7 @@ "index": 47, "level": 3, "faction": "inferno", + "doubleWide" : true, "abilities": { "noRetaliation" : @@ -148,8 +146,7 @@ "threeHeads" : { "type" : "THREE_HEADED_ATTACK" - }, - "FLYING_ARMY" : null //cerberus doesn't fly + } }, "graphics" : { @@ -361,12 +358,12 @@ "type" : "KING", "val" : 2 }, - "FLYING_ARMY" : + "canFly" : { - // type loaded from crtraits + "type" : "FLYING", "subtype" : "movementTeleporting" }, - "blockRetaliation" : + "noRetaliation" : { "type" : "BLOCKS_RETALIATION" }, @@ -421,12 +418,12 @@ "type" : "KING", "val" : 2 }, - "FLYING_ARMY" : + "canFly" : { - // type loaded from crtraits + "type" : "FLYING", "subtype" : "movementTeleporting" }, - "blockRetaliation" : + "noRetaliation" : { "type" : "BLOCKS_RETALIATION" }, diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index fa06ffa69..1844f0c24 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -7,7 +7,7 @@ "upgrades": ["skeletonWarrior"], "abilities" : { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" } @@ -32,7 +32,7 @@ "faction": "necropolis", "abilities" : { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" } @@ -59,7 +59,7 @@ "upgrades": ["zombieLord"], "abilities" : { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" } @@ -96,7 +96,7 @@ }, "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, @@ -115,11 +115,11 @@ "faction": "necropolis", "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -151,11 +151,11 @@ "faction": "necropolis", "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -190,11 +190,11 @@ "faction": "necropolis", "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -226,11 +226,11 @@ "faction": "necropolis", "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -267,11 +267,11 @@ "faction": "necropolis", "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -307,11 +307,11 @@ "faction": "necropolis", "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -344,9 +344,10 @@ "index": 66, "level": 6, "faction": "necropolis", + "doubleWide" : true, "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, @@ -376,9 +377,10 @@ "index": 67, "level": 6, "faction": "necropolis", + "doubleWide" : true, "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, @@ -412,9 +414,10 @@ "index": 68, "level": 7, "faction": "necropolis", + "doubleWide" : true, "abilities" : { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, @@ -423,7 +426,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -460,9 +463,10 @@ "index": 69, "level": 7, "faction": "necropolis", + "doubleWide" : true, "abilities": { - "IS_UNDEAD" : + "undead" : { "type" : "UNDEAD" }, @@ -471,7 +475,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 41556845d..731e65f58 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -68,6 +68,7 @@ "level": 10, "faction": "neutral", "excludeFromRandomization" : true, + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -75,7 +76,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -83,7 +84,7 @@ { "type" : "DRAGON_NATURE" }, - "fireBreath" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, @@ -120,6 +121,7 @@ "level": 10, "faction": "neutral", "excludeFromRandomization" : true, + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -139,8 +141,7 @@ "crystals" : { "type" : "SPECIAL_CRYSTAL_GENERATION" - }, - "FLYING_ARMY" : null + } }, "graphics" : { @@ -161,6 +162,7 @@ "level": 8, "faction": "neutral", "excludeFromRandomization" : true, + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -264,6 +266,7 @@ "level": 10, "faction": "neutral", "excludeFromRandomization" : true, + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -271,7 +274,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -279,7 +282,7 @@ { "type" : "DRAGON_NATURE" }, - "fireBreath" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, @@ -318,15 +321,15 @@ "excludeFromRandomization" : true, "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" }, - "noPenalty" : + "noWallPenalty" : { "type" : "NO_WALL_PENALTY" }, @@ -405,7 +408,7 @@ "excludeFromRandomization" : true, "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -413,7 +416,7 @@ { "type" : "NO_DISTANCE_PENALTY" }, - "noPenalty" : + "noWallPenalty" : { "type" : "NO_WALL_PENALTY" } @@ -443,7 +446,7 @@ "faction": "neutral", "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -545,6 +548,7 @@ "index": 142, "level": 3, "faction": "neutral", + "doubleWide" : true, "abilities": { "sandWalker" : @@ -554,7 +558,6 @@ "propagator" : "HERO" } }, - "doubleWide" : true, "graphics" : { "animation": "CNOMAD.DEF" diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index e81ded910..42259b2df 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -5,6 +5,7 @@ "level": 1, "faction": "rampart", "upgrades": ["centaurCaptain"], + "doubleWide" : true, "hasDoubleWeek": true, "graphics" : { @@ -26,6 +27,7 @@ "index": 15, "level": 1, "faction": "rampart", + "doubleWide" : true, "graphics" : { "missile" : null, @@ -101,7 +103,7 @@ "upgrades": ["grandElf"], "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -131,7 +133,7 @@ "faction": "rampart", "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, @@ -165,9 +167,10 @@ "index": 20, "level": 4, "faction": "rampart", + "doubleWide" : true, "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -197,9 +200,10 @@ "index": 21, "level": 4, "faction": "rampart", + "doubleWide" : true, "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -282,6 +286,7 @@ "index": 24, "level": 6, "faction": "rampart", + "doubleWide" : true, "abilities": { "blinds" : @@ -316,6 +321,7 @@ "index": 25, "level": 6, "faction": "rampart", + "doubleWide" : true, "abilities": { "blinds" : @@ -350,6 +356,7 @@ "index": 26, "level": 7, "faction": "rampart", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -357,7 +364,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -365,7 +372,7 @@ { "type" : "DRAGON_NATURE" }, - "fireBreath" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, @@ -394,6 +401,7 @@ "index": 27, "level": 7, "faction": "rampart", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -401,7 +409,7 @@ "type" : "KING", "val" : 0 }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -409,7 +417,7 @@ { "type" : "DRAGON_NATURE" }, - "fireBreath" : + "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" }, diff --git a/config/creatures/special.json b/config/creatures/special.json index b99c7867d..5ebdcf246 100644 --- a/config/creatures/special.json +++ b/config/creatures/special.json @@ -36,8 +36,14 @@ "index": 145, "level": 0, "faction": "neutral", - "abilities" : { - "siegeMachine" : { "type" : "CATAPULT", "subtype" : "spell.catapultShot" } + "doubleWide" : true, + "abilities" : + { + "siegeMachine" : + { + "type" : "CATAPULT", + "subtype" : "spell.catapultShot" + } }, "graphics" : { @@ -60,6 +66,7 @@ "index": 146, "level": 0, "faction": "neutral", + "doubleWide" : true, "graphics" : { "animation": "SMBAL.DEF", @@ -81,6 +88,7 @@ "index": 147, "level": 0, "faction": "neutral", + "doubleWide" : true, "abilities": { "heals" : { diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index 03234afcc..ddb27bb5f 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -44,6 +44,7 @@ "level": 2, "faction": "stronghold", "upgrades": ["hobgoblinWolfRider"], + "doubleWide" : true, "hasDoubleWeek": true, "graphics" : { @@ -63,6 +64,7 @@ "index": 87, "level": 2, "faction": "stronghold", + "doubleWide" : true, "abilities": { "extraAttack" : @@ -92,7 +94,7 @@ "upgrades": ["orcChieftain"], "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -122,7 +124,7 @@ "faction": "stronghold", "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -208,9 +210,10 @@ "level": 5, "faction": "stronghold", "upgrades": ["thunderbird"], + "doubleWide" : true, "abilities" : { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" } @@ -233,9 +236,10 @@ "index": 93, "level": 5, "faction": "stronghold", + "doubleWide" : true, "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -272,13 +276,14 @@ "faction": "stronghold", "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "siege" : { - "subtype" : "spell.cyclopsShot", - "type" : "CATAPULT" + "siege" : + { + "type" : "CATAPULT", + "subtype" : "spell.cyclopsShot" } }, "upgrades": ["cyclopKing"], @@ -307,18 +312,19 @@ "faction": "stronghold", "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "siege" : { - "subtype" : "spell.cyclopsShot", - "type" : "CATAPULT" + "siege" : + { + "type" : "CATAPULT", + "subtype" : "spell.cyclopsShot" }, "siegeLevel" : { - "subtype" : "spell.cyclopsShot", "type" : "CATAPULT_EXTRA_SHOTS", + "subtype" : "spell.cyclopsShot", "valueType" : "BASE_NUMBER", "val" : 1 } @@ -346,6 +352,7 @@ "index": 96, "level": 7, "faction": "stronghold", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise @@ -378,6 +385,7 @@ "index": 97, "level": 7, "faction": "stronghold", + "doubleWide" : true, "abilities": { "KING_1" : // Will be affected by Slayer with no expertise diff --git a/config/creatures/tower.json b/config/creatures/tower.json index a44be1dfe..2fd19fdac 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -28,7 +28,7 @@ "faction": "tower", "abilities" : { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" } @@ -62,7 +62,7 @@ { "type" : "GARGOYLE" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" } @@ -92,7 +92,7 @@ { "type" : "GARGOYLE" }, - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" } @@ -180,11 +180,11 @@ "faction": "tower", "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" }, @@ -220,15 +220,15 @@ "faction": "tower", "abilities": { - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" }, - "const_no_wall_penalty" : + "noWallPenalty" : { "type" : "NO_WALL_PENALTY" }, @@ -271,7 +271,7 @@ "faction": "tower", "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -309,7 +309,7 @@ "faction": "tower", "abilities": { - "FLYING_ARMY" : + "canFly" : { "type" : "FLYING" }, @@ -360,6 +360,7 @@ "index": 38, "level": 6, "faction": "tower", + "doubleWide" : true, "abilities" : { "noRetaliation" : @@ -386,6 +387,7 @@ "index": 39, "level": 6, "faction": "tower", + "doubleWide" : true, "abilities" : { "noRetaliation" : @@ -449,11 +451,11 @@ "type" : "KING", "val" : 3 }, - "SHOOTING_ARMY" : + "shooter" : { "type" : "SHOOTER" }, - "const_no_melee_penalty" : + "noMeleePenalty" : { "type" : "NO_MELEE_PENALTY" }, diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index a84e7ad69..ee445e617 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -462,56 +462,6 @@ void CCreatureHandler::loadCommanders() } } -void CCreatureHandler::loadBonuses(JsonNode & creature, std::string bonuses) const -{ - auto makeBonusNode = [&](const std::string & type, double val = 0) -> JsonNode - { - JsonNode ret; - ret["type"].String() = type; - ret["val"].Float() = val; - return ret; - }; - - static const std::map abilityMap = - { - {"FLYING_ARMY", makeBonusNode("FLYING")}, - {"SHOOTING_ARMY", makeBonusNode("SHOOTER")}, - {"SIEGE_WEAPON", makeBonusNode("SIEGE_WEAPON")}, - {"const_free_attack", makeBonusNode("BLOCKS_RETALIATION")}, - {"IS_UNDEAD", makeBonusNode("UNDEAD")}, - {"const_no_melee_penalty", makeBonusNode("NO_MELEE_PENALTY")}, - {"const_jousting", makeBonusNode("JOUSTING", 5)}, - {"KING_1", makeBonusNode("KING")}, // Slayer with no expertise - {"KING_2", makeBonusNode("KING", 2)}, // Advanced Slayer or better - {"KING_3", makeBonusNode("KING", 3)}, // Expert Slayer only - {"const_no_wall_penalty", makeBonusNode("NO_WALL_PENALTY")}, - {"MULTI_HEADED", makeBonusNode("ATTACKS_ALL_ADJACENT")}, - {"IMMUNE_TO_MIND_SPELLS", makeBonusNode("MIND_IMMUNITY")}, - {"HAS_EXTENDED_ATTACK", makeBonusNode("TWO_HEX_ATTACK_BREATH")} - }; - - auto hasAbility = [&](const std::string & name) -> bool - { - return boost::algorithm::find_first(bonuses, name); - }; - - for(const auto & a : abilityMap) - { - if(hasAbility(a.first)) - creature["abilities"][a.first] = a.second; - } - if(hasAbility("DOUBLE_WIDE")) - creature["doubleWide"].Bool() = true; - - if(hasAbility("const_raises_morale")) - { - JsonNode node = makeBonusNode("MORALE"); - node["val"].Float() = 1; - node["propagator"].String() = "HERO"; - creature["abilities"]["const_raises_morale"] = node; - } -} - std::vector CCreatureHandler::loadLegacyData() { size_t dataSize = VLC->engineSettings()->getInteger(EGameSettings::TEXTS_CREATURE); @@ -581,7 +531,7 @@ std::vector CCreatureHandler::loadLegacyData() // unused - ability text, not used since we no longer have original creature window parser.readString(); - loadBonuses(data, parser.readString()); //Attributes + parser.readString(); // unused - abilities, not used since we load them all from json configs h3Data.push_back(data); } diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 979978ba0..16cf2ca16 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -195,8 +195,6 @@ private: void loadStackExperience(CCreature * creature, const JsonNode & input) const; void loadCreatureJson(CCreature * creature, const JsonNode & config) const; - /// adding abilities from ZCRTRAIT.TXT - void loadBonuses(JsonNode & creature, std::string bonuses) const; /// load all creatures from H3 files void load(); void loadCommanders(); From acb66648b194f73b32c0bf7d6f8072cf869de3c8 Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 10 Nov 2024 15:47:42 +0200 Subject: [PATCH 555/726] Better names (HotA mod) for canShootWalls --- config/creatures/stronghold.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index ddb27bb5f..3c690bb2d 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -280,7 +280,7 @@ { "type" : "SHOOTER" }, - "siege" : + "canShootWalls" : { "type" : "CATAPULT", "subtype" : "spell.cyclopsShot" @@ -316,12 +316,12 @@ { "type" : "SHOOTER" }, - "siege" : + "canShootWalls" : { "type" : "CATAPULT", "subtype" : "spell.cyclopsShot" }, - "siegeLevel" : + "canShootWallsTimes" : { "type" : "CATAPULT_EXTRA_SHOTS", "subtype" : "spell.cyclopsShot", From ef46a87e6992dfa5e51dbfb84613b5a94c205f8b Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:37:28 +0200 Subject: [PATCH 556/726] ask assemble artifact dialog fix --- client/ArtifactsUIController.cpp | 1 + lib/networkPacks/NetPacksLib.cpp | 60 +++++++++++++++----------------- server/CGameHandler.cpp | 2 +- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/client/ArtifactsUIController.cpp b/client/ArtifactsUIController.cpp index 064b22244..7f018c208 100644 --- a/client/ArtifactsUIController.cpp +++ b/client/ArtifactsUIController.cpp @@ -26,6 +26,7 @@ ArtifactsUIController::ArtifactsUIController() { numOfMovedArts = 0; + numOfArtsAskAssembleSession = 0; } bool ArtifactsUIController::askToAssemble(const ArtifactLocation & al, const bool onlyEquipped, const bool checkIgnored) diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 5317855ed..30e88a3f5 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -1802,61 +1802,59 @@ void BulkMoveArtifacts::applyGs(CGameState *gs) void AssembledArtifact::applyGs(CGameState *gs) { - auto hero = gs->getHero(al.artHolder); - assert(hero); - const auto transformedArt = hero->getArt(al.slot); + auto artSet = gs->getArtSet(al.artHolder); + assert(artSet); + const auto transformedArt = artSet->getArt(al.slot); assert(transformedArt); const auto builtArt = artId.toArtifact(); - assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(hero, transformedArt->getTypeId()), [=](const CArtifact * art)->bool + assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(artSet, transformedArt->getTypeId()), [=](const CArtifact * art)->bool { return art->getId() == builtArt->getId(); })); - const auto transformedArtSlot = hero->getArtPos(transformedArt); auto * combinedArt = new CArtifactInstance(builtArt); gs->map->addNewArtifactInstance(combinedArt); // Find slots for all involved artifacts - std::vector slotsInvolved; - CArtifactFittingSet artSet(*hero); - for(const auto constituent : builtArt->getConstituents()) + std::set> slotsInvolved = { al.slot }; + CArtifactFittingSet fittingSet(*artSet); + auto parts = builtArt->getConstituents(); + parts.erase(std::find(parts.begin(), parts.end(), transformedArt->getType())); + for(const auto constituent : parts) { - const auto slot = artSet.getArtPos(constituent->getId(), false, false); - artSet.lockSlot(slot); + const auto slot = fittingSet.getArtPos(constituent->getId(), false, false); + fittingSet.lockSlot(slot); assert(slot != ArtifactPosition::PRE_FIRST); - slotsInvolved.emplace_back(slot); + slotsInvolved.insert(slot); } - std::sort(slotsInvolved.begin(), slotsInvolved.end(), std::greater<>()); // Find a slot for combined artifact - al.slot = transformedArtSlot; - for(const auto & slot : slotsInvolved) + if(ArtifactUtils::isSlotEquipment(al.slot) && ArtifactUtils::isSlotBackpack(*slotsInvolved.begin())) { - if(ArtifactUtils::isSlotEquipment(transformedArtSlot)) - { - + al.slot = ArtifactPosition::BACKPACK_START; + } + else if(ArtifactUtils::isSlotBackpack(al.slot)) + { + for(const auto & slot : slotsInvolved) if(ArtifactUtils::isSlotBackpack(slot)) + al.slot = slot; + } + else + { + for(const auto & slot : slotsInvolved) + if(!vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), al.slot) + && vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), slot)) { - al.slot = ArtifactPosition::BACKPACK_START; + al.slot = slot; break; } - - if(!vstd::contains(combinedArt->getType()->getPossibleSlots().at(hero->bearerType()), al.slot) - && vstd::contains(combinedArt->getType()->getPossibleSlots().at(hero->bearerType()), slot)) - al.slot = slot; - } - else - { - if(ArtifactUtils::isSlotBackpack(slot)) - al.slot = std::min(al.slot, slot); - } } // Delete parts from hero for(const auto & slot : slotsInvolved) { - const auto constituentInstance = hero->getArt(slot); - gs->map->removeArtifactInstance(*hero, slot); + const auto constituentInstance = artSet->getArt(slot); + gs->map->removeArtifactInstance(*artSet, slot); if(!combinedArt->getType()->isFused()) { @@ -1868,7 +1866,7 @@ void AssembledArtifact::applyGs(CGameState *gs) } // Put new combined artifacts - gs->map->putArtifactInstance(*hero, combinedArt, al.slot); + gs->map->putArtifactInstance(*artSet, combinedArt, al.slot); } void DisassembledArtifact::applyGs(CGameState *gs) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 822b876ad..e474bacb7 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2629,7 +2629,7 @@ bool CGameHandler::moveArtifact(const PlayerColor & player, const ArtifactLocati giveHeroNewArtifact(hero, ArtifactID::SPELLBOOK, ArtifactPosition::SPELLBOOK); ma.artsPack0.push_back(BulkMoveArtifacts::LinkedSlots(src.slot, dstSlot)); - if(src.artHolder != dst.artHolder) + if(src.artHolder != dst.artHolder && !isDstSlotBackpack) ma.artsPack0.back().askAssemble = true; sendAndApply(ma); return true; From 2621d4f5bfeef91eb4ba00e86cea3a161dbac8ce Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sun, 10 Nov 2024 23:02:36 +0800 Subject: [PATCH 557/726] fix spell research crash when no more spells can be researched due to map limit --- Mods/vcmi/config/vcmi/chinese.json | 1 + Mods/vcmi/config/vcmi/english.json | 1 + client/windows/CCastleInterface.cpp | 9 ++++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 96adec1ed..c4b70fa33 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -85,6 +85,7 @@ "vcmi.spellResearch.research" : "研究此法术", "vcmi.spellResearch.skip" : "跳过此法术", "vcmi.spellResearch.abort" : "中止", + "vcmi.spellResearch.noMoreSpells" : "没有更多的法术可供研究。", "vcmi.mainMenu.serverConnecting" : "连接中...", "vcmi.mainMenu.serverAddressEnter" : "使用地址:", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b6ec4f1ae..69dfe3c3b 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -85,6 +85,7 @@ "vcmi.spellResearch.research" : "Research this Spell", "vcmi.spellResearch.skip" : "Skip this Spell", "vcmi.spellResearch.abort" : "Abort", + "vcmi.spellResearch.noMoreSpells" : "There are no more spells available for research.", "vcmi.mainMenu.serverConnecting" : "Connecting...", "vcmi.mainMenu.serverAddressEnter" : "Enter address:", diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 28984167b..ea72008d7 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -2054,7 +2054,14 @@ void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition) auto cost = costBase * std::pow(town->spellResearchAcceptedCounter + 1, costExponent); std::vector> resComps; - auto newSpell = town->spells[level].at(town->spellsAtLevel(level, false)); + + int index = town->spellsAtLevel(level, false); + if (index >= town->spells[level].size()) + { + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.spellResearch.noMoreSpells")); + return; + } + auto newSpell = town->spells[level].at(index); resComps.push_back(std::make_shared(ComponentType::SPELL, spell->id)); resComps.push_back(std::make_shared(ComponentType::SPELL, newSpell)); resComps.back()->newLine = true; From ea14760e6c5a16f98a262a28423c1d2cd55b1e86 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sun, 10 Nov 2024 17:06:39 +0200 Subject: [PATCH 558/726] fix for fused artifacts creation --- lib/ArtifactUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index bfdb48468..3f6964c2d 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -236,7 +236,7 @@ DLL_LINKAGE CArtifactInstance * ArtifactUtils::createArtifact(const ArtifactID & assert(art); auto * artInst = new CArtifactInstance(art); - if(art->isCombined()) + if(art->isCombined() && !art->isFused()) { for(const auto & part : art->getConstituents()) artInst->addPart(createArtInst(part), ArtifactPosition::PRE_FIRST); From b32ec8607c8a8994fce8434beccc12638d3ba9f5 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:48:54 +0200 Subject: [PATCH 559/726] tradable grail --- lib/CArtHandler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index c566e8420..0232109c5 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -192,7 +192,6 @@ bool CArtifact::isTradable() const switch(id.toEnum()) { case ArtifactID::SPELLBOOK: - case ArtifactID::GRAIL: return false; default: return !isBig(); From 306c84572adf3e06ea9f9a67d7750b208559a828 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Mon, 11 Nov 2024 21:03:25 +0800 Subject: [PATCH 560/726] update Chinese translation --- Mods/vcmi/config/chinese.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mods/vcmi/config/chinese.json b/Mods/vcmi/config/chinese.json index b2493f799..67605102e 100644 --- a/Mods/vcmi/config/chinese.json +++ b/Mods/vcmi/config/chinese.json @@ -706,6 +706,8 @@ "core.bonus.DISINTEGRATE.description": "死亡后不会留下尸体", "core.bonus.INVINCIBLE.name": "无敌", "core.bonus.INVINCIBLE.description": "不受任何效果影响", + "core.bonus.MECHANICAL.name": "机械", + "core.bonus.MECHANICAL.description": "免疫大多数效果,可修复", "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "棱光吐息", "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "攻击后向三方向扩散攻击" } From 9c55ebf2ddf888153a6fc64236c054b9489b1ce4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:25:54 +0100 Subject: [PATCH 561/726] chronicles campaign screen --- client/mainmenu/CCampaignScreen.cpp | 3 +- client/mainmenu/CMainMenu.cpp | 8 +- client/render/AssetGenerator.cpp | 120 ++++++++++++++++++++++++++++ client/render/AssetGenerator.h | 2 + config/campaignSets.json | 16 ++++ 5 files changed, 147 insertions(+), 2 deletions(-) diff --git a/client/mainmenu/CCampaignScreen.cpp b/client/mainmenu/CCampaignScreen.cpp index 1957ce197..68d4cbdc4 100644 --- a/client/mainmenu/CCampaignScreen.cpp +++ b/client/mainmenu/CCampaignScreen.cpp @@ -67,7 +67,8 @@ CCampaignScreen::CCampaignScreen(const JsonNode & config, std::string name) } for(const JsonNode & node : config[name]["items"].Vector()) - campButtons.push_back(std::make_shared(node, config, campaignSet)); + if(CResourceHandler::get()->existsResource(ResourcePath(node["file"].String(), EResType::CAMPAIGN))) + campButtons.push_back(std::make_shared(node, config, campaignSet)); } void CCampaignScreen::activate() diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index b28b5f138..565d8bb9a 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -38,6 +38,7 @@ #include "../widgets/VideoWidget.h" #include "../windows/InfoWindows.h" #include "../CServerHandler.h" +#include "../render/AssetGenerator.h" #include "../CGameInfo.h" #include "../CPlayerInterface.h" @@ -403,6 +404,9 @@ void CMainMenu::openCampaignScreen(std::string name) { auto const & config = CMainMenuConfig::get().getCampaigns(); + AssetGenerator::createCampaignBackground(); + AssetGenerator::createChroniclesCampaignImages(); + if(!vstd::contains(config.Struct(), name)) { logGlobal->error("Unknown campaign set: %s", name); @@ -413,7 +417,9 @@ void CMainMenu::openCampaignScreen(std::string name) for (auto const & entry : config[name]["items"].Vector()) { ResourcePath resourceID(entry["file"].String(), EResType::CAMPAIGN); - if (!CResourceHandler::get()->existsResource(resourceID)) + if(entry["optional"].Bool()) + continue; + if(!CResourceHandler::get()->existsResource(resourceID)) campaignsFound = false; } diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index d887528e0..5aeb5f734 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -30,6 +30,8 @@ void AssetGenerator::generateAll() for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i) createPlayerColoredBackground(PlayerColor(i)); createCombatUnitNumberWindow(); + createCampaignBackground(); + createChroniclesCampaignImages(); } void AssetGenerator::createAdventureOptionsCleanBackground() @@ -206,3 +208,121 @@ void AssetGenerator::createCombatUnitNumberWindow() texture->adjustPalette(shifterNeutral, ignoredMask); texture->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePathNeutral)); } + +void AssetGenerator::createCampaignBackground() +{ + std::string filename = "data/CampaignBackground8.png"; + + if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation + return; + + if(!CResourceHandler::get("local")->createResource(filename)) + return; + ResourcePath savePath(filename, EResType::IMAGE); + + auto locator = ImageLocator(ImagePath::builtin("CAMPBACK")); + locator.scalingFactor = 1; + + std::shared_ptr img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); + Canvas canvas = Canvas(Point(800, 600), CanvasScalingPolicy::IGNORE); + + canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600)); + + // left image + canvas.draw(img, Point(220, 73), Rect(290, 73, 141, 115)); + canvas.draw(img, Point(37, 70), Rect(87, 70, 207, 120)); + + // right image + canvas.draw(img, Point(513, 67), Rect(463, 67, 71, 126)); + canvas.draw(img, Point(586, 71), Rect(536, 71, 207, 117)); + + // middle image + canvas.draw(img, Point(306, 68), Rect(86, 68, 209, 122)); + + // disabled fields + canvas.draw(img, Point(40, 72), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(310, 72), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(590, 72), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(43, 245), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(313, 244), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(586, 246), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(34, 417), Rect(313, 74, 197, 114)); + canvas.draw(img, Point(404, 414), Rect(313, 74, 197, 114)); + + // skull + auto locatorSkull = ImageLocator(ImagePath::builtin("CAMPNOSC")); + locatorSkull.scalingFactor = 1; + std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull, EImageBlitMode::OPAQUE); + canvas.draw(imgSkull, Point(562, 509), Rect(178, 108, 43, 19)); + + std::shared_ptr image = GH.renderHandler().createImage(canvas.getInternalSurface()); + + image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); +} + +void AssetGenerator::createChroniclesCampaignImages() +{ + for(int i = 1; i < 9; i++) + { + std::string filename = "data/CampaignHc" + std::to_string(i) + "Image.png"; + + if(CResourceHandler::get()->existsResource(ResourcePath(filename))) // overridden by mod, no generation + continue; + + auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(i) + "/GamSelBk"); + if(!CResourceHandler::get()->existsResource(imgPathBg)) // Chronicle episode not installed + continue; + + if(!CResourceHandler::get("local")->createResource(filename)) + continue; + ResourcePath savePath(filename, EResType::IMAGE); + + auto locator = ImageLocator(imgPathBg); + locator.scalingFactor = 1; + + std::shared_ptr img = GH.renderHandler().loadImage(locator, EImageBlitMode::OPAQUE); + Canvas canvas = Canvas(Point(200, 116), CanvasScalingPolicy::IGNORE); + + switch (i) + { + case 1: + canvas.draw(img, Point(0, 0), Rect(149, 144, 200, 116)); + break; + case 2: + canvas.draw(img, Point(0, 0), Rect(156, 150, 200, 116)); + break; + case 3: + canvas.draw(img, Point(0, 0), Rect(171, 153, 200, 116)); + break; + case 4: + canvas.draw(img, Point(0, 0), Rect(35, 358, 200, 116)); + break; + case 5: + canvas.draw(img, Point(0, 0), Rect(216, 248, 200, 116)); + break; + case 6: + canvas.draw(img, Point(0, 0), Rect(58, 234, 200, 116)); + break; + case 7: + canvas.draw(img, Point(0, 0), Rect(184, 219, 200, 116)); + break; + case 8: + canvas.draw(img, Point(0, 0), Rect(268, 210, 200, 116)); + + //skull + auto locatorSkull = ImageLocator(ImagePath::builtin("CampSP1")); + locatorSkull.scalingFactor = 1; + std::shared_ptr imgSkull = GH.renderHandler().loadImage(locatorSkull, EImageBlitMode::OPAQUE); + canvas.draw(imgSkull, Point(162, 94), Rect(162, 94, 41, 22)); + canvas.draw(img, Point(162, 94), Rect(424, 304, 14, 4)); + canvas.draw(img, Point(162, 98), Rect(424, 308, 10, 4)); + canvas.draw(img, Point(158, 102), Rect(424, 312, 10, 4)); + canvas.draw(img, Point(154, 106), Rect(424, 316, 10, 4)); + break; + } + + std::shared_ptr image = GH.renderHandler().createImage(canvas.getInternalSurface()); + + image->exportBitmap(*CResourceHandler::get("local")->getResourceName(savePath)); + } +} diff --git a/client/render/AssetGenerator.h b/client/render/AssetGenerator.h index 2b1c0b3a2..2eb73a886 100644 --- a/client/render/AssetGenerator.h +++ b/client/render/AssetGenerator.h @@ -21,4 +21,6 @@ public: static void createBigSpellBook(); static void createPlayerColoredBackground(const PlayerColor & player); static void createCombatUnitNumberWindow(); + static void createCampaignBackground(); + static void createChroniclesCampaignImages(); }; diff --git a/config/campaignSets.json b/config/campaignSets.json index eccafe67d..ce118a7fc 100644 --- a/config/campaignSets.json +++ b/config/campaignSets.json @@ -47,5 +47,21 @@ { "id": 6, "x":34, "y":417, "file":"DATA/FINAL", "image":"CAMPUA1", "video":"UNHOLY", "requires": [4] }, { "id": 7, "x":404, "y":414, "file":"DATA/SECRET", "image":"CAMPSP1", "video":"SPECTRE", "requires": [6] } ] + }, + "chr": + { + "images" : [ {"x": 0, "y": 0, "name":"data/CampaignBackground8"} ], + "exitbutton" : {"x": 658, "y": 482, "name":"CMPSCAN" }, + "items": + [ + { "id": 1, "x":40, "y":72, "file":"Maps/Chronicles/Hc1_Main", "image":"CampaignHc1Image", "video":"", "requires": [], "optional": true }, + { "id": 2, "x":310, "y":72, "file":"Maps/Chronicles/Hc2_Main", "image":"CampaignHc2Image", "video":"", "requires": [], "optional": true }, + { "id": 3, "x":590, "y":72, "file":"Maps/Chronicles/Hc3_Main", "image":"CampaignHc3Image", "video":"", "requires": [], "optional": true }, + { "id": 4, "x":43, "y":245, "file":"Maps/Chronicles/Hc4_Main", "image":"CampaignHc4Image", "video":"", "requires": [], "optional": true }, + { "id": 5, "x":313, "y":244, "file":"Maps/Chronicles/Hc5_Main", "image":"CampaignHc5Image", "video":"", "requires": [], "optional": true }, + { "id": 6, "x":586, "y":244, "file":"Maps/Chronicles/Hc6_Main", "image":"CampaignHc6Image", "video":"", "requires": [], "optional": true }, + { "id": 7, "x":34, "y":413, "file":"Maps/Chronicles/Hc7_Main", "image":"CampaignHc7Image", "video":"", "requires": [], "optional": true }, + { "id": 8, "x":404, "y":414, "file":"Maps/Chronicles/Hc8_Main", "image":"CampaignHc8Image", "video":"", "requires": [], "optional": true } + ] } } From 6b33f4d969cb68cf6ced622f58cf1e37df883339 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:44:48 +0100 Subject: [PATCH 562/726] skip campaigns from set --- client/lobby/SelectionTab.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 150526d6c..376557c70 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -875,6 +875,8 @@ void SelectionTab::parseSaves(const std::unordered_set & files) void SelectionTab::parseCampaigns(const std::unordered_set & files) { + auto campaignSets = JsonNode(JsonPath::builtin("config/campaignSets.json")); + allItems.reserve(files.size()); for(auto & file : files) { @@ -882,7 +884,15 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files info->fileURI = file.getOriginalName(); info->campaignInit(); info->name = info->getNameForList(); - if(info->campaign) + + // skip campaigns organized in sets + bool foundInSet = false; + for (auto const & set : campaignSets.Struct()) + for (auto const & item : set.second["items"].Vector()) + if(file.getName() == ResourcePath(item["file"].String()).getName()) + foundInSet = true; + + if(info->campaign && !foundInSet) allItems.push_back(info); } } From 44561a55dbc6339dcf9afb4c3b3da37d719f610a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:57:44 +0100 Subject: [PATCH 563/726] chronicles docs --- docs/players/Heroes Chronicles.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/players/Heroes Chronicles.md diff --git a/docs/players/Heroes Chronicles.md b/docs/players/Heroes Chronicles.md new file mode 100644 index 000000000..766d86e75 --- /dev/null +++ b/docs/players/Heroes Chronicles.md @@ -0,0 +1,9 @@ +# Heroes Chronicles + +It also possible to play the Heroes Chronicles with VCMI. You still need a completly installed VCMI (with heroes 3 sod / complete files). + +You also need Heroes Chronicles from [gog.com](https://www.gog.com/en/game/heroes_chronicles_all_chapters). You need to download the offline installer. CD installations are not supported yet. + +You can use the "Install file" button in the launcher to select the downloaded exe files. This process can take a while (especially on mobile platforms) and need some temporary free space. + +After that you can select Heroes Chronicles from Campaign selection menu. From 33220b2eb339d41e6615978f679efd53d0e86364 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:52:35 +0100 Subject: [PATCH 564/726] check if used in mainmenu --- client/lobby/SelectionTab.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 376557c70..4ef2f45c2 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -876,6 +876,7 @@ void SelectionTab::parseSaves(const std::unordered_set & files) void SelectionTab::parseCampaigns(const std::unordered_set & files) { auto campaignSets = JsonNode(JsonPath::builtin("config/campaignSets.json")); + auto mainmenu = JsonNode(JsonPath::builtin("config/mainmenu.json")); allItems.reserve(files.size()); for(auto & file : files) @@ -886,13 +887,21 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files info->name = info->getNameForList(); // skip campaigns organized in sets - bool foundInSet = false; + std::string foundInSet = ""; for (auto const & set : campaignSets.Struct()) for (auto const & item : set.second["items"].Vector()) if(file.getName() == ResourcePath(item["file"].String()).getName()) - foundInSet = true; - - if(info->campaign && !foundInSet) + foundInSet = set.first; + + // set has to be used in main menu + bool setInMainmenu = false; + if(!foundInSet.empty()) + for (auto const & item : mainmenu["window"]["items"].Vector()) + for (auto const & button : item["buttons"].Vector()) + if(boost::algorithm::contains(boost::algorithm::to_lower_copy(button["command"].String()), boost::algorithm::to_lower_copy(foundInSet))) + setInMainmenu = true; + + if(info->campaign && !setInMainmenu) allItems.push_back(info); } } From e660d30b8af449e71a15a35eee0ed0639b609c1a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:55:07 +0100 Subject: [PATCH 565/726] hint --- docs/players/Heroes Chronicles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/players/Heroes Chronicles.md b/docs/players/Heroes Chronicles.md index 766d86e75..d08987618 100644 --- a/docs/players/Heroes Chronicles.md +++ b/docs/players/Heroes Chronicles.md @@ -6,4 +6,4 @@ You also need Heroes Chronicles from [gog.com](https://www.gog.com/en/game/heroe You can use the "Install file" button in the launcher to select the downloaded exe files. This process can take a while (especially on mobile platforms) and need some temporary free space. -After that you can select Heroes Chronicles from Campaign selection menu. +After that you can select Heroes Chronicles from Campaign selection menu (button or custom campaign). From 719859cc62bbcd54dc3f5541bca697f5df8706b3 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:12:56 +0100 Subject: [PATCH 566/726] optimize --- client/lobby/SelectionTab.cpp | 38 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 4ef2f45c2..4b45c7ffd 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -885,24 +885,28 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files info->fileURI = file.getOriginalName(); info->campaignInit(); info->name = info->getNameForList(); - - // skip campaigns organized in sets - std::string foundInSet = ""; - for (auto const & set : campaignSets.Struct()) - for (auto const & item : set.second["items"].Vector()) - if(file.getName() == ResourcePath(item["file"].String()).getName()) - foundInSet = set.first; - - // set has to be used in main menu - bool setInMainmenu = false; - if(!foundInSet.empty()) - for (auto const & item : mainmenu["window"]["items"].Vector()) - for (auto const & button : item["buttons"].Vector()) - if(boost::algorithm::contains(boost::algorithm::to_lower_copy(button["command"].String()), boost::algorithm::to_lower_copy(foundInSet))) - setInMainmenu = true; - if(info->campaign && !setInMainmenu) - allItems.push_back(info); + if(info->campaign) + { + // skip campaigns organized in sets + std::string foundInSet = ""; + for (auto const & set : campaignSets.Struct()) + for (auto const & item : set.second["items"].Vector()) + if(file.getName() == ResourcePath(item["file"].String()).getName()) + foundInSet = set.first; + + // set has to be used in main menu + bool setInMainmenu = false; + if(!foundInSet.empty()) + for (auto const & item : mainmenu["window"]["items"].Vector()) + if(item["name"].String() == "campaign") + for (auto const & button : item["buttons"].Vector()) + if(boost::algorithm::contains(boost::algorithm::to_lower_copy(button["command"].String()), boost::algorithm::to_lower_copy(foundInSet))) + setInMainmenu = true; + + if(!setInMainmenu) + allItems.push_back(info); + } } } From 27b59dd7ee011acd20d6c64c4a35d9c05e342271 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Wed, 13 Nov 2024 06:24:00 +0200 Subject: [PATCH 567/726] crash fixed --- client/widgets/CArtifactsOfHeroBase.cpp | 42 +++++++++++------------ client/widgets/CArtifactsOfHeroMarket.cpp | 32 ++++++++--------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 22666df42..7113f114b 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -103,38 +103,38 @@ void CArtifactsOfHeroBase::setShowPopupArtPlacesCallback(const CArtPlace::ClickF void CArtifactsOfHeroBase::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - auto ownedPlace = getArtPlace(cursorPosition); - assert(ownedPlace != nullptr); + if(auto ownedPlace = getArtPlace(cursorPosition)) + { + if(ownedPlace->isLocked()) + return; - if(ownedPlace->isLocked()) - return; - - if(clickPressedCallback) - clickPressedCallback(*ownedPlace, cursorPosition); + if(clickPressedCallback) + clickPressedCallback(*ownedPlace, cursorPosition); + } } void CArtifactsOfHeroBase::showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - auto ownedPlace = getArtPlace(cursorPosition); - assert(ownedPlace != nullptr); + if(auto ownedPlace = getArtPlace(cursorPosition)) + { + if(ownedPlace->isLocked()) + return; - if(ownedPlace->isLocked()) - return; - - if(showPopupCallback) - showPopupCallback(*ownedPlace, cursorPosition); + if(showPopupCallback) + showPopupCallback(*ownedPlace, cursorPosition); + } } void CArtifactsOfHeroBase::gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - auto ownedPlace = getArtPlace(cursorPosition); - assert(ownedPlace != nullptr); + if(auto ownedPlace = getArtPlace(cursorPosition)) + { + if(ownedPlace->isLocked()) + return; - if(ownedPlace->isLocked()) - return; - - if(gestureCallback) - gestureCallback(*ownedPlace, cursorPosition); + if(gestureCallback) + gestureCallback(*ownedPlace, cursorPosition); + } } void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero) diff --git a/client/widgets/CArtifactsOfHeroMarket.cpp b/client/widgets/CArtifactsOfHeroMarket.cpp index 0e94fd34c..fac271b46 100644 --- a/client/widgets/CArtifactsOfHeroMarket.cpp +++ b/client/widgets/CArtifactsOfHeroMarket.cpp @@ -24,24 +24,24 @@ CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int void CArtifactsOfHeroMarket::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) { - auto ownedPlace = getArtPlace(cursorPosition); - assert(ownedPlace != nullptr); - - if(ownedPlace->isLocked()) - return; - - if(const auto art = getArt(ownedPlace->slot)) + if(auto ownedPlace = getArtPlace(cursorPosition)) { - if(onSelectArtCallback && art->getType()->isTradable()) + if(ownedPlace->isLocked()) + return; + + if(const auto art = getArt(ownedPlace->slot)) { - unmarkSlots(); - artPlace.selectSlot(true); - onSelectArtCallback(ownedPlace.get()); - } - else - { - if(onClickNotTradableCallback) - onClickNotTradableCallback(); + if(onSelectArtCallback && art->getType()->isTradable()) + { + unmarkSlots(); + artPlace.selectSlot(true); + onSelectArtCallback(ownedPlace.get()); + } + else + { + if(onClickNotTradableCallback) + onClickNotTradableCallback(); + } } } } From e73cb7f45bc9de01516ecd94e6de60d6fc32bd3f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:03:20 +0100 Subject: [PATCH 568/726] Update client/windows/CCreatureWindow.cpp Co-authored-by: Ivan Savenko --- client/windows/CCreatureWindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/windows/CCreatureWindow.cpp b/client/windows/CCreatureWindow.cpp index ee3a6bf24..808fc0c93 100644 --- a/client/windows/CCreatureWindow.cpp +++ b/client/windows/CCreatureWindow.cpp @@ -830,7 +830,10 @@ void CStackWindow::initBonusesList() BonusList input; input = *(info->stackNode->getBonuses(CSelector(Bonus::Permanent), Selector::all)); std::sort(input.begin(), input.end(), [this](std::shared_ptr v1, std::shared_ptr & v2){ - return v1->source == v2->source ? info->stackNode->bonusToString(v1, false) < info->stackNode->bonusToString(v2, false) : v1->source == BonusSource::CREATURE_ABILITY || (v1->source < v2->source); + if (v1->source != v2->source) + return v1->source == BonusSource::CREATURE_ABILITY || (v1->source < v2->source); + else + return info->stackNode->bonusToString(v1, false) < info->stackNode->bonusToString(v2, false); }); while(!input.empty()) From 6199a13603e3bc5339001c6dc60c3bb39cbc887a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:20:16 +0100 Subject: [PATCH 569/726] code review --- client/lobby/SelectionTab.cpp | 2 +- docs/Readme.md | 16 +++++++++------- ...Heroes Chronicles.md => Heroes_Chronicles.md} | 0 3 files changed, 10 insertions(+), 8 deletions(-) rename docs/players/{Heroes Chronicles.md => Heroes_Chronicles.md} (100%) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 4b45c7ffd..4d27f2a33 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -901,7 +901,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files for (auto const & item : mainmenu["window"]["items"].Vector()) if(item["name"].String() == "campaign") for (auto const & button : item["buttons"].Vector()) - if(boost::algorithm::contains(boost::algorithm::to_lower_copy(button["command"].String()), boost::algorithm::to_lower_copy(foundInSet))) + if(boost::algorithm::ends_with(boost::algorithm::to_lower_copy(button["command"].String()), boost::algorithm::to_lower_copy(foundInSet))) setInMainmenu = true; if(!setInMainmenu) diff --git a/docs/Readme.md b/docs/Readme.md index b50df62b8..284cf3c0b 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -9,10 +9,10 @@ VCMI is an open-source recreation of Heroes of Might & Magic III engine, giving it new and extended possibilities.

-Vanilla town siege in extended window -Vanilla town view with radial menu for touchscreen devices -Large Spellbook with German translation -New widget for Hero selection, featuring Pavillon Town + Vanilla town siege in extended window + Vanilla town view with radial menu for touchscreen devices + Large Spellbook with German translation + New widget for Hero selection, featuring Pavillon Town

@@ -37,10 +37,12 @@ Please see corresponding installation guide articles for details for your platfo - [Android](players/Installation_Android.md) - [iOS](players/Installation_iOS.md) +See also installation guide for [Heroes Chronicles](players/Heroes_Chronicles.md). +

-Forge Town in battle -Asylum town with new creature dialog -Ruins town siege + Forge Town in battle + Asylum town with new creature dialog + Ruins town siege Map editor

diff --git a/docs/players/Heroes Chronicles.md b/docs/players/Heroes_Chronicles.md similarity index 100% rename from docs/players/Heroes Chronicles.md rename to docs/players/Heroes_Chronicles.md From a6c985c4f57860d2ee307ecfd1c8f5c3f46cee11 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:10:41 +0100 Subject: [PATCH 570/726] code review --- client/renderSDL/RenderHandler.cpp | 6 +++--- client/renderSDL/SDLImage.cpp | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 72076ab97..7e052eb11 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -246,9 +246,9 @@ std::shared_ptr RenderHandler::loadImageFromFileUncached(const Ima if(!defFile) // no prescale for this frame { auto tmpPath = (*locator.defFile).getName(); - boost::algorithm::replace_all(tmpPath, "2X/", "/"); - boost::algorithm::replace_all(tmpPath, "3X/", "/"); - boost::algorithm::replace_all(tmpPath, "4X/", "/"); + boost::algorithm::replace_all(tmpPath, "SPRITES2X/", "SPRITES/"); + boost::algorithm::replace_all(tmpPath, "SPRITES3X/", "SPRITES/"); + boost::algorithm::replace_all(tmpPath, "SPRITES4X/", "SPRITES/"); preScaledFactor = 1; defFile = getAnimationFile(AnimationPath::builtin(tmpPath)); } diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index d4236436f..d74024a55 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -279,7 +279,10 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet SDL_Surface * scaled = nullptr; if(preScaleFactor == factor) - scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, 1, EScalingAlgorithm::NEAREST); // keep size + { + scaled = CSDL_Ext::newSurface(Point(surf->w, surf->h), surf); + SDL_BlitSurface(surf, nullptr, scaled, nullptr); + } else if(preScaleFactor == 1) scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ); else From 1e77d08d1af78ac9df0ea0a6a5f5d362d9a953f1 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:49:57 +0100 Subject: [PATCH 571/726] docs for modder --- docs/modders/HD_Graphics.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/modders/HD_Graphics.md diff --git a/docs/modders/HD_Graphics.md b/docs/modders/HD_Graphics.md new file mode 100644 index 000000000..1704f599f --- /dev/null +++ b/docs/modders/HD_Graphics.md @@ -0,0 +1,29 @@ +# HD Graphics + +It's possible to provide alternative HD-Graphics within mods. They will be used if any upscaling filter is activated. + +## Preconditions + +It's still necessary to add 1x graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled. + +Currently following scaling factors are possible to use: 2x, 3x, 4x. You can also provide multiple of them (increases size of mod, but improves loading performance for player). It's recommend to provide 2x and 3x images. + +If user for example selects 3x resolution and only 2x exists in mod then the 2x images are upscaled to 3x (same for other combinations > 1x). + +## Mod + +For upscaled images you have to use following folders (next to `sprites` and `data` folders): +- `sprites2x`, `sprites3x`, `sprites4x` for sprites +- `data2x`, `data3x`, `data4x` for images + +The sprites should have the same name and folder structure as in `sprites` and `data` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images. + +### Shadows / Overlays + +It's also possible (but not necessary) to add HD shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`. + +Same for overlays with `-overlay`. But overlays are **necessary** for some animation graphics. They will be colorized by VCMI. + +Currently needed for: +- flaggable adventure map objects (needs a transparent image with white flags on it) +- creature battle animations (needs a transparent image with white outline of creature for highlighting on mouse hover) From 0f94f35dcfc2ab722bbbb8b6875f22ef48c9aa2c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 14 Nov 2024 00:34:39 +0100 Subject: [PATCH 572/726] code review --- Mods/vcmi/config/english.json | 2 +- client/lobby/SelectionTab.cpp | 68 +++++++++++++------------- client/lobby/SelectionTab.h | 6 ++- lib/networkPacks/PacksForLobby.h | 3 +- lib/serializer/ESerializationVersion.h | 7 ++- 5 files changed, 44 insertions(+), 42 deletions(-) diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/config/english.json index bab849734..0e58e8a2d 100644 --- a/Mods/vcmi/config/english.json +++ b/Mods/vcmi/config/english.json @@ -106,7 +106,7 @@ "vcmi.lobby.handicap.resource" : "Gives players appropriate resources to start with in addition to the normal starting resources. Negative values are allowed, but are limited to 0 in total (the player never starts with negative resources).", "vcmi.lobby.handicap.income" : "Changes the player's various incomes by the percentage. Is rounded up.", "vcmi.lobby.handicap.growth" : "Changes the growth rate of creatures in the towns owned by the player. Is rounded up.", - "vcmi.lobby.deleteUnsupportedSave" : "Unsupported saves found (e.g. from previous versions).\n\nDelete them?", + "vcmi.lobby.deleteUnsupportedSave" : "{Unsupported saves found}\n\nVCMI has found %d saved games that are no longer supported, possibly due to differences in VCMI versions.\n\nDo you want to delete them?", "vcmi.lobby.deleteSaveGameTitle" : "Select a Saved Game to delete", "vcmi.lobby.deleteMapTitle" : "Select a Scenario to delete", "vcmi.lobby.deleteFile" : "Do you want to delete following file?", diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 7a336febf..2d3108416 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -282,9 +282,12 @@ void SelectionTab::toggleMode() } case ESelectionScreen::loadGame: - inputName->disable(); - parseSaves(getFiles("Saves/", EResType::SAVEGAME)); - break; + { + inputName->disable(); + auto unsupported = parseSaves(getFiles("Saves/", EResType::SAVEGAME)); + handleUnsupportedSavegames(unsupported); + break; + } case ESelectionScreen::saveGame: parseSaves(getFiles("Saves/", EResType::SAVEGAME)); @@ -876,16 +879,9 @@ void SelectionTab::parseMaps(const std::unordered_set & files) } } -void SelectionTab::parseSaves(const std::unordered_set & files) +std::vector SelectionTab::parseSaves(const std::unordered_set & files) { - enum Choice { NONE, REMAIN, DELETE }; - Choice deleteUnsupported = NONE; - auto doDeleteUnsupported = [](std::string file){ - LobbyDelete ld; - ld.type = LobbyDelete::SAVEGAME; - ld.name = file; - CSH->sendLobbyPack(ld); - }; + std::vector unsupported; for(auto & file : files) { @@ -925,32 +921,36 @@ void SelectionTab::parseSaves(const std::unordered_set & files) allItems.push_back(mapInfo); } + catch(const IdentifierResolutionException & e) + { + logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what()); + } catch(const std::exception & e) { - // asking for deletion of unsupported saves - if(CSH->isHost()) - { - if(deleteUnsupported == DELETE) - doDeleteUnsupported(file.getName()); - else if(deleteUnsupported == NONE) - { - CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteUnsupportedSave"), std::vector>(), [&deleteUnsupported, doDeleteUnsupported, file](){ - doDeleteUnsupported(file.getName()); - deleteUnsupported = DELETE; - }, [&deleteUnsupported](){ deleteUnsupported = REMAIN; }); - - while(deleteUnsupported == NONE) - { - auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); - boost::this_thread::sleep_for(boost::chrono::milliseconds(5)); - } - } - } - - if(deleteUnsupported != DELETE) - logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what()); + unsupported.push_back(file); // IdentifierResolutionException is not relevant -> not ask to delete, when mods are disabled + logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what()); } } + + return unsupported; +} + +void SelectionTab::handleUnsupportedSavegames(const std::vector files) +{ + if(CSH->isHost() && files.size()) + { + MetaString text = MetaString::createFromTextID("vcmi.lobby.deleteUnsupportedSave"); + text.replaceNumber(files.size()); + CInfoWindow::showYesNoDialog(text.toString(), std::vector>(), [files](){ + for(auto & file : files) + { + LobbyDelete ld; + ld.type = LobbyDelete::SAVEGAME; + ld.name = file.getName(); + CSH->sendLobbyPack(ld); + } + }, nullptr); + } } void SelectionTab::parseCampaigns(const std::unordered_set & files) diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index ab26fe30d..dae702b09 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -72,6 +72,8 @@ class SelectionTab : public CIntObject // FIXME: CSelectionBase use them too! std::shared_ptr iconsVictoryCondition; std::shared_ptr iconsLossCondition; + + std::vector> unSupportedSaves; public: std::vector> allItems; std::vector> curItems; @@ -127,7 +129,9 @@ private: bool isMapSupported(const CMapInfo & info); void parseMaps(const std::unordered_set & files); - void parseSaves(const std::unordered_set & files); + std::vector parseSaves(const std::unordered_set & files); void parseCampaigns(const std::unordered_set & files); std::unordered_set getFiles(std::string dirURI, EResType resType); + + void handleUnsupportedSavegames(const std::vector files); }; diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index 8f16a80af..c299c4920 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -177,8 +177,7 @@ struct DLL_LINKAGE LobbyUpdateState : public CLobbyPackToPropagate template void serialize(Handler &h) { h & state; - if (h.version >= Handler::Version::LOBBY_DELETE) - h & refreshList; + h & refreshList; } }; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index f96736a12..52b4709bb 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -66,8 +66,7 @@ enum class ESerializationVersion : int32_t REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance REMOVE_VLC_POINTERS, // 869 removed remaining pointers to VLC entities - FOLDER_NAME_REWORK, // 870 - rework foldername - LOBBY_DELETE, // 871 - possibility to delete savegames and random maps - - CURRENT = LOBBY_DELETE + FOLDER_NAME_REWORK, // 870 - rework foldername + + CURRENT = FOLDER_NAME_REWORK }; From e7682cfd5c683cfe08036e2657b7a63a4ae800f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Thu, 14 Nov 2024 23:06:22 +0100 Subject: [PATCH 573/726] Add qt5-tools and tbb as required dependencies when using the windows-mingw-release preset --- docs/developers/Building_Windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers/Building_Windows.md b/docs/developers/Building_Windows.md index 91f7a1fd2..e1f574125 100644 --- a/docs/developers/Building_Windows.md +++ b/docs/developers/Building_Windows.md @@ -115,7 +115,7 @@ Extract `ccache` to a folder of your choosing, add the folder to the `PATH` envi ### Compile VCMI with MinGW via MSYS2 - Install MSYS2 from https://www.msys2.org/ - Start the `MSYS MinGW x64`-shell -- Install dependencies: `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-boost mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-static` +- Install dependencies: `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-boost mingw-w64-x86_64-gcc mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-static mingw-w64-x86_64-qt5-tools mingw-w64-x86_64-tbb` - Generate and build solution from VCMI-root dir: `cmake --preset windows-mingw-release && cmake --build --preset windows-mingw-release` **NOTE:** This will link Qt5 statically to `VCMI_launcher.exe` and `VCMI_Mapeditor.exe`. See [PR #3421](https://github.com/vcmi/vcmi/pull/3421) for some background. From 9a1e48c34974daefc8b25538d1d22c951c777f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Thu, 14 Nov 2024 23:26:24 +0100 Subject: [PATCH 574/726] Set CMAKE_C_COMPILER and CMAKE_CXX_COMPILER to gcc and g++ for windows-mingw-release preset. Prevents various situations where clang is chosen over gcc, which is unintended when using windows-mingw-release preset --- CMakePresets.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 500c48d68..1dbf2df4c 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -134,7 +134,9 @@ "description": "VCMI Windows Ninja using MinGW", "inherits": "default-release", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++" } }, { From 9611a324cd47e533f4511e2f5722b1854399f6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Fri, 15 Nov 2024 00:49:22 +0100 Subject: [PATCH 575/726] Fix linker error in EntryPoint if ffmpeg is missing due to missing DISABLE_VIDEO in vcmiclient target --- clientapp/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clientapp/CMakeLists.txt b/clientapp/CMakeLists.txt index edb0cb873..40061748d 100644 --- a/clientapp/CMakeLists.txt +++ b/clientapp/CMakeLists.txt @@ -56,6 +56,11 @@ if(WIN32) endif() target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH) + if(NOT ffmpeg_LIBRARIES) + target_compile_definitions(vcmiclient PRIVATE DISABLE_VIDEO) + endif() + + # TODO: very hacky, find proper solution to copy AI dlls into bin dir if(MSVC) add_custom_command(TARGET vcmiclient POST_BUILD From ac12574f6100e6bd405a4e9990e679a354b2dfb4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 15 Nov 2024 03:25:16 +0100 Subject: [PATCH 576/726] fix picture (regression) --- client/windows/CreaturePurchaseCard.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/windows/CreaturePurchaseCard.cpp b/client/windows/CreaturePurchaseCard.cpp index b61adf9f1..50ddc9175 100644 --- a/client/windows/CreaturePurchaseCard.cpp +++ b/client/windows/CreaturePurchaseCard.cpp @@ -54,8 +54,8 @@ void CreaturePurchaseCard::switchCreatureLevel() auto index = vstd::find_pos(upgradesID, creatureOnTheCard->getId()); auto nextCreatureId = vstd::circularAt(upgradesID, ++index); creatureOnTheCard = nextCreatureId.toCreature(); - picture = std::make_shared(parent->pos.x, parent->pos.y, creatureOnTheCard); - creatureClickArea = std::make_shared(Point(parent->pos.x, parent->pos.y), picture, creatureOnTheCard); + picture = std::make_shared(picture->pos.x - pos.x, picture->pos.y - pos.y, creatureOnTheCard); + creatureClickArea = std::make_shared(Point(picture->pos.x - pos.x, picture->pos.y - pos.y), picture, creatureOnTheCard); parent->updateAllSliders(); cost->set(creatureOnTheCard->getFullRecruitCost() * slider->getValue()); } From 149c6cd7782a8bccd9dd29940655da04fed1aa1c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:09:34 +0100 Subject: [PATCH 577/726] code review --- client/renderSDL/RenderHandler.cpp | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 7e052eb11..e08821bd8 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -326,24 +326,24 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator & loc std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode) { - ImageLocator loc = locator; - if(loc.defFile && loc.scalingFactor == 0) + ImageLocator adjustedLocator = locator; + if(adjustedLocator.defFile && adjustedLocator.scalingFactor == 0) { - auto tmp = getScalePath(*loc.defFile); - loc.defFile = AnimationPath::builtin(tmp.first.getName()); - loc.preScaledFactor = tmp.second; + auto tmp = getScalePath(*adjustedLocator.defFile); + adjustedLocator.defFile = AnimationPath::builtin(tmp.first.getName()); + adjustedLocator.preScaledFactor = tmp.second; } - if(loc.image && loc.scalingFactor == 0) + if(adjustedLocator.image && adjustedLocator.scalingFactor == 0) { - auto tmp = getScalePath(*loc.image); - loc.image = ImagePath::builtin(tmp.first.getName()); - loc.preScaledFactor = tmp.second; + auto tmp = getScalePath(*adjustedLocator.image); + adjustedLocator.image = ImagePath::builtin(tmp.first.getName()); + adjustedLocator.preScaledFactor = tmp.second; } - if (loc.scalingFactor == 0 && getScalingFactor() != 1 ) + if (adjustedLocator.scalingFactor == 0 && getScalingFactor() != 1 ) { - auto unscaledLocator = loc; - auto scaledLocator = loc; + auto unscaledLocator = adjustedLocator; + auto scaledLocator = adjustedLocator; unscaledLocator.scalingFactor = 1; scaledLocator.scalingFactor = getScalingFactor(); @@ -352,28 +352,28 @@ std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, E return std::make_shared(scaledLocator, unscaledImage, mode); } - if (loc.scalingFactor == 0) + if (adjustedLocator.scalingFactor == 0) { - auto scaledLocator = loc; + auto scaledLocator = adjustedLocator; scaledLocator.scalingFactor = getScalingFactor(); return loadImageImpl(scaledLocator)->createImageReference(mode); } else { - if(loc.image) + if(adjustedLocator.image) { - std::string imgPath = (*loc.image).getName(); - if(loc.layer == EImageLayer::OVERLAY) + std::string imgPath = (*adjustedLocator.image).getName(); + if(adjustedLocator.layer == EImageLayer::OVERLAY) imgPath += "-overlay"; - if(loc.layer == EImageLayer::SHADOW) + if(adjustedLocator.layer == EImageLayer::SHADOW) imgPath += "-shadow"; if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath))) - loc.image = ImagePath::builtin(imgPath); + adjustedLocator.image = ImagePath::builtin(imgPath); } - return loadImageImpl(loc)->createImageReference(mode); + return loadImageImpl(adjustedLocator)->createImageReference(mode); } } From b23c69b9d6d80d89494fd6c8b4dd2ccb006cdeae Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:38:44 +0100 Subject: [PATCH 578/726] code review --- client/lobby/SelectionTab.cpp | 8 ++++---- client/lobby/SelectionTab.h | 2 +- lib/networkPacks/PacksForLobby.h | 4 ++-- server/NetPacksLobbyServer.cpp | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 2d3108416..d6893fb37 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -348,14 +348,14 @@ void SelectionTab::clickReleased(const Point & cursorPosition) if(!curItems[py]->isFolder) CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteFile") + "\n\n" + curItems[py]->fullFileURI, std::vector>(), [this, py](){ LobbyDelete ld; - ld.type = tabType == ESelectionScreen::newGame ? LobbyDelete::RANDOMMAP : LobbyDelete::SAVEGAME; + ld.type = tabType == ESelectionScreen::newGame ? LobbyDelete::EType::RANDOMMAP : LobbyDelete::EType::SAVEGAME; ld.name = curItems[py]->fileURI; CSH->sendLobbyPack(ld); }, nullptr); else CInfoWindow::showYesNoDialog(CGI->generaltexth->translate("vcmi.lobby.deleteFolder") + "\n\n" + curFolder + curItems[py]->folderName, std::vector>(), [this, py](){ LobbyDelete ld; - ld.type = LobbyDelete::SAVEGAME_FOLDER; + ld.type = LobbyDelete::EType::SAVEGAME_FOLDER; ld.name = curFolder + curItems[py]->folderName; CSH->sendLobbyPack(ld); }, nullptr); @@ -935,7 +935,7 @@ std::vector SelectionTab::parseSaves(const std::unordered_set files) +void SelectionTab::handleUnsupportedSavegames(const std::vector & files) { if(CSH->isHost() && files.size()) { @@ -945,7 +945,7 @@ void SelectionTab::handleUnsupportedSavegames(const std::vector fi for(auto & file : files) { LobbyDelete ld; - ld.type = LobbyDelete::SAVEGAME; + ld.type = LobbyDelete::EType::SAVEGAME; ld.name = file.getName(); CSH->sendLobbyPack(ld); } diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index dae702b09..2cef080ed 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -133,5 +133,5 @@ private: void parseCampaigns(const std::unordered_set & files); std::unordered_set getFiles(std::string dirURI, EResType resType); - void handleUnsupportedSavegames(const std::vector files); + void handleUnsupportedSavegames(const std::vector & files); }; diff --git a/lib/networkPacks/PacksForLobby.h b/lib/networkPacks/PacksForLobby.h index c299c4920..c702935c2 100644 --- a/lib/networkPacks/PacksForLobby.h +++ b/lib/networkPacks/PacksForLobby.h @@ -385,11 +385,11 @@ struct DLL_LINKAGE LobbyPvPAction : public CLobbyPackToServer struct DLL_LINKAGE LobbyDelete : public CLobbyPackToServer { - enum EType : ui8 { + enum class EType : ui8 { SAVEGAME, SAVEGAME_FOLDER, RANDOMMAP }; - EType type = SAVEGAME; + EType type = EType::SAVEGAME; std::string name; void visitTyped(ICPackVisitor & visitor) override; diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 2f645d9f7..1b45736fb 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -442,15 +442,15 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack void ApplyOnServerNetPackVisitor::visitLobbyDelete(LobbyDelete & pack) { - if(pack.type == LobbyDelete::SAVEGAME || pack.type == LobbyDelete::RANDOMMAP) + if(pack.type == LobbyDelete::EType::SAVEGAME || pack.type == LobbyDelete::EType::RANDOMMAP) { - auto res = ResourcePath(pack.name, pack.type == LobbyDelete::SAVEGAME ? EResType::SAVEGAME : EResType::MAP); + auto res = ResourcePath(pack.name, pack.type == LobbyDelete::EType::SAVEGAME ? EResType::SAVEGAME : EResType::MAP); auto file = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); boost::filesystem::remove(file); if(boost::filesystem::is_empty(file.parent_path())) boost::filesystem::remove(file.parent_path()); } - else if(pack.type == LobbyDelete::SAVEGAME_FOLDER) + else if(pack.type == LobbyDelete::EType::SAVEGAME_FOLDER) { auto res = ResourcePath("Saves/" + pack.name, EResType::DIRECTORY); auto folder = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(res)); From fccf3132a8fcf864e2c8a11940cdcf283542712f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Zaj=C4=85c?= <4561094+ToRRent1812@users.noreply.github.com> Date: Fri, 15 Nov 2024 23:27:10 +0100 Subject: [PATCH 579/726] Update polish.json added new strings. --- Mods/vcmi/config/polish.json | 38 +++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Mods/vcmi/config/polish.json b/Mods/vcmi/config/polish.json index 3a4bb6e04..4bf7ad2aa 100644 --- a/Mods/vcmi/config/polish.json +++ b/Mods/vcmi/config/polish.json @@ -12,9 +12,11 @@ "vcmi.adventureMap.monsterThreat.levels.9" : "Przytłaczający", "vcmi.adventureMap.monsterThreat.levels.10" : "Śmiertelny", "vcmi.adventureMap.monsterThreat.levels.11" : "Nie do pokonania", - "vcmi.adventureMap.monsterLevel" : "\n\n%Jednostka %ATTACK_TYPE %LEVEL poziomu z miasta %TOWN", + "vcmi.adventureMap.monsterLevel" : "\n\nJednostka %ATTACK_TYPE %LEVEL poziomu z miasta %TOWN", "vcmi.adventureMap.monsterMeleeType" : "Walcząca wręcz", "vcmi.adventureMap.monsterRangedType" : "Dystansowa", + "vcmi.adventureMap.search.hover" : "Wyszukiwarka obiektów na mapie", + "vcmi.adventureMap.search.help" : "Wybierz obiekt który chcesz znaleźć na mapie.", "vcmi.adventureMap.confirmRestartGame" : "Czy na pewno chcesz zrestartować grę?", "vcmi.adventureMap.noTownWithMarket" : "Brak dostępnego targowiska!", @@ -26,6 +28,13 @@ "vcmi.adventureMap.movementPointsHeroInfo" : "(Punkty ruchu: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Wybacz, powtórka ruchu wroga nie została jeszcze zaimplementowana!", + "vcmi.bonusSource.artifact" : "Artefakt", + "vcmi.bonusSource.creature" : "Umiej.", + "vcmi.bonusSource.spell" : "Zaklęcie", + "vcmi.bonusSource.hero" : "Bohater", + "vcmi.bonusSource.commander" : "Dowódca", + "vcmi.bonusSource.other" : "Inne", + "vcmi.capitalColors.0" : "Czerwony", "vcmi.capitalColors.1" : "Niebieski", "vcmi.capitalColors.2" : "Brązowy", @@ -40,6 +49,12 @@ "vcmi.heroOverview.secondarySkills" : "Umiejętności drugorzędne", "vcmi.heroOverview.spells" : "Zaklęcia", + "vcmi.quickExchange.moveUnit" : "Przenieś jednostkę", + "vcmi.quickExchange.moveAllUnits" : "Przenieś wszystkie jednostki", + "vcmi.quickExchange.swapAllUnits" : "Zamień armię", + "vcmi.quickExchange.moveAllArtifacts" : "Przenieś wszystkie artefakty", + "vcmi.quickExchange.swapAllArtifacts" : "Zamień artefakty", + "vcmi.radialWheel.mergeSameUnit" : "Złącz takie same stworzenia", "vcmi.radialWheel.fillSingleUnit" : "Wypełnij pojedynczymi stworzeniami", "vcmi.radialWheel.splitSingleUnit" : "Wydziel pojedyncze stworzenie", @@ -59,14 +74,25 @@ "vcmi.radialWheel.moveDown" : "Przenieś w dół", "vcmi.radialWheel.moveBottom" : "Przenieś na spód", + "vcmi.randomMap.description" : "Mapa stworzona przez generator map losowych.\nSzablon: %s, rozmiar %dx%d, poziomów %d, graczy %d, komputerowych %d, woda %s, potwory %s, mapa VCMI", + "vcmi.randomMap.description.isHuman" : ", %s jest człowiekiem", + "vcmi.randomMap.description.townChoice" : ", %s wybiera %s", + "vcmi.randomMap.description.water.none" : "brak", + "vcmi.randomMap.description.water.normal" : "norm.", + "vcmi.randomMap.description.water.islands" : "wyspy", + "vcmi.randomMap.description.monster.weak" : "słabi", + "vcmi.randomMap.description.monster.normal" : "norm.", + "vcmi.randomMap.description.monster.strong" : "silni", + "vcmi.spellBook.search" : "szukaj...", "vcmi.spellResearch.canNotAfford" : "Nie stać Cię na zastąpienie {%SPELL1} przez {%SPELL2}, ale za to możesz odrzucić ten czar i kontynuować badania.", "vcmi.spellResearch.comeAgain" : "Badania zostały już przeprowadzone dzisiaj. Wróć jutro.", - "vcmi.spellResearch.pay" : "Czy chcesz zastąpić {%SPELL1} czarem {%SPELL2}? Czy odrzucić ten czar i kontynuować badania?", + "vcmi.spellResearch.pay" : "Czy chcesz zastąpić {%SPELL1} zaklęciem {%SPELL2}? Czy odrzucić ten czar i kontynuować badania?", "vcmi.spellResearch.research" : "Zamień zaklęcia", "vcmi.spellResearch.skip" : "Kontynuuj badania", "vcmi.spellResearch.abort" : "Anuluj", + "vcmi.spellResearch.noMoreSpells" : "Nie ma już więcej zaklęć do zbadania.", "vcmi.mainMenu.serverConnecting" : "Łączenie...", "vcmi.mainMenu.serverAddressEnter" : "Wprowadź adres:", @@ -157,6 +183,7 @@ "vcmi.server.errors.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}", "vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}", "vcmi.server.errors.modNoDependency" : "Nie udało się wczytać moda {'%s'}!\n Jest on zależny od moda {'%s'} który nie jest aktywny!\n", + "vcmi.server.errors.modDependencyLoop" : "Nie udało się wczytać moda {'%s'}!\n Być może znajduje się w pętli zależności", "vcmi.server.errors.modConflict" : "Nie udało się wczytać moda {'%s'}!\n Konflikty z aktywnym modem {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Nie udało się wczytać zapisu! Nieznany element '%s' znaleziony w pliku zapisu! Zapis może nie być zgodny z aktualnie zainstalowaną wersją modów!", @@ -356,6 +383,7 @@ "vcmi.heroWindow.sortBackpackBySlot.help" : "Sortuj artefakty w sakwie według umiejscowienia na ciele", "vcmi.heroWindow.sortBackpackByClass.hover" : "Sortuj wg. jakości", "vcmi.heroWindow.sortBackpackByClass.help" : "Sortuj artefakty w sakwie według jakości: Skarb, Pomniejszy, Potężny, Relikt", + "vcmi.heroWindow.fusingArtifact.fusing" : "Posiadasz wszystkie niezbędne komponenty do stworzenia %s. Czy chcesz wykonać fuzję? {Wszystkie komponenty zostaną użyte}", "vcmi.tavernWindow.inviteHero" : "Zaproś bohatera", @@ -686,5 +714,9 @@ "core.bonus.DISINTEGRATE.name": "Rozpadanie", "core.bonus.DISINTEGRATE.description": "Po śmierci nie pozostaje żaden trup", "core.bonus.INVINCIBLE.name": "Niezwyciężony", - "core.bonus.INVINCIBLE.description": "Nic nie może mieć na niego wpływu" + "core.bonus.INVINCIBLE.description": "Nic nie może mieć na niego wpływu", + "core.bonus.MECHANICAL.name": "Mechaniczny", + "core.bonus.MECHANICAL.description": "Odporny na wiele efektów, naprawialny", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Pryzmaty oddech", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Atakuje pryzmatycznym zionięciem (trzy kierunki)" } From e2fe20e26d41b832a5195d23cf4930ea082622d6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 16 Nov 2024 15:20:33 +0000 Subject: [PATCH 580/726] Use upper_bound instead of sort since predicate does not fulfills strict weak ordering requirement for std::sort --- client/mapView/MapRendererContextState.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/mapView/MapRendererContextState.cpp b/client/mapView/MapRendererContextState.cpp index bb2632ce3..4f7b11726 100644 --- a/client/mapView/MapRendererContextState.cpp +++ b/client/mapView/MapRendererContextState.cpp @@ -54,9 +54,8 @@ void MapRendererContextState::addObject(const CGObjectInstance * obj) if(LOCPLINT->cb->isInTheMap(currTile) && obj->coveringAt(currTile)) { auto & container = objects[currTile.z][currTile.x][currTile.y]; - - container.push_back(obj->id); - boost::range::sort(container, compareObjectBlitOrder); + auto position = std::upper_bound(container.begin(), container.end(), obj->id, compareObjectBlitOrder); + container.insert(position, obj->id); } } } From a4ef45c4f8ffed6374200d9a9a0aae4eb4280a53 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 16 Nov 2024 15:32:36 +0000 Subject: [PATCH 581/726] Move list of war machines in war machine factory to config --- config/objects/generic.json | 1 + lib/mapObjects/CGDwelling.cpp | 8 +------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/config/objects/generic.json b/config/objects/generic.json index d91c008b7..96113cf74 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -223,6 +223,7 @@ "ambient" : ["LOOPFACT"], "visit" : ["MILITARY"] } + "creatures": [["ballista"], ["firstAidTent"], ["ammoCart"] ] }, "types" : { "object" : { diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp index 638480ed2..ac9ca1905 100644 --- a/lib/mapObjects/CGDwelling.cpp +++ b/lib/mapObjects/CGDwelling.cpp @@ -180,6 +180,7 @@ void CGDwelling::initObj(vstd::RNG & rand) { case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR4: + case Obj::WAR_MACHINE_FACTORY: { getObjectHandler()->configureObject(this, rand); assert(!creatures.empty()); @@ -190,13 +191,6 @@ void CGDwelling::initObj(vstd::RNG & rand) //is handled within newturn func break; - case Obj::WAR_MACHINE_FACTORY: - creatures.resize(3); - creatures[0].second.emplace_back(CreatureID::BALLISTA); - creatures[1].second.emplace_back(CreatureID::FIRST_AID_TENT); - creatures[2].second.emplace_back(CreatureID::AMMO_CART); - break; - default: assert(0); break; From 7f0cb6ce6af67de782ae654af48452daf542fb0e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 16 Nov 2024 15:40:05 +0000 Subject: [PATCH 582/726] ISharedImage is now always const, remove creation of image copy when upscaling to same factor --- client/render/IImage.h | 12 ++++++------ client/renderSDL/ImageScaled.cpp | 4 ++-- client/renderSDL/ImageScaled.h | 12 ++++++------ client/renderSDL/RenderHandler.cpp | 12 ++++++------ client/renderSDL/RenderHandler.h | 14 +++++++------- client/renderSDL/SDLImage.cpp | 21 +++++++++------------ client/renderSDL/SDLImage.h | 18 +++++++++--------- 7 files changed, 45 insertions(+), 48 deletions(-) diff --git a/client/render/IImage.h b/client/render/IImage.h index 5e60b0c6a..0fd238ee0 100644 --- a/client/render/IImage.h +++ b/client/render/IImage.h @@ -78,7 +78,7 @@ public: virtual void setShadowEnabled(bool on) = 0; virtual void setBodyEnabled(bool on) = 0; virtual void setOverlayEnabled(bool on) = 0; - virtual std::shared_ptr getSharedImage() const = 0; + virtual std::shared_ptr getSharedImage() const = 0; virtual ~IImage() = default; }; @@ -94,12 +94,12 @@ public: virtual bool isTransparent(const Point & coords) const = 0; virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0; - virtual std::shared_ptr createImageReference(EImageBlitMode mode) = 0; + virtual std::shared_ptr createImageReference(EImageBlitMode mode) const = 0; - virtual std::shared_ptr horizontalFlip() const = 0; - virtual std::shared_ptr verticalFlip() const = 0; - virtual std::shared_ptr scaleInteger(int factor, SDL_Palette * palette) const = 0; - virtual std::shared_ptr scaleTo(const Point & size, SDL_Palette * palette) const = 0; + virtual std::shared_ptr horizontalFlip() const = 0; + virtual std::shared_ptr verticalFlip() const = 0; + virtual std::shared_ptr scaleInteger(int factor, SDL_Palette * palette) const = 0; + virtual std::shared_ptr scaleTo(const Point & size, SDL_Palette * palette) const = 0; virtual ~ISharedImage() = default; diff --git a/client/renderSDL/ImageScaled.cpp b/client/renderSDL/ImageScaled.cpp index 8ad15e00a..4c80a9f52 100644 --- a/client/renderSDL/ImageScaled.cpp +++ b/client/renderSDL/ImageScaled.cpp @@ -21,7 +21,7 @@ #include -ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_ptr & source, EImageBlitMode mode) +ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_ptr & source, EImageBlitMode mode) : source(source) , locator(inputLocator) , colorMultiplier(Colors::WHITE_TRUE) @@ -33,7 +33,7 @@ ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_pt setShadowEnabled(true); } -std::shared_ptr ImageScaled::getSharedImage() const +std::shared_ptr ImageScaled::getSharedImage() const { return body; } diff --git a/client/renderSDL/ImageScaled.h b/client/renderSDL/ImageScaled.h index b0dedc227..8d13d0376 100644 --- a/client/renderSDL/ImageScaled.h +++ b/client/renderSDL/ImageScaled.h @@ -25,16 +25,16 @@ class ImageScaled final : public IImage private: /// Original unscaled image - std::shared_ptr source; + std::shared_ptr source; /// Upscaled shadow of our image, may be null - std::shared_ptr shadow; + std::shared_ptr shadow; /// Upscaled main part of our image, may be null - std::shared_ptr body; + std::shared_ptr body; /// Upscaled overlay (player color, selection highlight) of our image, may be null - std::shared_ptr overlay; + std::shared_ptr overlay; ImageLocator locator; @@ -45,7 +45,7 @@ private: EImageBlitMode blitMode; public: - ImageScaled(const ImageLocator & locator, const std::shared_ptr & source, EImageBlitMode mode); + ImageScaled(const ImageLocator & locator, const std::shared_ptr & source, EImageBlitMode mode); void scaleInteger(int factor) override; void scaleTo(const Point & size) override; @@ -63,5 +63,5 @@ public: void setShadowEnabled(bool on) override; void setBodyEnabled(bool on) override; void setOverlayEnabled(bool on) override; - std::shared_ptr getSharedImage() const override; + std::shared_ptr getSharedImage() const override; }; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index e08821bd8..fc22d20ff 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -212,7 +212,7 @@ ImageLocator RenderHandler::getLocatorForAnimationFrame(const AnimationPath & pa return ImageLocator(path, frame, group); } -std::shared_ptr RenderHandler::loadImageImpl(const ImageLocator & locator) +std::shared_ptr RenderHandler::loadImageImpl(const ImageLocator & locator) { auto it = imageFiles.find(locator); if (it != imageFiles.end()) @@ -231,7 +231,7 @@ std::shared_ptr RenderHandler::loadImageImpl(const ImageLocator & return scaledImage; } -std::shared_ptr RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) +std::shared_ptr RenderHandler::loadImageFromFileUncached(const ImageLocator & locator) { if (locator.image) { @@ -258,7 +258,7 @@ std::shared_ptr RenderHandler::loadImageFromFileUncached(const Ima throw std::runtime_error("Invalid image locator received!"); } -void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr image) +void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_ptr image) { imageFiles[locator] = image; @@ -271,7 +271,7 @@ void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_p #endif } -std::shared_ptr RenderHandler::loadImageFromFile(const ImageLocator & locator) +std::shared_ptr RenderHandler::loadImageFromFile(const ImageLocator & locator) { if (imageFiles.count(locator)) return imageFiles.at(locator); @@ -281,7 +281,7 @@ std::shared_ptr RenderHandler::loadImageFromFile(const ImageLocato return result; } -std::shared_ptr RenderHandler::transformImage(const ImageLocator & locator, std::shared_ptr image) +std::shared_ptr RenderHandler::transformImage(const ImageLocator & locator, std::shared_ptr image) { if (imageFiles.count(locator)) return imageFiles.at(locator); @@ -298,7 +298,7 @@ std::shared_ptr RenderHandler::transformImage(const ImageLocator & return result; } -std::shared_ptr RenderHandler::scaleImage(const ImageLocator & locator, std::shared_ptr image) +std::shared_ptr RenderHandler::scaleImage(const ImageLocator & locator, std::shared_ptr image) { if (imageFiles.count(locator)) return imageFiles.at(locator); diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index 7014251ed..d7b028762 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -25,7 +25,7 @@ class RenderHandler : public IRenderHandler std::map> animationFiles; std::map animationLayouts; - std::map> imageFiles; + std::map> imageFiles; std::map> fonts; std::shared_ptr getAnimationFile(const AnimationPath & path); @@ -36,15 +36,15 @@ class RenderHandler : public IRenderHandler void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName); void addImageListEntries(const EntityService * service); - void storeCachedImage(const ImageLocator & locator, std::shared_ptr image); + void storeCachedImage(const ImageLocator & locator, std::shared_ptr image); - std::shared_ptr loadImageImpl(const ImageLocator & config); + std::shared_ptr loadImageImpl(const ImageLocator & config); - std::shared_ptr loadImageFromFileUncached(const ImageLocator & locator); - std::shared_ptr loadImageFromFile(const ImageLocator & locator); + std::shared_ptr loadImageFromFileUncached(const ImageLocator & locator); + std::shared_ptr loadImageFromFile(const ImageLocator & locator); - std::shared_ptr transformImage(const ImageLocator & locator, std::shared_ptr image); - std::shared_ptr scaleImage(const ImageLocator & locator, std::shared_ptr image); + std::shared_ptr transformImage(const ImageLocator & locator, std::shared_ptr image); + std::shared_ptr scaleImage(const ImageLocator & locator, std::shared_ptr image); ImageLocator getLocatorForAnimationFrame(const AnimationPath & path, int frame, int group); diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index d74024a55..fe3f110e3 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -269,7 +269,7 @@ void SDLImageShared::optimizeSurface() } } -std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palette * palette) const +std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palette * palette) const { if (factor <= 0) throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor)); @@ -279,10 +279,7 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet SDL_Surface * scaled = nullptr; if(preScaleFactor == factor) - { - scaled = CSDL_Ext::newSurface(Point(surf->w, surf->h), surf); - SDL_BlitSurface(surf, nullptr, scaled, nullptr); - } + return shared_from_this(); else if(preScaleFactor == 1) scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ); else @@ -306,7 +303,7 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL_Palet return ret; } -std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const +std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const { float scaleX = float(size.x) / fullSize.x; float scaleY = float(size.y) / fullSize.y; @@ -370,7 +367,7 @@ Point SDLImageShared::dimensions() const return fullSize / preScaleFactor; } -std::shared_ptr SDLImageShared::createImageReference(EImageBlitMode mode) +std::shared_ptr SDLImageShared::createImageReference(EImageBlitMode mode) const { if (surf && surf->format->palette) return std::make_shared(shared_from_this(), originalPalette, mode); @@ -378,7 +375,7 @@ std::shared_ptr SDLImageShared::createImageReference(EImageBlitMode mode return std::make_shared(shared_from_this(), mode); } -std::shared_ptr SDLImageShared::horizontalFlip() const +std::shared_ptr SDLImageShared::horizontalFlip() const { SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf); auto ret = std::make_shared(flipped, preScaleFactor); @@ -390,7 +387,7 @@ std::shared_ptr SDLImageShared::horizontalFlip() const return ret; } -std::shared_ptr SDLImageShared::verticalFlip() const +std::shared_ptr SDLImageShared::verticalFlip() const { SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf); auto ret = std::make_shared(flipped, preScaleFactor); @@ -444,7 +441,7 @@ void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colors } } -SDLImageIndexed::SDLImageIndexed(const std::shared_ptr & image, SDL_Palette * originalPalette, EImageBlitMode mode) +SDLImageIndexed::SDLImageIndexed(const std::shared_ptr & image, SDL_Palette * originalPalette, EImageBlitMode mode) :SDLImageBase::SDLImageBase(image, mode) ,originalPalette(originalPalette) { @@ -541,13 +538,13 @@ SDLImageShared::~SDLImageShared() SDL_FreePalette(originalPalette); } -SDLImageBase::SDLImageBase(const std::shared_ptr & image, EImageBlitMode mode) +SDLImageBase::SDLImageBase(const std::shared_ptr & image, EImageBlitMode mode) :image(image) , alphaValue(SDL_ALPHA_OPAQUE) , blitMode(mode) {} -std::shared_ptr SDLImageBase::getSharedImage() const +std::shared_ptr SDLImageBase::getSharedImage() const { return image; } diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index b5dcfb57a..c6ba35a63 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -57,11 +57,11 @@ public: void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override; Point dimensions() const override; bool isTransparent(const Point & coords) const override; - std::shared_ptr createImageReference(EImageBlitMode mode) override; - std::shared_ptr horizontalFlip() const override; - std::shared_ptr verticalFlip() const override; - std::shared_ptr scaleInteger(int factor, SDL_Palette * palette) const override; - std::shared_ptr scaleTo(const Point & size, SDL_Palette * palette) const override; + std::shared_ptr createImageReference(EImageBlitMode mode) const override; + std::shared_ptr horizontalFlip() const override; + std::shared_ptr verticalFlip() const override; + std::shared_ptr scaleInteger(int factor, SDL_Palette * palette) const override; + std::shared_ptr scaleTo(const Point & size, SDL_Palette * palette) const override; friend class SDLImageLoader; }; @@ -69,19 +69,19 @@ public: class SDLImageBase : public IImage, boost::noncopyable { protected: - std::shared_ptr image; + std::shared_ptr image; uint8_t alphaValue; EImageBlitMode blitMode; public: - SDLImageBase(const std::shared_ptr & image, EImageBlitMode mode); + SDLImageBase(const std::shared_ptr & image, EImageBlitMode mode); bool isTransparent(const Point & coords) const override; Point dimensions() const override; void setAlpha(uint8_t value) override; void setBlitMode(EImageBlitMode mode) override; - std::shared_ptr getSharedImage() const override; + std::shared_ptr getSharedImage() const override; }; class SDLImageIndexed final : public SDLImageBase @@ -95,7 +95,7 @@ class SDLImageIndexed final : public SDLImageBase void setShadowTransparency(float factor); public: - SDLImageIndexed(const std::shared_ptr & image, SDL_Palette * palette, EImageBlitMode mode); + SDLImageIndexed(const std::shared_ptr & image, SDL_Palette * palette, EImageBlitMode mode); ~SDLImageIndexed(); void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override; From c3f675c3ea66c76c0143298c46991296387a6877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Sat, 16 Nov 2024 17:15:55 +0100 Subject: [PATCH 583/726] Remove '-Og' from MINGW debug builds. It interferes with debugging. See https://stackoverflow.com/a/63386263/2323908. Instead, the default O0 is used. This change makes it possible to use whatever optimization levels you want debug builds, which was previously impossible due to the passing of -Og in CMakeLists.txt. --- CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78a4a7027..0629cf1ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,13 +355,6 @@ if(MINGW OR MSVC) if(ICONV_FOUND) set(SYSTEM_LIBS ${SYSTEM_LIBS} iconv) endif() - - # Prevent compiler issues when building Debug - # Assembler might fail with "too many sections" - # With big-obj or 64-bit build will take hours - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Og") - endif() endif(MINGW) endif(MINGW OR MSVC) From 227e403e88e9037f82c0f1cb1c065b73943897b1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 16 Nov 2024 16:22:43 +0000 Subject: [PATCH 584/726] Fix crash on advancing to 3rd scenario of Birth of Barbarian campaign - spellbook removal was not working correctly when hero does not keeps his spells --- lib/mapObjects/CGHeroInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 182494206..a512065be 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1243,7 +1243,7 @@ void CGHeroInstance::removeSpellbook() if(hasSpellbook()) { - cb->removeArtifact(ArtifactLocation(this->id, ArtifactPosition::SPELLBOOK)); + cb->gameState()->map->removeArtifactInstance(*this, ArtifactPosition::SPELLBOOK); } } From 4ae81cbac081493d52ac0ee0832b109dc51953e9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 16 Nov 2024 16:23:02 +0000 Subject: [PATCH 585/726] Add missing field to serialization --- lib/rewardable/Reward.h | 2 ++ lib/serializer/ESerializationVersion.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rewardable/Reward.h b/lib/rewardable/Reward.h index 1f279a61d..a9ad214c9 100644 --- a/lib/rewardable/Reward.h +++ b/lib/rewardable/Reward.h @@ -129,6 +129,8 @@ struct DLL_LINKAGE Reward final h & removeObject; h & manaPercentage; h & movePercentage; + if (h.version >= Handler::Version::REWARDABLE_GUARDS) + h & guards; h & heroExperience; h & heroLevel; h & manaDiff; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 52b4709bb..5e038bbf0 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -67,6 +67,7 @@ enum class ESerializationVersion : int32_t REMOVE_OBJECT_TYPENAME, // 868 - remove typename from CGObjectInstance REMOVE_VLC_POINTERS, // 869 removed remaining pointers to VLC entities FOLDER_NAME_REWORK, // 870 - rework foldername + REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects - CURRENT = FOLDER_NAME_REWORK + CURRENT = REWARDABLE_GUARDS }; From 888825592cf09fba2e37b0a736c1225bd9dd01fd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 16 Nov 2024 16:44:42 +0000 Subject: [PATCH 586/726] Fix json formatting --- config/objects/generic.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/objects/generic.json b/config/objects/generic.json index 96113cf74..238cfd848 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -222,8 +222,8 @@ "sounds" : { "ambient" : ["LOOPFACT"], "visit" : ["MILITARY"] - } - "creatures": [["ballista"], ["firstAidTent"], ["ammoCart"] ] + }, + "creatures": [ ["ballista"], ["firstAidTent"], ["ammoCart"] ] }, "types" : { "object" : { From 7cb8ca174ecf0c1a5392248fac42cb2c78d83278 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sun, 17 Nov 2024 00:28:25 +0800 Subject: [PATCH 587/726] fix crash: defender is killed by spell before attack, but attacker still do attack action --- server/battles/BattleActionProcessor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 6c0799cbd..6855fd6c9 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -916,6 +916,10 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const if(defender && first && !counter) handleAttackBeforeCasting(battle, ranged, attacker, defender); + // If the attacker or defender is not alive before the attack action, the action should be skipped. + if((attacker && !attacker->alive()) || (defender && !defender->alive())) + return; + FireShieldInfo fireShield; BattleAttack bat; BattleLogMessage blm; From d862351ad68a1a09431e92c119230b9e40ce71c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Zaj=C4=85c?= <4561094+ToRRent1812@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:08:51 +0100 Subject: [PATCH 588/726] Pryzmatyczny --- Mods/vcmi/config/polish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/polish.json b/Mods/vcmi/config/polish.json index 4bf7ad2aa..9f9a68bab 100644 --- a/Mods/vcmi/config/polish.json +++ b/Mods/vcmi/config/polish.json @@ -717,6 +717,6 @@ "core.bonus.INVINCIBLE.description": "Nic nie może mieć na niego wpływu", "core.bonus.MECHANICAL.name": "Mechaniczny", "core.bonus.MECHANICAL.description": "Odporny na wiele efektów, naprawialny", - "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Pryzmaty oddech", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Pryzmatyczny oddech", "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Atakuje pryzmatycznym zionięciem (trzy kierunki)" } From 812a1217de2402928044c8b170fa7b54a4babcf7 Mon Sep 17 00:00:00 2001 From: Damien Abeloos <17027356+abd-msyukyu-odoo@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:16:48 +0100 Subject: [PATCH 589/726] [FIX] angels are not doubleWide --- config/creatures/castle.json | 1 - 1 file changed, 1 deletion(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 23e9f840d..97dce1531 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -344,7 +344,6 @@ "index": 12, "level": 7, "faction": "castle", - "doubleWide" : true, "abilities": { "KING_2" : // Will be affected by Advanced Slayer or better From 89d77767759ab33ac5200d344cf111ddca085c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Sat, 16 Nov 2024 22:41:05 +0100 Subject: [PATCH 590/726] fix compilation error due to 'too many sections' for MinGW debug builds on Windows --- lib/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ad817733a..e47027b22 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -790,6 +790,16 @@ if(WIN32) ) endif() +# Use -Og for files that generate very large object files +# when compiling with MinGW lest you get "too many sections" assembler errors +if(MINGW AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set_source_files_properties( + serializer/SerializerReflection.cpp + IGameCallback.cpp + PROPERTIES + COMPILE_OPTIONS "-Og") +endif() + vcmi_set_output_dir(vcmi "") enable_pch(vcmi) From dc1908ad2a16aa4e8bbb317856029dba6b1cb4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Sat, 16 Nov 2024 22:46:27 +0100 Subject: [PATCH 591/726] Use -Wa,-mbig-obj instead of -Og to retain debuggability in SerializerReflection.cpp and IGameCallback.cpp files --- lib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e47027b22..1584506c9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -797,7 +797,7 @@ if(MINGW AND CMAKE_BUILD_TYPE STREQUAL "Debug") serializer/SerializerReflection.cpp IGameCallback.cpp PROPERTIES - COMPILE_OPTIONS "-Og") + COMPILE_OPTIONS "-Wa,-mbig-obj") endif() vcmi_set_output_dir(vcmi "") From f28502c9816f5250d7cbb11323f52969e37cfa3a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:53:12 +0100 Subject: [PATCH 592/726] update german --- Mods/vcmi/config/german.json | 59 +++++++- launcher/translation/chinese.ts | 2 +- launcher/translation/czech.ts | 2 +- launcher/translation/english.ts | 2 +- launcher/translation/french.ts | 2 +- launcher/translation/german.ts | 60 ++++---- launcher/translation/polish.ts | 2 +- launcher/translation/portuguese.ts | 2 +- launcher/translation/russian.ts | 2 +- launcher/translation/spanish.ts | 2 +- launcher/translation/swedish.ts | 2 +- launcher/translation/ukrainian.ts | 2 +- launcher/translation/vietnamese.ts | 2 +- mapeditor/translation/chinese.ts | 106 ++++++++------ mapeditor/translation/czech.ts | 108 ++++++++------ mapeditor/translation/english.ts | 107 ++++++++------ mapeditor/translation/french.ts | 107 ++++++++------ mapeditor/translation/german.ts | 209 +++++++++++++++------------- mapeditor/translation/polish.ts | 108 ++++++++------ mapeditor/translation/portuguese.ts | 107 ++++++++------ mapeditor/translation/russian.ts | 108 ++++++++------ mapeditor/translation/spanish.ts | 107 ++++++++------ mapeditor/translation/ukrainian.ts | 108 ++++++++------ mapeditor/translation/vietnamese.ts | 106 ++++++++------ 24 files changed, 821 insertions(+), 601 deletions(-) diff --git a/Mods/vcmi/config/german.json b/Mods/vcmi/config/german.json index 5d6abf103..cb7e315d0 100644 --- a/Mods/vcmi/config/german.json +++ b/Mods/vcmi/config/german.json @@ -25,8 +25,16 @@ "vcmi.adventureMap.playerAttacked" : "Spieler wurde attackiert: %s", "vcmi.adventureMap.moveCostDetails" : "Bewegungspunkte - Kosten: %TURNS Runden + %POINTS Punkte, Verbleibende Punkte: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Bewegungspunkte - Kosten: %POINTS Punkte, Verbleibende Punkte: %REMAINING", + "vcmi.adventureMap.movementPointsHeroInfo" : "(Bewegungspunkte: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Das Wiederholen des gegnerischen Zuges ist aktuell noch nicht implementiert!", + "vcmi.bonusSource.artifact" : "Artefakt", + "vcmi.bonusSource.creature" : "Fähigkeit", + "vcmi.bonusSource.spell" : "Zauber", + "vcmi.bonusSource.hero" : "Held", + "vcmi.bonusSource.commander" : "Commander", + "vcmi.bonusSource.other" : "Anderes", + "vcmi.capitalColors.0" : "Rot", "vcmi.capitalColors.1" : "Blau", "vcmi.capitalColors.2" : "Braun", @@ -41,6 +49,12 @@ "vcmi.heroOverview.secondarySkills" : "Sekundäre Skills", "vcmi.heroOverview.spells" : "Zaubersprüche", + "vcmi.quickExchange.moveUnit" : "Einheit bewegen", + "vcmi.quickExchange.moveAllUnits" : "Alle Einheiten bewegen", + "vcmi.quickExchange.swapAllUnits" : "Einheiten tauschen", + "vcmi.quickExchange.moveAllArtifacts" : "Alle Artefakte bewegen", + "vcmi.quickExchange.swapAllArtifacts" : "Artefakte tauschen", + "vcmi.radialWheel.mergeSameUnit" : "Gleiche Kreaturen zusammenführen", "vcmi.radialWheel.fillSingleUnit" : "Füllen mit einzelnen Kreaturen", "vcmi.radialWheel.splitSingleUnit" : "Wegtrennen einzelner Kreaturen", @@ -59,6 +73,16 @@ "vcmi.radialWheel.moveUp" : "Nach oben bewegen", "vcmi.radialWheel.moveDown" : "Nach unten bewegen", "vcmi.radialWheel.moveBottom" : "Ganz nach unten bewegen", + + "vcmi.randomMap.description" : "Die Karte wurde mit dem Zufallsgenerator erstellt.\nTemplate war %s, Größe %dx%d, Level %d, Spieler %d, Computer %d, Wasser %s, Monster %s, VCMI-Karte", + "vcmi.randomMap.description.isHuman" : ", %s ist Mensch", + "vcmi.randomMap.description.townChoice" : ", %s Stadt-Wahl ist %s", + "vcmi.randomMap.description.water.none" : "Kein", + "vcmi.randomMap.description.water.normal" : "Normal", + "vcmi.randomMap.description.water.islands" : "Inseln", + "vcmi.randomMap.description.monster.weak" : "Schwach", + "vcmi.randomMap.description.monster.normal" : "Normal", + "vcmi.randomMap.description.monster.strong" : "Stark", "vcmi.spellBook.search" : "suchen...", @@ -68,6 +92,7 @@ "vcmi.spellResearch.research" : "Erforsche diesen Zauberspruch", "vcmi.spellResearch.skip" : "Überspringe diesen Zauberspruch", "vcmi.spellResearch.abort" : "Abbruch", + "vcmi.spellResearch.noMoreSpells" : "Es sind keine weiteren Zaubersprüche für die Forschung verfügbar.", "vcmi.mainMenu.serverConnecting" : "Verbinde...", "vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:", @@ -89,7 +114,13 @@ "vcmi.lobby.handicap.resource" : "Gibt den Spielern entsprechende Ressourcen zum Start zusätzlich zu den normalen Startressourcen. Negative Werte sind erlaubt, werden aber insgesamt auf 0 begrenzt (der Spieler beginnt nie mit negativen Ressourcen).", "vcmi.lobby.handicap.income" : "Verändert die verschiedenen Einkommen des Spielers um den Prozentsatz. Wird aufgerundet.", "vcmi.lobby.handicap.growth" : "Verändert die Wachstumsrate der Kreaturen in den Städten, die der Spieler besitzt. Wird aufgerundet.", - + "vcmi.lobby.deleteUnsupportedSave" : "{Nicht unterstützte Spielstände gefunden}\n\nVCMI hat %d gespeicherte Spiele gefunden, die nicht mehr unterstützt werden, möglicherweise aufgrund von Unterschieden in VCMI-Versionen.\n\nMöchtet Ihr sie löschen?", + "vcmi.lobby.deleteSaveGameTitle" : "Wählt gespeichertes Spiel zum Löschen aus", + "vcmi.lobby.deleteMapTitle" : "Wählt ein zu löschendes Szenario", + "vcmi.lobby.deleteFile" : "Möchtet Ihr folgende Datei löschen?", + "vcmi.lobby.deleteFolder" : "Möchtet Ihr folgenden Ordner löschen?", + "vcmi.lobby.deleteMode" : "In den Löschmodus wechseln und zurück", + "vcmi.lobby.login.title" : "VCMI Online Lobby", "vcmi.lobby.login.username" : "Benutzername:", "vcmi.lobby.login.connecting" : "Verbinde...", @@ -153,10 +184,12 @@ "vcmi.client.errors.invalidMap" : "{Ungültige Karte oder Kampagne}\n\nDas Spiel konnte nicht gestartet werden! Die ausgewählte Karte oder Kampagne ist möglicherweise ungültig oder beschädigt. Grund:\n%s", "vcmi.client.errors.missingCampaigns" : "{Fehlende Dateien}\n\nEs wurden keine Kampagnendateien gefunden! Möglicherweise verwendest du unvollständige oder beschädigte Heroes 3 Datendateien. Bitte installiere die Spieldaten neu.", "vcmi.server.errors.disconnected" : "{Netzwerkfehler}\n\nDie Verbindung zum Spielserver wurde unterbrochen!", + "vcmi.server.errors.playerLeft" : "{Verlassen eines Spielers}\n\n%s Spieler hat die Verbindung zum Spiel unterbrochen!", //%s -> player color "vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst", "vcmi.server.errors.modsToEnable" : "{Erforderliche Mods um das Spiel zu laden}", "vcmi.server.errors.modsToDisable" : "{Folgende Mods müssen deaktiviert werden}", "vcmi.server.errors.modNoDependency" : "Mod {'%s'} konnte nicht geladen werden!\n Sie hängt von Mod {'%s'} ab, die nicht aktiv ist!\n", + "vcmi.server.errors.modDependencyLoop" : "Mod {'%s'} konnte nicht geladen werden.!\n Möglicherweise befindet sie sich in einer (weichen) Abhängigkeitsschleife.", "vcmi.server.errors.modConflict" : "Mod {'%s'} konnte nicht geladen werden!\n Konflikte mit aktiver Mod {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Spielstand konnte nicht geladen werden! Unbekannte Entität '%s' im gespeicherten Spiel gefunden! Der Spielstand ist möglicherweise nicht mit der aktuell installierten Version der Mods kompatibel!", @@ -339,6 +372,9 @@ "vcmi.townHall.missingBase" : "Basis Gebäude %s muss als erstes gebaut werden", "vcmi.townHall.noCreaturesToRecruit" : "Es gibt keine Kreaturen zu rekrutieren!", + "vcmi.townStructure.bank.borrow" : "Ihr betretet die Bank. Ein Bankangestellter sieht Euch und sagt: \"Wir haben ein spezielles Angebot für Euch gemacht. Ihr könnt bei uns einen Kredit von 2500 Gold für 5 Tage aufnehmen. Ihr werdet jeden Tag 500 Gold zurückzahlen müssen.\"", + "vcmi.townStructure.bank.payBack" : "Ihr betretet die Bank. Ein Bankangestellter sieht Euch und sagt: \"Ihr habt Euren Kredit bereits erhalten. Zahlt Ihn ihn zurück, bevor Ihr einen neuen aufnehmt.\"", + "vcmi.logicalExpressions.anyOf" : "Eines der folgenden:", "vcmi.logicalExpressions.allOf" : "Alles der folgenden:", "vcmi.logicalExpressions.noneOf" : "Keines der folgenden:", @@ -347,6 +383,13 @@ "vcmi.heroWindow.openCommander.help" : "Zeige Informationen über Kommandanten dieses Helden", "vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen", "vcmi.heroWindow.openBackpack.help" : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert", + "vcmi.heroWindow.sortBackpackByCost.hover" : "Nach Kosten sortieren", + "vcmi.heroWindow.sortBackpackByCost.help" : "Artefakte im Rucksack nach Kosten sortieren.", + "vcmi.heroWindow.sortBackpackBySlot.hover" : "Nach Slot sortieren", + "vcmi.heroWindow.sortBackpackBySlot.help" : "Artefakte im Rucksack nach Ausrüstungsslot sortieren.", + "vcmi.heroWindow.sortBackpackByClass.hover" : "Nach Klasse sortieren", + "vcmi.heroWindow.sortBackpackByClass.help" : "Artefakte im Rucksack nach Artefaktklasse sortieren. Schatz, Klein, Groß, Relikt", + "vcmi.heroWindow.fusingArtifact.fusing" : "Ihr verfügt über alle Komponenten, die für die Fusion der %s benötigt werden. Möchtet Ihr die Verschmelzung durchführen? {Alle Komponenten werden bei der Fusion verbraucht.}", "vcmi.tavernWindow.inviteHero" : "Helden einladen", @@ -523,7 +566,9 @@ "core.seerhut.quest.reachDate.visit.3" : "Geschlossen bis %s.", "core.seerhut.quest.reachDate.visit.4" : "Geschlossen bis %s.", "core.seerhut.quest.reachDate.visit.5" : "Geschlossen bis %s.", - + + "mapObject.core.hillFort.object.description" : "Aufwertungen von Kreaturen. Die Stufen 1 - 4 sind billiger als in der zugehörigen Stadt.", + "core.bonus.ADDITIONAL_ATTACK.name": "Doppelschlag", "core.bonus.ADDITIONAL_ATTACK.description": "Greift zweimal an", "core.bonus.ADDITIONAL_RETALIATION.name": "Zusätzliche Vergeltungsmaßnahmen", @@ -671,5 +716,13 @@ "core.bonus.WATER_IMMUNITY.name": "Wasser-Immunität", "core.bonus.WATER_IMMUNITY.description": "Immun gegen alle Zauber der Wasserschule", "core.bonus.WIDE_BREATH.name": "Breiter Atem", - "core.bonus.WIDE_BREATH.description": "Breiter Atem-Angriff (mehrere Felder)" + "core.bonus.WIDE_BREATH.description": "Breiter Atem-Angriff (mehrere Felder)", + "core.bonus.DISINTEGRATE.name": "Auflösen", + "core.bonus.DISINTEGRATE.description": "Kein Leichnam bleibt nach dem Tod übrig", + "core.bonus.INVINCIBLE.name": "Unbesiegbar", + "core.bonus.INVINCIBLE.description": "Kann durch nichts beeinflusst werden", + "core.bonus.MECHANICAL.name": "Mechanisch", + "core.bonus.MECHANICAL.description": "Immunität gegen viele Effekte, reparierbar", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Prisma-Atem", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Prisma-Atem-Angriff (drei Richtungen)" } diff --git a/launcher/translation/chinese.ts b/launcher/translation/chinese.ts index c80f1cfaa..fe5e964e6 100644 --- a/launcher/translation/chinese.ts +++ b/launcher/translation/chinese.ts @@ -1091,7 +1091,7 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Heroes Chronicles 英雄无敌历代记 diff --git a/launcher/translation/czech.ts b/launcher/translation/czech.ts index 9c219d942..3668ded50 100644 --- a/launcher/translation/czech.ts +++ b/launcher/translation/czech.ts @@ -1085,7 +1085,7 @@ Exkluzivní celá obrazovka - hra zakryje vaši celou obrazovku a použije vybra - + Heroes Chronicles Heroes Chronicles diff --git a/launcher/translation/english.ts b/launcher/translation/english.ts index 0114b4501..63a07403e 100644 --- a/launcher/translation/english.ts +++ b/launcher/translation/english.ts @@ -1071,7 +1071,7 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Heroes Chronicles diff --git a/launcher/translation/french.ts b/launcher/translation/french.ts index bd7f7dc30..c20d1f79a 100644 --- a/launcher/translation/french.ts +++ b/launcher/translation/french.ts @@ -1090,7 +1090,7 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra - + Heroes Chronicles diff --git a/launcher/translation/german.ts b/launcher/translation/german.ts index 90c01cbb3..4491995a6 100644 --- a/launcher/translation/german.ts +++ b/launcher/translation/german.ts @@ -442,17 +442,17 @@ Gog files - + Gog-Dateien All files (*.*) - + Alle Dateien (*.*) Select files (configs, mods, maps, campaigns, gog files) to install... - + Wähle zu installierenden Dateien aus (Konfigs, Mods, Karten, Kampagnen, Gog-Dateien)... @@ -499,7 +499,7 @@ Installation erfolgreich heruntergeladen? Installing chronicles - + Installation der Chronicles @@ -667,7 +667,7 @@ Installation erfolgreich heruntergeladen? Downscaling Filter - + Herunterskalierungsfilter @@ -692,7 +692,7 @@ Installation erfolgreich heruntergeladen? Automatic (Linear) - + Automatisch (linear) @@ -709,42 +709,42 @@ Installation erfolgreich heruntergeladen? Automatic - + Automatisch Mods Validation - + Mod-Validierung None - + Keiner xBRZ x2 - + xBRZ x2 xBRZ x3 - + xBRZ x3 xBRZ x4 - + xBRZ x4 Full - + Voll Use scalable fonts - + Skalierbare Schriftarten verwenden @@ -754,27 +754,27 @@ Installation erfolgreich heruntergeladen? Cursor Scaling - + Cursor-Skalierung Scalable - + Skalierbar Miscellaneous - + Sonstiges Font Scaling (experimental) - + Schriftskalierung (experimentell) Original - + Original @@ -784,7 +784,7 @@ Installation erfolgreich heruntergeladen? Basic - + Grundlegend @@ -1059,35 +1059,35 @@ Exklusiver Vollbildmodus - das Spiel bedeckt den gesamten Bildschirm und verwend File cannot opened - + Datei kann nicht geöffnet werden Invalid file selected - Ungültige Datei ausgewählt + Ungültige Datei ausgewählt You have to select an gog installer file! - + Sie müssen eine Gog-Installer-Datei auswählen! You have to select an chronicle installer file! - + Sie müssen eine Chronicle-Installationsdatei auswählen! Extracting error! - Fehler beim Extrahieren! + Fehler beim Extrahieren! - + Heroes Chronicles - + Heroes Chronicles @@ -1422,18 +1422,18 @@ Bitte wählen Sie ein Verzeichnis mit Heroes III: Complete Edition oder Heroes I Stream error while extracting files! error reason: - Stream-Fehler beim Extrahieren von Dateien! + Stream-Fehler beim Extrahieren von Dateien! Fehlerursache: Not a supported Inno Setup installer! - Kein unterstütztes Inno Setup Installationsprogramm! + Kein unterstütztes Inno Setup Installationsprogramm! VCMI was compiled without innoextract support, which is needed to extract exe files! - + VCMI wurde ohne innoextract-Unterstützung kompiliert, die zum Extrahieren von exe-Dateien benötigt wird! diff --git a/launcher/translation/polish.ts b/launcher/translation/polish.ts index 43b478497..75a62d829 100644 --- a/launcher/translation/polish.ts +++ b/launcher/translation/polish.ts @@ -1085,7 +1085,7 @@ Pełny ekran klasyczny - gra przysłoni cały ekran uruchamiając się w wybrane - + Heroes Chronicles diff --git a/launcher/translation/portuguese.ts b/launcher/translation/portuguese.ts index e102efcbe..2dd4103cf 100644 --- a/launcher/translation/portuguese.ts +++ b/launcher/translation/portuguese.ts @@ -1085,7 +1085,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu - + Heroes Chronicles Heroes Chronicles diff --git a/launcher/translation/russian.ts b/launcher/translation/russian.ts index 20a45663b..d1f2acfa0 100644 --- a/launcher/translation/russian.ts +++ b/launcher/translation/russian.ts @@ -1071,7 +1071,7 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Heroes Chronicles diff --git a/launcher/translation/spanish.ts b/launcher/translation/spanish.ts index a1e9e19f6..7573df603 100644 --- a/launcher/translation/spanish.ts +++ b/launcher/translation/spanish.ts @@ -1084,7 +1084,7 @@ Pantalla completa - el juego cubrirá la totalidad de la pantalla y utilizará l - + Heroes Chronicles diff --git a/launcher/translation/swedish.ts b/launcher/translation/swedish.ts index f92834d02..2bd48e043 100644 --- a/launcher/translation/swedish.ts +++ b/launcher/translation/swedish.ts @@ -1085,7 +1085,7 @@ Exklusivt helskärmsläge - spelet kommer att täcka hela skärmen och använda - + Heroes Chronicles diff --git a/launcher/translation/ukrainian.ts b/launcher/translation/ukrainian.ts index efdf8ea08..1d0563981 100644 --- a/launcher/translation/ukrainian.ts +++ b/launcher/translation/ukrainian.ts @@ -1085,7 +1085,7 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - + Heroes Chronicles diff --git a/launcher/translation/vietnamese.ts b/launcher/translation/vietnamese.ts index 0f96f2242..edaae3fa7 100644 --- a/launcher/translation/vietnamese.ts +++ b/launcher/translation/vietnamese.ts @@ -1077,7 +1077,7 @@ Toàn màn hình riêng biệt - Trò chơi chạy toàn màn hình và dùng đ - + Heroes Chronicles diff --git a/mapeditor/translation/chinese.ts b/mapeditor/translation/chinese.ts index a55371343..bd1064557 100644 --- a/mapeditor/translation/chinese.ts +++ b/mapeditor/translation/chinese.ts @@ -359,7 +359,7 @@ - + View underground 查看地下 @@ -441,9 +441,9 @@ - - - + + + Update appearance 更新外观 @@ -574,92 +574,92 @@ Ctrl+Shift+= - + Confirmation 确认 - + Unsaved changes will be lost, are you sure? 未保存的改动会丢失,你确定要这么做吗? - + Open map 打开地图 - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) 所有支持的地图类型(*.vmap *.h3m);;VCMI地图(*.vmap);;英雄无敌3地图(*.h3m) - + Save map 保存地图 - + VCMI maps (*.vmap) VCMI地图(*.vmap) - + Type 类型 - + View surface 查看地上 - + No objects selected 未选择任何物体 - + This operation is irreversible. Do you want to continue? 此操作无法被撤销,你确定要继续么? - + Errors occurred. %1 objects were not updated 发生错误!%1 物体未完成更新 - + Save to image 保存为图片 - + Select maps to convert 选择待转换的地图 - + HoMM3 maps(*.h3m) 英雄无敌3地图文件(*.h3m) - + Choose directory to save converted maps 选择保存转换地图的目录 - + Operation completed 操作完成 - + Successfully converted %1 maps 成功转换 %1 地图 - + Failed to convert the map. Abort operation 转换地图失败,操作终止 @@ -844,7 +844,7 @@ (默认) - + Player ID: %1 玩家ID: %1 @@ -909,53 +909,67 @@ 高级 - + Compliant 屈服的 - + Friendly 友善的 - + Aggressive 好斗的 - + Hostile 有敌意的 - + Savage 野蛮的 - - + + + No patrol + + + + + + %n tile(s) + + + + + + + neutral 中立 - + UNFLAGGABLE 没有旗帜 - + Can't place object 无法放置物体 - + There can only be one grail object on the map. 只能放置一个神器在地图上。 - + Hero %1 cannot be created as NEUTRAL. 英雄 %1 无法在中立阵营被创建。 @@ -1083,12 +1097,12 @@ 玩家
- + None - + Day %1 第 %1 日 @@ -1356,18 +1370,18 @@ 玩家
- + None - + Day %1 %1 天 - - + + Reward %1 奖励 %1 @@ -1433,32 +1447,32 @@ 首次发生天数
- + Repeat after (0 = no repeat) 重复周期 (0 = 不重复) - + Affected players 生效玩家 - + Resources 资源 - + type 类型 - + qty 数量 - + Ok 确定 diff --git a/mapeditor/translation/czech.ts b/mapeditor/translation/czech.ts index a7456cf42..448af1e3d 100644 --- a/mapeditor/translation/czech.ts +++ b/mapeditor/translation/czech.ts @@ -359,7 +359,7 @@
- + View underground Zobrazit podzemí @@ -441,9 +441,9 @@
- - - + + + Update appearance Aktualizovat vzhled @@ -574,92 +574,92 @@ Ctrl+Shift+=
- + Confirmation Potvrzení - + Unsaved changes will be lost, are you sure? Neuložené změny budou ztraceny, jste si jisti? - + Open map Otevřít mapu - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Všechny podporované mapy (*.vmap *.h3m);; Mapy VCMI(*.vmap);;Mapy HoMM3(*.h3m) - + Save map Uložit mapu - + VCMI maps (*.vmap) Mapy VCMI (*.vmap) - + Type Druh - + View surface Zobrazit povrch - + No objects selected Nejsou vybrány žádné objekty - + This operation is irreversible. Do you want to continue? Tento úkon je nezvratný. Chcete pokračovat? - + Errors occurred. %1 objects were not updated Nastaly chyby. Nebylo aktualizováno %1 objektů - + Save to image Uložit do obrázku - + Select maps to convert Vyberte mapy pro převod - + HoMM3 maps(*.h3m) Mapy HoMM3 (*.h3m) - + Choose directory to save converted maps Vyberte složku pro uložení převedených map - + Operation completed Operace dokončena - + Successfully converted %1 maps Úspěšně převedeno %1 map - + Failed to convert the map. Abort operation Převod map selhal. Úkon zrušen @@ -844,7 +844,7 @@ (výchozí)
- + Player ID: %1 ID hráče: %1 @@ -909,53 +909,69 @@ Expert
- + Compliant Ochotná - + Friendly Přátelská - + Aggressive Agresivní - + Hostile Nepřátelská - + Savage Brutální - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + + neutral neutrální - + UNFLAGGABLE NEOZNAČITELNÝ - + Can't place object Nelze umístit objekt - + There can only be one grail object on the map. Na mapě může být pouze jeden grál. - + Hero %1 cannot be created as NEUTRAL. Hrdina %1 nemůže být vytvořen jako NEUTRÁLNÍ. @@ -1083,12 +1099,12 @@ Hráči
- + None Žádný - + Day %1 Den %1 @@ -1356,18 +1372,18 @@ Hráči
- + None Žádný - + Day %1 Den %1 - - + + Reward %1 Odměna %1 @@ -1433,32 +1449,32 @@ Den prvního výskytu
- + Repeat after (0 = no repeat) Opakovat po (0 = bez opak.) - + Affected players Ovlivnění hráči - + Resources Zdroje - + type druh - + qty množství - + Ok Dobře diff --git a/mapeditor/translation/english.ts b/mapeditor/translation/english.ts index 2eec9132d..69f249ac0 100644 --- a/mapeditor/translation/english.ts +++ b/mapeditor/translation/english.ts @@ -359,7 +359,7 @@
- + View underground @@ -441,9 +441,9 @@
- - - + + + Update appearance @@ -574,92 +574,92 @@
- + Confirmation - + Unsaved changes will be lost, are you sure? - + Open map - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) - + Save map - + VCMI maps (*.vmap) - + Type - + View surface - + No objects selected - + This operation is irreversible. Do you want to continue? - + Errors occurred. %1 objects were not updated - + Save to image - + Select maps to convert - + HoMM3 maps(*.h3m) - + Choose directory to save converted maps - + Operation completed - + Successfully converted %1 maps - + Failed to convert the map. Abort operation @@ -844,7 +844,7 @@
- + Player ID: %1 @@ -909,53 +909,68 @@
- + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + neutral - + UNFLAGGABLE - + Can't place object - + There can only be one grail object on the map. - + Hero %1 cannot be created as NEUTRAL. @@ -1083,12 +1098,12 @@
- + None - + Day %1 @@ -1356,18 +1371,18 @@
- + None - + Day %1 - - + + Reward %1 @@ -1433,32 +1448,32 @@
- + Repeat after (0 = no repeat) - + Affected players - + Resources - + type - + qty - + Ok diff --git a/mapeditor/translation/french.ts b/mapeditor/translation/french.ts index 919ed20b9..303fe6915 100644 --- a/mapeditor/translation/french.ts +++ b/mapeditor/translation/french.ts @@ -359,7 +359,7 @@
- + View underground Voir le sous-sol @@ -441,9 +441,9 @@
- - - + + + Update appearance Mettre à jour l'apparence @@ -574,92 +574,92 @@ Ctrl+Maj+=
- + Confirmation Confirmation - + Unsaved changes will be lost, are you sure? Les modifications non sauvegardées seront perdues. Êtes-vous sûr ? - + Open map Ouvrir une carte - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Toutes les cartes prises en charge (*.vmap *.h3m);;Cartes VCMI (*.vmap);;Cartes HoMM3 (*.h3m) - + Save map Enregistrer la carte - + VCMI maps (*.vmap) Cartes VCMI (*.vmap) - + Type Type - + View surface Afficher la surface - + No objects selected Pas d'objets sélectionnés - + This operation is irreversible. Do you want to continue? Cette opération est irreversible. Voulez-vous continuer ? - + Errors occurred. %1 objects were not updated Erreur rencontrée. %1 objets n'ont pas étés mis à jour - + Save to image Sauvegarder en tant qu'image - + Select maps to convert Sélectionner les cartes à convertir - + HoMM3 maps(*.h3m) Cartes HoMM3(*.h3m) - + Choose directory to save converted maps Sélectionner le dossier ou sauvegarder les cartes converties - + Operation completed Opération terminée - + Successfully converted %1 maps Conversion éffectuée avec succès des %1 cartes - + Failed to convert the map. Abort operation Erreur de conversion de carte. Opération annulée @@ -844,7 +844,7 @@ (par défaut)
- + Player ID: %1 Identifiant du joueur : %1 @@ -909,53 +909,68 @@ Expert
- + Compliant Compérhensif - + Friendly Amical - + Aggressive Aggressif - + Hostile Hostile - + Savage Sauvage - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + neutral neutre - + UNFLAGGABLE INCLASSABLE - + Can't place object Impossible de placer l'objet - + There can only be one grail object on the map. Il ne peut y avoir qu'un objet Graal sur la carte. - + Hero %1 cannot be created as NEUTRAL. Le héro %1 ne peut pas être créé en tant que NEUTRE. @@ -1083,12 +1098,12 @@ Joueurs
- + None Aucune - + Day %1 Jour %1 @@ -1356,18 +1371,18 @@ Joueurs
- + None Aucune - + Day %1 Jour %1 - - + + Reward %1 Récompense %1 @@ -1433,32 +1448,32 @@ Jour de la première occurrence
- + Repeat after (0 = no repeat) Récurrence (0 = pas de récurrence) - + Affected players Joueurs affectés - + Resources Resources - + type type - + qty quantité - + Ok OK diff --git a/mapeditor/translation/german.ts b/mapeditor/translation/german.ts index eebafd7ee..71f0c0cd9 100644 --- a/mapeditor/translation/german.ts +++ b/mapeditor/translation/german.ts @@ -67,22 +67,22 @@ Author - + Autor Author contact (e.g. email) - + Autor-Kontakt (z.B. E-Mail) Map Creation Time - + Kartenerstellungszeitpunkt Map Version - + Kartenversion @@ -359,7 +359,7 @@ - + View underground Ansicht Untergrund @@ -441,9 +441,9 @@
- - - + + + Update appearance Aussehen aktualisieren @@ -574,92 +574,92 @@ Strg+Umschalt+= - + Confirmation Bestätigung - + Unsaved changes will be lost, are you sure? Ungespeicherte Änderungen gehen verloren, sind sie sicher? - + Open map Karte öffnen - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Alle unterstützten Karten (*.vmap *.h3m);;VCMI-Karten (*.vmap);;HoMM3-Karten (*.h3m) - + Save map Karte speichern - + VCMI maps (*.vmap) VCMI-Karten (*.vmap) - + Type Typ - + View surface Oberfläche anzeigen - + No objects selected Keine Objekte selektiert - + This operation is irreversible. Do you want to continue? Diese Operation ist unumkehrbar. Möchten sie fortsetzen? - + Errors occurred. %1 objects were not updated Fehler sind aufgetreten. %1 Objekte konnten nicht aktualisiert werden - + Save to image Als Bild speichern - + Select maps to convert Zu konvertierende Karten auswählen - + HoMM3 maps(*.h3m) HoMM3-Karten (*.h3m) - + Choose directory to save converted maps Verzeichnis zum Speichern der konvertierten Karten wählen - + Operation completed Vorgang abgeschlossen - + Successfully converted %1 maps Erfolgreiche Konvertierung von %1 Karten - + Failed to convert the map. Abort operation Die Karte konnte nicht konvertiert werden. Vorgang abgebrochen @@ -844,7 +844,7 @@ (Standard) - + Player ID: %1 Spieler-ID: %1 @@ -909,55 +909,70 @@ Experte - + Compliant Konform - + Friendly Freundlich - + Aggressive Aggressiv - + Hostile Feindlich - + Savage Wild - - + + + No patrol + Keine Streife + + + + + %n tile(s) + + %n Kachel + %n Kacheln + + + + + neutral neutral - + UNFLAGGABLE UNFLAGGBAR - + Can't place object - Objekt kann nicht platziert werden + Objekt kann nicht platziert werden - + There can only be one grail object on the map. - + Es kann sich nur ein Gral auf der Karte befinden. - + Hero %1 cannot be created as NEUTRAL. - + Held %1 kann nicht als NEUTRAL erstellt werden. @@ -1083,12 +1098,12 @@ Spieler - + None Keine - + Day %1 Tag %1 @@ -1356,18 +1371,18 @@ Spieler - + None Keine - + Day %1 Tag %1 - - + + Reward %1 Belohnung %1 @@ -1433,32 +1448,32 @@ Tag des ersten Auftretens - + Repeat after (0 = no repeat) Wiederholung nach (0 = keine Wiederholung) - + Affected players Betroffene Spieler - + Resources Ressourcen - + type Typ - + qty anz. - + Ok Ok @@ -1473,37 +1488,37 @@ Build all - + Alle bauen Demolish all - + Alle zerstören Enable all - + Alle aktivieren Disable all - + Alle deaktivieren Type - Typ + Typ Enabled - + Aktiviert Built - + Gebaut @@ -1511,77 +1526,77 @@ Town event - + Stadt Ereignis General - Allgemein + Allgemein Event name - Name des Ereignisses + Ereignis-Name Type event message text - Ereignistext eingeben + Ereignistext eingeben Day of first occurrence - Tag des ersten Auftretens + Tag des ersten Auftretens Repeat after (0 = no repeat) - Wiederholung nach (0 = keine Wiederholung) + Wiederholung nach (0 = keine Wiederholung) Affected players - Betroffene Spieler + Betroffene Spieler affects human - beeinflusst Menschen + beeinflusst Menschen affects AI - beeinflusst KI + beeinflusst KI Resources - Ressourcen + Ressourcen Buildings - Gebäude + Gebäude Creatures - Kreaturen + Kreaturen OK - + OK Creature level %1 / Creature level %1 Upgrade - + Kreaturlevel %1 / Kreaturlevel %1 Aufgerüstet Day %1 - %2 - + Tag %1 - %2 @@ -1589,32 +1604,32 @@ Town events - + Stadt Ereignisse Timed events - Zeitlich begrenzte Ereignisse + Zeitlich begrenzte Ereignisse Add - Hinzufügen + Hinzufügen Remove - Entfernen + Entfernen Day %1 - %2 - + Tag %1 - %2 New event - Neues Ereignis + Neues Ereignis @@ -1622,17 +1637,17 @@ Spells - Zaubersprüche + Zaubersprüche Customize spells - Zaubersprüche anpassen + Zaubersprüche anpassen Level 1 - Level 1 + Level 1 @@ -1641,7 +1656,7 @@ Spell that may appear in mage guild - + Zauberspruch, der in der Magiergilde erscheinen kann @@ -1650,27 +1665,27 @@ Spell that must appear in mage guild - + Zauberspruch, der in der Magiergilde erscheinen muss Level 2 - Level 2 + Level 2 Level 3 - Level 3 + Level 3 Level 4 - Level 4 + Level 4 Level 5 - Level 5 + Level 5 @@ -1762,17 +1777,17 @@ Spell scroll %1 doesn't have instance assigned and must be removed - + Zauberschriftrolle %1 hat keine Instanz zugewiesen und muss entfernt werden Artifact %1 is prohibited by map settings - + Artefakt %1 ist durch Karteneinstellungen verboten Player %1 has no towns and heroes assigned - + Spieler %1 hat keine Städte und Helden zugewiesen @@ -2052,22 +2067,22 @@ Roads - Straßen + Straßen Dirt - + Erde Gravel - + Kies Cobblestone - + Kopfsteinpflaster diff --git a/mapeditor/translation/polish.ts b/mapeditor/translation/polish.ts index 4d65128c2..e9d0e63ce 100644 --- a/mapeditor/translation/polish.ts +++ b/mapeditor/translation/polish.ts @@ -359,7 +359,7 @@ - + View underground Pokaż podziemia @@ -441,9 +441,9 @@ - - - + + + Update appearance Aktualizuj wygląd @@ -574,92 +574,92 @@ Ctrl+Shift+= - + Confirmation Potwierdzenie - + Unsaved changes will be lost, are you sure? Niezapisane zmiany zostaną utracone, jesteś pewny? - + Open map Otwórz mapę - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Wszystkie wspierane mapy (*.vmap *.h3m);;Mapy VCMI(*.vmap);;Mapy HoMM3(*.h3m) - + Save map Zapisz mapę - + VCMI maps (*.vmap) Mapy VCMI (*.vmap) - + Type Typ - + View surface Pokaż powierzchnię - + No objects selected Brak wybranych obiektów - + This operation is irreversible. Do you want to continue? Ta operacja jest nieodwracalna. Czy chcesz kontynuować? - + Errors occurred. %1 objects were not updated Wystąpiły błędy. %1 obiektów nie zostało zaktualizowanych - + Save to image Zapisz jako obraz - + Select maps to convert Wybierz mapy do konwersji - + HoMM3 maps(*.h3m) Mapy HoMM3(*.h3m) - + Choose directory to save converted maps Wybierz folder zapisu skonwertowanych map - + Operation completed Operacja zakończona - + Successfully converted %1 maps Pomyślnie skonwertowano %1 map - + Failed to convert the map. Abort operation Nieudana konwersja mapy. Przerywanie operacji @@ -844,7 +844,7 @@ (domyślny) - + Player ID: %1 ID gracza: %1 @@ -909,53 +909,69 @@ Ekspert - + Compliant Przyjazny - + Friendly Przychylny - + Aggressive Agresywny - + Hostile Wrogi - + Savage Nienawistny - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + + neutral neutralny - + UNFLAGGABLE NIEOFLAGOWYWALNY - + Can't place object Nie można umieścić obiektu - + There can only be one grail object on the map. - + Hero %1 cannot be created as NEUTRAL. @@ -1083,12 +1099,12 @@ Gracze - + None Brak - + Day %1 Dzień %1 @@ -1356,18 +1372,18 @@ Gracze - + None Brak - + Day %1 Dzień %1 - - + + Reward %1 Nagroda %1 @@ -1433,32 +1449,32 @@ Dzień pierwszego wystąpienia - + Repeat after (0 = no repeat) Powtórz po... (0 = nigdy) - + Affected players Dotyczy graczy - + Resources Zasoby - + type typ - + qty ilość - + Ok Ok diff --git a/mapeditor/translation/portuguese.ts b/mapeditor/translation/portuguese.ts index 80599e202..51968dcaa 100644 --- a/mapeditor/translation/portuguese.ts +++ b/mapeditor/translation/portuguese.ts @@ -359,7 +359,7 @@ - + View underground Visualizar subterrâneo @@ -441,9 +441,9 @@ - - - + + + Update appearance Atualizar aparência @@ -574,92 +574,92 @@ Ctrl+Shift+= - + Confirmation Confirmação - + Unsaved changes will be lost, are you sure? As alterações não salvas serão perdidas. Tem certeza? - + Open map Abrir mapa - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Todos os mapas suportados (*.vmap *.h3m);;Mapas do VCMI (*.vmap);;Mapas do HoMM3 (*.h3m) - + Save map Salvar mapa - + VCMI maps (*.vmap) Mapas do VCMI (*.vmap) - + Type Tipo - + View surface Visualizar superfície - + No objects selected Nenhum objeto selecionado - + This operation is irreversible. Do you want to continue? Esta operação é irreversível. Deseja continuar? - + Errors occurred. %1 objects were not updated Ocorreram erros. %1 objetos não foram atualizados - + Save to image Salvar como imagem - + Select maps to convert Selecionar mapas para converter - + HoMM3 maps(*.h3m) Mapas do HoMM3 (*.h3m) - + Choose directory to save converted maps Escolher diretório para salvar mapas convertidos - + Operation completed Operação concluída - + Successfully converted %1 maps %1 mapas foram convertidos com sucesso - + Failed to convert the map. Abort operation Falha ao converter o mapa. Abortar operação @@ -844,7 +844,7 @@ (padrão) - + Player ID: %1 ID do Jogador: %1 @@ -909,53 +909,68 @@ Experiente - + Compliant Conformista - + Friendly Amigável - + Aggressive Agressivo - + Hostile Hostil - + Savage Selvagem - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + neutral neutro - + UNFLAGGABLE NÃO TEM BANDEIRA - + Can't place object Não é possível colocar objeto - + There can only be one grail object on the map. Pode haver apenas um objeto graal no mapa. - + Hero %1 cannot be created as NEUTRAL. O herói %1 não pode ser criado como NEUTRO. @@ -1083,12 +1098,12 @@ Jogadores - + None Nenhum - + Day %1 Dia %1 @@ -1356,18 +1371,18 @@ Jogadores - + None Nenhum - + Day %1 Dia %1 - - + + Reward %1 Recompensa %1 @@ -1433,32 +1448,32 @@ Dia da primeira ocorrência - + Repeat after (0 = no repeat) Repetir após (0 = não repetir) - + Affected players Jogadores afetados - + Resources Recursos - + type tipo - + qty quantidade - + Ok Ok diff --git a/mapeditor/translation/russian.ts b/mapeditor/translation/russian.ts index c0f54b7a7..38f261dcd 100644 --- a/mapeditor/translation/russian.ts +++ b/mapeditor/translation/russian.ts @@ -359,7 +359,7 @@ - + View underground Вид на подземелье @@ -441,9 +441,9 @@ - - - + + + Update appearance Обновить вид @@ -574,92 +574,92 @@ - + Confirmation - + Unsaved changes will be lost, are you sure? - + Open map Открыть карту - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Все поддерживаемые карты (*.vmap *.h3m);;Карты VCMI (*.vmap);;Карты Героев III (*.h3m) - + Save map Сохранить карту - + VCMI maps (*.vmap) Карты VCMI (*.vmap) - + Type Тип - + View surface Вид на поверхность - + No objects selected - + This operation is irreversible. Do you want to continue? - + Errors occurred. %1 objects were not updated - + Save to image - + Select maps to convert - + HoMM3 maps(*.h3m) - + Choose directory to save converted maps - + Operation completed - + Successfully converted %1 maps - + Failed to convert the map. Abort operation @@ -844,7 +844,7 @@ (по умолчанию) - + Player ID: %1 Игрок: %1 @@ -909,53 +909,69 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + + neutral - + UNFLAGGABLE - + Can't place object - + There can only be one grail object on the map. - + Hero %1 cannot be created as NEUTRAL. @@ -1083,12 +1099,12 @@ - + None Нет - + Day %1 @@ -1356,18 +1372,18 @@ - + None Нет - + Day %1 - - + + Reward %1 @@ -1433,32 +1449,32 @@ - + Repeat after (0 = no repeat) - + Affected players - + Resources - + type - + qty - + Ok ОК diff --git a/mapeditor/translation/spanish.ts b/mapeditor/translation/spanish.ts index bd6340e4f..9107e3904 100644 --- a/mapeditor/translation/spanish.ts +++ b/mapeditor/translation/spanish.ts @@ -359,7 +359,7 @@ - + View underground Ver subterráneo @@ -441,9 +441,9 @@ - - - + + + Update appearance Actualizar apariencia @@ -574,92 +574,92 @@ - + Confirmation Confirmación - + Unsaved changes will be lost, are you sure? Los cambios no guardados se perderán. Está usted seguro ? - + Open map Abrir mapa - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Todos los mapas soportados (*.vmap *.h3m);;Mapas VCMI (*.vmap);;Mapas HoMM3 (*.h3m) - + Save map Guardar mapa - + VCMI maps (*.vmap) Mapas VCMI (*.vmap) - + Type Tipo - + View surface Ver superficie - + No objects selected - + This operation is irreversible. Do you want to continue? - + Errors occurred. %1 objects were not updated - + Save to image - + Select maps to convert - + HoMM3 maps(*.h3m) - + Choose directory to save converted maps - + Operation completed - + Successfully converted %1 maps - + Failed to convert the map. Abort operation @@ -844,7 +844,7 @@ (predeterminado) - + Player ID: %1 ID de jugador: %1 @@ -909,53 +909,68 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + neutral - + UNFLAGGABLE - + Can't place object - + There can only be one grail object on the map. - + Hero %1 cannot be created as NEUTRAL. @@ -1083,12 +1098,12 @@ Jugadores - + None Ninguno - + Day %1 @@ -1356,18 +1371,18 @@ Jugadores - + None Ninguno - + Day %1 - - + + Reward %1 @@ -1433,32 +1448,32 @@ - + Repeat after (0 = no repeat) - + Affected players - + Resources - + type - + qty - + Ok Aceptar diff --git a/mapeditor/translation/ukrainian.ts b/mapeditor/translation/ukrainian.ts index bb7d4e795..ab0c88093 100644 --- a/mapeditor/translation/ukrainian.ts +++ b/mapeditor/translation/ukrainian.ts @@ -359,7 +359,7 @@ - + View underground Дивитись підземелля @@ -441,9 +441,9 @@ - - - + + + Update appearance Оновити вигляд @@ -574,92 +574,92 @@ - + Confirmation - + Unsaved changes will be lost, are you sure? - + Open map Відкрити мапу - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Всі підтримувані мапи (*.vmap *.h3m);;Мапи VCMI (*.vmap);;Мапи HoMM3 (*.h3m) - + Save map Зберегти мапу - + VCMI maps (*.vmap) Мапи VCMI - + Type Тип - + View surface Дивитись поверхню - + No objects selected - + This operation is irreversible. Do you want to continue? - + Errors occurred. %1 objects were not updated - + Save to image - + Select maps to convert - + HoMM3 maps(*.h3m) - + Choose directory to save converted maps - + Operation completed - + Successfully converted %1 maps - + Failed to convert the map. Abort operation @@ -844,7 +844,7 @@ (за замовчуванням) - + Player ID: %1 Гравець %1 @@ -909,53 +909,69 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + + No patrol + + + + + + %n tile(s) + + + + + + + + + neutral - + UNFLAGGABLE - + Can't place object - + There can only be one grail object on the map. - + Hero %1 cannot be created as NEUTRAL. @@ -1083,12 +1099,12 @@ - + None Відсутня - + Day %1 @@ -1356,18 +1372,18 @@ - + None Відсутня - + Day %1 - - + + Reward %1 @@ -1433,32 +1449,32 @@ - + Repeat after (0 = no repeat) - + Affected players - + Resources - + type - + qty - + Ok Підтвердити diff --git a/mapeditor/translation/vietnamese.ts b/mapeditor/translation/vietnamese.ts index 825f16c59..bf1d31fde 100644 --- a/mapeditor/translation/vietnamese.ts +++ b/mapeditor/translation/vietnamese.ts @@ -359,7 +359,7 @@ - + View underground Xem hang ngầm @@ -441,9 +441,9 @@ - - - + + + Update appearance Cập nhật hiện thị @@ -574,92 +574,92 @@ - + Confirmation Xác nhận - + Unsaved changes will be lost, are you sure? Thay đổi chưa lưu sẽ bị mất, bạn có chắc chắn? - + Open map Mở bản đồ - + All supported maps (*.vmap *.h3m);;VCMI maps(*.vmap);;HoMM3 maps(*.h3m) Tất cả bản đồ hỗ trợ (*.vmap *.h3m);;Bản đồ VCMI (*.vmap);;Bản đồ HoMM3 (*.h3m) - + Save map Lưu bản đồ - + VCMI maps (*.vmap) Bản đồ VCMI (*.vmap) - + Type Loại - + View surface Xem bề mặt - + No objects selected Không mục tiêu được chọn - + This operation is irreversible. Do you want to continue? Thao tác này không thể đảo ngược. Bạn muốn tiếp tục? - + Errors occurred. %1 objects were not updated Xảy ra lỗi. %1 mục tiêu không được cập nhật - + Save to image Lưu thành ảnh - + Select maps to convert - + HoMM3 maps(*.h3m) - + Choose directory to save converted maps - + Operation completed - + Successfully converted %1 maps - + Failed to convert the map. Abort operation @@ -844,7 +844,7 @@ (mặc định) - + Player ID: %1 ID người chơi: %1 @@ -909,53 +909,67 @@ - + Compliant - + Friendly - + Aggressive - + Hostile - + Savage - - + + + No patrol + + + + + + %n tile(s) + + + + + + + neutral - + UNFLAGGABLE - + Can't place object Không thể đặt vật thể - + There can only be one grail object on the map. - + Hero %1 cannot be created as NEUTRAL. @@ -1083,12 +1097,12 @@ Người chơi - + None Không - + Day %1 @@ -1356,18 +1370,18 @@ Người chơi - + None Không - + Day %1 - - + + Reward %1 @@ -1433,32 +1447,32 @@ - + Repeat after (0 = no repeat) - + Affected players - + Resources - + type - + qty - + Ok Đồng ý From 51490f8f85aadd13169312e6588a73ac5755e26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Zaj=C4=85c?= <4561094+ToRRent1812@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:57:32 +0100 Subject: [PATCH 593/726] fit text into the box --- Mods/vcmi/config/polish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/polish.json b/Mods/vcmi/config/polish.json index 9f9a68bab..03e537f18 100644 --- a/Mods/vcmi/config/polish.json +++ b/Mods/vcmi/config/polish.json @@ -15,7 +15,7 @@ "vcmi.adventureMap.monsterLevel" : "\n\nJednostka %ATTACK_TYPE %LEVEL poziomu z miasta %TOWN", "vcmi.adventureMap.monsterMeleeType" : "Walcząca wręcz", "vcmi.adventureMap.monsterRangedType" : "Dystansowa", - "vcmi.adventureMap.search.hover" : "Wyszukiwarka obiektów na mapie", + "vcmi.adventureMap.search.hover" : "Wyszukiwarka obiektów", "vcmi.adventureMap.search.help" : "Wybierz obiekt który chcesz znaleźć na mapie.", "vcmi.adventureMap.confirmRestartGame" : "Czy na pewno chcesz zrestartować grę?", From db93f776c642b74ed735aa256aa45c6836f31cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Sun, 17 Nov 2024 00:02:13 +0100 Subject: [PATCH 594/726] Fix MinGW date formatting error, see https://sourceforge.net/p/mingw-w64/bugs/793/. Workaround is to replace %F with %Y-%m-%d and %T with %H:%M:%S, should produce the same output as before per https://en.cppreference.com/w/cpp/io/manip/put_time --- lib/texts/Languages.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/texts/Languages.h b/lib/texts/Languages.h index a454f1dfa..9e0f87585 100644 --- a/lib/texts/Languages.h +++ b/lib/texts/Languages.h @@ -77,23 +77,23 @@ inline const auto & getLanguageList() { static const std::array languages { { - { "czech", "Czech", "Čeština", "CP1250", "cs", "cze", "%d.%m.%Y %T", EPluralForms::CZ_3 }, - { "chinese", "Chinese", "简体中文", "GBK", "zh", "chi", "%F %T", EPluralForms::VI_1 }, // Note: actually Simplified Chinese - { "english", "English", "English", "CP1252", "en", "eng", "%F %T", EPluralForms::EN_2 }, // English uses international date/time format here - { "finnish", "Finnish", "Suomi", "CP1252", "fi", "fin", "%d.%m.%Y %T", EPluralForms::EN_2, }, - { "french", "French", "Français", "CP1252", "fr", "fre", "%d/%m/%Y %T", EPluralForms::FR_2, }, - { "german", "German", "Deutsch", "CP1252", "de", "ger", "%d.%m.%Y %T", EPluralForms::EN_2, }, - { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "hun", "%Y. %m. %d. %T", EPluralForms::EN_2 }, - { "italian", "Italian", "Italiano", "CP1250", "it", "ita", "%d/%m/%Y %T", EPluralForms::EN_2 }, - { "korean", "Korean", "한국어", "CP949", "ko", "kor", "%F %T", EPluralForms::VI_1 }, - { "polish", "Polish", "Polski", "CP1250", "pl", "pol", "%d.%m.%Y %T", EPluralForms::PL_3 }, - { "portuguese", "Portuguese", "Português", "CP1252", "pt", "por", "%d/%m/%Y %T", EPluralForms::EN_2 }, // Note: actually Brazilian Portuguese - { "russian", "Russian", "Русский", "CP1251", "ru", "rus", "%d.%m.%Y %T", EPluralForms::UK_3 }, - { "spanish", "Spanish", "Español", "CP1252", "es", "spa", "%d/%m/%Y %T", EPluralForms::EN_2 }, - { "swedish", "Swedish", "Svenska", "CP1252", "sv", "swe", "%F %T", EPluralForms::EN_2 }, - { "turkish", "Turkish", "Türkçe", "CP1254", "tr", "tur", "%d.%m.%Y %T", EPluralForms::EN_2 }, - { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "ukr", "%d.%m.%Y %T", EPluralForms::UK_3 }, - { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "vie", "%d/%m/%Y %T", EPluralForms::VI_1 }, // Fan translation uses special encoding + { "czech", "Czech", "Čeština", "CP1250", "cs", "cze", "%d.%m.%Y %H:%M:%S", EPluralForms::CZ_3 }, + { "chinese", "Chinese", "简体中文", "GBK", "zh", "chi", "%Y-%m-%d %H:%M:%S", EPluralForms::VI_1 }, // Note: actually Simplified Chinese + { "english", "English", "English", "CP1252", "en", "eng", "%Y-%m-%d %H:%M:%S", EPluralForms::EN_2 }, // English uses international date/time format here + { "finnish", "Finnish", "Suomi", "CP1252", "fi", "fin", "%d.%m.%Y %H:%M:%S", EPluralForms::EN_2, }, + { "french", "French", "Français", "CP1252", "fr", "fre", "%d/%m/%Y %H:%M:%S", EPluralForms::FR_2, }, + { "german", "German", "Deutsch", "CP1252", "de", "ger", "%d.%m.%Y %H:%M:%S", EPluralForms::EN_2, }, + { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "hun", "%Y. %m. %d. %H:%M:%S", EPluralForms::EN_2 }, + { "italian", "Italian", "Italiano", "CP1250", "it", "ita", "%d/%m/%Y %H:%M:%S", EPluralForms::EN_2 }, + { "korean", "Korean", "한국어", "CP949", "ko", "kor", "%Y-%m-%d %H:%M:%S", EPluralForms::VI_1 }, + { "polish", "Polish", "Polski", "CP1250", "pl", "pol", "%d.%m.%Y %H:%M:%S", EPluralForms::PL_3 }, + { "portuguese", "Portuguese", "Português", "CP1252", "pt", "por", "%d/%m/%Y %H:%M:%S", EPluralForms::EN_2 }, // Note: actually Brazilian Portuguese + { "russian", "Russian", "Русский", "CP1251", "ru", "rus", "%d.%m.%Y %H:%M:%S", EPluralForms::UK_3 }, + { "spanish", "Spanish", "Español", "CP1252", "es", "spa", "%d/%m/%Y %H:%M:%S", EPluralForms::EN_2 }, + { "swedish", "Swedish", "Svenska", "CP1252", "sv", "swe", "%Y-%m-%d %H:%M:%S", EPluralForms::EN_2 }, + { "turkish", "Turkish", "Türkçe", "CP1254", "tr", "tur", "%d.%m.%Y %H:%M:%S", EPluralForms::EN_2 }, + { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "ukr", "%d.%m.%Y %H:%M:%S", EPluralForms::UK_3 }, + { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "vie", "%d/%m/%Y %H:%M:%S", EPluralForms::VI_1 }, // Fan translation uses special encoding } }; static_assert(languages.size() == static_cast(ELanguages::COUNT), "Languages array is missing a value!"); From 1fc04f5ea87736a9d424ad04f09991e071a7f56d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Thor=C3=A9n?= Date: Sun, 17 Nov 2024 00:14:54 +0100 Subject: [PATCH 595/726] Updated out-of-date comment --- lib/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1584506c9..aca9b5919 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -790,7 +790,7 @@ if(WIN32) ) endif() -# Use -Og for files that generate very large object files +# Use '-Wa,-mbig-obj' for files that generate very large object files # when compiling with MinGW lest you get "too many sections" assembler errors if(MINGW AND CMAKE_BUILD_TYPE STREQUAL "Debug") set_source_files_properties( From b0178e472fcde8d7a0d17e92909340b8a2436a13 Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 17 Nov 2024 17:51:50 +0200 Subject: [PATCH 596/726] Add missing abilities Siege weapons had incomplete abilities. Fairy Dragon was missing canFly --- config/creatures/neutral.json | 4 ++++ config/creatures/special.json | 38 +++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index 731e65f58..e420bcbc5 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -174,6 +174,10 @@ { "type" : "DRAGON_NATURE" }, + "canFly" : + { + "type" : "FLYING" + }, "mirror" : { "type" : "MAGIC_MIRROR", diff --git a/config/creatures/special.json b/config/creatures/special.json index 5ebdcf246..f2457fcb4 100644 --- a/config/creatures/special.json +++ b/config/creatures/special.json @@ -39,6 +39,14 @@ "doubleWide" : true, "abilities" : { + "siegeWeapon" : + { + "type" : "SIEGE_WEAPON" + }, + "shooter" : + { + "type" : "SHOOTER" + }, "siegeMachine" : { "type" : "CATAPULT", @@ -67,6 +75,17 @@ "level": 0, "faction": "neutral", "doubleWide" : true, + "abilities" : + { + "siegeWeapon" : + { + "type" : "SIEGE_WEAPON" + }, + "shooter" : + { + "type" : "SHOOTER" + } + }, "graphics" : { "animation": "SMBAL.DEF", @@ -91,7 +110,12 @@ "doubleWide" : true, "abilities": { - "heals" : { + "siegeWeapon" : + { + "type" : "SIEGE_WEAPON" + }, + "heals" : + { "type" : "HEALER" , "subtype" : "spell.firstAid" } @@ -112,7 +136,17 @@ "index": 148, "level": 0, "faction": "neutral", - "abilities": { "inactive" : { "type" : "NOT_ACTIVE" } }, + "abilities": + { + "siegeWeapon" : + { + "type" : "SIEGE_WEAPON" + }, + "inactive" : + { + "type" : "NOT_ACTIVE" + } + }, "graphics" : { "animation": "SMCART.DEF" From 695298c16c62ea87ce38a74319ce200fc75eba74 Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 17 Nov 2024 18:08:46 +0200 Subject: [PATCH 597/726] Change placement of King and Dragon_Nature abilities King is not that important that is first all the time. Is a minor spell vulnerability in the end. Undead always comes first. Second would be dragon nature, even though right now is used only for necromancy. --- config/creatures/castle.json | 20 ++++++------ config/creatures/conflux.json | 30 +++++++++--------- config/creatures/dungeon.json | 28 ++++++++--------- config/creatures/fortress.json | 20 ++++++------ config/creatures/inferno.json | 20 ++++++------ config/creatures/necropolis.json | 28 ++++++++--------- config/creatures/neutral.json | 52 ++++++++++++++++---------------- config/creatures/rampart.json | 28 ++++++++--------- config/creatures/stronghold.json | 20 ++++++------ config/creatures/tower.json | 18 +++++------ 10 files changed, 132 insertions(+), 132 deletions(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 97dce1531..34f13b06b 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -346,11 +346,6 @@ "faction": "castle", "abilities": { - "KING_2" : // Will be affected by Advanced Slayer or better - { - "type" : "KING", - "val" : 2 - }, "canFly" : { "type" : "FLYING" @@ -362,6 +357,11 @@ "propagator" : "HERO", "stacking" : "Angels" }, + "KING_2" : // Will be affected by Advanced Slayer or better + { + "type" : "KING", + "val" : 2 + }, "hateDevils" : { "type" : "HATE", @@ -397,11 +397,6 @@ "doubleWide" : true, "abilities": { - "KING_2" : // Will be affected by Advanced Slayer or better - { - "type" : "KING", - "val" : 2 - }, "canFly" : { "type" : "FLYING" @@ -429,6 +424,11 @@ "propagator" : "HERO", "stacking" : "Angels" }, + "KING_2" : // Will be affected by Advanced Slayer or better + { + "type" : "KING", + "val" : 2 + }, "hateDevils" : { "type" : "HATE", diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index c16d6024e..dd0d9fc8a 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -767,11 +767,6 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "canFly" : { "type" : "FLYING" @@ -784,6 +779,11 @@ { "type" : "SPELL_SCHOOL_IMMUNITY", "subtype" : "spellSchool.fire" + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : @@ -807,11 +807,6 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "canFly" : { "type" : "FLYING" @@ -820,11 +815,6 @@ { "type" : "TWO_HEX_ATTACK_BREATH" }, - "immuneToFire" : - { - "type" : "SPELL_SCHOOL_IMMUNITY", - "subtype" : "spellSchool.fire" - }, "rebirthOnce" : { "type" : "CASTS", @@ -834,6 +824,16 @@ { "type" : "REBIRTH", "val" : 20 + }, + "immuneToFire" : + { + "type" : "SPELL_SCHOOL_IMMUNITY", + "subtype" : "spellSchool.fire" + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : diff --git a/config/creatures/dungeon.json b/config/creatures/dungeon.json index 41791fb96..39e6f8b67 100644 --- a/config/creatures/dungeon.json +++ b/config/creatures/dungeon.json @@ -425,19 +425,14 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" @@ -446,6 +441,11 @@ { "type" : "LEVEL_SPELL_IMMUNITY", "val" : 3 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "upgrades": ["blackDragon"], @@ -470,19 +470,14 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" @@ -492,6 +487,11 @@ "type" : "LEVEL_SPELL_IMMUNITY", "val" : 5 }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 + }, "hateTitans" : { "type" : "HATE", diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index de7fa6ca7..3d3cf1688 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -343,11 +343,6 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "attackAllAdjacent" : { "type" : "ATTACKS_ALL_ADJACENT" @@ -355,6 +350,11 @@ "noRetaliation" : { "type" : "BLOCKS_RETALIATION" + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "upgrades": ["chaosHydra"], @@ -379,11 +379,6 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "attackAllAdjacent" : { "type" : "ATTACKS_ALL_ADJACENT" @@ -391,6 +386,11 @@ "noRetaliation" : { "type" : "BLOCKS_RETALIATION" + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index 4faeca5ae..491968747 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -353,11 +353,6 @@ "faction": "inferno", "abilities": { - "KING_2" : // Will be affected by Advanced Slayer or better - { - "type" : "KING", - "val" : 2 - }, "canFly" : { "type" : "FLYING", @@ -376,6 +371,11 @@ "propagationUpdater" : "BONUS_OWNER_UPDATER", "limiters" : [ "OPPOSITE_SIDE" ] }, + "KING_2" : // Will be affected by Advanced Slayer or better + { + "type" : "KING", + "val" : 2 + }, "hateAngels" : { "type" : "HATE", @@ -413,11 +413,6 @@ "faction": "inferno", "abilities" : { - "KING_2" : // Will be affected by Advanced Slayer or better - { - "type" : "KING", - "val" : 2 - }, "canFly" : { "type" : "FLYING", @@ -436,6 +431,11 @@ "propagationUpdater" : "BONUS_OWNER_UPDATER", "limiters" : [ "OPPOSITE_SIDE" ] }, + "KING_2" : // Will be affected by Advanced Slayer or better + { + "type" : "KING", + "val" : 2 + }, "hateAngels" : { "type" : "HATE", diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index 1844f0c24..ebbb5dc0b 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -421,19 +421,14 @@ { "type" : "UNDEAD" }, - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "decreaseMorale" : { "type" : "MORALE", @@ -442,6 +437,11 @@ "propagator": "BATTLE_WIDE", "propagationUpdater" : "BONUS_OWNER_UPDATER", "limiters" : [ "OPPOSITE_SIDE" ] + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "upgrades": ["ghostDragon"], @@ -470,19 +470,14 @@ { "type" : "UNDEAD" }, - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "age" : { "type" : "SPELL_AFTER_ATTACK", @@ -497,6 +492,11 @@ "propagator": "BATTLE_WIDE", "propagationUpdater" : "BONUS_OWNER_UPDATER", "limiters" : [ "OPPOSITE_SIDE" ] + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index e420bcbc5..a8e7db89d 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -71,19 +71,14 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" @@ -100,6 +95,11 @@ { "type" : "LEVEL_SPELL_IMMUNITY", "val" : 3 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : @@ -124,23 +124,23 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "dragon" : { "type" : "DRAGON_NATURE" }, + "crystals" : + { + "type" : "SPECIAL_CRYSTAL_GENERATION" + }, "magicResistance" : { "type" : "MAGIC_RESISTANCE", "val" : 20 }, - "crystals" : + "KING_1" : // Will be affected by Slayer with no expertise { - "type" : "SPECIAL_CRYSTAL_GENERATION" + "type" : "KING", + "val" : 0 } }, "graphics" : @@ -165,11 +165,6 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "dragon" : { "type" : "DRAGON_NATURE" @@ -248,6 +243,11 @@ "subtype" : "spell.meteorShower", "addInfo" : 5, "val" : 2 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : @@ -273,19 +273,14 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" @@ -301,6 +296,11 @@ "type" : "SPELL_AFTER_ATTACK", "subtype" : "spell.acidBreath", "val" : 100 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index 42259b2df..7a64a1460 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -359,19 +359,14 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" @@ -380,6 +375,11 @@ { "type" : "LEVEL_SPELL_IMMUNITY", "val" : 3 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "upgrades": ["goldDragon"], @@ -404,19 +404,14 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise + "dragon" : { - "type" : "KING", - "val" : 0 + "type" : "DRAGON_NATURE" }, "canFly" : { "type" : "FLYING" }, - "dragon" : - { - "type" : "DRAGON_NATURE" - }, "twoHexAttackBreath" : { "type" : "TWO_HEX_ATTACK_BREATH" @@ -425,6 +420,11 @@ { "type" : "LEVEL_SPELL_IMMUNITY", "val" : 4 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index 3c690bb2d..b52a3cc82 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -355,15 +355,15 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "reduceDefence" : { "type" : "ENEMY_DEFENCE_REDUCTION", "val" : 40 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "upgrades": ["ancientBehemoth"], @@ -388,15 +388,15 @@ "doubleWide" : true, "abilities": { - "KING_1" : // Will be affected by Slayer with no expertise - { - "type" : "KING", - "val" : 0 - }, "reduceDefence" : { "type" : "ENEMY_DEFENCE_REDUCTION", "val" : 80 + }, + "KING_1" : // Will be affected by Slayer with no expertise + { + "type" : "KING", + "val" : 0 } }, "graphics" : diff --git a/config/creatures/tower.json b/config/creatures/tower.json index 2fd19fdac..259b6e056 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -415,14 +415,14 @@ "faction": "tower", "abilities" : { + "immuneToMind" : + { + "type" : "MIND_IMMUNITY" + }, "KING_3" : // Will be affected by Expert Slayer only { "type" : "KING", "val" : 3 - }, - "immuneToMind" : - { - "type" : "MIND_IMMUNITY" } }, "upgrades": ["titan"], @@ -446,11 +446,6 @@ "faction": "tower", "abilities" : { - "KING_3" : // Will be affected by Expert Slayer only - { - "type" : "KING", - "val" : 3 - }, "shooter" : { "type" : "SHOOTER" @@ -463,6 +458,11 @@ { "type" : "MIND_IMMUNITY" }, + "KING_3" : // Will be affected by Expert Slayer only + { + "type" : "KING", + "val" : 3 + }, "hateBlackDragons" : { "type" : "HATE", From 8cd77fc5fcc41c735ef6ef44b9d504d87636068c Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:09:13 +0100 Subject: [PATCH 598/726] allow overlay and shadow also for unscaled images --- client/renderSDL/RenderHandler.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index e08821bd8..6567e2c1c 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -327,6 +327,21 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator & loc std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode) { ImageLocator adjustedLocator = locator; + + if(adjustedLocator.image) + { + std::string imgPath = (*adjustedLocator.image).getName(); + if(adjustedLocator.layer == EImageLayer::OVERLAY) + imgPath += "-OVERLAY"; + if(adjustedLocator.layer == EImageLayer::SHADOW) + imgPath += "-SHADOW"; + + if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)) || + CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath).addPrefix("DATA/")) || + CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath).addPrefix("SPRITES/"))) + adjustedLocator.image = ImagePath::builtin(imgPath); + } + if(adjustedLocator.defFile && adjustedLocator.scalingFactor == 0) { auto tmp = getScalePath(*adjustedLocator.defFile); @@ -360,21 +375,7 @@ std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, E return loadImageImpl(scaledLocator)->createImageReference(mode); } else - { - if(adjustedLocator.image) - { - std::string imgPath = (*adjustedLocator.image).getName(); - if(adjustedLocator.layer == EImageLayer::OVERLAY) - imgPath += "-overlay"; - if(adjustedLocator.layer == EImageLayer::SHADOW) - imgPath += "-shadow"; - - if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath))) - adjustedLocator.image = ImagePath::builtin(imgPath); - } - return loadImageImpl(adjustedLocator)->createImageReference(mode); - } } std::shared_ptr RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) From bda71ef8e67df643af44555e64e5018c4b9b23c5 Mon Sep 17 00:00:00 2001 From: krs Date: Sun, 17 Nov 2024 18:14:26 +0200 Subject: [PATCH 599/726] Change Fortress creatures configs to be in level order Like in the rest of configs --- config/creatures/fortress.json | 96 +++++++++++++++++----------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index 3d3cf1688..ddcafe3a6 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -99,54 +99,6 @@ "wince": "ALIZWNCE.wav" } }, - "gorgon" : - { - "index": 102, - "level": 5, - "faction": "fortress", - "upgrades": ["mightyGorgon"], - "doubleWide" : true, - "graphics" : - { - "animation": "CCGORG.DEF" - }, - "sound" : - { - "attack": "CGORATTK.wav", - "defend": "CGORDFND.wav", - "killed": "CGORKILL.wav", - "move": "CGORMOVE.wav", - "wince": "CGORWNCE.wav" - } - }, - "mightyGorgon" : - { - "index": 103, - "level": 5, - "faction": "fortress", - "doubleWide" : true, - "abilities": - { - "deathStare" : - { - "type" : "DEATH_STARE", - "subtype" : "deathStareGorgon", - "val" : 10 - } - }, - "graphics" : - { - "animation": "CBGOG.DEF" - }, - "sound" : - { - "attack": "BGORATTK.wav", - "defend": "BGORDFND.wav", - "killed": "BGORKILL.wav", - "move": "BGORMOVE.wav", - "wince": "BGORWNCE.wav" - } - }, "serpentFly" : { "index": 104, @@ -276,6 +228,54 @@ "wince": "GBASWNCE.wav" } }, + "gorgon" : + { + "index": 102, + "level": 5, + "faction": "fortress", + "upgrades": ["mightyGorgon"], + "doubleWide" : true, + "graphics" : + { + "animation": "CCGORG.DEF" + }, + "sound" : + { + "attack": "CGORATTK.wav", + "defend": "CGORDFND.wav", + "killed": "CGORKILL.wav", + "move": "CGORMOVE.wav", + "wince": "CGORWNCE.wav" + } + }, + "mightyGorgon" : + { + "index": 103, + "level": 5, + "faction": "fortress", + "doubleWide" : true, + "abilities": + { + "deathStare" : + { + "type" : "DEATH_STARE", + "subtype" : "deathStareGorgon", + "val" : 10 + } + }, + "graphics" : + { + "animation": "CBGOG.DEF" + }, + "sound" : + { + "attack": "BGORATTK.wav", + "defend": "BGORDFND.wav", + "killed": "BGORKILL.wav", + "move": "BGORMOVE.wav", + "wince": "BGORWNCE.wav" + } + }, "wyvern" : { "index": 108, From 4ccce20edab7ba74a2acaf4fa460c66f6711a869 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 13:51:36 +0000 Subject: [PATCH 600/726] War machines factory are now never guarded by default --- .../DwellingInstanceConstructor.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp index 5254bc70a..3572dfb8a 100644 --- a/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/DwellingInstanceConstructor.cpp @@ -88,25 +88,28 @@ void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::R dwelling->creatures.back().second.push_back(cre->getId()); } - bool guarded = false; //TODO: serialize for sanity + bool guarded = false; - if(guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch + if(guards.getType() == JsonNode::JsonType::DATA_BOOL) { + //simple switch if(guards.Bool()) { guarded = true; } } - else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux) + else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR) { + //custom guards (eg. Elemental Conflux) JsonRandom::Variables emptyVariables; for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables)) { dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.getId(), stack.count)); } } - else //default condition - creatures are of level 5 or higher + else if (dwelling->ID == Obj::CREATURE_GENERATOR1 || dwelling->ID == Obj::CREATURE_GENERATOR4) { + //default condition - this is dwelling with creatures of level 5 or higher for(auto creatureEntry : availableCreatures) { if(creatureEntry.at(0)->getLevel() >= 5) From 56ee307b3a4f0a65742a4181c57c97dec2c4c953 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 13:52:16 +0000 Subject: [PATCH 601/726] Extend AI garrison troop removal logic to all RoE campaigns to emulate H3 logic --- AI/Nullkiller/AIGateway.cpp | 2 +- AI/VCAI/VCAI.cpp | 2 +- lib/StartInfo.cpp | 20 ++++++++++++-------- lib/StartInfo.h | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index d3d1b8401..d0d2d998a 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -763,7 +763,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan //you can't request action from action-response thread requestActionASAP([=]() { - if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isSteadwickFallCampaignMission()) + if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign()) { pickBestCreatures(down, up); } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index ccdea26ad..4e5f41ea2 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -731,7 +731,7 @@ void VCAI::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * //you can't request action from action-response thread requestActionASAP([=]() { - if(removableUnits && !cb->getStartInfo()->isSteadwickFallCampaignMission()) + if(removableUnits && !cb->getStartInfo()->isRestorationOfErathiaCampaign()) pickBestCreatures(down, up); answerQuery(queryID, 0); diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index f6ca7d6cc..46b365375 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -90,18 +90,22 @@ std::string StartInfo::getCampaignName() const return VLC->generaltexth->allTexts[508]; } -bool StartInfo::isSteadwickFallCampaignMission() const +bool StartInfo::isRestorationOfErathiaCampaign() const { + constexpr std::array roeCampaigns = { + "DATA/GOOD1", + "DATA/EVIL1", + "DATA/GOOD2", + "DATA/NEUTRAL1", + "DATA/EVIL2", + "DATA/GOOD3", + "DATA/SECRET1", + }; + if (!campState) return false; - if (campState->getFilename() != "DATA/EVIL1") - return false; - - if (campState->currentScenario() != CampaignScenarioID(2)) - return false; - - return true; + return vstd::contains(roeCampaigns, campState->getFilename()); } void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const diff --git a/lib/StartInfo.h b/lib/StartInfo.h index e6f427650..8a79dc344 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -164,8 +164,8 @@ struct DLL_LINKAGE StartInfo : public Serializeable // TODO: Must be client-side std::string getCampaignName() const; - /// Controls hardcoded check for "Steadwick's Fall" scenario from "Dungeon and Devils" campaign - bool isSteadwickFallCampaignMission() const; + /// Controls hardcoded check for handling of garrisons by AI in Restoration of Erathia campaigns to match H3 behavior + bool isRestorationOfErathiaCampaign() const; template void serialize(Handler &h) From c82db9d574ea7c207be96242f2517616910cdd80 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 13:55:33 +0000 Subject: [PATCH 602/726] Fix crash on rearranging troops on non-owned hero --- client/widgets/CGarrisonInt.cpp | 6 +++--- client/windows/CHeroWindow.cpp | 3 ++- lib/CGameInfoCallback.cpp | 4 ++-- server/CGameHandler.cpp | 13 +++++++------ 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp index f8ebd2347..c859f0d83 100644 --- a/client/widgets/CGarrisonInt.cpp +++ b/client/widgets/CGarrisonInt.cpp @@ -274,12 +274,12 @@ bool CGarrisonSlot::mustForceReselection() const if (!LOCPLINT->makingTurn) return true; - if (!creature || !selection->creature) - return false; - // Attempt to take creatures from ally (select theirs first) if (!selection->our()) return true; + + if (!creature || !selection->creature) + return false; // Attempt to swap creatures with ally (select ours first) if (selection->creature != creature && withAlly) diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 5698d8328..1cd49584f 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -199,10 +199,11 @@ void CHeroWindow::update() OBJECT_CONSTRUCTION; if(!garr) { + bool removableTroops = curHero->getOwner() == LOCPLINT->playerID; std::string helpBox = heroscrn[32]; boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]); - garr = std::make_shared(Point(15, 485), 8, Point(), curHero); + garr = std::make_shared(Point(15, 485), 8, Point(), curHero, nullptr, removableTroops); auto split = std::make_shared(Point(539, 519), AnimationPath::builtin("hsbtns9.def"), CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [this](){ garr->splitClick(); }, EShortcut::HERO_ARMY_SPLIT); garr->addSplitBtn(split); } diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 94e0cea5a..b426b5418 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -957,12 +957,12 @@ void CGameInfoCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo & const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const { - return gs->map->artInstances[aid.num]; + return gs->map->artInstances.at(aid.num); } const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid ) const { - return gs->map->objects[oid.num]; + return gs->map->objects.at(oid.num); } const CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index cf68a18ea..4a860e869 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1861,12 +1861,8 @@ bool CGameHandler::bulkSmartSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player) { - const CArmedInstance * s1 = static_cast(getObjInstance(id1)); - const CArmedInstance * s2 = static_cast(getObjInstance(id2)); - const CCreatureSet & S1 = *s1; - const CCreatureSet & S2 = *s2; - StackLocation sl1(s1, p1); - StackLocation sl2(s2, p2); + const CArmedInstance * s1 = static_cast(getObj(id1)); + const CArmedInstance * s2 = static_cast(getObj(id2)); if (s1 == nullptr || s2 == nullptr) { @@ -1874,6 +1870,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 return false; } + const CCreatureSet & S1 = *s1; + const CCreatureSet & S2 = *s2; + StackLocation sl1(s1, p1); + StackLocation sl2(s2, p2); + if (!sl1.slot.validSlot() || !sl2.slot.validSlot()) { complain(complainInvalidSlot); From 251155d913e524203f862266724f73a20ec48406 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 17:54:55 +0000 Subject: [PATCH 603/726] More robust management of body/shadow/overlay split --- client/ClientCommandManager.cpp | 2 +- client/battle/BattleAnimationClasses.cpp | 2 +- client/battle/BattleFieldController.cpp | 4 +- client/battle/BattleInterfaceClasses.cpp | 2 +- client/battle/BattleObstacleController.cpp | 8 +- client/battle/CreatureAnimation.cpp | 10 +-- client/mapView/MapRenderer.cpp | 37 ++++----- client/mapView/MapRenderer.h | 2 +- client/render/IImage.h | 29 ++++++-- client/render/ImageLocator.cpp | 8 +- client/render/ImageLocator.h | 15 +--- client/renderSDL/ImageScaled.cpp | 87 +++++++++++++--------- client/renderSDL/ImageScaled.h | 4 +- client/renderSDL/RenderHandler.cpp | 18 ++--- client/renderSDL/SDLImage.cpp | 86 +++++++++------------ client/renderSDL/SDLImage.h | 13 +--- client/widgets/Images.cpp | 8 +- client/windows/CCastleInterface.cpp | 2 +- client/windows/GUIClasses.cpp | 2 +- 19 files changed, 163 insertions(+), 176 deletions(-) diff --git a/client/ClientCommandManager.cpp b/client/ClientCommandManager.cpp index 58aa38af1..fc41ffab1 100644 --- a/client/ClientCommandManager.cpp +++ b/client/ClientCommandManager.cpp @@ -394,7 +394,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu { std::string URI; singleWordBuffer >> URI; - auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::ALPHA); + auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::SIMPLE); anim->exportBitmaps(VCMIDirs::get().userExtractedPath()); } diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index a2175e1db..b41b64bf6 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -883,7 +883,7 @@ uint32_t CastAnimation::getAttackClimaxFrame() const EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed): BattleAnimation(owner), - animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::ALPHA)), + animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::SIMPLE)), effectFlags(effects), effectFinished(false), reversed(reversed) diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 95ab1c349..27c62535e 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -114,7 +114,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): //preparing cells and hexes cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY); - cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"), EImageBlitMode::ALPHA); + cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"), EImageBlitMode::SIMPLE); cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY); cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY); @@ -124,8 +124,6 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"), EImageBlitMode::COLORKEY); shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY); - cellShade->setShadowEnabled(true); - if(!owner.siegeController) { auto bfieldType = owner.getBattle()->battleGetBattlefieldType(); diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index add607dad..b7622316c 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -398,7 +398,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her else animationPath = hero->getHeroClass()->imageBattleMale; - animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA); + animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::WITH_SHADOW); pos.w = 64; pos.h = 136; diff --git a/client/battle/BattleObstacleController.cpp b/client/battle/BattleObstacleController.cpp index e291ee62b..c5c8391b3 100644 --- a/client/battle/BattleObstacleController.cpp +++ b/client/battle/BattleObstacleController.cpp @@ -50,11 +50,11 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi) if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) { // obstacle uses single bitmap image for animations - obstacleImages[oi.uniqueID] = GH.renderHandler().loadImage(animationName.toType(), EImageBlitMode::COLORKEY); + obstacleImages[oi.uniqueID] = GH.renderHandler().loadImage(animationName.toType(), EImageBlitMode::SIMPLE); } else { - obstacleAnimations[oi.uniqueID] = GH.renderHandler().loadAnimation(animationName, EImageBlitMode::COLORKEY); + obstacleAnimations[oi.uniqueID] = GH.renderHandler().loadAnimation(animationName, EImageBlitMode::SIMPLE); obstacleImages[oi.uniqueID] = obstacleAnimations[oi.uniqueID]->getImage(0); } } @@ -78,7 +78,7 @@ void BattleObstacleController::obstacleRemoved(const std::vectorgetImage(0, 0); if(!first) continue; @@ -105,7 +105,7 @@ void BattleObstacleController::obstaclePlaced(const std::vectorvisibleForSide(side, owner.getBattle()->battleHasNativeStack(side))) continue; - auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation(), EImageBlitMode::ALPHA); + auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation(), EImageBlitMode::SIMPLE); auto first = animation->getImage(0, 0); if(!first) continue; diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index b028a597f..31ab870cf 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -17,6 +17,7 @@ #include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/ColorFilter.h" +#include "../render/Colors.h" #include "../render/IRenderHandler.h" static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 }; @@ -199,8 +200,8 @@ CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedControll speedController(controller), once(false) { - forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA); - reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA); + forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_OVERLAY); + reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_OVERLAY); // if necessary, add one frame into vcmi-only group DEAD if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0) @@ -339,15 +340,14 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, if(image) { - image->setShadowEnabled(true); - image->setOverlayEnabled(isIdle()); if (isIdle()) image->setOverlayColor(genBorderColor(getBorderStrength(elapsedTime), border)); + else + image->setOverlayColor(Colors::TRANSPARENCY); image->adjustPalette(shifter, 0); canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h)); - } } diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 294d0bcc1..9b56ccd9d 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -316,7 +316,7 @@ uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 & MapRendererFow::MapRendererFow() { fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"), EImageBlitMode::OPAQUE); - fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::ALPHA); + fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::SIMPLE); static const std::vector rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27}; @@ -383,24 +383,25 @@ std::shared_ptr MapRendererObjects::getBaseAnimation(const CGObjectI } bool generateMovementGroups = (info->id == Obj::BOAT) || (info->id == Obj::HERO); + bool enableOverlay = obj->ID != Obj::BOAT && obj->ID != Obj::HERO && obj->getOwner() != PlayerColor::UNFLAGGABLE; // Boat appearance files only contain single, unanimated image // proper boat animations are actually in different file if (info->id == Obj::BOAT) if(auto boat = dynamic_cast(obj); boat && !boat->actualAnimation.empty()) - return getAnimation(boat->actualAnimation, generateMovementGroups); + return getAnimation(boat->actualAnimation, generateMovementGroups, enableOverlay); - return getAnimation(info->animationFile, generateMovementGroups); + return getAnimation(info->animationFile, generateMovementGroups, enableOverlay); } -std::shared_ptr MapRendererObjects::getAnimation(const AnimationPath & filename, bool generateMovementGroups) +std::shared_ptr MapRendererObjects::getAnimation(const AnimationPath & filename, bool generateMovementGroups, bool enableOverlay) { auto it = animations.find(filename); if(it != animations.end()) return it->second; - auto ret = GH.renderHandler().loadAnimation(filename, EImageBlitMode::ALPHA); + auto ret = GH.renderHandler().loadAnimation(filename, enableOverlay ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::WITH_SHADOW); animations[filename] = ret; if(generateMovementGroups) @@ -427,14 +428,14 @@ std::shared_ptr MapRendererObjects::getFlagAnimation(const CGObjectI { assert(dynamic_cast(obj) != nullptr); assert(obj->tempOwner.isValidPlayer()); - return getAnimation(AnimationPath::builtin(heroFlags[obj->tempOwner.getNum()]), true); + return getAnimation(AnimationPath::builtin(heroFlags[obj->tempOwner.getNum()]), true, false); } if(obj->ID == Obj::BOAT) { const auto * boat = dynamic_cast(obj); if(boat && boat->hero && !boat->flagAnimations[boat->hero->tempOwner.getNum()].empty()) - return getAnimation(boat->flagAnimations[boat->hero->tempOwner.getNum()], true); + return getAnimation(boat->flagAnimations[boat->hero->tempOwner.getNum()], true, false); } return nullptr; @@ -447,7 +448,7 @@ std::shared_ptr MapRendererObjects::getOverlayAnimation(const CGObje // Boats have additional animation with waves around boat const auto * boat = dynamic_cast(obj); if(boat && boat->hero && !boat->overlayAnimation.empty()) - return getAnimation(boat->overlayAnimation, true); + return getAnimation(boat->overlayAnimation, true, false); } return nullptr; } @@ -478,22 +479,14 @@ void MapRendererObjects::renderImage(IMapRendererContext & context, Canvas & tar return; image->setAlpha(transparency); - image->setShadowEnabled(true); - if (object->ID != Obj::HERO) + if (object->ID != Obj::HERO) // heroes use separate image with flag instead of player-colored palette { - image->setOverlayEnabled(object->getOwner().isValidPlayer() || object->getOwner() == PlayerColor::NEUTRAL); - if (object->getOwner().isValidPlayer()) image->setOverlayColor(graphics->playerColors[object->getOwner().getNum()]); if (object->getOwner() == PlayerColor::NEUTRAL) image->setOverlayColor(graphics->neutralColor); } - else - { - // heroes use separate image with flag instead of player-colored palette - image->setOverlayEnabled(false); - } Point offsetPixels = context.objectImageOffset(object->id, coordinates); @@ -567,10 +560,10 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 & } MapRendererOverlay::MapRendererOverlay() - : imageGrid(GH.renderHandler().loadImage(ImagePath::builtin("debug/grid"), EImageBlitMode::ALPHA)) - , imageBlocked(GH.renderHandler().loadImage(ImagePath::builtin("debug/blocked"), EImageBlitMode::ALPHA)) - , imageVisitable(GH.renderHandler().loadImage(ImagePath::builtin("debug/visitable"), EImageBlitMode::ALPHA)) - , imageSpellRange(GH.renderHandler().loadImage(ImagePath::builtin("debug/spellRange"), EImageBlitMode::ALPHA)) + : imageGrid(GH.renderHandler().loadImage(ImagePath::builtin("debug/grid"), EImageBlitMode::COLORKEY)) + , imageBlocked(GH.renderHandler().loadImage(ImagePath::builtin("debug/blocked"), EImageBlitMode::COLORKEY)) + , imageVisitable(GH.renderHandler().loadImage(ImagePath::builtin("debug/visitable"), EImageBlitMode::COLORKEY)) + , imageSpellRange(GH.renderHandler().loadImage(ImagePath::builtin("debug/spellRange"), EImageBlitMode::COLORKEY)) { } @@ -626,7 +619,7 @@ uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 & } MapRendererPath::MapRendererPath() - : pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::ALPHA)) + : pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::SIMPLE)) { } diff --git a/client/mapView/MapRenderer.h b/client/mapView/MapRenderer.h index e5bc2066c..e248f8cec 100644 --- a/client/mapView/MapRenderer.h +++ b/client/mapView/MapRenderer.h @@ -77,7 +77,7 @@ class MapRendererObjects std::shared_ptr getFlagAnimation(const CGObjectInstance * obj); std::shared_ptr getOverlayAnimation(const CGObjectInstance * obj); - std::shared_ptr getAnimation(const AnimationPath & filename, bool generateMovementGroups); + std::shared_ptr getAnimation(const AnimationPath & filename, bool generateMovementGroups, bool enableOverlay); std::shared_ptr getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr & animation) const; diff --git a/client/render/IImage.h b/client/render/IImage.h index 0fd238ee0..a6e4e8b7b 100644 --- a/client/render/IImage.h +++ b/client/render/IImage.h @@ -37,9 +37,29 @@ enum class EImageBlitMode : uint8_t /// RGBA: full alpha transparency range, e.g. shadows COLORKEY, - /// Should be avoided if possible, use only for images that use def's with semi-transparency - /// Indexed or RGBA: Image might have full alpha transparency range, e.g. shadows - ALPHA + /// Full transparency including shadow, but treated as a single image + /// Indexed: Image can have alpha transparency, e.g. shadow + /// RGBA: full alpha transparency range, e.g. shadows + /// Upscaled form: single image, no option to display shadow separately + SIMPLE, + + /// RGBA, may consist from 2 separate parts: base and shadow, overlay not preset or treated as part of body + WITH_SHADOW, + + /// RGBA, may consist from 3 separate parts: base, shadow, and overlay + WITH_SHADOW_AND_OVERLAY, + + /// RGBA, contains only body, with shadow and overlay disabled + ONLY_BODY, + + /// RGBA, contains only body, with shadow disabled and overlay treated as part of body + ONLY_BODY_IGNORE_OVERLAY, + + /// RGBA, contains only shadow + ONLY_SHADOW, + + /// RGBA, contains only overlay + ONLY_OVERLAY, }; /// Base class for images for use in client code. @@ -75,9 +95,6 @@ public: //only indexed bitmaps with 7 special colors virtual void setOverlayColor(const ColorRGBA & color) = 0; - virtual void setShadowEnabled(bool on) = 0; - virtual void setBodyEnabled(bool on) = 0; - virtual void setOverlayEnabled(bool on) = 0; virtual std::shared_ptr getSharedImage() const = 0; virtual ~IImage() = default; diff --git a/client/render/ImageLocator.cpp b/client/render/ImageLocator.cpp index 27c377d5b..aefbc6d30 100644 --- a/client/render/ImageLocator.cpp +++ b/client/render/ImageLocator.cpp @@ -124,8 +124,12 @@ std::string ImageLocator::toString() const if (playerColored.isValidPlayer()) result += "-player" + playerColored.toString(); - if (layer != EImageLayer::ALL) - result += "-layer" + std::to_string(static_cast(layer)); + if (layer == EImageBlitMode::ONLY_OVERLAY) + result += "-overlay"; + + if (layer == EImageBlitMode::ONLY_SHADOW) + result += "-shadow"; + return result; } diff --git a/client/render/ImageLocator.h b/client/render/ImageLocator.h index f03150981..1868caaf2 100644 --- a/client/render/ImageLocator.h +++ b/client/render/ImageLocator.h @@ -9,18 +9,11 @@ */ #pragma once +#include "IImage.h" + #include "../../lib/filesystem/ResourcePath.h" #include "../../lib/constants/EntityIdentifiers.h" -enum class EImageLayer -{ - ALL, - - BODY, - SHADOW, - OVERLAY, -}; - struct ImageLocator { std::optional image; @@ -28,13 +21,13 @@ struct ImageLocator int defFrame = -1; int defGroup = -1; - PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE; + PlayerColor playerColored = PlayerColor::CANNOT_DETERMINE; // FIXME: treat as identical to blue to avoid double-loading? bool verticalFlip = false; bool horizontalFlip = false; int8_t scalingFactor = 0; // 0 = auto / use default scaling int8_t preScaledFactor = 1; - EImageLayer layer = EImageLayer::ALL; + EImageBlitMode layer = EImageBlitMode::OPAQUE; ImageLocator() = default; ImageLocator(const AnimationPath & path, int frame, int group); diff --git a/client/renderSDL/ImageScaled.cpp b/client/renderSDL/ImageScaled.cpp index 4c80a9f52..219efb999 100644 --- a/client/renderSDL/ImageScaled.cpp +++ b/client/renderSDL/ImageScaled.cpp @@ -28,9 +28,7 @@ ImageScaled::ImageScaled(const ImageLocator & inputLocator, const std::shared_pt , alphaValue(SDL_ALPHA_OPAQUE) , blitMode(mode) { - setBodyEnabled(true); - if (mode == EImageBlitMode::ALPHA) - setShadowEnabled(true); + prepareImages(); } std::shared_ptr ImageScaled::getSharedImage() const @@ -92,8 +90,7 @@ void ImageScaled::setOverlayColor(const ColorRGBA & color) void ImageScaled::playerColored(PlayerColor player) { playerColor = player; - if (body) - setBodyEnabled(true); // regenerate + prepareImages(); } void ImageScaled::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) @@ -106,41 +103,63 @@ void ImageScaled::adjustPalette(const ColorFilter &shifter, uint32_t colorsToSki // TODO: implement } -void ImageScaled::setShadowEnabled(bool on) +void ImageScaled::prepareImages() { - assert(blitMode == EImageBlitMode::ALPHA); - if (on) + switch(blitMode) { - locator.layer = EImageLayer::SHADOW; - locator.playerColored = PlayerColor::CANNOT_DETERMINE; - shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); - } - else - shadow = nullptr; -} + case EImageBlitMode::OPAQUE: + case EImageBlitMode::COLORKEY: + case EImageBlitMode::SIMPLE: + locator.layer = blitMode; + locator.playerColored = playerColor; + body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + break; -void ImageScaled::setBodyEnabled(bool on) -{ - if (on) + case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: + case EImageBlitMode::ONLY_BODY: + locator.layer = EImageBlitMode::ONLY_BODY; + locator.playerColored = playerColor; + body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + break; + + case EImageBlitMode::WITH_SHADOW: + case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: + locator.layer = EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY; + locator.playerColored = playerColor; + body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + break; + + case EImageBlitMode::ONLY_SHADOW: + case EImageBlitMode::ONLY_OVERLAY: + body = nullptr; + break; + } + + switch(blitMode) { - locator.layer = blitMode == EImageBlitMode::ALPHA ? EImageLayer::BODY : EImageLayer::ALL; - locator.playerColored = playerColor; - body = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + case EImageBlitMode::SIMPLE: + case EImageBlitMode::WITH_SHADOW: + case EImageBlitMode::ONLY_SHADOW: + case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: + locator.layer = EImageBlitMode::ONLY_SHADOW; + locator.playerColored = PlayerColor::CANNOT_DETERMINE; + shadow = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + break; + default: + shadow = nullptr; + break; } - else - body = nullptr; -} - -void ImageScaled::setOverlayEnabled(bool on) -{ - assert(blitMode == EImageBlitMode::ALPHA); - if (on) + switch(blitMode) { - locator.layer = EImageLayer::OVERLAY; - locator.playerColored = PlayerColor::CANNOT_DETERMINE; - overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + case EImageBlitMode::ONLY_OVERLAY: + case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: + locator.layer = EImageBlitMode::ONLY_OVERLAY; + locator.playerColored = PlayerColor::CANNOT_DETERMINE; + overlay = GH.renderHandler().loadImage(locator, blitMode)->getSharedImage(); + break; + default: + overlay = nullptr; + break; } - else - overlay = nullptr; } diff --git a/client/renderSDL/ImageScaled.h b/client/renderSDL/ImageScaled.h index 8d13d0376..40f4b2c7e 100644 --- a/client/renderSDL/ImageScaled.h +++ b/client/renderSDL/ImageScaled.h @@ -44,6 +44,7 @@ private: uint8_t alphaValue; EImageBlitMode blitMode; + void prepareImages(); public: ImageScaled(const ImageLocator & locator, const std::shared_ptr & source, EImageBlitMode mode); @@ -60,8 +61,5 @@ public: void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; - void setShadowEnabled(bool on) override; - void setBodyEnabled(bool on) override; - void setOverlayEnabled(bool on) override; std::shared_ptr getSharedImage() const override; }; diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 1ae7009ba..ccfd5bec8 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -303,22 +303,14 @@ std::shared_ptr RenderHandler::scaleImage(const ImageLocator if (imageFiles.count(locator)) return imageFiles.at(locator); - auto handle = image->createImageReference(locator.layer == EImageLayer::ALL ? EImageBlitMode::OPAQUE : EImageBlitMode::ALPHA); + auto handle = image->createImageReference(locator.layer); assert(locator.scalingFactor != 1); // should be filtered-out before - - handle->setBodyEnabled(locator.layer == EImageLayer::ALL || locator.layer == EImageLayer::BODY); - if (locator.layer != EImageLayer::ALL) - { - handle->setOverlayEnabled(locator.layer == EImageLayer::OVERLAY); - handle->setShadowEnabled( locator.layer == EImageLayer::SHADOW); - } - if (locator.layer == EImageLayer::ALL && locator.playerColored != PlayerColor::CANNOT_DETERMINE) + if (locator.playerColored != PlayerColor::CANNOT_DETERMINE) handle->playerColored(locator.playerColored); handle->scaleInteger(locator.scalingFactor); - // TODO: try to optimize image size (possibly even before scaling?) - trim image borders if they are completely transparent auto result = handle->getSharedImage(); storeCachedImage(locator, result); return result; @@ -331,9 +323,9 @@ std::shared_ptr RenderHandler::loadImage(const ImageLocator & locator, E if(adjustedLocator.image) { std::string imgPath = (*adjustedLocator.image).getName(); - if(adjustedLocator.layer == EImageLayer::OVERLAY) + if(adjustedLocator.layer == EImageBlitMode::ONLY_OVERLAY) imgPath += "-OVERLAY"; - if(adjustedLocator.layer == EImageLayer::SHADOW) + if(adjustedLocator.layer == EImageBlitMode::ONLY_SHADOW) imgPath += "-SHADOW"; if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)) || @@ -394,7 +386,7 @@ std::shared_ptr RenderHandler::loadImage(const ImagePath & path, EImageB std::shared_ptr RenderHandler::createImage(SDL_Surface * source) { - return std::make_shared(source)->createImageReference(EImageBlitMode::ALPHA); + return std::make_shared(source)->createImageReference(EImageBlitMode::SIMPLE); } std::shared_ptr RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index fe3f110e3..37cb8711c 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -180,7 +180,7 @@ void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Poin if (palette && surf->format->palette) SDL_SetSurfacePalette(surf, palette); - if(surf->format->palette && mode == EImageBlitMode::ALPHA) + if(surf->format->palette && mode != EImageBlitMode::OPAQUE && mode != EImageBlitMode::COLORKEY) { CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift, alpha); } @@ -425,7 +425,7 @@ void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) { // If shadow is enabled, following colors must be skipped unconditionally - if (shadowEnabled) + if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY) colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4); // Note: here we skip first colors in the palette that are predefined in H3 images @@ -445,15 +445,10 @@ SDLImageIndexed::SDLImageIndexed(const std::shared_ptr & ima :SDLImageBase::SDLImageBase(image, mode) ,originalPalette(originalPalette) { - currentPalette = SDL_AllocPalette(originalPalette->ncolors); SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors); - if (mode == EImageBlitMode::ALPHA) - { - setOverlayColor(Colors::TRANSPARENCY); - setShadowTransparency(1.0); - } + preparePalette(); } SDLImageIndexed::~SDLImageIndexed() @@ -500,36 +495,42 @@ void SDLImageIndexed::setOverlayColor(const ColorRGBA & color) } } -void SDLImageIndexed::setShadowEnabled(bool on) +void SDLImageIndexed::preparePalette() { - if (on) - setShadowTransparency(1.0); + switch(blitMode) + { + case EImageBlitMode::ONLY_SHADOW: + case EImageBlitMode::ONLY_OVERLAY: + adjustPalette(ColorFilter::genAlphaShifter(0), 0); + break; + } - if (!on && blitMode == EImageBlitMode::ALPHA) - setShadowTransparency(0.0); + switch(blitMode) + { + case EImageBlitMode::SIMPLE: + case EImageBlitMode::WITH_SHADOW: + case EImageBlitMode::ONLY_SHADOW: + case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: + setShadowTransparency(1.0); + break; + case EImageBlitMode::ONLY_BODY: + case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: + case EImageBlitMode::ONLY_OVERLAY: + setShadowTransparency(0.0); + break; + } - shadowEnabled = on; -} - -void SDLImageIndexed::setBodyEnabled(bool on) -{ - if (on) - adjustPalette(ColorFilter::genEmptyShifter(), 0); - else - adjustPalette(ColorFilter::genAlphaShifter(0), 0); - - bodyEnabled = on; -} - -void SDLImageIndexed::setOverlayEnabled(bool on) -{ - if (on) - setOverlayColor(Colors::WHITE_TRUE); - - if (!on && blitMode == EImageBlitMode::ALPHA) - setOverlayColor(Colors::TRANSPARENCY); - - overlayEnabled = on; + switch(blitMode) + { + case EImageBlitMode::ONLY_OVERLAY: + case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: + setOverlayColor(Colors::WHITE_TRUE); + break; + case EImageBlitMode::ONLY_SHADOW: + case EImageBlitMode::ONLY_BODY: + setOverlayColor(Colors::TRANSPARENCY); + break; + } } SDLImageShared::~SDLImageShared() @@ -609,21 +610,6 @@ void SDLImageBase::setBlitMode(EImageBlitMode mode) blitMode = mode; } -void SDLImageRGB::setShadowEnabled(bool on) -{ - // Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images -} - -void SDLImageRGB::setBodyEnabled(bool on) -{ - // Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images -} - -void SDLImageRGB::setOverlayEnabled(bool on) -{ - // Not supported. Theoretically we can try to extract all pixels of specific colors, but better to use 8-bit images or composite images -} - void SDLImageRGB::setOverlayColor(const ColorRGBA & color) {} diff --git a/client/renderSDL/SDLImage.h b/client/renderSDL/SDLImage.h index c6ba35a63..833ccec42 100644 --- a/client/renderSDL/SDLImage.h +++ b/client/renderSDL/SDLImage.h @@ -89,11 +89,8 @@ class SDLImageIndexed final : public SDLImageBase SDL_Palette * currentPalette = nullptr; SDL_Palette * originalPalette = nullptr; - bool bodyEnabled = true; - bool shadowEnabled = false; - bool overlayEnabled = false; - void setShadowTransparency(float factor); + void preparePalette(); public: SDLImageIndexed(const std::shared_ptr & image, SDL_Palette * palette, EImageBlitMode mode); ~SDLImageIndexed(); @@ -106,10 +103,6 @@ public: void scaleInteger(int factor) override; void scaleTo(const Point & size) override; void exportBitmap(const boost::filesystem::path & path) const override; - - void setShadowEnabled(bool on) override; - void setBodyEnabled(bool on) override; - void setOverlayEnabled(bool on) override; }; class SDLImageRGB final : public SDLImageBase @@ -125,8 +118,4 @@ public: void scaleInteger(int factor) override; void scaleTo(const Point & size) override; void exportBitmap(const boost::filesystem::path & path) const override; - - void setShadowEnabled(bool on) override; - void setBodyEnabled(bool on) override; - void setOverlayEnabled(bool on) override; }; diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 06a16a6d4..68af0252e 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -194,12 +194,12 @@ CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, size_t Group, i { pos.x += x; pos.y += y; - anim = GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY); + anim = GH.renderHandler().loadAnimation(name, (Flags & CCreatureAnim::CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY); init(); } CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags): - anim(GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY)), + anim(GH.renderHandler().loadAnimation(name, (Flags & CCreatureAnim::CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY)), frame(Frame), group(Group), flags(Flags), @@ -317,7 +317,7 @@ bool CAnimImage::isPlayerColored() const } CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha): - anim(GH.renderHandler().loadAnimation(name, (Flags & CREATURE_MODE) ? EImageBlitMode::ALPHA : EImageBlitMode::COLORKEY)), + anim(GH.renderHandler().loadAnimation(name, (Flags & CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY)), group(Group), frame(0), first(0), @@ -430,8 +430,6 @@ void CShowableAnim::blitImage(size_t frame, size_t group, Canvas & to) auto img = anim->getImage(frame, group); if(img) { - if (flags & CREATURE_MODE) - img->setShadowEnabled(true); img->setAlpha(alpha); to.draw(img, pos.topLeft(), src); } diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 0c210734a..eba40eedc 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -98,7 +98,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::COLORKEY); if(!str->areaName.empty()) - area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::ALPHA); + area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::SIMPLE); } const CBuilding * CBuildingRect::getBuilding() diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index e97aab221..209b03ef7 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -950,7 +950,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu } else if(auto uni = dynamic_cast(_market); uni->appearance) { - titlePic = std::make_shared(uni->appearance->animationFile, 0); + titlePic = std::make_shared(uni->appearance->animationFile, 0, 0, 0, 0, CShowableAnim::CREATURE_MODE); titleStr = uni->title; speechStr = uni->speech; } From 51c5536b5082c3ec2edf551d78cf1a356dba9ff1 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 17:55:09 +0000 Subject: [PATCH 604/726] Banned skills known by hero now have non-zero selection chance --- lib/entities/hero/CHeroClass.cpp | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/lib/entities/hero/CHeroClass.cpp b/lib/entities/hero/CHeroClass.cpp index da6355e70..4a8d6d308 100644 --- a/lib/entities/hero/CHeroClass.cpp +++ b/lib/entities/hero/CHeroClass.cpp @@ -23,29 +23,20 @@ SecondarySkill CHeroClass::chooseSecSkill(const std::set & possi { assert(!possibles.empty()); - if (possibles.size() == 1) - return *possibles.begin(); + std::vector weights; + std::vector skills; - int totalProb = 0; - for(const auto & possible : possibles) - if (secSkillProbability.count(possible) != 0) - totalProb += secSkillProbability.at(possible); - - if (totalProb == 0) // may trigger if set contains only banned skills (0 probability) - return *RandomGeneratorUtil::nextItem(possibles, rand); - - auto ran = rand.nextInt(totalProb - 1); for(const auto & possible : possibles) { + skills.push_back(possible); if (secSkillProbability.count(possible) != 0) - ran -= secSkillProbability.at(possible); - - if(ran < 0) - return possible; + weights.push_back(secSkillProbability.at(possible)); + else + weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked } - assert(0); // should not be possible - return *possibles.begin(); + int selectedIndex = RandomGeneratorUtil::nextItemWeighted(weights, rand); + return skills.at(selectedIndex); } bool CHeroClass::isMagicHero() const From 0c4eb5156b4bd00e2a428fe56a4f30d859ada902 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 18:07:28 +0000 Subject: [PATCH 605/726] Put imprisoned heroes back to prison --- lib/mapObjects/CGHeroInstance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index a512065be..7380ef70b 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -338,7 +338,8 @@ void CGHeroInstance::setHeroType(HeroTypeID heroType) void CGHeroInstance::initObj(vstd::RNG & rand) { - updateAppearance(); + if (ID == Obj::HERO) + updateAppearance(); } void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID) From fd1a31253b5ca55d8e885771cfcbdc312ecb0fdd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 19:35:10 +0000 Subject: [PATCH 606/726] Improve HD Graphics docs a bit --- docs/modders/HD_Graphics.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/modders/HD_Graphics.md b/docs/modders/HD_Graphics.md index 1704f599f..fc0f155e8 100644 --- a/docs/modders/HD_Graphics.md +++ b/docs/modders/HD_Graphics.md @@ -1,10 +1,10 @@ # HD Graphics -It's possible to provide alternative HD-Graphics within mods. They will be used if any upscaling filter is activated. +It's possible to provide alternative high-definition graphics within mods. They will be used if any upscaling filter is activated. ## Preconditions -It's still necessary to add 1x graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled. +It's still necessary to add 1x standard definition graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled. Currently following scaling factors are possible to use: 2x, 3x, 4x. You can also provide multiple of them (increases size of mod, but improves loading performance for player). It's recommend to provide 2x and 3x images. @@ -20,10 +20,15 @@ The sprites should have the same name and folder structure as in `sprites` and ` ### Shadows / Overlays -It's also possible (but not necessary) to add HD shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`. +It's also possible (but not necessary) to add high-definition shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`. +In future, such shadows will likely become required to correctly exclude shadow from effects such as Clone spell. + +Shadow images are used only for animations of following objects: +- All adventure map objects +- All creature animations in combat Same for overlays with `-overlay`. But overlays are **necessary** for some animation graphics. They will be colorized by VCMI. Currently needed for: -- flaggable adventure map objects (needs a transparent image with white flags on it) -- creature battle animations (needs a transparent image with white outline of creature for highlighting on mouse hover) +- Flaggable adventure map objects. Overlay must contain a transparent image with white flags on it and will be used to colorize flags to owning player +- Creature battle animations, idle and mouse hover group. Overlay must contain a transparent image with white outline of creature for highlighting on mouse hover) From dfe6e0446454fbac6b948ed11d9cc0fca6c38b2a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 19:48:16 +0000 Subject: [PATCH 607/726] Implemented semi-transparent spell effects --- client/battle/BattleAnimationClasses.cpp | 16 +++++++++------- client/battle/BattleAnimationClasses.h | 7 ++++--- client/battle/BattleEffectsController.cpp | 9 +++++---- client/battle/BattleEffectsController.h | 5 +++-- client/battle/BattleInterface.cpp | 4 ++-- client/battle/BattleStacksController.cpp | 2 +- config/schemas/spell.json | 3 ++- config/spells/ability.json | 2 +- config/spells/offensive.json | 4 ++-- config/spells/other.json | 2 +- config/spells/timed.json | 4 ++-- lib/spells/CSpellHandler.cpp | 10 ++++++++-- lib/spells/CSpellHandler.h | 1 + 13 files changed, 41 insertions(+), 28 deletions(-) diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index b41b64bf6..7d34e8978 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -881,9 +881,10 @@ uint32_t CastAnimation::getAttackClimaxFrame() const return maxFrames / 2; } -EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed): +EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, float transparencyFactor, bool reversed): BattleAnimation(owner), animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::SIMPLE)), + transparencyFactor(transparencyFactor), effectFlags(effects), effectFinished(false), reversed(reversed) @@ -892,32 +893,32 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & } EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector hex, int effects, bool reversed): - EffectAnimation(owner, animationName, effects, reversed) + EffectAnimation(owner, animationName, effects, 1.0f, reversed) { battlehexes = hex; } -EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, bool reversed): - EffectAnimation(owner, animationName, effects, reversed) +EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, float transparencyFactor, bool reversed): + EffectAnimation(owner, animationName, effects, transparencyFactor, reversed) { assert(hex.isValid()); battlehexes.push_back(hex); } EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector pos, int effects, bool reversed): - EffectAnimation(owner, animationName, effects, reversed) + EffectAnimation(owner, animationName, effects, 1.0f, reversed) { positions = pos; } EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, int effects, bool reversed): - EffectAnimation(owner, animationName, effects, reversed) + EffectAnimation(owner, animationName, effects, 1.0f, reversed) { positions.push_back(pos); } EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects, bool reversed): - EffectAnimation(owner, animationName, effects, reversed) + EffectAnimation(owner, animationName, effects, 1.0f, reversed) { assert(hex.isValid()); battlehexes.push_back(hex); @@ -951,6 +952,7 @@ bool EffectAnimation::init() be.effectID = ID; be.animation = animation; be.currentFrame = 0; + be.transparencyFactor = transparencyFactor; be.type = reversed ? BattleEffect::AnimType::REVERSE : BattleEffect::AnimType::DEFAULT; for (size_t i = 0; i < std::max(battlehexes.size(), positions.size()); ++i) diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index fb2e6f85b..83233819b 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -309,9 +309,10 @@ public: class EffectAnimation : public BattleAnimation { std::string soundName; + int effectFlags; + float transparencyFactor; bool effectFinished; bool reversed; - int effectFlags; std::shared_ptr animation; std::vector positions; @@ -335,14 +336,14 @@ public: }; /// Create animation with screen-wide effect - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, float transparencyFactor = 1.f, bool reversed = false); /// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos , int effects = 0, bool reversed = false); EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector pos , int effects = 0, bool reversed = false); /// Create animation positioned at certain hex(es) - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex , int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false); EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector hex, int effects = 0, bool reversed = false); EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects = 0, bool reversed = false); diff --git a/client/battle/BattleEffectsController.cpp b/client/battle/BattleEffectsController.cpp index d390093d9..0d3caf2ea 100644 --- a/client/battle/BattleEffectsController.cpp +++ b/client/battle/BattleEffectsController.cpp @@ -44,7 +44,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHe displayEffect(effect, AudioPath(), destTile); } -void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile) +void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile, float transparencyFactor) { size_t effectID = static_cast(effect); @@ -52,7 +52,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPat CCS->soundh->playSound( soundFile ); - owner.stacksController->addNewAnim(new EffectAnimation(owner, customAnim, destTile)); + owner.stacksController->addNewAnim(new EffectAnimation(owner, customAnim, destTile, 0, transparencyFactor)); } void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte) @@ -69,7 +69,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt switch(static_cast(bte.effect)) { case BonusType::HP_REGENERATION: - displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition()); + displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition(), 0.5); break; case BonusType::MANA_DRAIN: displayEffect(EBattleEffect::MANA_DRAIN, AudioPath::builtin("MANADRAI"), stack->getPosition()); @@ -78,7 +78,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt displayEffect(EBattleEffect::POISON, AudioPath::builtin("POISON"), stack->getPosition()); break; case BonusType::FEAR: - displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition()); + displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition(), 0.5); break; case BonusType::MORALE: { @@ -124,6 +124,7 @@ void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer currentFrame %= elem.animation->size(); auto img = elem.animation->getImage(currentFrame, static_cast(elem.type)); + img->setAlpha(255 * elem.transparencyFactor); canvas.draw(img, elem.pos); }); diff --git a/client/battle/BattleEffectsController.h b/client/battle/BattleEffectsController.h index 5e551901a..255313d21 100644 --- a/client/battle/BattleEffectsController.h +++ b/client/battle/BattleEffectsController.h @@ -39,7 +39,8 @@ struct BattleEffect AnimType type; Point pos; //position on the screen - float currentFrame; + float currentFrame = 0.0; + float transparencyFactor = 1.0; std::shared_ptr animation; int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim BattleHex tile; //Indicates if effect which hex the effect is drawn on @@ -65,7 +66,7 @@ public: //displays custom effect on the battlefield void displayEffect(EBattleEffect effect, const BattleHex & destTile); - void displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile); + void displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile, float transparencyFactor = 1.f); void battleTriggerEffect(const BattleTriggerEffect & bte); diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 3017eff04..db02f9a2c 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -535,9 +535,9 @@ void BattleInterface::displaySpellAnimationQueue(const CSpell * spell, const CSp flags |= EffectAnimation::SCREEN_FILL; if (!destinationTile.isValid()) - stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags)); + stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags, animation.transparency)); else - stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags)); + stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags, animation.transparency)); } } } diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 398337a38..173925244 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -636,7 +636,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) { owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]() { - owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, AudioPath::builtin("DRAINLIF"), attacker->getPosition()); + owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, AudioPath::builtin("DRAINLIF"), attacker->getPosition(), 0.5); }); } diff --git a/config/schemas/spell.json b/config/schemas/spell.json index 4aac0ca66..816595068 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -22,7 +22,8 @@ "properties" : { "verticalPosition" : {"type" : "string", "enum" :["top","bottom"]}, "defName" : {"type" : "string", "format" : "animationFile"}, - "effectName" : { "type" : "string" } + "effectName" : { "type" : "string" }, + "transparency" : {"type" : "number", "minimum" : 0, "maximum" : 1} }, "additionalProperties" : false } diff --git a/config/spells/ability.json b/config/spells/ability.json index 92afcec15..4364312c1 100644 --- a/config/spells/ability.json +++ b/config/spells/ability.json @@ -252,7 +252,7 @@ "targetType": "NO_TARGET", "animation":{ - "hit":["SP04_"] + "hit":[{ "defName" : "SP04_", "transparency" : 0.5}] }, "sounds": { "cast": "DEATHCLD" diff --git a/config/spells/offensive.json b/config/spells/offensive.json index dd8583432..8f952527d 100644 --- a/config/spells/offensive.json +++ b/config/spells/offensive.json @@ -44,7 +44,7 @@ {"minimumAngle": 1.20 ,"defName":"C08SPW1"}, {"minimumAngle": 1.50 ,"defName":"C08SPW0"} ], - "hit":["C08SPW5"] + "hit":[ {"defName" : "C08SPW5", "transparency" : 0.5 }] }, "sounds": { "cast": "ICERAY" @@ -309,7 +309,7 @@ "targetType" : "CREATURE", "animation":{ - "affect":["C14SPA0"] + "affect":[{"defName" : "C14SPA0", "transparency" : 0.5}] }, "sounds": { "cast": "SACBRETH" diff --git a/config/spells/other.json b/config/spells/other.json index 81abc1e5f..5f6aa02fc 100644 --- a/config/spells/other.json +++ b/config/spells/other.json @@ -483,7 +483,7 @@ "targetType" : "CREATURE", "animation":{ - "affect":["C01SPE0"] + "affect":[{ "defName" : "C01SPE0", "transparency" : 0.5}] }, "sounds": { "cast": "RESURECT" diff --git a/config/spells/timed.json b/config/spells/timed.json index e6b7f6697..76db6315b 100644 --- a/config/spells/timed.json +++ b/config/spells/timed.json @@ -652,7 +652,7 @@ "targetType" : "CREATURE", "animation":{ - "affect":["C07SPA1"], + "affect":[{"defName" : "C07SPA1", "transparency" : 0.5}], "projectile":[{"defName":"C07SPA0"}]//??? }, "sounds": { @@ -696,7 +696,7 @@ "targetType" : "CREATURE", "animation":{ - "affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}] + "affect":[{"defName":"C10SPW", "verticalPosition":"bottom", "transparency" : 0.5}] }, "sounds": { "cast": "PRAYER" diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index a96fed944..e1154b107 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -544,7 +544,8 @@ void CSpell::serializeJson(JsonSerializeFormat & handler) ///CSpell::AnimationInfo CSpell::AnimationItem::AnimationItem() : verticalPosition(VerticalPosition::TOP), - pause(0) + pause(0), + transparency(1) { } @@ -965,10 +966,15 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c auto vPosStr = item["verticalPosition"].String(); if("bottom" == vPosStr) newItem.verticalPosition = VerticalPosition::BOTTOM; + + if (item["transparency"].isNumber()) + newItem.transparency = item["transparency"].Float(); + else + newItem.transparency = 1.0; } else if(item.isNumber()) { - newItem.pause = static_cast(item.Float()); + newItem.pause = item.Integer(); } q.push_back(newItem); diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 30154ed4c..77e69ff47 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -74,6 +74,7 @@ public: AnimationPath resourceName; std::string effectName; VerticalPosition verticalPosition; + float transparency; int pause; AnimationItem(); From 91940dbfc6d762212a2a4e84052f9c21b7e321e8 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 20:40:38 +0000 Subject: [PATCH 608/726] Do not show selection highlight on creature preview images --- client/widgets/Images.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 68af0252e..4cbb6be1a 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -20,6 +20,7 @@ #include "../render/CAnimation.h" #include "../render/Canvas.h" #include "../render/ColorFilter.h" +#include "../render/Colors.h" #include "../battle/BattleInterface.h" #include "../battle/BattleInterfaceClasses.h" @@ -431,6 +432,7 @@ void CShowableAnim::blitImage(size_t frame, size_t group, Canvas & to) if(img) { img->setAlpha(alpha); + img->setOverlayColor(Colors::TRANSPARENCY); to.draw(img, pos.topLeft(), src); } } From 29c040fa695f9b1b7164c0fc79a9834831e1198e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 17 Nov 2024 20:41:12 +0000 Subject: [PATCH 609/726] Try to crop borders for images that are not from pre-optimized def --- client/renderSDL/SDLImage.cpp | 2 ++ client/renderSDL/SDL_Extensions.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 37cb8711c..3c6dbf284 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -139,6 +139,8 @@ SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor) savePalette(); fullSize.x = surf->w; fullSize.y = surf->h; + + optimizeSurface(); } } diff --git a/client/renderSDL/SDL_Extensions.cpp b/client/renderSDL/SDL_Extensions.cpp index b7d398242..237f6800d 100644 --- a/client/renderSDL/SDL_Extensions.cpp +++ b/client/renderSDL/SDL_Extensions.cpp @@ -90,7 +90,7 @@ SDL_Surface * CSDL_Ext::newSurface(const Point & dimensions, SDL_Surface * mod) if (mod->format->palette) { assert(ret->format->palette); - assert(ret->format->palette->ncolors == mod->format->palette->ncolors); + assert(ret->format->palette->ncolors >= mod->format->palette->ncolors); memcpy(ret->format->palette->colors, mod->format->palette->colors, mod->format->palette->ncolors * sizeof(SDL_Color)); } return ret; From 931017f58bf962f8fc444d3c29b96f684dbffd24 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 18 Nov 2024 10:19:48 +0000 Subject: [PATCH 610/726] Fix bugs caused by image optimization procedure --- client/renderSDL/SDLImage.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 3c6dbf284..449b3bbfe 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -263,6 +263,13 @@ void SDLImageShared::optimizeSurface() SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE); SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr); + if (SDL_HasColorKey(surf)) + { + uint32_t colorKey; + SDL_GetColorKey(surf, &colorKey); + SDL_SetColorKey(newSurface, SDL_TRUE, colorKey); + } + SDL_FreeSurface(surf); surf = newSurface; @@ -359,7 +366,7 @@ void SDLImageIndexed::playerColored(PlayerColor player) bool SDLImageShared::isTransparent(const Point & coords) const { if (surf) - return CSDL_Ext::isTransparent(surf, coords.x, coords.y); + return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y - margins.y); else return true; } From be0da8442eddc1f0b7eab1142ce992ab9e5b2280 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 18 Nov 2024 10:45:43 +0000 Subject: [PATCH 611/726] Adjust docs --- docs/modders/Entities_Format/Spell_Format.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modders/Entities_Format/Spell_Format.md b/docs/modders/Entities_Format/Spell_Format.md index 105dba65d..e0910ca09 100644 --- a/docs/modders/Entities_Format/Spell_Format.md +++ b/docs/modders/Entities_Format/Spell_Format.md @@ -167,7 +167,7 @@ TODO ], "cast" : [] "hit":["C20SPX"], - "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] + "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom", "transparency" : 0.5}, "C11SPA1"] } ``` From d93ca47afd63b97739bb925a9a89824d4bedde68 Mon Sep 17 00:00:00 2001 From: OriginMD Date: Mon, 18 Nov 2024 10:54:41 +0000 Subject: [PATCH 612/726] [docs][iOS] improve installation manual (#4888) * Update Installation_iOS.md * Update_Installation_iOS_2.md Alternative version of the update * Update Installation_iOS_2.md * Update Installation_iOS_2.md * Update Installation_iOS_3.md * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov * Update docs/players/Installation_iOS.md Co-authored-by: Andrey Filipenkov --------- Co-authored-by: Andrey Filipenkov --- docs/players/Installation_iOS.md | 61 ++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/docs/players/Installation_iOS.md b/docs/players/Installation_iOS.md index 254bf5dcf..bf71d7225 100644 --- a/docs/players/Installation_iOS.md +++ b/docs/players/Installation_iOS.md @@ -4,41 +4,74 @@ You can run VCMI on iOS 12.0 and later, all devices are supported. If you wish t ## Step 1: Download and install VCMI +The easiest and recommended way to install on a non-jailbroken device is to install the [AltStore Classic](https://altstore.io/) or [Sideloadly](https://sideloadly.io/). We will use AltStore as an example below. Using this method means the VCMI certificate is auto-signed automatically. + +i) Use [AltStore Windows](https://faq.altstore.io/altstore-classic/how-to-install-altstore-windows) or [AltStore macOS](https://faq.altstore.io/altstore-classic/how-to-install-altstore-macos) instructions to install the store depending on the operating system you are using. + +If you're having trouble enabling "sync with this iOS device over Wi-Fi" press on the rectangular shape below "Account". Example shown below. + +![image](https://github.com/user-attachments/assets/74fe2ca2-b55c-4b05-b083-89df604248f3) + +ii) Download the VCMI-iOS.ipa file on your iOS device directly from the [latest releases](https://github.com/vcmi/vcmi/releases/latest). + +iii) To install the .ipa file on your device do one of the following: + +- In AltStore go to >My Apps > press + in the top left corner. Select VCMI-iOS.ipa to install, +- or drag and drop the .ipa file into your iOS device in iTunes + + +## Step 2: Installing Heroes III data files + +If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser in the device. + +Launch VCMI app on the device and the launcher will prompt two files to complete the installation. Select the **.bin** file first, then the **.exe** file. This may take a few seconds. Please be patient. + + +## Step 3: Configuration settings +Once you have installed VCMI and have the launcher opened, select Settings on the left bar. The following Video settings are recommended: + +- Lower reserved screen area to zero. +- Increase interface Scaling to maximum. This number will depend on your device. For 11" iPad Air it was at 273% as an example + +Together, the two options should eliminate black bars and enable full screen VCMI experience. Enjoy! + +## Alternative Step 1: Download and install VCMI + - The latest release (recommended): - Daily builds: -To run on a non-jailbroken device you need to sign the IPA file, you -have the following options: +To run on a non-jailbroken device you need to sign the IPA file, you have the following aternative options: -- (Easiest way) [AltStore](https://altstore.io/) or [Sideloadly](https://sideloadly.io/) - can be installed on Windows or macOS, don't require dealing with signing on your own -- if you're on iOS 14.0-15.4.1, you can try +- if you're on iOS 14.0-15.4.1, you can try . - Get signer tool [here](https://dantheman827.github.io/ios-app-signer/) and a guide [here](https://forum.kodi.tv/showthread.php?tid=245978) (it's for Kodi, but the logic is the same). Signing with this app can only be done on macOS. - [Create signing assets on macOS from terminal](https://github.com/kambala-decapitator/xcode-auto-signing-assets). In the command replace `your.bundle.id` with something like `com.MY-NAME.vcmi`. After that use the above signer tool. - [Sign from any OS (Rust)](https://github.com/indygreg/PyOxidizer/tree/main/tugger-code-signing) / [alternative project (C++)](https://github.com/zhlynn/zsign). You'd still need to find a way to create signing assets (private key and provisioning profile) though. -To install the signed ipa on your device, you can use Xcode or Apple Configurator (available on the Mac App Store for free). The latter also allows installing ipa from the command line, here's an example that assumes you have only 1 device connected to your Mac and the signed ipa is on your desktop: +The easiest way to install the ipa on your device is to do one of the following: + +- In AltStore go to >My Apps > press + in the top left corner. Select VCMI-iOS.ipa to install or + +- Drag and drop the .ipa file into your iOS device in iTunes + +Alternatively, to install the signed ipa on your device, you can use Xcode or Apple Configurator (available on the Mac App Store for free). The latter also allows installing ipa from the command line, here's an example that assumes you have only 1 device connected to your Mac and the signed ipa is on your desktop: /Applications/Apple\ Configurator.app/Contents/MacOS/cfgutil install-app ~/Desktop/vcmi.ipa -## Step 2: Installing Heroes III data files +## Alternative Step 2: Installing Heroes III data files -Note: if you don't need in-game videos, you can omit downloading/copying file VIDEO.VID from data folder - it will save your time and space. The same applies to the Mp3 directory. +Note: if you don't need in-game videos, you can omit downloading/copying file VIDEO.VID from the Data folder - it will save your time and space. The same applies to the Mp3 directory. -### Step 2.a: Installing data files with GOG offline installer - -If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .bin file first, then the .exe file. This may take a few seconds. Please be patient. - -### Step 2.b: Installing data files with Finder or Windows explorer +### Step 2.a: Installing data files with Finder or Windows explorer To play the game, you need to upload HoMM3 data files - **Data**, **Maps** and **Mp3** directories - to the device. Use Finder (or iTunes, if you're on Windows or your macOS is 10.14 or earlier) for that. You can also add various mods by uploading **Mods** directory. Follow [official Apple guide](https://support.apple.com/en-us/HT210598) and place files into VCMI app. Unfortunately, Finder doesn't display copy progress, give it about 10 minutes to finish. -### Step 2.c: Installing data files using iOS device only +### Step 2.b: Installing data files using iOS device only If you have data somewhere on device or in shared folder or you have downloaded it, you can copy it directly on your iPhone/iPad using Files application. Place **Data**, **Maps** and **Mp3** folders into vcmi application - it will be visible in Files along with other applications' folders. -### Step 2.d: Installing data files with Xcode on macOS +### Step 2.c: Installing data files with Xcode on macOS You can also upload files with Xcode. You need to prepare "container" for that. From bdd31cea61b784a3168e7710e37481dbb9a5f4e2 Mon Sep 17 00:00:00 2001 From: krs Date: Mon, 18 Nov 2024 20:42:32 +0200 Subject: [PATCH 613/726] Fix missing shots ability for shooters --- config/creatures/castle.json | 4 ++++ config/creatures/conflux.json | 2 ++ config/creatures/dungeon.json | 4 ++++ config/creatures/fortress.json | 2 ++ config/creatures/inferno.json | 2 ++ config/creatures/necropolis.json | 2 ++ config/creatures/neutral.json | 3 +++ config/creatures/rampart.json | 4 +++- config/creatures/special.json | 3 +++ config/creatures/stronghold.json | 4 ++++ config/creatures/tower.json | 4 ++++ 11 files changed, 33 insertions(+), 1 deletion(-) diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 34f13b06b..d769f8679 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -57,6 +57,7 @@ "extraNames": [ "lightCrossbowman" ], "faction": "castle", "upgrades": ["marksman"], + "shots" : 12, "abilities" : { "shooter" : { @@ -86,6 +87,7 @@ "index": 3, "level": 2, "faction": "castle", + "shots" : 24, "abilities": { "shooter" : { @@ -228,6 +230,7 @@ "level": 5, "faction": "castle", "upgrades": ["zealot"], + "shots" : 12, "abilities" : { "shooter" : { @@ -257,6 +260,7 @@ "index": 9, "level": 5, "faction": "castle", + "shots" : 24, "abilities" : { "shooter" : { diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index dd0d9fc8a..8d3997b33 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -127,6 +127,7 @@ "index": 127, "level": 2, "faction": "conflux", + "shots" : 24, "abilities": { "nonLiving" : @@ -301,6 +302,7 @@ "level": 3, "faction": "conflux", "doubleWide" : true, + "shots" : 24, "abilities": { "nonLiving" : diff --git a/config/creatures/dungeon.json b/config/creatures/dungeon.json index 39e6f8b67..4d7492983 100644 --- a/config/creatures/dungeon.json +++ b/config/creatures/dungeon.json @@ -138,6 +138,7 @@ "level": 3, "faction": "dungeon", "upgrades": ["evilEye"], + "shots" : 12, "abilities" : { "shooter" : @@ -179,6 +180,7 @@ "index": 75, "level": 3, "faction": "dungeon", + "shots" : 24, "abilities" : { "shooter" : @@ -221,6 +223,7 @@ "level": 4, "faction": "dungeon", "doubleWide" : true, + "shots" : 4, "abilities": { "shooter" : @@ -264,6 +267,7 @@ "level": 4, "faction": "dungeon", "doubleWide" : true, + "shots" : 8, "abilities": { "shooter" : diff --git a/config/creatures/fortress.json b/config/creatures/fortress.json index ddcafe3a6..f834f993a 100644 --- a/config/creatures/fortress.json +++ b/config/creatures/fortress.json @@ -44,6 +44,7 @@ "faction": "fortress", "upgrades": ["lizardWarrior"], "hasDoubleWeek": true, + "shots" : 12, "abilities" : { "shooter" : @@ -74,6 +75,7 @@ "index": 101, "level": 2, "faction": "fortress", + "shots" : 24, "abilities" : { "shooter" : diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index 491968747..28109e759 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -51,6 +51,7 @@ "faction": "inferno", "upgrades": ["magog"], "hasDoubleWeek": true, + "shots" : 12, "abilities" : { "shooter" : @@ -81,6 +82,7 @@ "index": 45, "level": 2, "faction": "inferno", + "shots" : 24, "abilities": { "shooter" : diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index ebbb5dc0b..06a71382d 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -265,6 +265,7 @@ "index": 64, "level": 5, "faction": "necropolis", + "shots" : 12, "abilities": { "undead" : @@ -305,6 +306,7 @@ "index": 65, "level": 5, "faction": "necropolis", + "shots" : 24, "abilities": { "undead" : diff --git a/config/creatures/neutral.json b/config/creatures/neutral.json index a8e7db89d..0e25c5231 100644 --- a/config/creatures/neutral.json +++ b/config/creatures/neutral.json @@ -323,6 +323,7 @@ "extraNames": [ "enchanters" ], "faction": "neutral", "excludeFromRandomization" : true, + "shots" : 32, "abilities": { "shooter" : @@ -410,6 +411,7 @@ "extraNames": [ "sharpshooters" ], "faction": "neutral", "excludeFromRandomization" : true, + "shots" : 32, "abilities": { "shooter" : @@ -448,6 +450,7 @@ "index": 138, "level": 1, "faction": "neutral", + "shots" : 24, "abilities": { "shooter" : diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index 7a64a1460..3b28901d7 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -101,6 +101,7 @@ "level": 3, "faction": "rampart", "upgrades": ["grandElf"], + "shots" : 24, "abilities" : { "shooter" : @@ -131,7 +132,8 @@ "index": 19, "level": 3, "faction": "rampart", - "abilities": + "shots" : 24, + "abilities" : { "shooter" : { diff --git a/config/creatures/special.json b/config/creatures/special.json index f2457fcb4..f10a35556 100644 --- a/config/creatures/special.json +++ b/config/creatures/special.json @@ -37,6 +37,7 @@ "level": 0, "faction": "neutral", "doubleWide" : true, + "shots" : 24, "abilities" : { "siegeWeapon" : @@ -75,6 +76,7 @@ "level": 0, "faction": "neutral", "doubleWide" : true, + "shots" : 24, "abilities" : { "siegeWeapon" : @@ -163,6 +165,7 @@ "index": 149, "level": 0, "faction": "neutral", + "shots" : 99, "abilities": { "shooter" : { "type" : "SHOOTER" }, diff --git a/config/creatures/stronghold.json b/config/creatures/stronghold.json index b52a3cc82..7824a2b58 100644 --- a/config/creatures/stronghold.json +++ b/config/creatures/stronghold.json @@ -92,6 +92,7 @@ "level": 3, "faction": "stronghold", "upgrades": ["orcChieftain"], + "shots" : 12, "abilities" : { "shooter" : @@ -122,6 +123,7 @@ "index": 89, "level": 3, "faction": "stronghold", + "shots" : 24, "abilities" : { "shooter" : @@ -274,6 +276,7 @@ "index": 94, "level": 6, "faction": "stronghold", + "shots" : 16, "abilities" : { "shooter" : @@ -310,6 +313,7 @@ "index": 95, "level": 6, "faction": "stronghold", + "shots" : 24, "abilities": { "shooter" : diff --git a/config/creatures/tower.json b/config/creatures/tower.json index 259b6e056..99781a907 100644 --- a/config/creatures/tower.json +++ b/config/creatures/tower.json @@ -26,6 +26,7 @@ "index": 29, "level": 1, "faction": "tower", + "shots" : 8, "abilities" : { "shooter" : @@ -178,6 +179,7 @@ "index": 34, "level": 4, "faction": "tower", + "shots" : 24, "abilities": { "shooter" : @@ -218,6 +220,7 @@ "index": 35, "level": 4, "faction": "tower", + "shots" : 24, "abilities": { "shooter" : @@ -444,6 +447,7 @@ "index": 41, "level": 7, "faction": "tower", + "shots" : 24, "abilities" : { "shooter" : From 487b5cfaf68403cafcdb251c99666078543ec130 Mon Sep 17 00:00:00 2001 From: krs Date: Mon, 18 Nov 2024 23:37:53 +0200 Subject: [PATCH 614/726] Minor fixes for creature abilities Added back siege weapon for arrow towers like before the change. Teleports uses now same name as in HotA --- config/creatures/conflux.json | 4 ++-- config/creatures/inferno.json | 4 ++-- config/creatures/special.json | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config/creatures/conflux.json b/config/creatures/conflux.json index 8d3997b33..78cb70ec1 100755 --- a/config/creatures/conflux.json +++ b/config/creatures/conflux.json @@ -474,11 +474,11 @@ { "type" : "NON_LIVING" }, - "canFly" : + "energizes" : { "type" : "FLYING" }, - "spellcaster": + "spellcaster" : { "type" : "SPELLCASTER", "subtype" : "spell.protectFire", diff --git a/config/creatures/inferno.json b/config/creatures/inferno.json index 28109e759..cbea72a91 100755 --- a/config/creatures/inferno.json +++ b/config/creatures/inferno.json @@ -355,7 +355,7 @@ "faction": "inferno", "abilities": { - "canFly" : + "teleports" : { "type" : "FLYING", "subtype" : "movementTeleporting" @@ -415,7 +415,7 @@ "faction": "inferno", "abilities" : { - "canFly" : + "teleports" : { "type" : "FLYING", "subtype" : "movementTeleporting" diff --git a/config/creatures/special.json b/config/creatures/special.json index f10a35556..4e528117a 100644 --- a/config/creatures/special.json +++ b/config/creatures/special.json @@ -168,6 +168,7 @@ "shots" : 99, "abilities": { + "siegeWeapon" : { "type" : "SIEGE_WEAPON" }, "shooter" : { "type" : "SHOOTER" }, "ignoreDefence" : { "type" : "ENEMY_DEFENCE_REDUCTION", "val" : 100 }, "noWallPenalty" : { "type" : "NO_WALL_PENALTY" }, From 7e2cad158ab07a39561f4395a90910789b613cfb Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 14 Nov 2024 12:31:11 +0100 Subject: [PATCH 615/726] HillFort fix --- lib/mapObjectConstructors/HillFortInstanceConstructor.cpp | 4 +++- lib/mapObjects/MiscObjects.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp index f71f4990e..5fdaf76c0 100644 --- a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp @@ -18,7 +18,9 @@ VCMI_LIB_NAMESPACE_BEGIN void HillFortInstanceConstructor::initTypeData(const JsonNode & config) { parameters = config; - VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "unavailableUpgradeMessage"), parameters["unavailableUpgradeMessage"].String()); + if(getModScope() != "core") + VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "unavailableUpgradeMessage"), parameters["unavailableUpgradeMessage"].String()); + VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "description"), parameters["description"].String()); } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 78ab960ab..5ba03e4a9 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1356,6 +1356,7 @@ std::string HillFort::getDescriptionToolTip() const std::string HillFort::getUnavailableUpgradeMessage() const { + assert(getObjectHandler()->getModScope() != "core"); return TextIdentifier(getObjectHandler()->getBaseTextID(), "unavailableUpgradeMessage").get(); } From ab1f1d6d99853861d1b1a81dcd588ec970f1db6e Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Fri, 15 Nov 2024 00:58:35 +0100 Subject: [PATCH 616/726] Fix crash on miniHillFort popup window --- lib/mapObjects/MiscObjects.cpp | 2 +- lib/texts/MetaString.cpp | 7 ++++++- lib/texts/MetaString.h | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 5ba03e4a9..910baa639 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1337,7 +1337,7 @@ std::string HillFort::getPopupText(PlayerColor player) const { MetaString message = MetaString::createFromRawString("{%s}\r\n\r\n%s"); - message.replaceName(ID); + message.replaceName(ID, subID); message.replaceTextID(getDescriptionToolTip()); return message.toString(); diff --git a/lib/texts/MetaString.cpp b/lib/texts/MetaString.cpp index e77fc65e6..0bb7304ec 100644 --- a/lib/texts/MetaString.cpp +++ b/lib/texts/MetaString.cpp @@ -393,11 +393,16 @@ void MetaString::replaceName(const FactionID & id) replaceTextID(id.toEntity(VLC)->getNameTextID()); } -void MetaString::replaceName(const MapObjectID& id) +void MetaString::replaceName(const MapObjectID & id) { replaceTextID(VLC->objtypeh->getObjectName(id, 0)); } +void MetaString::replaceName(const MapObjectID & id, const MapObjectSubID & subId) +{ + replaceTextID(VLC->objtypeh->getObjectName(id, subId)); +} + void MetaString::replaceName(const PlayerColor & id) { replaceTextID(TextIdentifier("vcmi.capitalColors", id.getNum()).get()); diff --git a/lib/texts/MetaString.h b/lib/texts/MetaString.h index d55cc279e..e13b55709 100644 --- a/lib/texts/MetaString.h +++ b/lib/texts/MetaString.h @@ -99,7 +99,8 @@ public: void replaceName(const ArtifactID & id); void replaceName(const FactionID& id); - void replaceName(const MapObjectID& id); + void replaceName(const MapObjectID & id); + void replaceName(const MapObjectID & id, const MapObjectSubID & subId); void replaceName(const PlayerColor& id); void replaceName(const SecondarySkill& id); void replaceName(const SpellID& id); From f823b8addfa97666ad211044f9b964c52c52fee3 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Tue, 19 Nov 2024 10:57:20 +0100 Subject: [PATCH 617/726] Following review --- lib/mapObjectConstructors/HillFortInstanceConstructor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp index 5fdaf76c0..889868b0a 100644 --- a/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp +++ b/lib/mapObjectConstructors/HillFortInstanceConstructor.cpp @@ -18,7 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN void HillFortInstanceConstructor::initTypeData(const JsonNode & config) { parameters = config; - if(getModScope() != "core") + if(!parameters["unavailableUpgradeMessage"].isNull()) VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "unavailableUpgradeMessage"), parameters["unavailableUpgradeMessage"].String()); VLC->generaltexth->registerString(parameters.getModScope(), TextIdentifier(getBaseTextID(), "description"), parameters["description"].String()); From a7a9f5d777f8d16e474fc108824aa35f038d60ce Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 19 Nov 2024 14:39:40 +0000 Subject: [PATCH 618/726] Fix possible crash on levelup when hero can only levelup banned skills --- lib/entities/hero/CHeroClass.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/entities/hero/CHeroClass.cpp b/lib/entities/hero/CHeroClass.cpp index 4a8d6d308..80e06cabf 100644 --- a/lib/entities/hero/CHeroClass.cpp +++ b/lib/entities/hero/CHeroClass.cpp @@ -30,7 +30,10 @@ SecondarySkill CHeroClass::chooseSecSkill(const std::set & possi { skills.push_back(possible); if (secSkillProbability.count(possible) != 0) - weights.push_back(secSkillProbability.at(possible)); + { + int weight = secSkillProbability.at(possible); + weights.push_back(std::max(1, weight)); + } else weights.push_back(1); // H3 behavior - banned skills have minimal (1) chance to be picked } From f59834afe1f1b812e9dd9ae9d061e0f9c2063607 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 19 Nov 2024 14:38:27 +0000 Subject: [PATCH 619/726] Fixes for configurable markets support - string "speech" can now be translated - removed "title" string, VCMI will now use object name instead - moved configuration of all "markets" into a separate json file - added schema for validation of market objects - removed serialization of translated strings from University --- client/windows/GUIClasses.cpp | 4 +- config/gameConfig.json | 1 + config/objects/generic.json | 138 ------------------ config/objects/markets.json | 138 ++++++++++++++++++ config/schemas/market.json | 50 +++++++ .../CommonConstructors.cpp | 55 ++++--- .../CommonConstructors.h | 14 +- lib/mapObjects/CGMarket.cpp | 7 +- lib/mapObjects/CGMarket.h | 21 ++- lib/serializer/ESerializationVersion.h | 3 +- 10 files changed, 252 insertions(+), 179 deletions(-) create mode 100644 config/objects/markets.json create mode 100644 config/schemas/market.json diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 209b03ef7..ddc78eace 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -951,8 +951,8 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu else if(auto uni = dynamic_cast(_market); uni->appearance) { titlePic = std::make_shared(uni->appearance->animationFile, 0, 0, 0, 0, CShowableAnim::CREATURE_MODE); - titleStr = uni->title; - speechStr = uni->speech; + titleStr = uni->getObjectName(); + speechStr = uni->getSpeechTranslated(); } else { diff --git a/config/gameConfig.json b/config/gameConfig.json index 0f51e2ea7..9692abfaf 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -56,6 +56,7 @@ "config/objects/lighthouse.json", "config/objects/magicSpring.json", "config/objects/magicWell.json", + "config/objects/markets.json", "config/objects/moddables.json", "config/objects/observatory.json", "config/objects/pyramid.json", diff --git a/config/objects/generic.json b/config/objects/generic.json index 238cfd848..3be342df6 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -18,115 +18,6 @@ } }, - "altarOfSacrifice" : { - "index" :2, - "handler" : "market", - "base" : { - "sounds" : { - "visit" : ["MYSTERY"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 20 - }, - "modes" : ["creature-experience", "artifact-experience"] - } - } - }, - "tradingPost" : { - "index" :221, - "handler" : "market", - "base" : { - "sounds" : { - "ambient" : ["LOOPMARK"], - "visit" : ["STORE"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 100 - }, - "modes" : ["resource-resource", "resource-player"], - "efficiency" : 5, - "title" : "core.genrltxt.159" - } - } - }, - "tradingPostDUPLICATE" : { - "index" :99, - "handler" : "market", - "base" : { - "sounds" : { - "ambient" : ["LOOPMARK"], - "visit" : ["STORE"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 100 - }, - "modes" : ["resource-resource", "resource-player"], - "efficiency" : 5, - "title" : "core.genrltxt.159" - } - } - }, - "freelancersGuild" : { - "index" :213, - "handler" : "market", - "types" : { - "object" : { - "index" : 0, - "aiValue" : 100, - "rmg" : { - "zoneLimit" : 1, - "value" : 100, - "rarity" : 100 - }, - "modes" : ["creature-resource"] - } - } - }, - - "blackMarket" : { - "index" :7, - "handler" : "market", - "base" : { - "sounds" : { - "ambient" : ["LOOPMARK"], - "visit" : ["MYSTERY"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 8000, - "rmg" : { - "value" : 8000, - "rarity" : 20 - }, - "modes" : ["resource-artifact"], - "title" : "core.genrltxt.349" - } - } - }, - "pandoraBox" : { "index" :6, "handler" : "pandora", @@ -393,35 +284,6 @@ } } }, - "university" : { - "index" :104, - "handler" : "market", - "base" : { - "sounds" : { - "visit" : ["GAZEBO"] - } - }, - "types" : { - "object" : { - "index" : 0, - "aiValue" : 2500, - "rmg" : { - "value" : 2500, - "rarity" : 20 - }, - "modes" : ["resource-skill"], - "title" : "core.genrltxt.602", - "speech" : "core.genrltxt.603", - "offer": - [ - { "noneOf" : ["necromancy"] }, - { "noneOf" : ["necromancy"] }, - { "noneOf" : ["necromancy"] }, - { "noneOf" : ["necromancy"] } - ] - } - } - }, "questGuard" : { "index" :215, "handler" : "questGuard", diff --git a/config/objects/markets.json b/config/objects/markets.json new file mode 100644 index 000000000..ea5c517b9 --- /dev/null +++ b/config/objects/markets.json @@ -0,0 +1,138 @@ +{ + "altarOfSacrifice" : { + "index" :2, + "handler" : "market", + "base" : { + "sounds" : { + "visit" : ["MYSTERY"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 20 + }, + "modes" : ["creature-experience", "artifact-experience"] + } + } + }, + + "tradingPost" : { + "index" :221, + "handler" : "market", + "base" : { + "sounds" : { + "ambient" : ["LOOPMARK"], + "visit" : ["STORE"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 100 + }, + "modes" : ["resource-resource", "resource-player"], + "efficiency" : 5 + } + } + }, + + "tradingPostDUPLICATE" : { + "index" :99, + "handler" : "market", + "base" : { + "sounds" : { + "ambient" : ["LOOPMARK"], + "visit" : ["STORE"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 100 + }, + "modes" : ["resource-resource", "resource-player"], + "efficiency" : 5 + } + } + }, + + "freelancersGuild" : { + "index" :213, + "handler" : "market", + "types" : { + "object" : { + "index" : 0, + "aiValue" : 100, + "rmg" : { + "zoneLimit" : 1, + "value" : 100, + "rarity" : 100 + }, + "modes" : ["creature-resource"] + } + } + }, + + "blackMarket" : { + "index" :7, + "handler" : "market", + "base" : { + "sounds" : { + "ambient" : ["LOOPMARK"], + "visit" : ["MYSTERY"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 8000, + "rmg" : { + "value" : 8000, + "rarity" : 20 + }, + "modes" : ["resource-artifact"] + } + } + }, + "university" : { + "index" :104, + "handler" : "market", + "base" : { + "sounds" : { + "visit" : ["GAZEBO"] + } + }, + "types" : { + "object" : { + "index" : 0, + "aiValue" : 2500, + "rmg" : { + "value" : 2500, + "rarity" : 20 + }, + "modes" : ["resource-skill"], + "speech" : "@core.genrltxt.603", + "offer": + [ + { "noneOf" : ["necromancy"] }, + { "noneOf" : ["necromancy"] }, + { "noneOf" : ["necromancy"] }, + { "noneOf" : ["necromancy"] } + ] + } + } + } +} \ No newline at end of file diff --git a/config/schemas/market.json b/config/schemas/market.json new file mode 100644 index 000000000..7554b453a --- /dev/null +++ b/config/schemas/market.json @@ -0,0 +1,50 @@ +{ + "type" : "object", + "$schema" : "http://json-schema.org/draft-04/schema", + "title" : "VCMI map object format", + "description" : "Description of map object class", + "required" : [ "modes" ], + + "additionalProperties" : false, + + "properties" : { + "description" : { + "description" : "Message that will be shown on right-clicking this object", + "type" : "string" + }, + + "speech" : { + "description" : "Message that will be shown to player on visiting this object", + "type" : "string" + }, + + "modes" : { + "type" : "array", + "items" : { + "enum" : [ "resource-resource", "resource-player", "creature-resource", "resource-artifact", "artifact-resource", "artifact-experience", "creature-experience", "creature-undead", "resource-skill" ], + "type" : "string" + } + }, + "efficiency" : { + "type" : "number", + "minimum" : 1, + "maximum" : 9 + }, + "offer" : { + "type" : "array" + }, + + // Properties that might appear since this node is shared with object config + "compatibilityIdentifiers" : { }, + "blockedVisitable" : { }, + "removable" : { }, + "aiValue" : { }, + "index" : { }, + "base" : { }, + "name" : { }, + "rmg" : { }, + "templates" : { }, + "battleground" : { }, + "sounds" : { } + } +} diff --git a/lib/mapObjectConstructors/CommonConstructors.cpp b/lib/mapObjectConstructors/CommonConstructors.cpp index 57b52bd43..a3986304a 100644 --- a/lib/mapObjectConstructors/CommonConstructors.cpp +++ b/lib/mapObjectConstructors/CommonConstructors.cpp @@ -17,8 +17,10 @@ #include "../TerrainHandler.h" #include "../VCMI_Lib.h" +#include "../CConfigHandler.h" #include "../entities/faction/CTownHandler.h" #include "../entities/hero/CHeroClass.h" +#include "../json/JsonUtils.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGMarket.h" #include "../mapObjects/CGTownInstance.h" @@ -242,10 +244,28 @@ AnimationPath BoatInstanceConstructor::getBoatAnimationName() const void MarketInstanceConstructor::initTypeData(const JsonNode & input) { + if (settings["mods"]["validation"].String() != "off") + JsonUtils::validate(input, "vcmi:market", getJsonKey()); + if (!input["description"].isNull()) { - description = input["description"].String(); - VLC->generaltexth->registerString(input.getModScope(), TextIdentifier(getBaseTextID(), "description"), description); + std::string description = input["description"].String(); + descriptionTextID = TextIdentifier(getBaseTextID(), "description").get(); + VLC->generaltexth->registerString( input.getModScope(), descriptionTextID, input["description"]); + } + + if (!input["speech"].isNull()) + { + std::string speech = input["speech"].String(); + if (!speech.empty() && speech.at(0) == '@') + { + speechTextID = speech.substr(1); + } + else + { + speechTextID = TextIdentifier(getBaseTextID(), "speech").get(); + VLC->generaltexth->registerString( input.getModScope(), speechTextID, input["speech"]); + } } for(auto & element : input["modes"].Vector()) @@ -256,14 +276,11 @@ void MarketInstanceConstructor::initTypeData(const JsonNode & input) marketEfficiency = input["efficiency"].isNull() ? 5 : input["efficiency"].Integer(); predefinedOffer = input["offer"]; - - title = input["title"].String(); - speech = input["speech"].String(); } bool MarketInstanceConstructor::hasDescription() const { - return !description.empty(); + return !descriptionTextID.empty(); } CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const @@ -283,21 +300,6 @@ CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const return new CGMarket(cb); } -void MarketInstanceConstructor::initializeObject(CGMarket * market) const -{ - market->marketEfficiency = marketEfficiency; - - if(auto university = dynamic_cast(market)) - { - university->title = market->getObjectName(); - if(!title.empty()) - university->title = VLC->generaltexth->translate(title); - - if(!speech.empty()) - university->speech = VLC->generaltexth->translate(speech); - } -} - const std::set & MarketInstanceConstructor::availableModes() const { return marketModes; @@ -315,4 +317,15 @@ void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & r } } +std::string MarketInstanceConstructor::getSpeechTranslated() const +{ + assert(marketModes.count(EMarketMode::RESOURCE_SKILL)); + return VLC->generaltexth->translate(speechTextID); +} + +int MarketInstanceConstructor::getMarketEfficiency() const +{ + return marketEfficiency; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjectConstructors/CommonConstructors.h b/lib/mapObjectConstructors/CommonConstructors.h index 8d4abcc67..73ba43874 100644 --- a/lib/mapObjectConstructors/CommonConstructors.h +++ b/lib/mapObjectConstructors/CommonConstructors.h @@ -115,25 +115,23 @@ public: class MarketInstanceConstructor : public CDefaultObjectTypeHandler { -protected: - void initTypeData(const JsonNode & config) override; + std::string descriptionTextID; + std::string speechTextID; std::set marketModes; JsonNode predefinedOffer; int marketEfficiency; - - std::string description; - std::string title; - std::string speech; - + + void initTypeData(const JsonNode & config) override; public: CGMarket * createObject(IGameCallback * cb) const override; - void initializeObject(CGMarket * object) const override; void randomizeObject(CGMarket * object, vstd::RNG & rng) const override; const std::set & availableModes() const; bool hasDescription() const; + std::string getSpeechTranslated() const; + int getMarketEfficiency() const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index ff2118bf2..a7eef18ad 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -57,7 +57,7 @@ std::string CGMarket::getPopupText(const CGHeroInstance * hero) const int CGMarket::getMarketEfficiency() const { - return marketEfficiency; + return getMarketHandler()->getMarketEfficiency(); } int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const @@ -125,6 +125,11 @@ std::vector CGUniversity::availableItemsIds(EMarketMode mode) cons } } +std::string CGUniversity::getSpeechTranslated() const +{ + return getMarketHandler()->getSpeechTranslated(); +} + void CGUniversity::onHeroVisit(const CGHeroInstance * h) const { cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true); diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index b28a386bc..6f4f92a42 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -19,11 +19,10 @@ class MarketInstanceConstructor; class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket { +protected: std::shared_ptr getMarketHandler() const; public: - int marketEfficiency; - CGMarket(IGameCallback *cb); ///IObjectInterface void onHeroVisit(const CGHeroInstance * h) const override; //open trading window @@ -48,7 +47,12 @@ public: h & marketModes; } - h & marketEfficiency; + if (h.version < Handler::Version::MARKET_TRANSLATION_FIX) + { + int unused = 0; + h & unused; + } + if (h.version < Handler::Version::NEW_MARKETS) { std::string speech; @@ -103,8 +107,8 @@ class DLL_LINKAGE CGUniversity : public CGMarket { public: using CGMarket::CGMarket; - std::string speech; //currently shown only in university - std::string title; + + std::string getSpeechTranslated() const; std::vector skills; //available skills @@ -115,10 +119,11 @@ public: { h & static_cast(*this); h & skills; - if (h.version >= Handler::Version::NEW_MARKETS) + if (h.version >= Handler::Version::NEW_MARKETS && h.version < Handler::Version::MARKET_TRANSLATION_FIX) { - h & speech; - h & title; + std::string temp; + h & temp; + h & temp; } } }; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 5e038bbf0..fbf723a88 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -68,6 +68,7 @@ enum class ESerializationVersion : int32_t REMOVE_VLC_POINTERS, // 869 removed remaining pointers to VLC entities FOLDER_NAME_REWORK, // 870 - rework foldername REWARDABLE_GUARDS, // 871 - fix missing serialization of guards in rewardable objects + MARKET_TRANSLATION_FIX, // 872 - remove serialization of markets translateable strings - CURRENT = REWARDABLE_GUARDS + CURRENT = MARKET_TRANSLATION_FIX }; From aef6b0cc00fb9ba7961bfe69f13c0d5fb01c173a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 20 Nov 2024 16:06:38 +0000 Subject: [PATCH 620/726] Fix several new issues detected by SonarCloud --- client/renderSDL/RenderHandler.cpp | 4 ++-- client/renderSDL/RenderHandler.h | 4 ++-- client/renderSDL/SDLImage.cpp | 11 +++++++---- lib/CCreatureSet.cpp | 2 +- lib/spells/CSpellHandler.cpp | 4 ++-- server/battles/BattleActionProcessor.cpp | 2 +- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index ccfd5bec8..c76af1242 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -55,7 +55,7 @@ std::shared_ptr RenderHandler::getAnimationFile(const AnimationPath & return result; } -std::optional RenderHandler::getPathForScaleFactor(ResourcePath path, std::string factor) +std::optional RenderHandler::getPathForScaleFactor(const ResourcePath & path, const std::string & factor) { if(path.getType() == EResType::IMAGE) { @@ -80,7 +80,7 @@ std::optional RenderHandler::getPathForScaleFactor(ResourcePath pa return std::nullopt; } -std::pair RenderHandler::getScalePath(ResourcePath p) +std::pair RenderHandler::getScalePath(const ResourcePath & p) { auto path = p; int scaleFactor = 1; diff --git a/client/renderSDL/RenderHandler.h b/client/renderSDL/RenderHandler.h index d7b028762..43df617a1 100644 --- a/client/renderSDL/RenderHandler.h +++ b/client/renderSDL/RenderHandler.h @@ -29,8 +29,8 @@ class RenderHandler : public IRenderHandler std::map> fonts; std::shared_ptr getAnimationFile(const AnimationPath & path); - std::optional getPathForScaleFactor(ResourcePath path, std::string factor); - std::pair getScalePath(ResourcePath p); + std::optional getPathForScaleFactor(const ResourcePath & path, const std::string & factor); + std::pair getScalePath(const ResourcePath & p); AnimationLayoutMap & getAnimationLayout(const AnimationPath & path); void initFromJson(AnimationLayoutMap & layout, const JsonNode & config); diff --git a/client/renderSDL/SDLImage.cpp b/client/renderSDL/SDLImage.cpp index 449b3bbfe..84d37e7ee 100644 --- a/client/renderSDL/SDLImage.cpp +++ b/client/renderSDL/SDLImage.cpp @@ -283,7 +283,10 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL if (factor <= 0) throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor)); - if (palette && surf && surf->format->palette) + if (!surf) + return shared_from_this(); + + if (palette && surf->format->palette) SDL_SetSurfacePalette(surf, palette); SDL_Surface * scaled = nullptr; @@ -306,7 +309,7 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL // erase our own reference SDL_FreeSurface(scaled); - if (surf && surf->format->palette) + if (surf->format->palette) SDL_SetSurfacePalette(surf, originalPalette); return ret; @@ -314,8 +317,8 @@ std::shared_ptr SDLImageShared::scaleInteger(int factor, SDL std::shared_ptr SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const { - float scaleX = float(size.x) / fullSize.x; - float scaleY = float(size.y) / fullSize.y; + float scaleX = static_cast(size.x) / fullSize.x; + float scaleY = static_cast(size.y) / fullSize.y; if (palette && surf->format->palette) SDL_SetSurfacePalette(surf, palette); diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index d96f4e79d..f99232333 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -725,7 +725,7 @@ int CStackInstance::getExpRank() const int CStackInstance::getLevel() const { - return std::max(1, static_cast(getType()->getLevel())); + return std::max(1, getType()->getLevel()); } void CStackInstance::giveStackExp(TExpType exp) diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index e1154b107..79619ac51 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -544,8 +544,8 @@ void CSpell::serializeJson(JsonSerializeFormat & handler) ///CSpell::AnimationInfo CSpell::AnimationItem::AnimationItem() : verticalPosition(VerticalPosition::TOP), - pause(0), - transparency(1) + transparency(1), + pause(0) { } diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index 6855fd6c9..767cfc065 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -917,7 +917,7 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const handleAttackBeforeCasting(battle, ranged, attacker, defender); // If the attacker or defender is not alive before the attack action, the action should be skipped. - if((attacker && !attacker->alive()) || (defender && !defender->alive())) + if((!attacker->alive()) || (defender && !defender->alive())) return; FireShieldInfo fireShield; From e00c2b0c6d167ccab301fb6de2c1bb08a57f87b9 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:04:14 +0100 Subject: [PATCH 621/726] Update swedish.json --- Mods/vcmi/config/swedish.json | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Mods/vcmi/config/swedish.json b/Mods/vcmi/config/swedish.json index e981350dc..6da1005be 100644 --- a/Mods/vcmi/config/swedish.json +++ b/Mods/vcmi/config/swedish.json @@ -28,6 +28,13 @@ "vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!", + "vcmi.bonusSource.artifact" : "Artefakt", + "vcmi.bonusSource.creature" : "Förmåga", + "vcmi.bonusSource.spell" : "Trollformel", + "vcmi.bonusSource.hero" : "Hjälte", + "vcmi.bonusSource.commander": "Befälhavare", + "vcmi.bonusSource.other" : "Annan", + "vcmi.capitalColors.0" : "Röd", "vcmi.capitalColors.1" : "Blå", "vcmi.capitalColors.2" : "Ljusbrun", @@ -42,6 +49,12 @@ "vcmi.heroOverview.secondarySkills" : "Sekundärförmågor", "vcmi.heroOverview.spells" : "Trollformler", + "vcmi.quickExchange.moveUnit" : "Flytta enhet", + "vcmi.quickExchange.moveAllUnits" : "Flytta alla enheter", + "vcmi.quickExchange.swapAllUnits" : "Byt arméer", + "vcmi.quickExchange.moveAllArtifacts": "Flytta alla artefakter", + "vcmi.quickExchange.swapAllArtifacts": "Byt artefakter", + "vcmi.radialWheel.mergeSameUnit" : "Slå samman varelser av samma sort", "vcmi.radialWheel.fillSingleUnit" : "Fyll på med enstaka varelser", "vcmi.radialWheel.splitSingleUnit" : "Dela av en enda varelse", @@ -61,6 +74,16 @@ "vcmi.radialWheel.moveDown" : "Flytta nedåt", "vcmi.radialWheel.moveBottom" : "Flytta längst ner", + "vcmi.randomMap.description" : "Kartan skapades av den slumpmässiga kartgeneratorn.\nMallen var %s, storlek %dx%d, nivåer %d, spelare %d, datorspelare %d, vatten %s, monster %s, VCMI karta", + "vcmi.randomMap.description.isHuman" : ", %s är människa", + "vcmi.randomMap.description.townChoice" : ", %s valde stadstyp: %s", + "vcmi.randomMap.description.water.none" : "inget", + "vcmi.randomMap.description.water.normal" : "normalt", + "vcmi.randomMap.description.water.islands" : "öar", + "vcmi.randomMap.description.monster.weak" : "svaga", + "vcmi.randomMap.description.monster.normal": "normala", + "vcmi.randomMap.description.monster.strong": "starka", + "vcmi.spellBook.search" : "sök...", "vcmi.spellResearch.canNotAfford" : "Du har inte råd att byta ut '{%SPELL1}' med '{%SPELL2}'. Du kan fortfarande göra dig av med den här trollformeln och forska vidare.", @@ -69,6 +92,7 @@ "vcmi.spellResearch.research" : "Forska fram denna trollformel", "vcmi.spellResearch.skip" : "Strunta i denna trollformel", "vcmi.spellResearch.abort" : "Avbryt", + "vcmi.spellResearch.noMoreSpells" : "Det finns inga fler trollformler tillgängliga för forskning.", "vcmi.mainMenu.serverConnecting" : "Ansluter...", "vcmi.mainMenu.serverAddressEnter" : "Ange adress:", @@ -90,6 +114,12 @@ "vcmi.lobby.handicap.resource" : "Ger spelarna lämpliga resurser att börja med utöver de normala startresurserna. Negativa värden är tillåtna men är begränsade till 0 totalt (spelaren börjar aldrig med negativa resurser).", "vcmi.lobby.handicap.income" : "Ändrar spelarens olika inkomster i procent (resultaten avrundas uppåt).", "vcmi.lobby.handicap.growth" : "Ändrar tillväxttakten för varelser i de städer som ägs av spelaren (resultaten avrundas uppåt).", + "vcmi.lobby.deleteUnsupportedSave": "{Ostödda sparningar av spel hittades}\n\nVCMI har hittat %d sparade spelfiler som inte längre stöds, möjligen på grund av skillnader i VCMI-versioner.\n\nVill du ta bort dem?", + "vcmi.lobby.deleteSaveGameTitle" : "Välj ett sparat spel som ska raderas", + "vcmi.lobby.deleteMapTitle" : "Välj ett scenario som ska raderas", + "vcmi.lobby.deleteFile" : "Vill du radera följande fil?", + "vcmi.lobby.deleteFolder" : "Vill du radera följande mapp?", + "vcmi.lobby.deleteMode" : "Växla till raderingsläge och tillbaka", "vcmi.lobby.login.title" : "VCMI Online Lobby", "vcmi.lobby.login.username" : "Användarnamn:", @@ -159,6 +189,7 @@ "vcmi.server.errors.modsToEnable" : "{Följande modd(ar) krävs}", "vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}", "vcmi.server.errors.modNoDependency" : "Misslyckades med att ladda modd {'%s'}!\n Den är beroende av modd {'%s'} som inte är aktiverad!\n", + "vcmi.server.errors.modDependencyLoop": "Misslyckades med att ladda modd {'%s'}!\n Den kanske är i en (mjuk) beroendeloop.", "vcmi.server.errors.modConflict" : "Misslyckades med att ladda modd {'%s'}!\n Konflikter med aktiverad modd {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Misslyckades med att ladda sparat spel! Okänd enhet '%s' hittades i sparat spel! Sparningen kanske inte är kompatibel med den aktuella versionen av moddarna!", @@ -536,6 +567,8 @@ "core.seerhut.quest.reachDate.visit.4" : "Stängt fram till %s.", "core.seerhut.quest.reachDate.visit.5" : "Stängt fram till %s.", + "mapObject.core.hillFort.object.description" : "Uppgraderar varelser. Nivåerna 1 - 4 är billigare än i associerad stad.", + "core.bonus.ADDITIONAL_ATTACK.name" : "Dubbelslag", "core.bonus.ADDITIONAL_ATTACK.description" : "Attackerar två gånger.", "core.bonus.ADDITIONAL_RETALIATION.name" : "Ytterligare motattacker", @@ -688,4 +721,8 @@ "core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar på slagfältet.", "core.bonus.INVINCIBLE.name" : "Oövervinnerlig", "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting." + "core.bonus.MECHANICAL.name" : "Mekanisk", + "core.bonus.MECHANICAL.description" : "Immun mot många effekter, reparerbar.", + "core.bonus.PRISM_HEX_ATTACK_BREATH.name" : "Prism-andedräkt", + "core.bonus.PRISM_HEX_ATTACK_BREATH.description" : "Treriktad andedräkt." } From fdd22903666fffe703b952571a4d785ea5ff8c36 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Fri, 22 Nov 2024 01:24:50 +0800 Subject: [PATCH 622/726] update Chinese translation --- Mods/vcmi/config/chinese.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Mods/vcmi/config/chinese.json b/Mods/vcmi/config/chinese.json index 67605102e..4b8934ff5 100644 --- a/Mods/vcmi/config/chinese.json +++ b/Mods/vcmi/config/chinese.json @@ -28,6 +28,13 @@ "vcmi.adventureMap.movementPointsHeroInfo" : "(移动点数: %REMAINING / %POINTS)", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉,重放对手行动功能目前暂未实现!", + "vcmi.bonusSource.artifact" : "宝物", + "vcmi.bonusSource.creature" : "技能", + "vcmi.bonusSource.spell" : "法术", + "vcmi.bonusSource.hero" : "英雄", + "vcmi.bonusSource.commander" : "指挥官", + "vcmi.bonusSource.other" : "其他", + "vcmi.capitalColors.0" : "红色", "vcmi.capitalColors.1" : "蓝色", "vcmi.capitalColors.2" : "褐色", @@ -107,6 +114,12 @@ "vcmi.lobby.handicap.resource" : "给予玩家起始资源以外的更多资源,允许负值,但总量不会低于0(玩家永远不会能以负资源开始游戏)。", "vcmi.lobby.handicap.income" : "按百分比改变玩家的各种收入,向上取整。", "vcmi.lobby.handicap.growth" : "改变玩家拥有的城镇的生物增长率,向上取整。", + "vcmi.lobby.deleteUnsupportedSave" : "{检测到无法支持的存档}\n\nVCMI检测到%d个存档已不再受支持,这可能是由于 VCMI 版本不兼容导致的。\n\n你是否要删除这些存档?", + "vcmi.lobby.deleteSaveGameTitle" : "选择一个要删除的存档", + "vcmi.lobby.deleteMapTitle" : "选择一个要删除要删除的场景", + "vcmi.lobby.deleteFile" : "你确定要删除下列文件?", + "vcmi.lobby.deleteFolder" : "你确定要删除下列文件夹?", + "vcmi.lobby.deleteMode" : "切换删除模式并返回", "vcmi.lobby.login.title" : "VCMI大厅", "vcmi.lobby.login.username" : "用户名:", @@ -554,6 +567,8 @@ "core.seerhut.quest.reachDate.visit.4" : "关门直到%s。", "core.seerhut.quest.reachDate.visit.5" : "关门直到%s。", + "mapObject.core.hillFort.object.description" : "升级生物,1-4级生物升级比城镇中更便宜。", + "core.bonus.ADDITIONAL_ATTACK.name": "双击", "core.bonus.ADDITIONAL_ATTACK.description": "生物可以攻击两次", "core.bonus.ADDITIONAL_RETALIATION.name": "额外反击", From a10a305df815c97340b0eef301aa833f9355214a Mon Sep 17 00:00:00 2001 From: kdmcser Date: Fri, 22 Nov 2024 02:14:52 +0800 Subject: [PATCH 623/726] update Chinese translation --- Mods/vcmi/config/chinese.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Mods/vcmi/config/chinese.json b/Mods/vcmi/config/chinese.json index 4b8934ff5..081d0d4d4 100644 --- a/Mods/vcmi/config/chinese.json +++ b/Mods/vcmi/config/chinese.json @@ -724,5 +724,27 @@ "core.bonus.MECHANICAL.name": "机械", "core.bonus.MECHANICAL.description": "免疫大多数效果,可修复", "core.bonus.PRISM_HEX_ATTACK_BREATH.name": "棱光吐息", - "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "攻击后向三方向扩散攻击" + "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "攻击后向三方向扩散攻击", + + "spell.core.castleMoat.name" : "护城河", + "spell.core.castleMoatTrigger.name" : "护城河", + "spell.core.catapultShot.name" : "投石车射击", + "spell.core.cyclopsShot.name" : "攻城射击", + "spell.core.dungeonMoat.name" : "极热之油", + "spell.core.dungeonMoatTrigger.name" : "极热之油", + "spell.core.fireWallTrigger.name" : "烈火魔墙", + "spell.core.firstAid.name" : "急救术", + "spell.core.fortressMoat.name" : "焦油", + "spell.core.fortressMoatTrigger.name" : "焦油", + "spell.core.infernoMoat.name" : "熔岩", + "spell.core.infernoMoatTrigger.name" : "熔岩", + "spell.core.landMineTrigger.name" : "埋设地雷", + "spell.core.necropolisMoat.name" : "尸骨堆", + "spell.core.necropolisMoatTrigger.name" : "尸骨堆", + "spell.core.rampartMoat.name" : "护城河", + "spell.core.rampartMoatTrigger.name" : "护城河", + "spell.core.strongholdMoat.name" : "栅栏", + "spell.core.strongholdMoatTrigger.name" : "栅栏", + "spell.core.summonDemons.name" : "召唤恶鬼", + "spell.core.towerMoat.name" : "埋设地雷" } From b89db3f0449e7f182d8c10b87ed7201eaa6eac93 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:10:32 +0100 Subject: [PATCH 624/726] fix popup --- client/widgets/CGarrisonInt.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/widgets/CGarrisonInt.cpp b/client/widgets/CGarrisonInt.cpp index c859f0d83..78a809e72 100644 --- a/client/widgets/CGarrisonInt.cpp +++ b/client/widgets/CGarrisonInt.cpp @@ -370,6 +370,9 @@ void CGarrisonSlot::gesture(bool on, const Point & initialPosition, const Point if (!settings["input"]["radialWheelGarrisonSwipe"].Bool()) return; + if(GH.windows().topWindow()->isPopupWindow()) + return; + const auto * otherArmy = upg == EGarrisonType::UPPER ? owner->lowerArmy() : owner->upperArmy(); bool stackExists = myStack != nullptr; From 32afd08f2b3f35f8fa1b55795881087af3c0cac5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 22 Nov 2024 17:29:32 +0200 Subject: [PATCH 625/726] Update Mods/vcmi/config/swedish.json --- Mods/vcmi/config/swedish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/vcmi/config/swedish.json b/Mods/vcmi/config/swedish.json index 6da1005be..3c65868c5 100644 --- a/Mods/vcmi/config/swedish.json +++ b/Mods/vcmi/config/swedish.json @@ -720,7 +720,7 @@ "core.bonus.DISINTEGRATE.name" : "Desintegrerar", "core.bonus.DISINTEGRATE.description" : "Ingen kropp lämnas kvar på slagfältet.", "core.bonus.INVINCIBLE.name" : "Oövervinnerlig", - "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting." + "core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting.", "core.bonus.MECHANICAL.name" : "Mekanisk", "core.bonus.MECHANICAL.description" : "Immun mot många effekter, reparerbar.", "core.bonus.PRISM_HEX_ATTACK_BREATH.name" : "Prism-andedräkt", From b686a639a158c887b3855008bf8eef4260099e7f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 19 Nov 2024 16:58:26 +0000 Subject: [PATCH 626/726] Remove custom filesystem for built-in vcmi mod Leftover from ancient times that is no longer necessary. - VCMI mod now uses same filesystem as all mods, with all files located in Content directory - Removed definition of custom filesystem from vcmi mod.json - Moved all png images from Data directory to Sprites --- .../{ => Content}/Data/NotoSans-Medium.ttf | Bin .../{ => Content}/Data/NotoSerif-Black.ttf | Bin .../{ => Content}/Data/NotoSerif-Bold.ttf | Bin .../{ => Content}/Data/NotoSerif-Medium.ttf | Bin Mods/vcmi/{ => Content}/Data/s/std.verm | 0 Mods/vcmi/{ => Content}/Data/s/testy.erm | 0 Mods/vcmi/{ => Content}/Sounds/we5.wav | Bin .../{ => Content}/Sprites/PortraitsLarge.json | 0 .../{ => Content}/Sprites/PortraitsSmall.json | 0 .../CreaturePurchaseCard.png | Bin .../QuickRecruitmentAllButton.def | Bin .../QuickRecruitmentNoneButton.def | Bin .../QuickRecruitmentWindow/costBackground.png | Bin Mods/vcmi/{ => Content}/Sprites/ScSelC.json | 0 .../Sprites}/StackQueueLarge.png | Bin .../Sprites}/StackQueueSmall.png | Bin .../Sprites}/UnitMaxMovementHighlight.png | Bin .../Sprites}/UnitMovementHighlight.png | Bin .../battle/rangeHighlights/green/empty.png | Bin .../battle/rangeHighlights/green/fullHex.png | Bin .../battle/rangeHighlights/green/left.png | Bin .../battle/rangeHighlights/green/leftHalf.png | Bin .../battle/rangeHighlights/green/top.png | Bin .../battle/rangeHighlights/green/topLeft.png | Bin .../rangeHighlights/green/topLeftCorner.png | Bin .../green/topLeftHalfCorner.png | Bin .../rangeHighlights/rangeHighlightsGreen.json | 0 .../rangeHighlights/rangeHighlightsRed.json | 0 .../battle/rangeHighlights/red/empty.png | Bin .../battle/rangeHighlights/red/fullHex.png | Bin .../battle/rangeHighlights/red/left.png | Bin .../battle/rangeHighlights/red/leftHalf.png | Bin .../battle/rangeHighlights/red/top.png | Bin .../battle/rangeHighlights/red/topLeft.png | Bin .../rangeHighlights/red/topLeftCorner.png | Bin .../rangeHighlights/red/topLeftHalfCorner.png | Bin .../Sprites}/debug/blocked.png | Bin .../{Data => Content/Sprites}/debug/grid.png | Bin .../Sprites}/debug/spellRange.png | Bin .../Sprites}/debug/visitable.png | Bin .../Sprites}/heroWindow/artifactSlotEmpty.png | Bin .../heroWindow/backpackButtonIcon.png | Bin .../heroWindow/commanderButtonIcon.png | Bin Mods/vcmi/{ => Content}/Sprites/itpa.json | 0 .../{ => Content}/Sprites/lobby/checkbox.json | 0 .../Sprites/lobby/checkboxBlueOff.png | Bin .../Sprites/lobby/checkboxBlueOn.png | Bin .../Sprites/lobby/checkboxOff.png | Bin .../Sprites/lobby/checkboxOn.png | Bin .../Sprites/lobby/delete-normal.png | Bin .../Sprites/lobby/delete-pressed.png | Bin .../Sprites/lobby/deleteButton.json | 0 .../{ => Content}/Sprites/lobby/dropdown.json | 0 .../Sprites/lobby/dropdownNormal.png | Bin .../Sprites/lobby/dropdownPressed.png | Bin .../Sprites}/lobby/iconFolder.png | Bin .../Sprites}/lobby/iconPlayer.png | Bin .../Sprites}/lobby/iconSend.png | Bin .../Sprites}/lobby/selectionTabSortDate.png | Bin .../Sprites}/lobby/townBorderBig.png | Bin .../Sprites}/lobby/townBorderBigActivated.png | Bin .../Sprites}/lobby/townBorderBigGrayedOut.png | Bin .../lobby/townBorderSmallActivated.png | Bin .../Sprites/mapFormatIcons/vcmi1.png | Bin .../{Data => Content/Sprites}/questDialog.png | Bin .../Sprites}/radialMenu/altDown.png | Bin .../Sprites}/radialMenu/altDownBottom.png | Bin .../Sprites}/radialMenu/altUp.png | Bin .../Sprites}/radialMenu/altUpTop.png | Bin .../Sprites}/radialMenu/dismissHero.png | Bin .../Sprites}/radialMenu/heroMove.png | Bin .../Sprites}/radialMenu/heroSwap.png | Bin .../Sprites}/radialMenu/itemEmpty.png | Bin .../Sprites}/radialMenu/itemEmptyAlt.png | Bin .../Sprites}/radialMenu/itemInactive.png | Bin .../Sprites}/radialMenu/itemInactiveAlt.png | Bin .../Sprites}/radialMenu/moveArtifacts.png | Bin .../Sprites}/radialMenu/moveTroops.png | Bin .../Sprites}/radialMenu/stackFillOne.png | Bin .../Sprites}/radialMenu/stackMerge.png | Bin .../Sprites}/radialMenu/stackSplitDialog.png | Bin .../Sprites}/radialMenu/stackSplitEqual.png | Bin .../Sprites}/radialMenu/stackSplitOne.png | Bin .../Sprites}/radialMenu/statusBar.png | Bin .../Sprites}/radialMenu/swapArtifacts.png | Bin .../Sprites}/radialMenu/tradeHeroes.png | Bin .../Sprites}/settingsWindow/frameAudio.png | Bin .../Sprites}/settingsWindow/frameMovement.png | Bin .../settingsWindow/frameStackQueue.png | Bin .../Sprites}/settingsWindow/gear.png | Bin .../Sprites}/settingsWindow/scrollSpeed1.png | Bin .../Sprites}/settingsWindow/scrollSpeed2.png | Bin .../Sprites}/settingsWindow/scrollSpeed3.png | Bin .../Sprites}/settingsWindow/scrollSpeed4.png | Bin .../Sprites}/settingsWindow/scrollSpeed5.png | Bin .../Sprites}/settingsWindow/scrollSpeed6.png | Bin .../Sprites}/spellResearch/accept.png | Bin .../Sprites}/spellResearch/close.png | Bin .../Sprites}/spellResearch/reroll.png | Bin .../Sprites}/stackWindow/bonus-effects.png | Bin .../Sprites}/stackWindow/button-panel.png | Bin .../Sprites/stackWindow/cancel-normal.png | Bin .../Sprites/stackWindow/cancel-pressed.png | Bin .../Sprites/stackWindow/cancelButton.json | 0 .../stackWindow/commander-abilities.png | Bin .../Sprites}/stackWindow/commander-bg.png | Bin .../Sprites}/stackWindow/icons.png | Bin .../Sprites}/stackWindow/info-panel-0.png | Bin .../Sprites}/stackWindow/info-panel-1.png | Bin .../Sprites}/stackWindow/info-panel-2.png | Bin .../Sprites/stackWindow/level-0.png | Bin .../Sprites/stackWindow/level-1.png | Bin .../Sprites/stackWindow/level-10.png | Bin .../Sprites/stackWindow/level-2.png | Bin .../Sprites/stackWindow/level-3.png | Bin .../Sprites/stackWindow/level-4.png | Bin .../Sprites/stackWindow/level-5.png | Bin .../Sprites/stackWindow/level-6.png | Bin .../Sprites/stackWindow/level-7.png | Bin .../Sprites/stackWindow/level-8.png | Bin .../Sprites/stackWindow/level-9.png | Bin .../Sprites/stackWindow/levels.json | 0 .../Sprites}/stackWindow/spell-effects.png | Bin .../Sprites/stackWindow/switchModeIcons.json | 0 .../Sprites/stackWindow/upgrade-normal.png | Bin .../Sprites/stackWindow/upgrade-pressed.png | Bin .../Sprites/stackWindow/upgradeButton.json | 0 .../Sprites/vcmi/battleQueue/defendBig.png | Bin .../Sprites/vcmi/battleQueue/defendSmall.png | Bin .../Sprites/vcmi/battleQueue/statesBig.json | 0 .../Sprites/vcmi/battleQueue/statesSmall.json | 0 .../Sprites/vcmi/battleQueue/waitBig.png | Bin .../Sprites/vcmi/battleQueue/waitSmall.png | Bin .../Sprites/vcmi/creatureIcons/towerLarge.png | Bin .../Sprites/vcmi/creatureIcons/towerSmall.png | Bin .../Video/tutorial/AbortSpell.webm | Bin .../Video/tutorial/BattleDirection.webm | Bin .../Video/tutorial/BattleDirectionAbort.webm | Bin .../Video/tutorial/MapPanning.webm | Bin .../Video/tutorial/MapZooming.webm | Bin .../Video/tutorial/RadialWheel.webm | Bin .../Video/tutorial/RightClick.webm | Bin Mods/vcmi/{ => Content}/config/chinese.json | 0 Mods/vcmi/{ => Content}/config/czech.json | 0 Mods/vcmi/{ => Content}/config/english.json | 0 Mods/vcmi/{ => Content}/config/french.json | 0 Mods/vcmi/{ => Content}/config/german.json | 0 Mods/vcmi/{ => Content}/config/polish.json | 0 .../vcmi/{ => Content}/config/portuguese.json | 0 .../config/rmg/hdmod/aroundamarsh.json | 0 .../config/rmg/hdmod/balance.json | 0 .../config/rmg/hdmod/blockbuster.json | 0 .../config/rmg/hdmod/clashOfDragons.json | 0 .../config/rmg/hdmod/coldshadowsFantasy.json | 0 .../{ => Content}/config/rmg/hdmod/cube.json | 0 .../config/rmg/hdmod/diamond.json | 0 .../config/rmg/hdmod/extreme.json | 0 .../config/rmg/hdmod/extreme2.json | 0 .../{ => Content}/config/rmg/hdmod/fear.json | 0 .../config/rmg/hdmod/frozenDragons.json | 0 .../config/rmg/hdmod/gimlisRevenge.json | 0 .../config/rmg/hdmod/guerilla.json | 0 .../config/rmg/hdmod/headquarters.json | 0 .../config/rmg/hdmod/hypercube.json | 0 .../config/rmg/hdmod/jebusCross.json | 0 .../config/rmg/hdmod/longRun.json | 0 .../config/rmg/hdmod/marathon.json | 0 .../config/rmg/hdmod/miniNostalgia.json | 0 .../config/rmg/hdmod/nostalgia.json | 0 .../config/rmg/hdmod/oceansEleven.json | 0 .../{ => Content}/config/rmg/hdmod/panic.json | 0 .../config/rmg/hdmod/poorJebus.json | 0 .../config/rmg/hdmod/reckless.json | 0 .../config/rmg/hdmod/roadrunner.json | 0 .../config/rmg/hdmod/shaaafworld.json | 0 .../config/rmg/hdmod/skirmish.json | 0 .../config/rmg/hdmod/speed1.json | 0 .../config/rmg/hdmod/speed2.json | 0 .../config/rmg/hdmod/spider.json | 0 .../config/rmg/hdmod/superslam.json | 0 .../{ => Content}/config/rmg/hdmod/triad.json | 0 .../config/rmg/hdmod/vortex.json | 0 .../config/rmg/hdmodUnused/anarchy.json | 0 .../rmg/hdmodUnused/balance m+u 200%.json | 0 .../config/rmg/hdmodUnused/midnightMix.json | 0 .../rmg/hdmodUnused/skirmish m-u 200%.json | 0 .../config/rmg/hdmodUnused/true random.json | 0 .../config/rmg/heroes3/dwarvenTunnels.json | 0 .../config/rmg/heroes3/golemsAplenty.json | 0 .../config/rmg/heroes3/meetingInMuzgob.json | 0 .../config/rmg/heroes3/monksRetreat.json | 0 .../config/rmg/heroes3/newcomers.json | 0 .../config/rmg/heroes3/readyOrNot.json | 0 .../config/rmg/heroes3/smallRing.json | 0 .../config/rmg/heroes3/southOfHell.json | 0 .../config/rmg/heroes3/worldsAtWar.json | 0 .../config/rmg/heroes3unused/dragon.json | 0 .../config/rmg/heroes3unused/gauntlet.json | 0 .../config/rmg/heroes3unused/ring.json | 0 .../rmg/heroes3unused/riseOfPhoenix.json | 0 .../config/rmg/symmetric/2sm0k.json | 0 .../config/rmg/symmetric/2sm2a.json | 0 .../config/rmg/symmetric/2sm2b(2).json | 0 .../config/rmg/symmetric/2sm2b.json | 0 .../config/rmg/symmetric/2sm2c.json | 0 .../config/rmg/symmetric/2sm2f(2).json | 0 .../config/rmg/symmetric/2sm2f.json | 0 .../config/rmg/symmetric/2sm2h(2).json | 0 .../config/rmg/symmetric/2sm2h.json | 0 .../config/rmg/symmetric/2sm2i(2).json | 0 .../config/rmg/symmetric/2sm2i.json | 0 .../config/rmg/symmetric/2sm4d(2).json | 0 .../config/rmg/symmetric/2sm4d(3).json | 0 .../config/rmg/symmetric/2sm4d.json | 0 .../config/rmg/symmetric/3sb0b.json | 0 .../config/rmg/symmetric/3sb0c.json | 0 .../config/rmg/symmetric/3sm3d.json | 0 .../config/rmg/symmetric/4sm0d.json | 0 .../config/rmg/symmetric/4sm0f.json | 0 .../config/rmg/symmetric/4sm0g.json | 0 .../config/rmg/symmetric/4sm4e.json | 0 .../config/rmg/symmetric/5sb0a.json | 0 .../config/rmg/symmetric/5sb0b.json | 0 .../config/rmg/symmetric/6lm10.json | 0 .../config/rmg/symmetric/6lm10a.json | 0 .../config/rmg/symmetric/6sm0b.json | 0 .../config/rmg/symmetric/6sm0d.json | 0 .../config/rmg/symmetric/6sm0e.json | 0 .../config/rmg/symmetric/7sb0b.json | 0 .../config/rmg/symmetric/7sb0c.json | 0 .../config/rmg/symmetric/8mm0e.json | 0 .../config/rmg/symmetric/8mm6.json | 0 .../config/rmg/symmetric/8mm6a.json | 0 .../config/rmg/symmetric/8sm0c.json | 0 .../config/rmg/symmetric/8sm0f.json | 0 .../config/rmg/symmetric/8xm12.json | 0 .../config/rmg/symmetric/8xm12a.json | 0 .../config/rmg/symmetric/8xm8.json | 0 .../config/rmg/unknownUnused/2mm2h.json | 0 .../config/rmg/unknownUnused/2x2sm4d(3).json | 0 .../config/rmg/unknownUnused/4mm2h.json | 0 .../config/rmg/unknownUnused/4sm3i.json | 0 .../config/rmg/unknownUnused/6lm10a.json | 0 .../config/rmg/unknownUnused/8xm12 huge.json | 0 .../config/rmg/unknownUnused/8xm8 huge.json | 0 .../config/rmg/unknownUnused/analogy.json | 0 .../config/rmg/unknownUnused/cross.json | 0 .../config/rmg/unknownUnused/cross2.json | 0 .../config/rmg/unknownUnused/cross3.json | 0 .../config/rmg/unknownUnused/deux paires.json | 0 .../rmg/unknownUnused/doubled 8mm6.json | 0 .../config/rmg/unknownUnused/elka.json | 0 .../config/rmg/unknownUnused/goldenRing.json | 0 .../config/rmg/unknownUnused/greatSands.json | 0 .../config/rmg/unknownUnused/kite.json | 0 .../config/rmg/unknownUnused/upgrade.json | 0 .../config/rmg/unknownUnused/wheel.json | 0 Mods/vcmi/{ => Content}/config/russian.json | 0 Mods/vcmi/{ => Content}/config/spanish.json | 0 Mods/vcmi/{ => Content}/config/spells.json | 0 Mods/vcmi/{ => Content}/config/swedish.json | 0 .../{ => Content}/config/towerCreature.json | 0 .../{ => Content}/config/towerFactions.json | 0 Mods/vcmi/{ => Content}/config/ukrainian.json | 0 .../vcmi/{ => Content}/config/vietnamese.json | 0 .../CreaturePurchaseCard.png | Bin 13531 -> 0 bytes Mods/vcmi/mod.json | 30 +----------------- 267 files changed, 1 insertion(+), 29 deletions(-) rename Mods/vcmi/{ => Content}/Data/NotoSans-Medium.ttf (100%) rename Mods/vcmi/{ => Content}/Data/NotoSerif-Black.ttf (100%) rename Mods/vcmi/{ => Content}/Data/NotoSerif-Bold.ttf (100%) rename Mods/vcmi/{ => Content}/Data/NotoSerif-Medium.ttf (100%) rename Mods/vcmi/{ => Content}/Data/s/std.verm (100%) rename Mods/vcmi/{ => Content}/Data/s/testy.erm (100%) rename Mods/vcmi/{ => Content}/Sounds/we5.wav (100%) rename Mods/vcmi/{ => Content}/Sprites/PortraitsLarge.json (100%) rename Mods/vcmi/{ => Content}/Sprites/PortraitsSmall.json (100%) rename Mods/vcmi/{Data => Content/Sprites}/QuickRecruitmentWindow/CreaturePurchaseCard.png (100%) rename Mods/vcmi/{ => Content}/Sprites/QuickRecruitmentWindow/QuickRecruitmentAllButton.def (100%) rename Mods/vcmi/{ => Content}/Sprites/QuickRecruitmentWindow/QuickRecruitmentNoneButton.def (100%) rename Mods/vcmi/{ => Content}/Sprites/QuickRecruitmentWindow/costBackground.png (100%) rename Mods/vcmi/{ => Content}/Sprites/ScSelC.json (100%) rename Mods/vcmi/{Data => Content/Sprites}/StackQueueLarge.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/StackQueueSmall.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/UnitMaxMovementHighlight.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/UnitMovementHighlight.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/empty.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/fullHex.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/left.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/leftHalf.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/top.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/topLeft.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/topLeftCorner.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/green/topLeftHalfCorner.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/rangeHighlightsRed.json (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/empty.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/fullHex.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/left.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/leftHalf.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/top.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/topLeft.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/topLeftCorner.png (100%) rename Mods/vcmi/{ => Content}/Sprites/battle/rangeHighlights/red/topLeftHalfCorner.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/debug/blocked.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/debug/grid.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/debug/spellRange.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/debug/visitable.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/heroWindow/artifactSlotEmpty.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/heroWindow/backpackButtonIcon.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/heroWindow/commanderButtonIcon.png (100%) rename Mods/vcmi/{ => Content}/Sprites/itpa.json (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/checkbox.json (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/checkboxBlueOff.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/checkboxBlueOn.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/checkboxOff.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/checkboxOn.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/delete-normal.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/delete-pressed.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/deleteButton.json (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/dropdown.json (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/dropdownNormal.png (100%) rename Mods/vcmi/{ => Content}/Sprites/lobby/dropdownPressed.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/iconFolder.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/iconPlayer.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/iconSend.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/selectionTabSortDate.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/townBorderBig.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/townBorderBigActivated.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/townBorderBigGrayedOut.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/lobby/townBorderSmallActivated.png (100%) rename Mods/vcmi/{ => Content}/Sprites/mapFormatIcons/vcmi1.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/questDialog.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/altDown.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/altDownBottom.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/altUp.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/altUpTop.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/dismissHero.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/heroMove.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/heroSwap.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/itemEmpty.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/itemEmptyAlt.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/itemInactive.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/itemInactiveAlt.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/moveArtifacts.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/moveTroops.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/stackFillOne.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/stackMerge.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/stackSplitDialog.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/stackSplitEqual.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/stackSplitOne.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/statusBar.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/swapArtifacts.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/radialMenu/tradeHeroes.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/frameAudio.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/frameMovement.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/frameStackQueue.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/gear.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/scrollSpeed1.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/scrollSpeed2.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/scrollSpeed3.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/scrollSpeed4.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/scrollSpeed5.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/settingsWindow/scrollSpeed6.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/spellResearch/accept.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/spellResearch/close.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/spellResearch/reroll.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/bonus-effects.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/button-panel.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/cancel-normal.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/cancel-pressed.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/cancelButton.json (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/commander-abilities.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/commander-bg.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/icons.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/info-panel-0.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/info-panel-1.png (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/info-panel-2.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-0.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-1.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-10.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-2.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-3.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-4.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-5.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-6.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-7.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-8.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/level-9.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/levels.json (100%) rename Mods/vcmi/{Data => Content/Sprites}/stackWindow/spell-effects.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/switchModeIcons.json (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/upgrade-normal.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/upgrade-pressed.png (100%) rename Mods/vcmi/{ => Content}/Sprites/stackWindow/upgradeButton.json (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/battleQueue/defendBig.png (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/battleQueue/defendSmall.png (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/battleQueue/statesBig.json (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/battleQueue/statesSmall.json (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/battleQueue/waitBig.png (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/battleQueue/waitSmall.png (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/creatureIcons/towerLarge.png (100%) rename Mods/vcmi/{ => Content}/Sprites/vcmi/creatureIcons/towerSmall.png (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/AbortSpell.webm (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/BattleDirection.webm (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/BattleDirectionAbort.webm (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/MapPanning.webm (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/MapZooming.webm (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/RadialWheel.webm (100%) rename Mods/vcmi/{ => Content}/Video/tutorial/RightClick.webm (100%) rename Mods/vcmi/{ => Content}/config/chinese.json (100%) rename Mods/vcmi/{ => Content}/config/czech.json (100%) rename Mods/vcmi/{ => Content}/config/english.json (100%) rename Mods/vcmi/{ => Content}/config/french.json (100%) rename Mods/vcmi/{ => Content}/config/german.json (100%) rename Mods/vcmi/{ => Content}/config/polish.json (100%) rename Mods/vcmi/{ => Content}/config/portuguese.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/aroundamarsh.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/balance.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/blockbuster.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/clashOfDragons.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/coldshadowsFantasy.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/cube.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/diamond.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/extreme.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/extreme2.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/fear.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/frozenDragons.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/gimlisRevenge.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/guerilla.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/headquarters.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/hypercube.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/jebusCross.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/longRun.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/marathon.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/miniNostalgia.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/nostalgia.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/oceansEleven.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/panic.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/poorJebus.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/reckless.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/roadrunner.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/shaaafworld.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/skirmish.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/speed1.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/speed2.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/spider.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/superslam.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/triad.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmod/vortex.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmodUnused/anarchy.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmodUnused/balance m+u 200%.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmodUnused/midnightMix.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmodUnused/skirmish m-u 200%.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/hdmodUnused/true random.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/dwarvenTunnels.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/golemsAplenty.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/meetingInMuzgob.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/monksRetreat.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/newcomers.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/readyOrNot.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/smallRing.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/southOfHell.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3/worldsAtWar.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3unused/dragon.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3unused/gauntlet.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3unused/ring.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/heroes3unused/riseOfPhoenix.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm0k.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2a.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2b(2).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2b.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2c.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2f(2).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2f.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2h(2).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2h.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2i(2).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm2i.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm4d(2).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm4d(3).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/2sm4d.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/3sb0b.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/3sb0c.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/3sm3d.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/4sm0d.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/4sm0f.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/4sm0g.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/4sm4e.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/5sb0a.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/5sb0b.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/6lm10.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/6lm10a.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/6sm0b.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/6sm0d.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/6sm0e.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/7sb0b.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/7sb0c.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8mm0e.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8mm6.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8mm6a.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8sm0c.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8sm0f.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8xm12.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8xm12a.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/symmetric/8xm8.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/2mm2h.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/2x2sm4d(3).json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/4mm2h.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/4sm3i.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/6lm10a.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/8xm12 huge.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/8xm8 huge.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/analogy.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/cross.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/cross2.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/cross3.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/deux paires.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/doubled 8mm6.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/elka.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/goldenRing.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/greatSands.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/kite.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/upgrade.json (100%) rename Mods/vcmi/{ => Content}/config/rmg/unknownUnused/wheel.json (100%) rename Mods/vcmi/{ => Content}/config/russian.json (100%) rename Mods/vcmi/{ => Content}/config/spanish.json (100%) rename Mods/vcmi/{ => Content}/config/spells.json (100%) rename Mods/vcmi/{ => Content}/config/swedish.json (100%) rename Mods/vcmi/{ => Content}/config/towerCreature.json (100%) rename Mods/vcmi/{ => Content}/config/towerFactions.json (100%) rename Mods/vcmi/{ => Content}/config/ukrainian.json (100%) rename Mods/vcmi/{ => Content}/config/vietnamese.json (100%) delete mode 100644 Mods/vcmi/Sprites/QuickRecruitmentWindow/CreaturePurchaseCard.png diff --git a/Mods/vcmi/Data/NotoSans-Medium.ttf b/Mods/vcmi/Content/Data/NotoSans-Medium.ttf similarity index 100% rename from Mods/vcmi/Data/NotoSans-Medium.ttf rename to Mods/vcmi/Content/Data/NotoSans-Medium.ttf diff --git a/Mods/vcmi/Data/NotoSerif-Black.ttf b/Mods/vcmi/Content/Data/NotoSerif-Black.ttf similarity index 100% rename from Mods/vcmi/Data/NotoSerif-Black.ttf rename to Mods/vcmi/Content/Data/NotoSerif-Black.ttf diff --git a/Mods/vcmi/Data/NotoSerif-Bold.ttf b/Mods/vcmi/Content/Data/NotoSerif-Bold.ttf similarity index 100% rename from Mods/vcmi/Data/NotoSerif-Bold.ttf rename to Mods/vcmi/Content/Data/NotoSerif-Bold.ttf diff --git a/Mods/vcmi/Data/NotoSerif-Medium.ttf b/Mods/vcmi/Content/Data/NotoSerif-Medium.ttf similarity index 100% rename from Mods/vcmi/Data/NotoSerif-Medium.ttf rename to Mods/vcmi/Content/Data/NotoSerif-Medium.ttf diff --git a/Mods/vcmi/Data/s/std.verm b/Mods/vcmi/Content/Data/s/std.verm similarity index 100% rename from Mods/vcmi/Data/s/std.verm rename to Mods/vcmi/Content/Data/s/std.verm diff --git a/Mods/vcmi/Data/s/testy.erm b/Mods/vcmi/Content/Data/s/testy.erm similarity index 100% rename from Mods/vcmi/Data/s/testy.erm rename to Mods/vcmi/Content/Data/s/testy.erm diff --git a/Mods/vcmi/Sounds/we5.wav b/Mods/vcmi/Content/Sounds/we5.wav similarity index 100% rename from Mods/vcmi/Sounds/we5.wav rename to Mods/vcmi/Content/Sounds/we5.wav diff --git a/Mods/vcmi/Sprites/PortraitsLarge.json b/Mods/vcmi/Content/Sprites/PortraitsLarge.json similarity index 100% rename from Mods/vcmi/Sprites/PortraitsLarge.json rename to Mods/vcmi/Content/Sprites/PortraitsLarge.json diff --git a/Mods/vcmi/Sprites/PortraitsSmall.json b/Mods/vcmi/Content/Sprites/PortraitsSmall.json similarity index 100% rename from Mods/vcmi/Sprites/PortraitsSmall.json rename to Mods/vcmi/Content/Sprites/PortraitsSmall.json diff --git a/Mods/vcmi/Data/QuickRecruitmentWindow/CreaturePurchaseCard.png b/Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/CreaturePurchaseCard.png similarity index 100% rename from Mods/vcmi/Data/QuickRecruitmentWindow/CreaturePurchaseCard.png rename to Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/CreaturePurchaseCard.png diff --git a/Mods/vcmi/Sprites/QuickRecruitmentWindow/QuickRecruitmentAllButton.def b/Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/QuickRecruitmentAllButton.def similarity index 100% rename from Mods/vcmi/Sprites/QuickRecruitmentWindow/QuickRecruitmentAllButton.def rename to Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/QuickRecruitmentAllButton.def diff --git a/Mods/vcmi/Sprites/QuickRecruitmentWindow/QuickRecruitmentNoneButton.def b/Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/QuickRecruitmentNoneButton.def similarity index 100% rename from Mods/vcmi/Sprites/QuickRecruitmentWindow/QuickRecruitmentNoneButton.def rename to Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/QuickRecruitmentNoneButton.def diff --git a/Mods/vcmi/Sprites/QuickRecruitmentWindow/costBackground.png b/Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/costBackground.png similarity index 100% rename from Mods/vcmi/Sprites/QuickRecruitmentWindow/costBackground.png rename to Mods/vcmi/Content/Sprites/QuickRecruitmentWindow/costBackground.png diff --git a/Mods/vcmi/Sprites/ScSelC.json b/Mods/vcmi/Content/Sprites/ScSelC.json similarity index 100% rename from Mods/vcmi/Sprites/ScSelC.json rename to Mods/vcmi/Content/Sprites/ScSelC.json diff --git a/Mods/vcmi/Data/StackQueueLarge.png b/Mods/vcmi/Content/Sprites/StackQueueLarge.png similarity index 100% rename from Mods/vcmi/Data/StackQueueLarge.png rename to Mods/vcmi/Content/Sprites/StackQueueLarge.png diff --git a/Mods/vcmi/Data/StackQueueSmall.png b/Mods/vcmi/Content/Sprites/StackQueueSmall.png similarity index 100% rename from Mods/vcmi/Data/StackQueueSmall.png rename to Mods/vcmi/Content/Sprites/StackQueueSmall.png diff --git a/Mods/vcmi/Data/UnitMaxMovementHighlight.png b/Mods/vcmi/Content/Sprites/UnitMaxMovementHighlight.png similarity index 100% rename from Mods/vcmi/Data/UnitMaxMovementHighlight.png rename to Mods/vcmi/Content/Sprites/UnitMaxMovementHighlight.png diff --git a/Mods/vcmi/Data/UnitMovementHighlight.png b/Mods/vcmi/Content/Sprites/UnitMovementHighlight.png similarity index 100% rename from Mods/vcmi/Data/UnitMovementHighlight.png rename to Mods/vcmi/Content/Sprites/UnitMovementHighlight.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/empty.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/empty.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/empty.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/empty.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/fullHex.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/fullHex.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/fullHex.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/fullHex.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/left.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/left.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/left.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/left.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/leftHalf.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/leftHalf.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/leftHalf.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/leftHalf.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/top.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/top.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/top.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/top.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/topLeft.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/topLeft.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/topLeft.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/topLeft.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/topLeftCorner.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/topLeftCorner.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/topLeftCorner.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/topLeftCorner.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/green/topLeftHalfCorner.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/topLeftHalfCorner.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/green/topLeftHalfCorner.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/green/topLeftHalfCorner.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/rangeHighlightsRed.json similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/rangeHighlightsRed.json diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/empty.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/empty.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/empty.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/empty.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/fullHex.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/fullHex.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/fullHex.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/fullHex.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/left.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/left.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/left.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/left.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/leftHalf.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/leftHalf.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/leftHalf.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/leftHalf.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/top.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/top.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/top.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/top.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeft.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/topLeft.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeft.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/topLeft.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeftCorner.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/topLeftCorner.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeftCorner.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/topLeftCorner.png diff --git a/Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeftHalfCorner.png b/Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/topLeftHalfCorner.png similarity index 100% rename from Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeftHalfCorner.png rename to Mods/vcmi/Content/Sprites/battle/rangeHighlights/red/topLeftHalfCorner.png diff --git a/Mods/vcmi/Data/debug/blocked.png b/Mods/vcmi/Content/Sprites/debug/blocked.png similarity index 100% rename from Mods/vcmi/Data/debug/blocked.png rename to Mods/vcmi/Content/Sprites/debug/blocked.png diff --git a/Mods/vcmi/Data/debug/grid.png b/Mods/vcmi/Content/Sprites/debug/grid.png similarity index 100% rename from Mods/vcmi/Data/debug/grid.png rename to Mods/vcmi/Content/Sprites/debug/grid.png diff --git a/Mods/vcmi/Data/debug/spellRange.png b/Mods/vcmi/Content/Sprites/debug/spellRange.png similarity index 100% rename from Mods/vcmi/Data/debug/spellRange.png rename to Mods/vcmi/Content/Sprites/debug/spellRange.png diff --git a/Mods/vcmi/Data/debug/visitable.png b/Mods/vcmi/Content/Sprites/debug/visitable.png similarity index 100% rename from Mods/vcmi/Data/debug/visitable.png rename to Mods/vcmi/Content/Sprites/debug/visitable.png diff --git a/Mods/vcmi/Data/heroWindow/artifactSlotEmpty.png b/Mods/vcmi/Content/Sprites/heroWindow/artifactSlotEmpty.png similarity index 100% rename from Mods/vcmi/Data/heroWindow/artifactSlotEmpty.png rename to Mods/vcmi/Content/Sprites/heroWindow/artifactSlotEmpty.png diff --git a/Mods/vcmi/Data/heroWindow/backpackButtonIcon.png b/Mods/vcmi/Content/Sprites/heroWindow/backpackButtonIcon.png similarity index 100% rename from Mods/vcmi/Data/heroWindow/backpackButtonIcon.png rename to Mods/vcmi/Content/Sprites/heroWindow/backpackButtonIcon.png diff --git a/Mods/vcmi/Data/heroWindow/commanderButtonIcon.png b/Mods/vcmi/Content/Sprites/heroWindow/commanderButtonIcon.png similarity index 100% rename from Mods/vcmi/Data/heroWindow/commanderButtonIcon.png rename to Mods/vcmi/Content/Sprites/heroWindow/commanderButtonIcon.png diff --git a/Mods/vcmi/Sprites/itpa.json b/Mods/vcmi/Content/Sprites/itpa.json similarity index 100% rename from Mods/vcmi/Sprites/itpa.json rename to Mods/vcmi/Content/Sprites/itpa.json diff --git a/Mods/vcmi/Sprites/lobby/checkbox.json b/Mods/vcmi/Content/Sprites/lobby/checkbox.json similarity index 100% rename from Mods/vcmi/Sprites/lobby/checkbox.json rename to Mods/vcmi/Content/Sprites/lobby/checkbox.json diff --git a/Mods/vcmi/Sprites/lobby/checkboxBlueOff.png b/Mods/vcmi/Content/Sprites/lobby/checkboxBlueOff.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/checkboxBlueOff.png rename to Mods/vcmi/Content/Sprites/lobby/checkboxBlueOff.png diff --git a/Mods/vcmi/Sprites/lobby/checkboxBlueOn.png b/Mods/vcmi/Content/Sprites/lobby/checkboxBlueOn.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/checkboxBlueOn.png rename to Mods/vcmi/Content/Sprites/lobby/checkboxBlueOn.png diff --git a/Mods/vcmi/Sprites/lobby/checkboxOff.png b/Mods/vcmi/Content/Sprites/lobby/checkboxOff.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/checkboxOff.png rename to Mods/vcmi/Content/Sprites/lobby/checkboxOff.png diff --git a/Mods/vcmi/Sprites/lobby/checkboxOn.png b/Mods/vcmi/Content/Sprites/lobby/checkboxOn.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/checkboxOn.png rename to Mods/vcmi/Content/Sprites/lobby/checkboxOn.png diff --git a/Mods/vcmi/Sprites/lobby/delete-normal.png b/Mods/vcmi/Content/Sprites/lobby/delete-normal.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/delete-normal.png rename to Mods/vcmi/Content/Sprites/lobby/delete-normal.png diff --git a/Mods/vcmi/Sprites/lobby/delete-pressed.png b/Mods/vcmi/Content/Sprites/lobby/delete-pressed.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/delete-pressed.png rename to Mods/vcmi/Content/Sprites/lobby/delete-pressed.png diff --git a/Mods/vcmi/Sprites/lobby/deleteButton.json b/Mods/vcmi/Content/Sprites/lobby/deleteButton.json similarity index 100% rename from Mods/vcmi/Sprites/lobby/deleteButton.json rename to Mods/vcmi/Content/Sprites/lobby/deleteButton.json diff --git a/Mods/vcmi/Sprites/lobby/dropdown.json b/Mods/vcmi/Content/Sprites/lobby/dropdown.json similarity index 100% rename from Mods/vcmi/Sprites/lobby/dropdown.json rename to Mods/vcmi/Content/Sprites/lobby/dropdown.json diff --git a/Mods/vcmi/Sprites/lobby/dropdownNormal.png b/Mods/vcmi/Content/Sprites/lobby/dropdownNormal.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/dropdownNormal.png rename to Mods/vcmi/Content/Sprites/lobby/dropdownNormal.png diff --git a/Mods/vcmi/Sprites/lobby/dropdownPressed.png b/Mods/vcmi/Content/Sprites/lobby/dropdownPressed.png similarity index 100% rename from Mods/vcmi/Sprites/lobby/dropdownPressed.png rename to Mods/vcmi/Content/Sprites/lobby/dropdownPressed.png diff --git a/Mods/vcmi/Data/lobby/iconFolder.png b/Mods/vcmi/Content/Sprites/lobby/iconFolder.png similarity index 100% rename from Mods/vcmi/Data/lobby/iconFolder.png rename to Mods/vcmi/Content/Sprites/lobby/iconFolder.png diff --git a/Mods/vcmi/Data/lobby/iconPlayer.png b/Mods/vcmi/Content/Sprites/lobby/iconPlayer.png similarity index 100% rename from Mods/vcmi/Data/lobby/iconPlayer.png rename to Mods/vcmi/Content/Sprites/lobby/iconPlayer.png diff --git a/Mods/vcmi/Data/lobby/iconSend.png b/Mods/vcmi/Content/Sprites/lobby/iconSend.png similarity index 100% rename from Mods/vcmi/Data/lobby/iconSend.png rename to Mods/vcmi/Content/Sprites/lobby/iconSend.png diff --git a/Mods/vcmi/Data/lobby/selectionTabSortDate.png b/Mods/vcmi/Content/Sprites/lobby/selectionTabSortDate.png similarity index 100% rename from Mods/vcmi/Data/lobby/selectionTabSortDate.png rename to Mods/vcmi/Content/Sprites/lobby/selectionTabSortDate.png diff --git a/Mods/vcmi/Data/lobby/townBorderBig.png b/Mods/vcmi/Content/Sprites/lobby/townBorderBig.png similarity index 100% rename from Mods/vcmi/Data/lobby/townBorderBig.png rename to Mods/vcmi/Content/Sprites/lobby/townBorderBig.png diff --git a/Mods/vcmi/Data/lobby/townBorderBigActivated.png b/Mods/vcmi/Content/Sprites/lobby/townBorderBigActivated.png similarity index 100% rename from Mods/vcmi/Data/lobby/townBorderBigActivated.png rename to Mods/vcmi/Content/Sprites/lobby/townBorderBigActivated.png diff --git a/Mods/vcmi/Data/lobby/townBorderBigGrayedOut.png b/Mods/vcmi/Content/Sprites/lobby/townBorderBigGrayedOut.png similarity index 100% rename from Mods/vcmi/Data/lobby/townBorderBigGrayedOut.png rename to Mods/vcmi/Content/Sprites/lobby/townBorderBigGrayedOut.png diff --git a/Mods/vcmi/Data/lobby/townBorderSmallActivated.png b/Mods/vcmi/Content/Sprites/lobby/townBorderSmallActivated.png similarity index 100% rename from Mods/vcmi/Data/lobby/townBorderSmallActivated.png rename to Mods/vcmi/Content/Sprites/lobby/townBorderSmallActivated.png diff --git a/Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png b/Mods/vcmi/Content/Sprites/mapFormatIcons/vcmi1.png similarity index 100% rename from Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png rename to Mods/vcmi/Content/Sprites/mapFormatIcons/vcmi1.png diff --git a/Mods/vcmi/Data/questDialog.png b/Mods/vcmi/Content/Sprites/questDialog.png similarity index 100% rename from Mods/vcmi/Data/questDialog.png rename to Mods/vcmi/Content/Sprites/questDialog.png diff --git a/Mods/vcmi/Data/radialMenu/altDown.png b/Mods/vcmi/Content/Sprites/radialMenu/altDown.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/altDown.png rename to Mods/vcmi/Content/Sprites/radialMenu/altDown.png diff --git a/Mods/vcmi/Data/radialMenu/altDownBottom.png b/Mods/vcmi/Content/Sprites/radialMenu/altDownBottom.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/altDownBottom.png rename to Mods/vcmi/Content/Sprites/radialMenu/altDownBottom.png diff --git a/Mods/vcmi/Data/radialMenu/altUp.png b/Mods/vcmi/Content/Sprites/radialMenu/altUp.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/altUp.png rename to Mods/vcmi/Content/Sprites/radialMenu/altUp.png diff --git a/Mods/vcmi/Data/radialMenu/altUpTop.png b/Mods/vcmi/Content/Sprites/radialMenu/altUpTop.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/altUpTop.png rename to Mods/vcmi/Content/Sprites/radialMenu/altUpTop.png diff --git a/Mods/vcmi/Data/radialMenu/dismissHero.png b/Mods/vcmi/Content/Sprites/radialMenu/dismissHero.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/dismissHero.png rename to Mods/vcmi/Content/Sprites/radialMenu/dismissHero.png diff --git a/Mods/vcmi/Data/radialMenu/heroMove.png b/Mods/vcmi/Content/Sprites/radialMenu/heroMove.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/heroMove.png rename to Mods/vcmi/Content/Sprites/radialMenu/heroMove.png diff --git a/Mods/vcmi/Data/radialMenu/heroSwap.png b/Mods/vcmi/Content/Sprites/radialMenu/heroSwap.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/heroSwap.png rename to Mods/vcmi/Content/Sprites/radialMenu/heroSwap.png diff --git a/Mods/vcmi/Data/radialMenu/itemEmpty.png b/Mods/vcmi/Content/Sprites/radialMenu/itemEmpty.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/itemEmpty.png rename to Mods/vcmi/Content/Sprites/radialMenu/itemEmpty.png diff --git a/Mods/vcmi/Data/radialMenu/itemEmptyAlt.png b/Mods/vcmi/Content/Sprites/radialMenu/itemEmptyAlt.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/itemEmptyAlt.png rename to Mods/vcmi/Content/Sprites/radialMenu/itemEmptyAlt.png diff --git a/Mods/vcmi/Data/radialMenu/itemInactive.png b/Mods/vcmi/Content/Sprites/radialMenu/itemInactive.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/itemInactive.png rename to Mods/vcmi/Content/Sprites/radialMenu/itemInactive.png diff --git a/Mods/vcmi/Data/radialMenu/itemInactiveAlt.png b/Mods/vcmi/Content/Sprites/radialMenu/itemInactiveAlt.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/itemInactiveAlt.png rename to Mods/vcmi/Content/Sprites/radialMenu/itemInactiveAlt.png diff --git a/Mods/vcmi/Data/radialMenu/moveArtifacts.png b/Mods/vcmi/Content/Sprites/radialMenu/moveArtifacts.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/moveArtifacts.png rename to Mods/vcmi/Content/Sprites/radialMenu/moveArtifacts.png diff --git a/Mods/vcmi/Data/radialMenu/moveTroops.png b/Mods/vcmi/Content/Sprites/radialMenu/moveTroops.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/moveTroops.png rename to Mods/vcmi/Content/Sprites/radialMenu/moveTroops.png diff --git a/Mods/vcmi/Data/radialMenu/stackFillOne.png b/Mods/vcmi/Content/Sprites/radialMenu/stackFillOne.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/stackFillOne.png rename to Mods/vcmi/Content/Sprites/radialMenu/stackFillOne.png diff --git a/Mods/vcmi/Data/radialMenu/stackMerge.png b/Mods/vcmi/Content/Sprites/radialMenu/stackMerge.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/stackMerge.png rename to Mods/vcmi/Content/Sprites/radialMenu/stackMerge.png diff --git a/Mods/vcmi/Data/radialMenu/stackSplitDialog.png b/Mods/vcmi/Content/Sprites/radialMenu/stackSplitDialog.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/stackSplitDialog.png rename to Mods/vcmi/Content/Sprites/radialMenu/stackSplitDialog.png diff --git a/Mods/vcmi/Data/radialMenu/stackSplitEqual.png b/Mods/vcmi/Content/Sprites/radialMenu/stackSplitEqual.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/stackSplitEqual.png rename to Mods/vcmi/Content/Sprites/radialMenu/stackSplitEqual.png diff --git a/Mods/vcmi/Data/radialMenu/stackSplitOne.png b/Mods/vcmi/Content/Sprites/radialMenu/stackSplitOne.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/stackSplitOne.png rename to Mods/vcmi/Content/Sprites/radialMenu/stackSplitOne.png diff --git a/Mods/vcmi/Data/radialMenu/statusBar.png b/Mods/vcmi/Content/Sprites/radialMenu/statusBar.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/statusBar.png rename to Mods/vcmi/Content/Sprites/radialMenu/statusBar.png diff --git a/Mods/vcmi/Data/radialMenu/swapArtifacts.png b/Mods/vcmi/Content/Sprites/radialMenu/swapArtifacts.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/swapArtifacts.png rename to Mods/vcmi/Content/Sprites/radialMenu/swapArtifacts.png diff --git a/Mods/vcmi/Data/radialMenu/tradeHeroes.png b/Mods/vcmi/Content/Sprites/radialMenu/tradeHeroes.png similarity index 100% rename from Mods/vcmi/Data/radialMenu/tradeHeroes.png rename to Mods/vcmi/Content/Sprites/radialMenu/tradeHeroes.png diff --git a/Mods/vcmi/Data/settingsWindow/frameAudio.png b/Mods/vcmi/Content/Sprites/settingsWindow/frameAudio.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/frameAudio.png rename to Mods/vcmi/Content/Sprites/settingsWindow/frameAudio.png diff --git a/Mods/vcmi/Data/settingsWindow/frameMovement.png b/Mods/vcmi/Content/Sprites/settingsWindow/frameMovement.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/frameMovement.png rename to Mods/vcmi/Content/Sprites/settingsWindow/frameMovement.png diff --git a/Mods/vcmi/Data/settingsWindow/frameStackQueue.png b/Mods/vcmi/Content/Sprites/settingsWindow/frameStackQueue.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/frameStackQueue.png rename to Mods/vcmi/Content/Sprites/settingsWindow/frameStackQueue.png diff --git a/Mods/vcmi/Data/settingsWindow/gear.png b/Mods/vcmi/Content/Sprites/settingsWindow/gear.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/gear.png rename to Mods/vcmi/Content/Sprites/settingsWindow/gear.png diff --git a/Mods/vcmi/Data/settingsWindow/scrollSpeed1.png b/Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed1.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/scrollSpeed1.png rename to Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed1.png diff --git a/Mods/vcmi/Data/settingsWindow/scrollSpeed2.png b/Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed2.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/scrollSpeed2.png rename to Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed2.png diff --git a/Mods/vcmi/Data/settingsWindow/scrollSpeed3.png b/Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed3.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/scrollSpeed3.png rename to Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed3.png diff --git a/Mods/vcmi/Data/settingsWindow/scrollSpeed4.png b/Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed4.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/scrollSpeed4.png rename to Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed4.png diff --git a/Mods/vcmi/Data/settingsWindow/scrollSpeed5.png b/Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed5.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/scrollSpeed5.png rename to Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed5.png diff --git a/Mods/vcmi/Data/settingsWindow/scrollSpeed6.png b/Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed6.png similarity index 100% rename from Mods/vcmi/Data/settingsWindow/scrollSpeed6.png rename to Mods/vcmi/Content/Sprites/settingsWindow/scrollSpeed6.png diff --git a/Mods/vcmi/Data/spellResearch/accept.png b/Mods/vcmi/Content/Sprites/spellResearch/accept.png similarity index 100% rename from Mods/vcmi/Data/spellResearch/accept.png rename to Mods/vcmi/Content/Sprites/spellResearch/accept.png diff --git a/Mods/vcmi/Data/spellResearch/close.png b/Mods/vcmi/Content/Sprites/spellResearch/close.png similarity index 100% rename from Mods/vcmi/Data/spellResearch/close.png rename to Mods/vcmi/Content/Sprites/spellResearch/close.png diff --git a/Mods/vcmi/Data/spellResearch/reroll.png b/Mods/vcmi/Content/Sprites/spellResearch/reroll.png similarity index 100% rename from Mods/vcmi/Data/spellResearch/reroll.png rename to Mods/vcmi/Content/Sprites/spellResearch/reroll.png diff --git a/Mods/vcmi/Data/stackWindow/bonus-effects.png b/Mods/vcmi/Content/Sprites/stackWindow/bonus-effects.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/bonus-effects.png rename to Mods/vcmi/Content/Sprites/stackWindow/bonus-effects.png diff --git a/Mods/vcmi/Data/stackWindow/button-panel.png b/Mods/vcmi/Content/Sprites/stackWindow/button-panel.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/button-panel.png rename to Mods/vcmi/Content/Sprites/stackWindow/button-panel.png diff --git a/Mods/vcmi/Sprites/stackWindow/cancel-normal.png b/Mods/vcmi/Content/Sprites/stackWindow/cancel-normal.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/cancel-normal.png rename to Mods/vcmi/Content/Sprites/stackWindow/cancel-normal.png diff --git a/Mods/vcmi/Sprites/stackWindow/cancel-pressed.png b/Mods/vcmi/Content/Sprites/stackWindow/cancel-pressed.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/cancel-pressed.png rename to Mods/vcmi/Content/Sprites/stackWindow/cancel-pressed.png diff --git a/Mods/vcmi/Sprites/stackWindow/cancelButton.json b/Mods/vcmi/Content/Sprites/stackWindow/cancelButton.json similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/cancelButton.json rename to Mods/vcmi/Content/Sprites/stackWindow/cancelButton.json diff --git a/Mods/vcmi/Data/stackWindow/commander-abilities.png b/Mods/vcmi/Content/Sprites/stackWindow/commander-abilities.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/commander-abilities.png rename to Mods/vcmi/Content/Sprites/stackWindow/commander-abilities.png diff --git a/Mods/vcmi/Data/stackWindow/commander-bg.png b/Mods/vcmi/Content/Sprites/stackWindow/commander-bg.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/commander-bg.png rename to Mods/vcmi/Content/Sprites/stackWindow/commander-bg.png diff --git a/Mods/vcmi/Data/stackWindow/icons.png b/Mods/vcmi/Content/Sprites/stackWindow/icons.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/icons.png rename to Mods/vcmi/Content/Sprites/stackWindow/icons.png diff --git a/Mods/vcmi/Data/stackWindow/info-panel-0.png b/Mods/vcmi/Content/Sprites/stackWindow/info-panel-0.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/info-panel-0.png rename to Mods/vcmi/Content/Sprites/stackWindow/info-panel-0.png diff --git a/Mods/vcmi/Data/stackWindow/info-panel-1.png b/Mods/vcmi/Content/Sprites/stackWindow/info-panel-1.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/info-panel-1.png rename to Mods/vcmi/Content/Sprites/stackWindow/info-panel-1.png diff --git a/Mods/vcmi/Data/stackWindow/info-panel-2.png b/Mods/vcmi/Content/Sprites/stackWindow/info-panel-2.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/info-panel-2.png rename to Mods/vcmi/Content/Sprites/stackWindow/info-panel-2.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-0.png b/Mods/vcmi/Content/Sprites/stackWindow/level-0.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-0.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-0.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-1.png b/Mods/vcmi/Content/Sprites/stackWindow/level-1.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-1.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-1.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-10.png b/Mods/vcmi/Content/Sprites/stackWindow/level-10.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-10.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-10.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-2.png b/Mods/vcmi/Content/Sprites/stackWindow/level-2.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-2.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-2.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-3.png b/Mods/vcmi/Content/Sprites/stackWindow/level-3.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-3.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-3.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-4.png b/Mods/vcmi/Content/Sprites/stackWindow/level-4.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-4.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-4.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-5.png b/Mods/vcmi/Content/Sprites/stackWindow/level-5.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-5.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-5.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-6.png b/Mods/vcmi/Content/Sprites/stackWindow/level-6.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-6.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-6.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-7.png b/Mods/vcmi/Content/Sprites/stackWindow/level-7.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-7.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-7.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-8.png b/Mods/vcmi/Content/Sprites/stackWindow/level-8.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-8.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-8.png diff --git a/Mods/vcmi/Sprites/stackWindow/level-9.png b/Mods/vcmi/Content/Sprites/stackWindow/level-9.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/level-9.png rename to Mods/vcmi/Content/Sprites/stackWindow/level-9.png diff --git a/Mods/vcmi/Sprites/stackWindow/levels.json b/Mods/vcmi/Content/Sprites/stackWindow/levels.json similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/levels.json rename to Mods/vcmi/Content/Sprites/stackWindow/levels.json diff --git a/Mods/vcmi/Data/stackWindow/spell-effects.png b/Mods/vcmi/Content/Sprites/stackWindow/spell-effects.png similarity index 100% rename from Mods/vcmi/Data/stackWindow/spell-effects.png rename to Mods/vcmi/Content/Sprites/stackWindow/spell-effects.png diff --git a/Mods/vcmi/Sprites/stackWindow/switchModeIcons.json b/Mods/vcmi/Content/Sprites/stackWindow/switchModeIcons.json similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/switchModeIcons.json rename to Mods/vcmi/Content/Sprites/stackWindow/switchModeIcons.json diff --git a/Mods/vcmi/Sprites/stackWindow/upgrade-normal.png b/Mods/vcmi/Content/Sprites/stackWindow/upgrade-normal.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/upgrade-normal.png rename to Mods/vcmi/Content/Sprites/stackWindow/upgrade-normal.png diff --git a/Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png b/Mods/vcmi/Content/Sprites/stackWindow/upgrade-pressed.png similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png rename to Mods/vcmi/Content/Sprites/stackWindow/upgrade-pressed.png diff --git a/Mods/vcmi/Sprites/stackWindow/upgradeButton.json b/Mods/vcmi/Content/Sprites/stackWindow/upgradeButton.json similarity index 100% rename from Mods/vcmi/Sprites/stackWindow/upgradeButton.json rename to Mods/vcmi/Content/Sprites/stackWindow/upgradeButton.json diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendBig.png similarity index 100% rename from Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png rename to Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendBig.png diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendSmall.png similarity index 100% rename from Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png rename to Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendSmall.png diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesBig.json similarity index 100% rename from Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json rename to Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesBig.json diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesSmall.json similarity index 100% rename from Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json rename to Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesSmall.json diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitBig.png similarity index 100% rename from Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png rename to Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitBig.png diff --git a/Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitSmall.png similarity index 100% rename from Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png rename to Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitSmall.png diff --git a/Mods/vcmi/Sprites/vcmi/creatureIcons/towerLarge.png b/Mods/vcmi/Content/Sprites/vcmi/creatureIcons/towerLarge.png similarity index 100% rename from Mods/vcmi/Sprites/vcmi/creatureIcons/towerLarge.png rename to Mods/vcmi/Content/Sprites/vcmi/creatureIcons/towerLarge.png diff --git a/Mods/vcmi/Sprites/vcmi/creatureIcons/towerSmall.png b/Mods/vcmi/Content/Sprites/vcmi/creatureIcons/towerSmall.png similarity index 100% rename from Mods/vcmi/Sprites/vcmi/creatureIcons/towerSmall.png rename to Mods/vcmi/Content/Sprites/vcmi/creatureIcons/towerSmall.png diff --git a/Mods/vcmi/Video/tutorial/AbortSpell.webm b/Mods/vcmi/Content/Video/tutorial/AbortSpell.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/AbortSpell.webm rename to Mods/vcmi/Content/Video/tutorial/AbortSpell.webm diff --git a/Mods/vcmi/Video/tutorial/BattleDirection.webm b/Mods/vcmi/Content/Video/tutorial/BattleDirection.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/BattleDirection.webm rename to Mods/vcmi/Content/Video/tutorial/BattleDirection.webm diff --git a/Mods/vcmi/Video/tutorial/BattleDirectionAbort.webm b/Mods/vcmi/Content/Video/tutorial/BattleDirectionAbort.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/BattleDirectionAbort.webm rename to Mods/vcmi/Content/Video/tutorial/BattleDirectionAbort.webm diff --git a/Mods/vcmi/Video/tutorial/MapPanning.webm b/Mods/vcmi/Content/Video/tutorial/MapPanning.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/MapPanning.webm rename to Mods/vcmi/Content/Video/tutorial/MapPanning.webm diff --git a/Mods/vcmi/Video/tutorial/MapZooming.webm b/Mods/vcmi/Content/Video/tutorial/MapZooming.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/MapZooming.webm rename to Mods/vcmi/Content/Video/tutorial/MapZooming.webm diff --git a/Mods/vcmi/Video/tutorial/RadialWheel.webm b/Mods/vcmi/Content/Video/tutorial/RadialWheel.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/RadialWheel.webm rename to Mods/vcmi/Content/Video/tutorial/RadialWheel.webm diff --git a/Mods/vcmi/Video/tutorial/RightClick.webm b/Mods/vcmi/Content/Video/tutorial/RightClick.webm similarity index 100% rename from Mods/vcmi/Video/tutorial/RightClick.webm rename to Mods/vcmi/Content/Video/tutorial/RightClick.webm diff --git a/Mods/vcmi/config/chinese.json b/Mods/vcmi/Content/config/chinese.json similarity index 100% rename from Mods/vcmi/config/chinese.json rename to Mods/vcmi/Content/config/chinese.json diff --git a/Mods/vcmi/config/czech.json b/Mods/vcmi/Content/config/czech.json similarity index 100% rename from Mods/vcmi/config/czech.json rename to Mods/vcmi/Content/config/czech.json diff --git a/Mods/vcmi/config/english.json b/Mods/vcmi/Content/config/english.json similarity index 100% rename from Mods/vcmi/config/english.json rename to Mods/vcmi/Content/config/english.json diff --git a/Mods/vcmi/config/french.json b/Mods/vcmi/Content/config/french.json similarity index 100% rename from Mods/vcmi/config/french.json rename to Mods/vcmi/Content/config/french.json diff --git a/Mods/vcmi/config/german.json b/Mods/vcmi/Content/config/german.json similarity index 100% rename from Mods/vcmi/config/german.json rename to Mods/vcmi/Content/config/german.json diff --git a/Mods/vcmi/config/polish.json b/Mods/vcmi/Content/config/polish.json similarity index 100% rename from Mods/vcmi/config/polish.json rename to Mods/vcmi/Content/config/polish.json diff --git a/Mods/vcmi/config/portuguese.json b/Mods/vcmi/Content/config/portuguese.json similarity index 100% rename from Mods/vcmi/config/portuguese.json rename to Mods/vcmi/Content/config/portuguese.json diff --git a/Mods/vcmi/config/rmg/hdmod/aroundamarsh.json b/Mods/vcmi/Content/config/rmg/hdmod/aroundamarsh.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/aroundamarsh.json rename to Mods/vcmi/Content/config/rmg/hdmod/aroundamarsh.json diff --git a/Mods/vcmi/config/rmg/hdmod/balance.json b/Mods/vcmi/Content/config/rmg/hdmod/balance.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/balance.json rename to Mods/vcmi/Content/config/rmg/hdmod/balance.json diff --git a/Mods/vcmi/config/rmg/hdmod/blockbuster.json b/Mods/vcmi/Content/config/rmg/hdmod/blockbuster.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/blockbuster.json rename to Mods/vcmi/Content/config/rmg/hdmod/blockbuster.json diff --git a/Mods/vcmi/config/rmg/hdmod/clashOfDragons.json b/Mods/vcmi/Content/config/rmg/hdmod/clashOfDragons.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/clashOfDragons.json rename to Mods/vcmi/Content/config/rmg/hdmod/clashOfDragons.json diff --git a/Mods/vcmi/config/rmg/hdmod/coldshadowsFantasy.json b/Mods/vcmi/Content/config/rmg/hdmod/coldshadowsFantasy.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/coldshadowsFantasy.json rename to Mods/vcmi/Content/config/rmg/hdmod/coldshadowsFantasy.json diff --git a/Mods/vcmi/config/rmg/hdmod/cube.json b/Mods/vcmi/Content/config/rmg/hdmod/cube.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/cube.json rename to Mods/vcmi/Content/config/rmg/hdmod/cube.json diff --git a/Mods/vcmi/config/rmg/hdmod/diamond.json b/Mods/vcmi/Content/config/rmg/hdmod/diamond.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/diamond.json rename to Mods/vcmi/Content/config/rmg/hdmod/diamond.json diff --git a/Mods/vcmi/config/rmg/hdmod/extreme.json b/Mods/vcmi/Content/config/rmg/hdmod/extreme.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/extreme.json rename to Mods/vcmi/Content/config/rmg/hdmod/extreme.json diff --git a/Mods/vcmi/config/rmg/hdmod/extreme2.json b/Mods/vcmi/Content/config/rmg/hdmod/extreme2.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/extreme2.json rename to Mods/vcmi/Content/config/rmg/hdmod/extreme2.json diff --git a/Mods/vcmi/config/rmg/hdmod/fear.json b/Mods/vcmi/Content/config/rmg/hdmod/fear.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/fear.json rename to Mods/vcmi/Content/config/rmg/hdmod/fear.json diff --git a/Mods/vcmi/config/rmg/hdmod/frozenDragons.json b/Mods/vcmi/Content/config/rmg/hdmod/frozenDragons.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/frozenDragons.json rename to Mods/vcmi/Content/config/rmg/hdmod/frozenDragons.json diff --git a/Mods/vcmi/config/rmg/hdmod/gimlisRevenge.json b/Mods/vcmi/Content/config/rmg/hdmod/gimlisRevenge.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/gimlisRevenge.json rename to Mods/vcmi/Content/config/rmg/hdmod/gimlisRevenge.json diff --git a/Mods/vcmi/config/rmg/hdmod/guerilla.json b/Mods/vcmi/Content/config/rmg/hdmod/guerilla.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/guerilla.json rename to Mods/vcmi/Content/config/rmg/hdmod/guerilla.json diff --git a/Mods/vcmi/config/rmg/hdmod/headquarters.json b/Mods/vcmi/Content/config/rmg/hdmod/headquarters.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/headquarters.json rename to Mods/vcmi/Content/config/rmg/hdmod/headquarters.json diff --git a/Mods/vcmi/config/rmg/hdmod/hypercube.json b/Mods/vcmi/Content/config/rmg/hdmod/hypercube.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/hypercube.json rename to Mods/vcmi/Content/config/rmg/hdmod/hypercube.json diff --git a/Mods/vcmi/config/rmg/hdmod/jebusCross.json b/Mods/vcmi/Content/config/rmg/hdmod/jebusCross.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/jebusCross.json rename to Mods/vcmi/Content/config/rmg/hdmod/jebusCross.json diff --git a/Mods/vcmi/config/rmg/hdmod/longRun.json b/Mods/vcmi/Content/config/rmg/hdmod/longRun.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/longRun.json rename to Mods/vcmi/Content/config/rmg/hdmod/longRun.json diff --git a/Mods/vcmi/config/rmg/hdmod/marathon.json b/Mods/vcmi/Content/config/rmg/hdmod/marathon.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/marathon.json rename to Mods/vcmi/Content/config/rmg/hdmod/marathon.json diff --git a/Mods/vcmi/config/rmg/hdmod/miniNostalgia.json b/Mods/vcmi/Content/config/rmg/hdmod/miniNostalgia.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/miniNostalgia.json rename to Mods/vcmi/Content/config/rmg/hdmod/miniNostalgia.json diff --git a/Mods/vcmi/config/rmg/hdmod/nostalgia.json b/Mods/vcmi/Content/config/rmg/hdmod/nostalgia.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/nostalgia.json rename to Mods/vcmi/Content/config/rmg/hdmod/nostalgia.json diff --git a/Mods/vcmi/config/rmg/hdmod/oceansEleven.json b/Mods/vcmi/Content/config/rmg/hdmod/oceansEleven.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/oceansEleven.json rename to Mods/vcmi/Content/config/rmg/hdmod/oceansEleven.json diff --git a/Mods/vcmi/config/rmg/hdmod/panic.json b/Mods/vcmi/Content/config/rmg/hdmod/panic.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/panic.json rename to Mods/vcmi/Content/config/rmg/hdmod/panic.json diff --git a/Mods/vcmi/config/rmg/hdmod/poorJebus.json b/Mods/vcmi/Content/config/rmg/hdmod/poorJebus.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/poorJebus.json rename to Mods/vcmi/Content/config/rmg/hdmod/poorJebus.json diff --git a/Mods/vcmi/config/rmg/hdmod/reckless.json b/Mods/vcmi/Content/config/rmg/hdmod/reckless.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/reckless.json rename to Mods/vcmi/Content/config/rmg/hdmod/reckless.json diff --git a/Mods/vcmi/config/rmg/hdmod/roadrunner.json b/Mods/vcmi/Content/config/rmg/hdmod/roadrunner.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/roadrunner.json rename to Mods/vcmi/Content/config/rmg/hdmod/roadrunner.json diff --git a/Mods/vcmi/config/rmg/hdmod/shaaafworld.json b/Mods/vcmi/Content/config/rmg/hdmod/shaaafworld.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/shaaafworld.json rename to Mods/vcmi/Content/config/rmg/hdmod/shaaafworld.json diff --git a/Mods/vcmi/config/rmg/hdmod/skirmish.json b/Mods/vcmi/Content/config/rmg/hdmod/skirmish.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/skirmish.json rename to Mods/vcmi/Content/config/rmg/hdmod/skirmish.json diff --git a/Mods/vcmi/config/rmg/hdmod/speed1.json b/Mods/vcmi/Content/config/rmg/hdmod/speed1.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/speed1.json rename to Mods/vcmi/Content/config/rmg/hdmod/speed1.json diff --git a/Mods/vcmi/config/rmg/hdmod/speed2.json b/Mods/vcmi/Content/config/rmg/hdmod/speed2.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/speed2.json rename to Mods/vcmi/Content/config/rmg/hdmod/speed2.json diff --git a/Mods/vcmi/config/rmg/hdmod/spider.json b/Mods/vcmi/Content/config/rmg/hdmod/spider.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/spider.json rename to Mods/vcmi/Content/config/rmg/hdmod/spider.json diff --git a/Mods/vcmi/config/rmg/hdmod/superslam.json b/Mods/vcmi/Content/config/rmg/hdmod/superslam.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/superslam.json rename to Mods/vcmi/Content/config/rmg/hdmod/superslam.json diff --git a/Mods/vcmi/config/rmg/hdmod/triad.json b/Mods/vcmi/Content/config/rmg/hdmod/triad.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/triad.json rename to Mods/vcmi/Content/config/rmg/hdmod/triad.json diff --git a/Mods/vcmi/config/rmg/hdmod/vortex.json b/Mods/vcmi/Content/config/rmg/hdmod/vortex.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmod/vortex.json rename to Mods/vcmi/Content/config/rmg/hdmod/vortex.json diff --git a/Mods/vcmi/config/rmg/hdmodUnused/anarchy.json b/Mods/vcmi/Content/config/rmg/hdmodUnused/anarchy.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmodUnused/anarchy.json rename to Mods/vcmi/Content/config/rmg/hdmodUnused/anarchy.json diff --git a/Mods/vcmi/config/rmg/hdmodUnused/balance m+u 200%.json b/Mods/vcmi/Content/config/rmg/hdmodUnused/balance m+u 200%.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmodUnused/balance m+u 200%.json rename to Mods/vcmi/Content/config/rmg/hdmodUnused/balance m+u 200%.json diff --git a/Mods/vcmi/config/rmg/hdmodUnused/midnightMix.json b/Mods/vcmi/Content/config/rmg/hdmodUnused/midnightMix.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmodUnused/midnightMix.json rename to Mods/vcmi/Content/config/rmg/hdmodUnused/midnightMix.json diff --git a/Mods/vcmi/config/rmg/hdmodUnused/skirmish m-u 200%.json b/Mods/vcmi/Content/config/rmg/hdmodUnused/skirmish m-u 200%.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmodUnused/skirmish m-u 200%.json rename to Mods/vcmi/Content/config/rmg/hdmodUnused/skirmish m-u 200%.json diff --git a/Mods/vcmi/config/rmg/hdmodUnused/true random.json b/Mods/vcmi/Content/config/rmg/hdmodUnused/true random.json similarity index 100% rename from Mods/vcmi/config/rmg/hdmodUnused/true random.json rename to Mods/vcmi/Content/config/rmg/hdmodUnused/true random.json diff --git a/Mods/vcmi/config/rmg/heroes3/dwarvenTunnels.json b/Mods/vcmi/Content/config/rmg/heroes3/dwarvenTunnels.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/dwarvenTunnels.json rename to Mods/vcmi/Content/config/rmg/heroes3/dwarvenTunnels.json diff --git a/Mods/vcmi/config/rmg/heroes3/golemsAplenty.json b/Mods/vcmi/Content/config/rmg/heroes3/golemsAplenty.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/golemsAplenty.json rename to Mods/vcmi/Content/config/rmg/heroes3/golemsAplenty.json diff --git a/Mods/vcmi/config/rmg/heroes3/meetingInMuzgob.json b/Mods/vcmi/Content/config/rmg/heroes3/meetingInMuzgob.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/meetingInMuzgob.json rename to Mods/vcmi/Content/config/rmg/heroes3/meetingInMuzgob.json diff --git a/Mods/vcmi/config/rmg/heroes3/monksRetreat.json b/Mods/vcmi/Content/config/rmg/heroes3/monksRetreat.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/monksRetreat.json rename to Mods/vcmi/Content/config/rmg/heroes3/monksRetreat.json diff --git a/Mods/vcmi/config/rmg/heroes3/newcomers.json b/Mods/vcmi/Content/config/rmg/heroes3/newcomers.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/newcomers.json rename to Mods/vcmi/Content/config/rmg/heroes3/newcomers.json diff --git a/Mods/vcmi/config/rmg/heroes3/readyOrNot.json b/Mods/vcmi/Content/config/rmg/heroes3/readyOrNot.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/readyOrNot.json rename to Mods/vcmi/Content/config/rmg/heroes3/readyOrNot.json diff --git a/Mods/vcmi/config/rmg/heroes3/smallRing.json b/Mods/vcmi/Content/config/rmg/heroes3/smallRing.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/smallRing.json rename to Mods/vcmi/Content/config/rmg/heroes3/smallRing.json diff --git a/Mods/vcmi/config/rmg/heroes3/southOfHell.json b/Mods/vcmi/Content/config/rmg/heroes3/southOfHell.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/southOfHell.json rename to Mods/vcmi/Content/config/rmg/heroes3/southOfHell.json diff --git a/Mods/vcmi/config/rmg/heroes3/worldsAtWar.json b/Mods/vcmi/Content/config/rmg/heroes3/worldsAtWar.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3/worldsAtWar.json rename to Mods/vcmi/Content/config/rmg/heroes3/worldsAtWar.json diff --git a/Mods/vcmi/config/rmg/heroes3unused/dragon.json b/Mods/vcmi/Content/config/rmg/heroes3unused/dragon.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3unused/dragon.json rename to Mods/vcmi/Content/config/rmg/heroes3unused/dragon.json diff --git a/Mods/vcmi/config/rmg/heroes3unused/gauntlet.json b/Mods/vcmi/Content/config/rmg/heroes3unused/gauntlet.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3unused/gauntlet.json rename to Mods/vcmi/Content/config/rmg/heroes3unused/gauntlet.json diff --git a/Mods/vcmi/config/rmg/heroes3unused/ring.json b/Mods/vcmi/Content/config/rmg/heroes3unused/ring.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3unused/ring.json rename to Mods/vcmi/Content/config/rmg/heroes3unused/ring.json diff --git a/Mods/vcmi/config/rmg/heroes3unused/riseOfPhoenix.json b/Mods/vcmi/Content/config/rmg/heroes3unused/riseOfPhoenix.json similarity index 100% rename from Mods/vcmi/config/rmg/heroes3unused/riseOfPhoenix.json rename to Mods/vcmi/Content/config/rmg/heroes3unused/riseOfPhoenix.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm0k.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm0k.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm0k.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm0k.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2a.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2a.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2a.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2a.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2b(2).json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2b(2).json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2b(2).json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2b(2).json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2b.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2b.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2b.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2b.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2c.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2c.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2c.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2c.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2f(2).json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2f(2).json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2f(2).json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2f(2).json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2f.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2f.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2f.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2f.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2h(2).json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2h(2).json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2h(2).json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2h(2).json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2h.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2h.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2h.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2h.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2i(2).json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2i(2).json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2i(2).json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2i(2).json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm2i.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm2i.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm2i.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm2i.json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm4d(2).json b/Mods/vcmi/Content/config/rmg/symmetric/2sm4d(2).json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm4d(2).json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm4d(2).json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm4d(3).json b/Mods/vcmi/Content/config/rmg/symmetric/2sm4d(3).json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm4d(3).json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm4d(3).json diff --git a/Mods/vcmi/config/rmg/symmetric/2sm4d.json b/Mods/vcmi/Content/config/rmg/symmetric/2sm4d.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/2sm4d.json rename to Mods/vcmi/Content/config/rmg/symmetric/2sm4d.json diff --git a/Mods/vcmi/config/rmg/symmetric/3sb0b.json b/Mods/vcmi/Content/config/rmg/symmetric/3sb0b.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/3sb0b.json rename to Mods/vcmi/Content/config/rmg/symmetric/3sb0b.json diff --git a/Mods/vcmi/config/rmg/symmetric/3sb0c.json b/Mods/vcmi/Content/config/rmg/symmetric/3sb0c.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/3sb0c.json rename to Mods/vcmi/Content/config/rmg/symmetric/3sb0c.json diff --git a/Mods/vcmi/config/rmg/symmetric/3sm3d.json b/Mods/vcmi/Content/config/rmg/symmetric/3sm3d.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/3sm3d.json rename to Mods/vcmi/Content/config/rmg/symmetric/3sm3d.json diff --git a/Mods/vcmi/config/rmg/symmetric/4sm0d.json b/Mods/vcmi/Content/config/rmg/symmetric/4sm0d.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/4sm0d.json rename to Mods/vcmi/Content/config/rmg/symmetric/4sm0d.json diff --git a/Mods/vcmi/config/rmg/symmetric/4sm0f.json b/Mods/vcmi/Content/config/rmg/symmetric/4sm0f.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/4sm0f.json rename to Mods/vcmi/Content/config/rmg/symmetric/4sm0f.json diff --git a/Mods/vcmi/config/rmg/symmetric/4sm0g.json b/Mods/vcmi/Content/config/rmg/symmetric/4sm0g.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/4sm0g.json rename to Mods/vcmi/Content/config/rmg/symmetric/4sm0g.json diff --git a/Mods/vcmi/config/rmg/symmetric/4sm4e.json b/Mods/vcmi/Content/config/rmg/symmetric/4sm4e.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/4sm4e.json rename to Mods/vcmi/Content/config/rmg/symmetric/4sm4e.json diff --git a/Mods/vcmi/config/rmg/symmetric/5sb0a.json b/Mods/vcmi/Content/config/rmg/symmetric/5sb0a.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/5sb0a.json rename to Mods/vcmi/Content/config/rmg/symmetric/5sb0a.json diff --git a/Mods/vcmi/config/rmg/symmetric/5sb0b.json b/Mods/vcmi/Content/config/rmg/symmetric/5sb0b.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/5sb0b.json rename to Mods/vcmi/Content/config/rmg/symmetric/5sb0b.json diff --git a/Mods/vcmi/config/rmg/symmetric/6lm10.json b/Mods/vcmi/Content/config/rmg/symmetric/6lm10.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/6lm10.json rename to Mods/vcmi/Content/config/rmg/symmetric/6lm10.json diff --git a/Mods/vcmi/config/rmg/symmetric/6lm10a.json b/Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/6lm10a.json rename to Mods/vcmi/Content/config/rmg/symmetric/6lm10a.json diff --git a/Mods/vcmi/config/rmg/symmetric/6sm0b.json b/Mods/vcmi/Content/config/rmg/symmetric/6sm0b.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/6sm0b.json rename to Mods/vcmi/Content/config/rmg/symmetric/6sm0b.json diff --git a/Mods/vcmi/config/rmg/symmetric/6sm0d.json b/Mods/vcmi/Content/config/rmg/symmetric/6sm0d.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/6sm0d.json rename to Mods/vcmi/Content/config/rmg/symmetric/6sm0d.json diff --git a/Mods/vcmi/config/rmg/symmetric/6sm0e.json b/Mods/vcmi/Content/config/rmg/symmetric/6sm0e.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/6sm0e.json rename to Mods/vcmi/Content/config/rmg/symmetric/6sm0e.json diff --git a/Mods/vcmi/config/rmg/symmetric/7sb0b.json b/Mods/vcmi/Content/config/rmg/symmetric/7sb0b.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/7sb0b.json rename to Mods/vcmi/Content/config/rmg/symmetric/7sb0b.json diff --git a/Mods/vcmi/config/rmg/symmetric/7sb0c.json b/Mods/vcmi/Content/config/rmg/symmetric/7sb0c.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/7sb0c.json rename to Mods/vcmi/Content/config/rmg/symmetric/7sb0c.json diff --git a/Mods/vcmi/config/rmg/symmetric/8mm0e.json b/Mods/vcmi/Content/config/rmg/symmetric/8mm0e.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8mm0e.json rename to Mods/vcmi/Content/config/rmg/symmetric/8mm0e.json diff --git a/Mods/vcmi/config/rmg/symmetric/8mm6.json b/Mods/vcmi/Content/config/rmg/symmetric/8mm6.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8mm6.json rename to Mods/vcmi/Content/config/rmg/symmetric/8mm6.json diff --git a/Mods/vcmi/config/rmg/symmetric/8mm6a.json b/Mods/vcmi/Content/config/rmg/symmetric/8mm6a.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8mm6a.json rename to Mods/vcmi/Content/config/rmg/symmetric/8mm6a.json diff --git a/Mods/vcmi/config/rmg/symmetric/8sm0c.json b/Mods/vcmi/Content/config/rmg/symmetric/8sm0c.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8sm0c.json rename to Mods/vcmi/Content/config/rmg/symmetric/8sm0c.json diff --git a/Mods/vcmi/config/rmg/symmetric/8sm0f.json b/Mods/vcmi/Content/config/rmg/symmetric/8sm0f.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8sm0f.json rename to Mods/vcmi/Content/config/rmg/symmetric/8sm0f.json diff --git a/Mods/vcmi/config/rmg/symmetric/8xm12.json b/Mods/vcmi/Content/config/rmg/symmetric/8xm12.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8xm12.json rename to Mods/vcmi/Content/config/rmg/symmetric/8xm12.json diff --git a/Mods/vcmi/config/rmg/symmetric/8xm12a.json b/Mods/vcmi/Content/config/rmg/symmetric/8xm12a.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8xm12a.json rename to Mods/vcmi/Content/config/rmg/symmetric/8xm12a.json diff --git a/Mods/vcmi/config/rmg/symmetric/8xm8.json b/Mods/vcmi/Content/config/rmg/symmetric/8xm8.json similarity index 100% rename from Mods/vcmi/config/rmg/symmetric/8xm8.json rename to Mods/vcmi/Content/config/rmg/symmetric/8xm8.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/2mm2h.json b/Mods/vcmi/Content/config/rmg/unknownUnused/2mm2h.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/2mm2h.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/2mm2h.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/2x2sm4d(3).json b/Mods/vcmi/Content/config/rmg/unknownUnused/2x2sm4d(3).json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/2x2sm4d(3).json rename to Mods/vcmi/Content/config/rmg/unknownUnused/2x2sm4d(3).json diff --git a/Mods/vcmi/config/rmg/unknownUnused/4mm2h.json b/Mods/vcmi/Content/config/rmg/unknownUnused/4mm2h.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/4mm2h.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/4mm2h.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/4sm3i.json b/Mods/vcmi/Content/config/rmg/unknownUnused/4sm3i.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/4sm3i.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/4sm3i.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/6lm10a.json b/Mods/vcmi/Content/config/rmg/unknownUnused/6lm10a.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/6lm10a.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/6lm10a.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/8xm12 huge.json b/Mods/vcmi/Content/config/rmg/unknownUnused/8xm12 huge.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/8xm12 huge.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/8xm12 huge.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/8xm8 huge.json b/Mods/vcmi/Content/config/rmg/unknownUnused/8xm8 huge.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/8xm8 huge.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/8xm8 huge.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/analogy.json b/Mods/vcmi/Content/config/rmg/unknownUnused/analogy.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/analogy.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/analogy.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/cross.json b/Mods/vcmi/Content/config/rmg/unknownUnused/cross.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/cross.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/cross.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/cross2.json b/Mods/vcmi/Content/config/rmg/unknownUnused/cross2.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/cross2.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/cross2.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/cross3.json b/Mods/vcmi/Content/config/rmg/unknownUnused/cross3.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/cross3.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/cross3.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/deux paires.json b/Mods/vcmi/Content/config/rmg/unknownUnused/deux paires.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/deux paires.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/deux paires.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/doubled 8mm6.json b/Mods/vcmi/Content/config/rmg/unknownUnused/doubled 8mm6.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/doubled 8mm6.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/doubled 8mm6.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/elka.json b/Mods/vcmi/Content/config/rmg/unknownUnused/elka.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/elka.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/elka.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/goldenRing.json b/Mods/vcmi/Content/config/rmg/unknownUnused/goldenRing.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/goldenRing.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/goldenRing.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/greatSands.json b/Mods/vcmi/Content/config/rmg/unknownUnused/greatSands.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/greatSands.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/greatSands.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/kite.json b/Mods/vcmi/Content/config/rmg/unknownUnused/kite.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/kite.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/kite.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/upgrade.json b/Mods/vcmi/Content/config/rmg/unknownUnused/upgrade.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/upgrade.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/upgrade.json diff --git a/Mods/vcmi/config/rmg/unknownUnused/wheel.json b/Mods/vcmi/Content/config/rmg/unknownUnused/wheel.json similarity index 100% rename from Mods/vcmi/config/rmg/unknownUnused/wheel.json rename to Mods/vcmi/Content/config/rmg/unknownUnused/wheel.json diff --git a/Mods/vcmi/config/russian.json b/Mods/vcmi/Content/config/russian.json similarity index 100% rename from Mods/vcmi/config/russian.json rename to Mods/vcmi/Content/config/russian.json diff --git a/Mods/vcmi/config/spanish.json b/Mods/vcmi/Content/config/spanish.json similarity index 100% rename from Mods/vcmi/config/spanish.json rename to Mods/vcmi/Content/config/spanish.json diff --git a/Mods/vcmi/config/spells.json b/Mods/vcmi/Content/config/spells.json similarity index 100% rename from Mods/vcmi/config/spells.json rename to Mods/vcmi/Content/config/spells.json diff --git a/Mods/vcmi/config/swedish.json b/Mods/vcmi/Content/config/swedish.json similarity index 100% rename from Mods/vcmi/config/swedish.json rename to Mods/vcmi/Content/config/swedish.json diff --git a/Mods/vcmi/config/towerCreature.json b/Mods/vcmi/Content/config/towerCreature.json similarity index 100% rename from Mods/vcmi/config/towerCreature.json rename to Mods/vcmi/Content/config/towerCreature.json diff --git a/Mods/vcmi/config/towerFactions.json b/Mods/vcmi/Content/config/towerFactions.json similarity index 100% rename from Mods/vcmi/config/towerFactions.json rename to Mods/vcmi/Content/config/towerFactions.json diff --git a/Mods/vcmi/config/ukrainian.json b/Mods/vcmi/Content/config/ukrainian.json similarity index 100% rename from Mods/vcmi/config/ukrainian.json rename to Mods/vcmi/Content/config/ukrainian.json diff --git a/Mods/vcmi/config/vietnamese.json b/Mods/vcmi/Content/config/vietnamese.json similarity index 100% rename from Mods/vcmi/config/vietnamese.json rename to Mods/vcmi/Content/config/vietnamese.json diff --git a/Mods/vcmi/Sprites/QuickRecruitmentWindow/CreaturePurchaseCard.png b/Mods/vcmi/Sprites/QuickRecruitmentWindow/CreaturePurchaseCard.png deleted file mode 100644 index 5d7bfbfef627c3325f7f90a633611e795926536e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13531 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 !kYjA+dS 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 Date: Fri, 22 Nov 2024 16:46:48 +0000 Subject: [PATCH 627/726] Added existing HD assets for vcmi mod Some of our custom assets were actually made in high resolution, and then - greatly downscaled for use in vcmi. This PR adds (some of) these assets using recently introduced HD assets loading support. None of these were upscaled, but rather downscaled from existing high-resolution version. Following assets were added: - vcmi icon for map format - based on our main logo, with no text - 'defend' icon for stack queue - now also based on our logo, with no text and sepia effect - 'wait' icon for stack queue - added only x2 version. Was actually already present in vcmi mod, but unused. - icons for creature window (attack/defense/hit points/etc) - it was based on existing CC0 asset - 'gear' icon for settings - made from scratch by me. Will also upload all of these assets in their original resolution to vcmi-assets repository. --- .../Content/Sprites/battle/queueDefend.png | Bin 0 -> 594 bytes .../vcmi/Content/Sprites/battle/queueWait.png | Bin 0 -> 6726 bytes .../Content/Sprites/settingsWindow/gear.png | Bin 747 -> 877 bytes .../Sprites/vcmi/battleQueue/defendBig.png | Bin 2498 -> 0 bytes .../Sprites/vcmi/battleQueue/defendSmall.png | Bin 733 -> 0 bytes .../Sprites/vcmi/battleQueue/statesBig.json | 8 ---- .../Sprites/vcmi/battleQueue/statesSmall.json | 8 ---- .../Sprites/vcmi/battleQueue/waitBig.png | Bin 1721 -> 0 bytes .../Sprites/vcmi/battleQueue/waitSmall.png | Bin 759 -> 0 bytes .../Content/Sprites2x/battle/queueDefend.png | Bin 0 -> 1493 bytes .../Content/Sprites2x/battle/queueWait.png | Bin 0 -> 719 bytes .../Sprites2x/mapFormatIcons/vcmi1.png | Bin 0 -> 2314 bytes .../Content/Sprites2x/settingsWindow/gear.png | Bin 0 -> 2234 bytes .../Content/Sprites2x/stackWindow/icons.png | Bin 0 -> 4492 bytes .../Content/Sprites3x/battle/queueDefend.png | Bin 0 -> 2820 bytes .../Sprites3x/mapFormatIcons/vcmi1.png | Bin 0 -> 4052 bytes .../Content/Sprites3x/settingsWindow/gear.png | Bin 0 -> 3769 bytes .../Content/Sprites3x/stackWindow/icons.png | Bin 0 -> 7575 bytes .../Content/Sprites4x/battle/queueDefend.png | Bin 0 -> 4453 bytes .../Sprites4x/mapFormatIcons/vcmi1.png | Bin 0 -> 6314 bytes .../Content/Sprites4x/settingsWindow/gear.png | Bin 0 -> 5177 bytes .../Content/Sprites4x/stackWindow/icons.png | Bin 0 -> 10266 bytes client/battle/BattleInterfaceClasses.cpp | 39 ++++++++---------- client/battle/BattleInterfaceClasses.h | 3 +- 24 files changed, 19 insertions(+), 39 deletions(-) create mode 100644 Mods/vcmi/Content/Sprites/battle/queueDefend.png create mode 100644 Mods/vcmi/Content/Sprites/battle/queueWait.png delete mode 100644 Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendBig.png delete mode 100644 Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendSmall.png delete mode 100644 Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesBig.json delete mode 100644 Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesSmall.json delete mode 100644 Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitBig.png delete mode 100644 Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitSmall.png create mode 100644 Mods/vcmi/Content/Sprites2x/battle/queueDefend.png create mode 100644 Mods/vcmi/Content/Sprites2x/battle/queueWait.png create mode 100644 Mods/vcmi/Content/Sprites2x/mapFormatIcons/vcmi1.png create mode 100644 Mods/vcmi/Content/Sprites2x/settingsWindow/gear.png create mode 100644 Mods/vcmi/Content/Sprites2x/stackWindow/icons.png create mode 100644 Mods/vcmi/Content/Sprites3x/battle/queueDefend.png create mode 100644 Mods/vcmi/Content/Sprites3x/mapFormatIcons/vcmi1.png create mode 100644 Mods/vcmi/Content/Sprites3x/settingsWindow/gear.png create mode 100644 Mods/vcmi/Content/Sprites3x/stackWindow/icons.png create mode 100644 Mods/vcmi/Content/Sprites4x/battle/queueDefend.png create mode 100644 Mods/vcmi/Content/Sprites4x/mapFormatIcons/vcmi1.png create mode 100644 Mods/vcmi/Content/Sprites4x/settingsWindow/gear.png create mode 100644 Mods/vcmi/Content/Sprites4x/stackWindow/icons.png diff --git a/Mods/vcmi/Content/Sprites/battle/queueDefend.png b/Mods/vcmi/Content/Sprites/battle/queueDefend.png new file mode 100644 index 0000000000000000000000000000000000000000..87e61b3abfbaa0d6d3971df92f3cd213ba31eb7f GIT binary patch literal 594 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 delta 724 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 M07*qoM6N<$f*mcDzyJUM diff --git a/Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendSmall.png b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/defendSmall.png deleted file mode 100644 index b22a1b5d64d0a96e98c740ec1d961a722df4c00d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 733 zcmV<30wVp1P)WFU8GbZ8()Nlj2>E@cM*00KctL_t(I%hgj|NYil? zJ^OFdZRV2c+DGi;Pg_l?<}gr}qY_4D3o6`01VJx`^yWGpS*;-IA7Cn#N4^JZkI9Y9g6Ydu?%Y&f|@E`$@pYCzWNAj z1^8ycmCJM&D;h5Bl={9d@i$5pGL?jmOpl_cuSbWuaKO@SVGfT@%vv4p!)qP^ko@Ke zGD-gM-fH zVhb&V&lf`XLnn4u^5JyNyc-Xs`hR=g>ZTC!0g5E&0D>=X?;sosKwQd&Xp;f}z$sY3 zVk%4JGFdGLDZ*OLQbK~I=de7u49#{OOotkw-oXauKcl>&3XVY+1Bhc?CU$!U2TUf$ zkj;rJ*SbKfwFvsgFf#ZChCK$B$zleEC=~ObYc^)>ED-+)GjX(5#7Q$bW*IHLy-lSkdT$%CMIwpQ15W?gKTGN_IXlxiE5;N@ P00000NkvXXu0mjf0K+qH diff --git a/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesBig.json b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesBig.json deleted file mode 100644 index e8383883c..000000000 --- a/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesBig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "basepath": "vcmi/battleQueue/", - "images" : - [ - { "frame" : 0, "file" : "defendBig"}, - { "frame" : 1, "file" : "waitBig"} - ] -} diff --git a/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesSmall.json b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesSmall.json deleted file mode 100644 index 796657130..000000000 --- a/Mods/vcmi/Content/Sprites/vcmi/battleQueue/statesSmall.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "basepath": "vcmi/battleQueue/", - "images" : - [ - { "frame" : 0, "file" : "defendSmall"}, - { "frame" : 1, "file" : "waitSmall"} - ] -} diff --git a/Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitBig.png b/Mods/vcmi/Content/Sprites/vcmi/battleQueue/waitBig.png deleted file mode 100644 index ed0b70ae650aa15acd67891309d72602aa46c2d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1721 zcmV;q21fabP)WFU8GbZ8()Nlj2>E@cM*00tdNL_t(Y$K{r7Y*XhM zhA%s5eVh+C4x~N@$cI^+;AELNiIn1yDa;IQO4TV1lh#Hpv<;~3!d8t`A{IhK^!>HME-JxRW&~W><_&(6T8mPwLfyH;{qKW9r%4gI((oT z3A`kzM?lPZPn}@s&KURZMKQwnFo4R{1&oeOQMRgp$ti(hOfxk( zN>Oncn}2p@9#H^saL?o7zX`R@jgTY+ki3`vpNBHDm!!=X_K;c+807sBNrG1m}ZTsoHli(PDN=;r+oebl`5 zEE_jI13*u2FZs(A031#S`+mL`$!bHgM1TcAu}XY7I>!37wKTu}Yr6Ul@Y0We3K>sK$Z|a7mkZ~$SdNLgJPW~2}Mrqoo#Cp-RHK8 zlfSD^0Zq{~5b*KQp=XFpe1)q>#vcr#8<^I7yHQL`4pOwzNm!q=xQp!{Gik(UJ7*zH zRTocfJ%ysGgeT_K9S)ctZ>PDbDkZX_;xe|pxS5K|s)bG3ttn!6^LqBa>qQTMFNn#l zDdM|pEEE-&abv*Ggb_))lt)7-HZ$p2i#UBwPezc+)MmqCTZ&DBupyy2WbyK*3VwBD zh>6Guz5PChN20X0XyU%BrDrYT_APzkk_rR@^k4lH!vMoD@OV^y`Q{l612AV|mD1_# z)yZ3-;?WQR9YVu8eSQ`OwMr6&%ZljmLA3_VMDWd;Z}3ilVBj9%F#P#19qicAM4<{{ zJ<;9^f-0&5*}fEy23uaJXaAAg$-U-nFY)egc2eqqW2bfEQG@56u17Z@_I0nRTT_HX z4N;+0P+ne6Z*MQ_YisycMI~g+Wq7dX_k7;x!%vxO;oB&0jgc0yu6(J zp`gJ)ki|K6_PqTWrkn=Ud^=W48aX++^!t2F zkGC^D-cI}3qpaSrpKDjT$jw!;SnnI>Lo}6TgvSLZFJW?Ph>kA7pAJ8p&}(Lb(Qz}S zC3!r)ejES1_#Q1g-e$24#=^|%D&Y?rrUx|@stu|=Rb=H3h-0U9Q|U^RxYFrmIxQc7 zN8&eWYdgWNwNB1=CkP#E^@yu|eLxU@z+m3D=X^JuZEK>lvx}U?k5ag-h=1L^P9Tsd z)}cFNM4~$NPu4LU3h~;$zngZyQXtgj-{5LrACaiRvD5k-Q*HGMUTdzUcAcB-%q()2 zWFU8GbZ8()Nlj2>E@cM*00LV{L_t(I%e_-wNK{~n?jNz*-a9?y^8;rtH21N<-K89{fo*YI!qI;<%v zXeERQnPkNJ^g*j|rKO1b*FL+EaOZ)l@L3ybYxcI=&8mLr#Y5U8l#^ZgYgBEu6C;o^k$;eM#AF&D+szkxu>wxU232EwjdVQ*VkiyF#$=6E3e*(RQPMg6;G^1 zw|_d%PylkzHsG=Np-Wj)-)ot0fDHF{sn`8Yidly_Ge`4T?P`nZQS5U-AwTYsKlnJ*K{y6M z09#g-`5VdQYW9hV^`nt_+LoiJMxF`MpQeVTH+?;`V`Gv{;PE__!S`)<9{_xAi@CM+ zNL87`;GCWbqqjE%DYjr~K7Cx3A*FLqD>IL p43N~)3?(Hpt2Dz70`jKk{{l_~@gWU&rm+A3002ovPDHLkV1hj^PT&9l diff --git a/Mods/vcmi/Content/Sprites2x/battle/queueDefend.png b/Mods/vcmi/Content/Sprites2x/battle/queueDefend.png new file mode 100644 index 0000000000000000000000000000000000000000..7ba9c0a58ed0f4b5805fb4763bf6d1e300fe6678 GIT binary patch literal 1493 zcmV;`1uFW9P)_}e0meloFkzV-rPS6|cBEBlqPlTwJcTWcS4?Zb0De>Ve0|9Y@qt18cPg_rd5 zt$K65w$ZqmicZ|7UOKhl=Q1KqnxN&AOS{9?^759_cf8i&+4ot?_l>bdumNBlM|wZ} z<#MwlfM$91`&LLD`z;x^f_#0w*e?_HIra*{`Akr8+tc@AYWl|KzdzdRs+0KW+ScWGGl!^hZS{c9LgKDAb zAy>QBG})g0_+b)@Ll7#IYT-DNrXvwT;7G~%%XgA{Q#f;-1pe5*XS6abYj&nUMQ!9QDnOM zag%u<-hL^-vX!0YthMbIfa^%SM51k@TB%*eZYaaj%zPTxwOAVfbs*d+e(9yR-i<|T zgRyNAq}T$Bv?hr0J2oJ-A0}0ZwK2+H+ny}}IDi54_HV#$cMpj6=hkl_G@#K&>5c(> zk%<5B>+^(>At;@JehcX|h#cUtV?eau*)9xVti>1sQ9w#U(wf>vk&Q|VjO`p?@i!;esP*AEPzoje-AP0%M&w$M z@<5~r`~)LCF4w=tC}@k06goC-15IDCymStw49!5bItHlJqYHmJUBSjN@BH~G1_yGu z84t&Ek)Dg^dW;YC^PoEx$Pes;AjVdFtkMLbW^s9wFa7K!^W(joDmBh^46s)5fAiZD zn|6M*Tby2PvQli3NqHn(iR(J#GakuZnp}2Fu$#ZLQXB?RGX+mkQIJhI|UEEf>}sTqyh0TM?amO#8f~@;8*7M_xd;i7hFC!|0T~YjpbZ=KfLta?S2kH2=uUsT zV}P4I=7ZI%U2pb-g~?uhU~Y(^o)lrEP+DUxZ6j?fkph2Pe<}s8%j!mx)k;WrHp!l; zz8~J`(8Mn<>w;wO{^^fhb*$%?2`RbO-ciCRZtGWi=LKSGuSjIU$r2mEZx+{Sg@&n- zeDmnS;2R$InlH633t)WrQ;JvY8hpv%V5T+Lm*ep406`SDJC9>=ASmm(z z^V9t4FYE1j4$bv{=EldyzwtL+l3i`r;1?$+y54VvFxS{O&hCls_D}?&Y8N4!1TT%% z4kv%T%um0wf)oyi76yKQcz*Etg*(03|C<2ZnP>X5v;9vUAI^TLE1R-6JTlF~Xr3@u zXvcwk7Hd3C{OZsA>}$Wn4-CD-`QIP9+T^1S-~U~I`nz0|j$P~J8>4;cCups|6e+mw zfyuTBWi8st^Ov8m@T>3sfz4`&#jK2tc0K%BcY5RZxRPf7`hGP4@g4#9r5R z3d6lQ93ekl2(&&SRU$4J?hqve*bsqAc79^u@~HBI*QF3HyhE-Q`wZr=93i9 zm6;sM{;s#nd(4d<{ekcPL+_04`m~RqkWxHE8}or?XjHWtS_xluq_|h-#XB~zlgHin v2Hl}j&j=!w>B%IY`||gFr^x^FpPIh{L!$x=PQ|#(00000NkvXXu0mjfo{{=q literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites2x/battle/queueWait.png b/Mods/vcmi/Content/Sprites2x/battle/queueWait.png new file mode 100644 index 0000000000000000000000000000000000000000..53e203dda3edf315b3bd2df2f1209596ce396e63 GIT binary patch literal 719 zcmV;=0xa&01yxW zDIpL-IRGgk0O{3`As7fbDFE2UgcuMA`0vAhX(|5x_pX{yIWYk2+n~(7dnp(Q_~@K- zV=4dt)|`w|F)0uT000mO2-wiP7#ILjLMhP0ra&y^cS$Ci^08U4&YweZ2;g8D#nFDv3|6^td)U6aSWbH9v>gL=WJ&AsCdEPRrn$} zx8*M?s+Sc8Z%MIt&dmvTVn4gW;6BrlPKS)Kl^`?k5qOv7P$cAou}%7+3S9WqFS|6I zua1r)N{;Ib1jacuREYg)!ct!IYSH@LOe>fTH=ALX13dS5)dJi_rWB==sD)*7FA`z>BD4?}NO;(J4Z8H!eF%|sB{Q|LqDi%v8fm{Fp002ovPDHLkV1oKv BB@O@p literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites2x/mapFormatIcons/vcmi1.png b/Mods/vcmi/Content/Sprites2x/mapFormatIcons/vcmi1.png new file mode 100644 index 0000000000000000000000000000000000000000..1be03a4600b7a17bb9abcd9508f6dc34e51e7924 GIT binary patch literal 2314 zcmV+l3HA1gP) z)wy*mZvOcvPF8C~Mr1@bX^H4tl%tM1s-~r-#eeFIDd{6SY&*T&C)ciwp?*4f#4;p)|^e>!&T zSmkoLs&ct}>-_oimo8hjY~uysjW^y}p zs+B8OCdZ5!qpw>+`tpKj8_lynHIKGuv)M>>b#*8jjZW(A?QI2_Nmlk}E_NxP|MfF*5jx?Q{3 zBp3jE_St6(&A``^$t0ddWnRa-T$4SxCQX_I)n(uSm@dEka<#U$>gMBqt*S|_DkK|} zsoYalRi(zpMj3FjTmc{e11K_JwcBpHWemL9TW+}}5FxMIbd<#-p*LS^^B7_8;Xm+Rw?Kh{k*-NbcK^EcmobJf+= z5fXN9TU34`cW~A%IMn01!+G3;-lG9%}L8#Tc&JZo7>= z=XpG)?Y7$vgP}8E$%$VMN%q!>m+w(I*Ne4Eglm+D*DD`RX!FfB(>;$oqB(Qs5C(il zZ~!1y@eXtafEQkPK`U0Q(B#RJT|(nJOhI|=_Ea1!D;bx(5}{NkE3pqNR4Qgv%4g&i zr0>q1ts58>kSt?z-!I z>b~pnc+f(jfTsY##s08RaL&iG2n%@lrtI^PQrYd5ev?asUsAxH3pp zAaS`4Z~YKJSP*A4AcVBO@gX5Fj4&g|0B)fAfHiB@;N`&y6DCCH{xH~ealCeUz$L}? zSr7?v&E>)h{nty1MQ>>BNu^*E-@$hVivcga^b#Ffhn?@IP@{Q3Xz$Jh81K{>&S#J^ zjga%Y5aQoe13oW#D(mAlYlT=TB%I2D0^q*;?xV$BZ4;d-)P9U}NznI8cx#9a79LTf zF7F)^yuyGYvA7%yN=A9l4_BCAtQnN36sy}iT-d;_0YWV>09<|b)mpS@(PtRO=?`PC zJoQgnJavMS$tp#{QQ`e$B8l;eN1|%59={<`r9WTsxa}uglZ0`oD|Z^OrU$d{zUwYp zZRQJvK|9xiG!TKB@4x!$t9tn1hj~B^6r)D}wDP%>8I+UCzVd~vu_7nmE0`8>H}?UK zTTu+Bi)4@TN1vhh-hUrp-NEbKOE0}NSO75Ug7$cchp%p)tUTWIedk%1{8&-f;0&uI zD3t?>0{}vcZDGYAh410Mw}UnkV}S9HdB-E}snb(u&Q*`CB~rR9nGw65FV8f@A621T zlIQzw*nt87#ALlism({ zfh&O^k}D3X=y@3J;(A!Qbm6jg?OG;Gd>;TXTD#4LBt3zg3fpY|Dx355Fwe85Nb?td zSKXy-dAh;2CO0!<&_MC%A5e>3+DM~#aJqb%ls&b^pj6M5v&0eSTJwiygy%Z$t4Oh z0L=e#_MpFYWpC`b@oJv2jry}$i}|6rM@fE^20nwwVSqE9jL%q&J#qj^aOTXJw^#_= z6ZBt(99?gmF=NJEjLHCFyX|*WCO%o&VnKL5XKSudR3@KOxKj2IR>Fr(LYpo8)!tt-d-m)N|35_b*=L^*Ed*Y*P*@r=kc#d&OZBWyuY7?!?i|+7>3f>8894nNOoN3=d(ZB zwWDaQH>&>-`N>ay0^M)6F`2YX_07*qoM6N<$g7*f63;+NC literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites2x/settingsWindow/gear.png b/Mods/vcmi/Content/Sprites2x/settingsWindow/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..ff89fc0e666626d491c5715cd17160630ab6c95f GIT binary patch literal 2234 zcmV;r2u1gaP)(ADp!jY5yG&*kPvRHgZlLUixB!E12cuP>rAigWhGFRkAi!e=Dn%6*yK!EZ9guSfiYRZK`Ve9T zh#>R`Q3%B}V3-#0Bn4n<$0Ek-PifKtY`;uWtW!rYM_807&tYOEB<6WG2w?$6j>fZ; zf{T$9b%mrBvk_fRvRnYah&7zoyJvO5!o?eDA3?{9Sj0*^Hx0r7Krwo(QlV0NMK~~$ zcVOe3-o0v43m0v`{X5wVAx*5Dk2Gxfvk$Mk;JmZPj4m%Pi2d>Bq8xornW9`1oj88K zira6!s7t3#btN?9&`<~yaAVleepWhd%SDSdbUHUDCtNdVVAs9IjTuqAaZ_V@)#|M!C^T|-uXfKrbA8{_ zPn+DQOPBgm@RP{Q2};gTa-Eg%*fIOp9&*T@1F{_cmFaXvz^uC6FVQTg|`TW%^UD(GhzA^-8%j!gdf*P<0ur!BQF zzUY{qr6nb~xL@FL#KfEk1MWvnPo@DMbu8ALc;Lb37LOX$KfL!|1Gjy>FCLH2 z?>p$s+W??hv0V{Iu^zh5l)d`=DF+N0)a6P+Qh^*qNSdLrz%oZLITXX3chQzRGg4G(aBt_a*uKaRh$LZknJ}nLHhEh z8Mq-63HdiMaA1!U4%lx%1bvVYje_E$Tw`nF_NmpCrK7kg zvA}-NBK-Y;PsCV~>Mj}@;!{gX3izmmGDT$BiYDu=cc-tlLa?;9wzPeIUL-15$EFPa6q5)}yp1`PX@H+Sw07w)FO!55QrE z?z@XVXG3$Wx#jcEzx(&1OYWWn02|R!(V%Fi&fk%(eeFn0Q5!AKsr%rq3x-F_i}Qj5 zV%6#fIArWCu)FlnpYK4A9$mN`&L_~Yb<55@_L?wt)27Dt0DzEKqZVs1_}sx4R-~~h z>E?umm2E(L`~7riD}DLZPkw|DydIZ@f`TGTN%8hLE<$36o2_i2nCoOl9p)%09r*mK zscnjV`0@9dn_=K~ZcdKH+6uN!Z?L;fx?ec|Z3_kHA=pO3bMRUrvCQhk9c?6oVP-4a zX_>}lhUPMQQyqQiKxivX7{8B-vK^T7Y}m1*CB2)r1!81-Tcz!G)EE*p$8B3A0MBDTU+Dls>&#Dk7FW1DT}!CmLsb^|LB?_YCiHuBt;G@?^s_B zPds$&E~Q?2_ENQwSNM_Qa%E)&d4K)!?vN)QyQz}5$>lI?-4(?&k6m%e#GaEUj+hcH z&+n0`h?XTBmDKsAKW(gD+XO%TvKYSlb~Zi$7&m%2Mhd!htM?cA-L0)#w#Jsc_4XGZ z-FEw9i`hK34aIt**p$h8Rb77Zk*8Ev6pfbBu}GpVIfNR)GDX+lHg_S$uUZw{9bAJ4Jwmo;=syFz%6`$>KzYrnGqe26MMynrX7HvA_y0*;rMnVH( z`t;dveDm#ZDY{2fFGUve?)yJApLNFg-jQ&)J+PEYImzGtn7`z`5C7WGzh8%vNF;30 z9Eb6c6cgMd{y)m`mPF#gM?RQu+fH*$b+jlHGDG}~mX_A}!$%$SmMYS_f6~fo`CBex73ddkyy93eRrYfqn7MBhFg3+69a z9sGRD&GKDYvS7xYhq%;4tDx@b!45QSNEm~ZHLv>T*f~^*U!T)Uqe|6>y0R>X}qn7{bOsZo^b*z=oO}s1c zo6H79j6_^6#v&*gKzF4YcJydX1b+@oiTeotPmDz?OTEPZ0KnVdPaz9a5dZ)H07*qo IM6N<$f|S!!y#N3J literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites2x/stackWindow/icons.png b/Mods/vcmi/Content/Sprites2x/stackWindow/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..b1cdf1c2245b04b8676b25cb4d2f4654021e1e13 GIT binary patch literal 4492 zcmV;75p(W|P) z7t+T01bVoUIcOq@)W{56K_`Lh7%V>orsRD>>I-(DH6o&k4fvRl`kdKF3PWgMA8LeD zCp*;{)5L~+L&!LRroxbn(Rhjw6H^fJDie^D3xRogD=#J@;$xP6^4?98`a)p-vN0th zmZgEhkocJRh=P##nD~fnNX3|4*fc&O8&WN%hwk`@Y)JK(_=u7r%{hzr1^Jwi#kZ{Z z8xf7P#D=u98!P9G==X#yy4m?RMtd<1X0Uog8rYmJo@30y5iK6=5qO9`_NK9X2rQZT z)Q8iO6Y&L~LPx0ZX#s#vmeblTVvbOOUQVTiQ_^sW}rp^eUg@ss#MjBX*C21o8O2))Q0GaI?yV5yi) z$ne}y91*V|=6=S=hAhiKd_;6ijrFr3V{<26oD&~WI_4NUc!-HfLtsqiVQ`W^SUYnt zUeWme`Th_2Kjgm_G8FuEA&tz&9o){WH2j$%4UEm6e8PLY&&TY^*fBvx+p5zt4^uD^ z(=azj@+Mte!Su|)m2~kYM>99mFcDKQH`6_J-)3b)fCK2_6F%iTI@0soS&!guB_E z5NUhvW|Jx*i}M_7(;pGh&pJHM;#ESX#W3w5g{M360b8x zm5?EMiuHLeBIJ42<4K0B64Ju0?9A;EA$PC~x6l$Dk{(((jng?RBE)k%m-Mw_LV)eK zHG3?rq<*&HR(7Cr$Wna35?o6t=~|ZH1D2t3$S_>bqb$hN1d2S(f;`Ig3{yP>CgBAx zVNqTqZ^S;F|6?p}=U+O}=mwr%s;wvDx~ZLiIz)wQ)A&%W(@etk}ydR5(7 zRoT@K{YUN&s(QXkM1Ju_MAk9XqfOjXOE-D$gE{v(zzic#rU18IhT zTr`$I>fvg<%qHhO5Cpm%kfDV!nXJTfUIj!NYGF)(B;eWqf-Y9Z&b>4(K=Ke+9t1J= z+ejZB4WoAt1VZ$fYThEidlJ#Sf}~n^KoI}ekl`88T_rE^SyahG*c*31{|NCduG zg$`m{J`?WeR9%y`0xVt0iV~XN7|g68tXf0|5Fa@sBNeaDi*|M|4p#?V%eN^KLyBTg zTtNUpIfkqnEPe)|KOzX6t$QR0T|oL1qY3B-p$?=du4VP8fuMyUJG1{ndd@tIP*gzB z62s=8(Hljzf}mspQ{U=RyXI26-qO(~EpVVt5E#O?!MzGq=^W`I6;1gkiqCRZj1~}L zxF3@FX`%UN))S)<;3n&|6XYolMdS<07kj8)`M1gHBf#?q>BC(tl<2EuZmL4_6R2Ls zeU+IT^{iG+b5Y}PiujuZFp%+F2MCo3Sq(G)ET8X-w0sjG!53-UMZCWbqHQ|ShAxUb z?;Jo6;`1Zh5keVhV#}T=nEEaO@UsZ>B0mE0S~7%-DH}ADp1m>{$Okkw1QpK$*s_L( zJ?Z}7N??W9vaGU%C5H*y@=`k}KxC4D5Rf+?*z#wbzY^j5W!MP6QxpHOA->WRb}pma z&l)DEpLsA1vNT-&-VqR_$uq3&g(!m=0>~Q-RT3fft10B#f;}mz%daVclT?ApR*&fQ z1-!W$aFN$V4OJ!#%oj~rA{mz6nia5&j9#g^ujjhx>Lb{Y8%=To(g9J%E1{+(c140X zi93xQEA~w(Y#=klK)dbMJ3yxzO>IvAOYNxV}=Tpie66E09N^|0Ff0WQ%82pQ(ZOs zLC`*7wU`6(2*lUnBrGpM^bB`z>_O0C4}unZ5H#;VM9}U%0f9_)Q#hsLd;p(Vu zlJ)r|@|E(?^y81_B6TisutZ0Q-jEw#Z@U%H%egs?X4g*iz)>?M+qQ920c{I};H7WK zIYRY7id`U8OmY){Ll9P7>LtkHd=7!nR8o%w;uEtxW{S?MKm)a8RH%Z;Axrm&_}M>D z2eec=s)AU~cvnQwKP8IPTWbZP074qIZuHPNUGkM(5JpESLAT3U*&2j*zORp^6&nx& zNC4S7D^?)n(4Ly9O6&n4 zU_1uSaa_dwc5y&OC9CK+#{{i}>;6oL|L=radI3n8p>2@vgGoC~gxfEJ&lI4373RLv z9Q#o`ED*f*cU4N8i1Q6)sC-07(-rQ^g?8jM1;PhjX{X=|cXBXs-`{N9ca8&gEYGMp zDQ~dRGk@5ZB3Cs7GR-Rx!k=zw24sfEK+>&%bmEs@0r^!X+8NVC zzxEKw9(hWKS*mD`N5eAV(AK%;1`Uh1*#jUO5ecnLJgHeHq6lIq%T{UA>&Ygk6$Da7 z6T9asju!HRj`lSx(>5RRlfrdD(^4Ib3hkH}-9hSxBM?ZUlOuVOssZC?94_m0lA{8; zIBO9yt?Ztn@E~kYD|s$sqhvpqG{FMVV(~TLt&xDdfpLtgJJAGtUzbu{!H&3U=a0>xy{qqMVCJyiABC5VnH<-U{eAkT{SykT{Sy zkUS2}xR3qF6_9eIY&^he$7_gWw~X-NiIl!v!GSriQ(G~RD7Z3^yl{;}7R>#3LT{xl zsN{MxUx-lM=Oo4XA_4JJHF8dY9B)R@l|J*|BJo*DxJMt_Ha9GgE{f`4F1+!vQP<{m;tF{B4z&wL+@p1JuHgN z%NehwO*dw2A&-?X1CopdHBKllcGYT|Jmk~ZEdhka+ATGUij;Dcg99EX?Z0YiU;lTQs0JTX?QXOSiHk zX4liZPq}$XN=3GAR|bu=z{0K=8un$s3n1?^jP9u-T;sR{AUl{h`p7fZ^alZC5GzWy zMTV-8M%r0CNspsK_5e^{$rh5b__|Xx-3dQ-;Cuw#eITVP+JP9!^(_|q{26v&pH~i6 zGIl`{sdQY3fTx}wcHsp;WaIxN#>(X^jh$EnlaHzITaqk`xEc@#5(g4{Adu)hF$Y5I zzEZ4#B(P_ci#3pHymKU3f)nSBl!j~6(RrYOS&HAyh;TmP=QM_kOrek?6Pc=Vw0fih zMZ!0T1CWMOHeK2zPmMPRY6Nn0-=g6E3y~0p3SW35r7JZ}^tEMBNUc3m5=rwMxyPJ6;Tc8S}KB8si6h1N5P?9g;&^3*Y4XQB=plW z@2F^}h%Qst0*s*Sa zLGqISlEI(SbRrxu<0hp6uu{-l@CD|L;BipV!{q}4P@UX z?!mD#QR0KGO@#05Xb1~o%ngp4~&tX*Ol?*PW`ltDMUlF9BTT7HhX z@&j|}G!Hd{o!D=8uSw*1!{P=o-I)hDVlR1+iJgZaK$_XohV^aWT!Q*+l0H?ftc=Ck z-d8h3q1CdMq0G-5d4g4VoLbB!t+*nua~Nbm zwI`8B2j~D&6w*A{$x(-R4P|u=O`f=h76%ds5(lCf<*W&1ENVytOf=aHQ=`=*euSHJ zUXoG_5EEekMTDijmP5Am1K}yx3WBo|pZIqO(lR!&&3wOF=WFcpL5vb`tXCK`l62`t zNEZV>8$f$YL1W3|6i7YPqh-hnuS)8NX7V%K^qj%r+6x`LkI#4$0__M$N#Vm}3ythc z{y=-&iSn@Zr5rVu%k|PPz60mXJ`lLZr-tEp87C|;%&YD0H4IZ4`n@wCjPEmIg05jz z45K%~(SoId$^R2iz*U{zgM8R#7E`Hu9^ylKxToD!;}_f`JyN_y!Mc9Ksu^k*NK-?gXr+sJGDS#S zvux!CFj>zTg#HG0L12Nmi^xVlyFF5vV89%a&-#MRcTM=>^c*zY+)heU36+f#FJIYv_q$ zm?nudoTQMhVz6@fHPU_fmIF<8B2!BD;w1u5r&+$Gb^5+DjY_h~0XP|eE6;rDhKE>H zCy@Taky?SiE-U1_5^}3;R1b9@INhA_9*5};#q`n%lXxRbuvygFw^=s84;+EwIw6;?^Y9A3gu{^=JOY8?g2_m9`mv{M7M7A1@VK zYb!kktOaB2xk8A~Za;hUPB%Meqxy?8VDzQsB);U9Q^#AMKYHEO?|$-xtj!Cg7e=BG#8iRxw&M|{sZ)utE^aCQ;1RpE7!$gw0(AR z`ffq}$T`KrY>y6(aoO+N-0ZF^Xtg9kGeAngP+yUDoDNJk!WRi4?wh9iYunEn{WgMK zX{wt-pf9>=pC!lqHubwJc5D6z5d1d?gG+v4)1)2g#7JX=khYx0VJ&FQEH~j)rMD(! zsi%OnDOde+FKtnywt6++`PxtT(GPx4BS^toQhkyJ-*YKA(rROyVVv%_*6zi!8^J6V zBc&*x_D{=KeEf62eQZ|@%pU!wb&Dlp0|i&2RE$uX=ERNs;yb@#+wC{8e8X}u73xzS z7hSZ2tAG7FZg;CuKKY$%`1P-L(O2~`2BIXzaTGlzkD>k|dymf0XhqpNC51hC{HHOw^H zNGb6Oih}D=d^Sna)J=#=!m!W)NYw6 zzu+K+gQFCQHbnJS_YBOnlZm~5xOiyE=okVYZEY?zSs;Aj)Si<1Z;_4oJOw#y$DHd( zva?!)ooAyJcJEGXY&w16ZARFBt2MX|Sd*sLoMLtLd}9msD%bjnZW)VgZU~YI5h}TNKzxx zt{L#kP8ioiOe?@qmezC~(>{i(^yWDp<)@LkFukCV6#{_;u(=5YVu2}}*Fj!O2w_Nc zlkP>JSke2guX7?gf++M;pAOJ7lPDR1?}K!KVovjK>OkZ|7moWMBVlY-&>f3WNY!^K zyNcG)<8<>zX2V9r;BrIkYN9xWMhHa*B;W(coO%0PXhM*$&9&#z$y;egi6*GmnQerm zDeT!h-Mu1i?B3CGC9E58DXKXdt(c^pK#)M3f-!WE0%4v~=HH**+T6jZkYGtdl@Bh$@@gU9DsW-TYCn*8FT{VeG#qADJoq$3d} z58*fn6iW7@C~=oF`?>1{ckYclhZghZ*WC5yplzHPc-) z5XI(TY>e4+qArgfuQNVx;AJ1ZglgHR;Hs>LJB~!Dc?vVI08!||NF>;<3gg{7{iN$j}(^zNGqEJA14!Rx^#fHnTJ3b%bCXKm- zR+!>Ca&H&!#OzJAIo{vnW2|7Pzl_!dXL;P16OYfNa@D3%ZmL zndnTs?}D+UVM;TI8Lx-=({N#_LUty7sgC9LWY)Bh~u== zQ8JRjvvH82Tr6tBj2iUY`2`dLGL`l*~9b674(pfqbIztg+F?wRW`gHw(AtuooLYfU3Q;OR zxdhS&Jix`qhF#a*$iz%UoSN(&vSoGev74-0^4@Ka|8%k_284W&-ne<_Lz~A2ud9?@ zX67Q!8(YDWic6BD=sa~2tuqtW8W40kk5GUM_+a41e?G>gmmOug(PpL<(qDGXO}7qz z^t6#B*DdO|(aP|1k8T|uc(YqnbB!RubzJUr!3bEBwbi@>%`+uQl1|Z<{Z9VlftAz` z9_3d*x`8m#xUNgBufzpogBRUo{n8Kg-S%C2(Le64T+#Ejty_oRH#|^^T}QEH{Sf!O z*@jGT9)-+=MnI_mg?Y*%Sck^p{rvLVSJG<6q*~Bd_Bi*n!9!(7fn#IL?}DM2Gf8-bF7 zo?3O^xi{PJ#1$jMzx$JZUiU{|+1k9k=M{r}-d9`g#7;L8Hmx4WOz1Qvj1!DC*kTz} z4^#UO^W(4mj<6LIM4I_yP(^iU^BJoiyZPqZe&}y~rTvo{09bXm_s=Y?de2vaU&e{1 z*-kSP_qgdOu7pIJEOo~B9OsumxEinQ6UT9Lj77u@Xp)xQ+tUY7p{6#ox= z@_W}b9)72-7fZp}Qpi;xsFpoe*LrA0DeG%xuDECqzFR^D0iF{01v$NbS@pR?{lzcT z&VRk({|P}C@a5OtcZ=5cqhYMKwZarEIIh6!^${p^D`aW4fKp8KRSVB?l=}R($9+ow z#rz|?|4Y8Q!z;+g`kwlYtAq`rlyZL$wSiua9iC=52ce}t6H`!%fnNV=SILLABmI>NAOC4X7a`~}UitXj4MvH6gI9DP zu8omlko;t*xA^Smz2^rzSOf?b0gt}d1wHL3c||LTi&BbLe(UGgjI${C2mXP7;9mek WYRz@zWF!;-0000Qqxs=NPx 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%0D&e4kO08Fb3{F(S? zzAc>Z+Xpu$9YRCpn&Te6qZ7tO^b6#aNh76wPDoZ)D5Q&W$6GF+7aq&he?knynt1S+0Z?T`_g_75&6rUULk$MKo@n}FB2}Oe z8JLU)GHq(CY-;ikSDh8$Q;$t2@4j^}@$<6+4%K)R12swq89qEV_=(5v9xz})K<&Xp z8Ld1>{FNG~KP)J<=y(4;mk*Irkv9bg`A=l=!;L|5-FZ6+w{WAA8pIdKGMQKD%8zD#$k~EX3u@Y-_Lq2yZi_sr_!ks z?oa^DNO{IUF(?6rWzeSz44&1w2ek^9*__6vwtbP&m;S`UEeN#)!WGHy+$D(&?fVg^ z1@3Sx)ei@U=d)^EY-ZG)2#Wi#RB)~%at>fE>XYWsq@?uS7!RNR(6M^kj*PB{9+)}M zVlhW@@gkQR5IM!bYLV4yT36|3?Tg4TaEgeZ0}VJwU47%hYi2yWk5@w2L8lP1%wM#9 zEv1^S)7+7WyDwO@O3LaR8yY)1d9I(;VL&2{xrez0eYLfXorOh}jg)$j7vzCCnDflt z>-uN*@^zq5v7^pT;m3bq&9a~L+6+ZCjuf?^st!+$!B&8f}T(o%SiP58?8iNMg2C`2D z7^u_bay<3y=YLmL)pcC&EKo4su!=gY+OS~=%7oLJgd93v^w$K59e+8>DSGU z-d>Sy;KE;f_v}B~k(XamH+Zn07~pR;8}wQohyV|i70ijwE~hgmx1{Q$&sQB=usE?6 zl2JgOUfQv7b4umOlckv>Mnt;(eXSwV{nWDU?e^n;{qWQBQv87k-%0ipL$Jrg|FKcQ zvDF{B-7fda)w?%-@%6F}`~@1X<6S{NUxVd|N3O6v{J>@L=nisrRFLoP;uYzq<}XTY zY-x3Mf&pj5C#CSgKX~^6>pgc(zeO*V_>uR0h5%x6!Kb@{eDVs@jOmjsd-mD|?jxvy zTci4i5sZ#ze|ZH0scJ{ZO#LEJ0oRE<71hie)X<)O&3JL|{@l+07iu23uEQ`8q|)zy z)ZfdKoZ?Yv3}{5aBE5{)A_aNpDVlKknbVAcAV#JbAAtXXIXO3=3$g2$DxL9M(Tp+E z9x62KbrXemT=+5U)k{4!e?(YTG!m8yvEg2c%zAabyOlpsfu0PcIaI%jS4ql+|f zGUw#W(YAhU#(02~Ty*5n=X@RNr(&=2fCjgn+Z2%Ad^X_fXFY00000NkvXXu0mjfJG)Ra literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites3x/stackWindow/icons.png b/Mods/vcmi/Content/Sprites3x/stackWindow/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec5a9e3207409b9bcdf6b6012c3105c9493611a GIT binary patch literal 7575 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 literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites4x/mapFormatIcons/vcmi1.png b/Mods/vcmi/Content/Sprites4x/mapFormatIcons/vcmi1.png new file mode 100644 index 0000000000000000000000000000000000000000..0193036bedc1c85e460bfca507c92bef08a22976 GIT binary patch literal 6314 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 literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites4x/settingsWindow/gear.png b/Mods/vcmi/Content/Sprites4x/settingsWindow/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..83d451dbe5aa3f3c022ed71e81b5dce4a54b9978 GIT binary patch literal 5177 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 literal 0 HcmV?d00001 diff --git a/Mods/vcmi/Content/Sprites4x/stackWindow/icons.png b/Mods/vcmi/Content/Sprites4x/stackWindow/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..95abe6c3c35c159a2dd784f07949b7901f5af7b8 GIT binary patch literal 10266 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≥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 O@U%4^sn@F7MgAAdlk(UA literal 0 HcmV?d00001 diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index b7622316c..f9dde7e68 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -1066,11 +1066,13 @@ StackQueue::StackBox::StackBox(StackQueue * owner): roundRect = std::make_shared(Rect(0, 0, 15, 18), ColorRGBA(0, 0, 0, 255), ColorRGBA(241, 216, 120, 255)); round = std::make_shared(4, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); - int icon_x = pos.w - 17; - int icon_y = pos.h - 18; + Point iconPos(pos.w - 16, pos.h - 16); - stateIcon = std::make_shared(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"), 0, 0, icon_x, icon_y); - stateIcon->visible = false; + defendIcon = std::make_shared(ImagePath::builtin("battle/QueueDefend"), iconPos); + waitIcon = std::make_shared(ImagePath::builtin("battle/QueueWait"), iconPos); + + defendIcon->setEnabled(false); + waitIcon->setEnabled(false); } roundRect->disable(); } @@ -1106,22 +1108,13 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std:: round->setText(tmp); } - if(stateIcon) + if(!owner->embedded) { - if(unit->defended((int)turn) || (turn > 0 && unit->defended((int)turn - 1))) - { - stateIcon->setFrame(0, 0); - stateIcon->visible = true; - } - else if(unit->waited((int)turn)) - { - stateIcon->setFrame(1, 0); - stateIcon->visible = true; - } - else - { - stateIcon->visible = false; - } + bool defended = unit->defended(turn) || (turn > 0 && unit->defended(turn - 1)); + bool waited = unit->waited(turn) && !defended; + + defendIcon->setEnabled(defended); + waitIcon->setEnabled(waited); } } else @@ -1131,9 +1124,11 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std:: icon->visible = false; icon->setFrame(0); amount->setText(""); - - if(stateIcon) - stateIcon->visible = false; + if(!owner->embedded) + { + defendIcon->setEnabled(false); + waitIcon->setEnabled(false); + } } } diff --git a/client/battle/BattleInterfaceClasses.h b/client/battle/BattleInterfaceClasses.h index 819ed3504..9465502bc 100644 --- a/client/battle/BattleInterfaceClasses.h +++ b/client/battle/BattleInterfaceClasses.h @@ -260,7 +260,8 @@ class StackQueue : public CIntObject std::shared_ptr background; std::shared_ptr icon; std::shared_ptr amount; - std::shared_ptr stateIcon; + std::shared_ptr waitIcon; + std::shared_ptr defendIcon; std::shared_ptr round; std::shared_ptr roundRect; From 4683b5d52a73ad5c4bba10b2a9ccb0ffdabc54d8 Mon Sep 17 00:00:00 2001 From: Maurycy <55395993+XCOM-HUB@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:19:20 +0100 Subject: [PATCH 628/726] Update swedish.ts --- launcher/translation/swedish.ts | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/launcher/translation/swedish.ts b/launcher/translation/swedish.ts index 2bd48e043..7ae569217 100644 --- a/launcher/translation/swedish.ts +++ b/launcher/translation/swedish.ts @@ -138,7 +138,7 @@ Other - Övrigt + Annan @@ -442,17 +442,17 @@ Gog files - + GOG-filer All files (*.*) - + Alla filer (*.*) Select files (configs, mods, maps, campaigns, gog files) to install... - + Välj filer (konfigurationsfiler, moddar, kartor, kampanjer och GOG-filer) som ska installeras... @@ -499,7 +499,7 @@ Installation framgångsrikt nedladdad? Installing chronicles - + Installera Chronicles @@ -704,7 +704,7 @@ Installation framgångsrikt nedladdad? Mods Validation - + Validering av moddar @@ -729,12 +729,12 @@ Installation framgångsrikt nedladdad? Full - + Hela Use scalable fonts - + Använd skalbara teckensnitt @@ -744,27 +744,27 @@ Installation framgångsrikt nedladdad? Cursor Scaling - + Skalning av markör Scalable - + Skalbar Miscellaneous - + Övrigt Font Scaling (experimental) - + Skalning av teckensnitt (experimentell) Original - + Original @@ -774,7 +774,7 @@ Installation framgångsrikt nedladdad? Basic - + Grundläggande @@ -1059,35 +1059,35 @@ Exklusivt helskärmsläge - spelet kommer att täcka hela skärmen och använda File cannot opened - + Filen kan inte öppnas Invalid file selected - Ogiltig fil vald + Ogiltig fil vald You have to select an gog installer file! - + Du måste välja en GOG-installationsfil! You have to select an chronicle installer file! - + Du måste välja en Chronicles-installationsfil! Extracting error! - Extraktionsfel! + Extraheringsfel! Heroes Chronicles - + Heroes Chronicles @@ -1420,18 +1420,18 @@ Vänligen välj en mapp som innehåller data från Heroes III: Complete Edition Stream error while extracting files! error reason: - Strömningsfel vid extrahering av filer! + Strömningsfel vid extrahering av filer! Orsak till fel: Not a supported Inno Setup installer! - Inno Setup-installationsprogrammet stöds inte! + Inno Setup-installationsprogrammet stöds inte! VCMI was compiled without innoextract support, which is needed to extract exe files! - + VCMI kompilerades utan stöd för innoextract, vilket behövs för att extrahera exe-filer! From 3c4064e09d397f3e63678bd5d2120b354c43fd04 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:10:01 +0100 Subject: [PATCH 629/726] prism attack fix --- lib/battle/CBattleInfoCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index d4ec621c6..251c1f9b7 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1424,7 +1424,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( { //friendly stacks can also be damaged by Dragon Breath const auto * st = battleGetUnitByPos(nextHex, true); - if(st != nullptr) + if(st != nullptr && st != attacker) //but not unit itself (doublewide + prism attack) at.friendlyCreaturePositions.insert(nextHex); } } From 5e5a85418037dcbbdcab4a699f74fca218916e16 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:33:25 +0100 Subject: [PATCH 630/726] fix misaligned button in randommap --- client/lobby/RandomMapTab.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index bf25bedbc..9555a7045 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -469,6 +469,8 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): variables["totalPlayers"].Integer() = totalPlayers; pos.w = variables["windowSize"]["x"].Integer() + totalPlayers * variables["cellMargin"]["x"].Integer(); + auto widthExtend = std::max(pos.w, 220) - pos.w; // too small for buttons + pos.w += widthExtend; pos.h = variables["windowSize"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer(); variables["backgroundRect"]["x"].Integer() = 0; variables["backgroundRect"]["y"].Integer() = 0; @@ -553,7 +555,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): for(int teamId = 0; teamId < totalPlayers; ++teamId) { - variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer(); + variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer() + (widthExtend / 2); variables["point"]["y"].Integer() = variables["cellOffset"]["y"].Integer() + teamId * variables["cellMargin"]["y"].Integer(); auto button = buildWidget(variables["button"]); players.back()->addToggle(teamId, std::dynamic_pointer_cast(button)); From c8e3458dfb0debdd9b00a94faf82e283dcdb4da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 23 Nov 2024 10:07:34 +0100 Subject: [PATCH 631/726] Handle connections by unique id --- lib/rmg/CRmgTemplate.cpp | 26 ++++++++++++++++++++++---- lib/rmg/CRmgTemplate.h | 5 ++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 8e94e2c2e..f10f0c3f5 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -420,6 +420,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) } ZoneConnection::ZoneConnection(): + id(-1), zoneA(-1), zoneB(-1), guardStrength(0), @@ -429,6 +430,16 @@ ZoneConnection::ZoneConnection(): } +int ZoneConnection::getId() const +{ + return id; +} + +void ZoneConnection::setId(int id) +{ + this->id = id; +} + TRmgTemplateZoneId ZoneConnection::getZoneA() const { return zoneA; @@ -472,7 +483,7 @@ rmg::ERoadOption ZoneConnection::getRoadOption() const bool operator==(const ZoneConnection & l, const ZoneConnection & r) { - return l.zoneA == r.zoneA && l.zoneB == r.zoneB && l.guardStrength == r.guardStrength; + return l.id == r.id; } void ZoneConnection::serializeJson(JsonSerializeFormat & handler) @@ -591,7 +602,7 @@ const CRmgTemplate::Zones & CRmgTemplate::getZones() const const std::vector & CRmgTemplate::getConnectedZoneIds() const { - return connectedZoneIds; + return connections; } void CRmgTemplate::validate() const @@ -720,7 +731,14 @@ void CRmgTemplate::serializeJson(JsonSerializeFormat & handler) { auto connectionsData = handler.enterArray("connections"); - connectionsData.serializeStruct(connectedZoneIds); + connectionsData.serializeStruct(connections); + if(!handler.saving) + { + for(size_t i = 0; i < connections.size(); ++i) + { + connections[i].setId(i); + } + } } { @@ -842,7 +860,7 @@ void CRmgTemplate::afterLoad() } } - for(const auto & connection : connectedZoneIds) + for(const auto & connection : connections) { auto id1 = connection.getZoneA(); auto id2 = connection.getZoneB(); diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 297f62387..f420e0acb 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -96,6 +96,8 @@ public: ZoneConnection(); + int getId() const; + void setId(int id); TRmgTemplateZoneId getZoneA() const; TRmgTemplateZoneId getZoneB() const; TRmgTemplateZoneId getOtherZoneId(TRmgTemplateZoneId id) const; @@ -107,6 +109,7 @@ public: friend bool operator==(const ZoneConnection &, const ZoneConnection &); private: + int id; TRmgTemplateZoneId zoneA; TRmgTemplateZoneId zoneB; int guardStrength; @@ -293,7 +296,7 @@ private: CPlayerCountRange players; CPlayerCountRange humanPlayers; Zones zones; - std::vector connectedZoneIds; + std::vector connections; std::set allowedWaterContent; std::unique_ptr mapSettings; From e5b151991b69a15b48d6dc0feb76334a5462f667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 23 Nov 2024 10:43:14 +0100 Subject: [PATCH 632/726] Fix duplicated offroad connections --- lib/rmg/modificators/ConnectionsPlacer.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index 79c4ff438..37f158855 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -143,7 +143,7 @@ void ConnectionsPlacer::forcePortalConnection(const rmg::ZoneConnection & connec void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & connection) { bool success = false; - auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA()); + auto otherZoneId = connection.getOtherZoneId(zone.getId()); auto & otherZone = map.getZones().at(otherZoneId); bool createRoad = shouldGenerateRoad(connection); @@ -327,10 +327,9 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con assert(otherZone->getModificator()); otherZone->getModificator()->addRoadNode(roadNode); - - assert(otherZone->getModificator()); - otherZone->getModificator()->otherSideConnection(connection); } + assert(otherZone->getModificator()); + otherZone->getModificator()->otherSideConnection(connection); success = true; } From 6bdb10444f908aa89894c44afa6a0dc117900f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 23 Nov 2024 12:19:25 +0100 Subject: [PATCH 633/726] New type of zone - "sealed" --- config/schemas/template.json | 2 +- docs/modders/Random_Map_Template.md | 11 ++++++----- lib/rmg/CRmgTemplate.cpp | 3 ++- lib/rmg/CRmgTemplate.h | 3 ++- lib/rmg/Zone.cpp | 12 +++++++++++- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/config/schemas/template.json b/config/schemas/template.json index 26ec3e3fe..4ed948449 100644 --- a/config/schemas/template.json +++ b/config/schemas/template.json @@ -12,7 +12,7 @@ "properties" : { "type" : { "type" : "string", - "enum" : ["playerStart", "cpuStart", "treasure", "junction"] + "enum" : ["playerStart", "cpuStart", "treasure", "junction", "sealed"] }, "size" : { "type" : "number", "minimum" : 1 }, "owner" : {}, diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index 309d84e8e..1660a2daf 100644 --- a/docs/modders/Random_Map_Template.md +++ b/docs/modders/Random_Map_Template.md @@ -28,10 +28,11 @@ /// List of game settings that were overriden by this template. See config/gameConfig.json in vcmi install directory for possible values /// Settings defined here will always override any settings from vcmi or from mods - "settings" : { - "heroes" : { - "perPlayerOnMapCap" : 1 - } + "settings" : + { + "heroes" : + { + "perPlayerOnMapCap" : 1 } }, @@ -59,7 +60,7 @@ ``` javascript { // Type of this zone. Possible values are: - // "playerStart", "cpuStart", "treasure", "junction" + // "playerStart", "cpuStart", "treasure", "junction", "sealed" "type" : "playerStart", // relative size of zone diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 8e94e2c2e..b06e355cf 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -335,7 +335,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler) "cpuStart", "treasure", "junction", - "water" + "water", + "sealed" }; handler.serializeEnum("type", type, zoneTypes); diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 297f62387..b8c42491c 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -28,7 +28,8 @@ enum class ETemplateZoneType CPU_START, TREASURE, JUNCTION, - WATER + WATER, + SEALED }; namespace EWaterContent // Not enum class, because it's used in method RandomMapTab::setMapGenOptions diff --git a/lib/rmg/Zone.cpp b/lib/rmg/Zone.cpp index 273dc226e..37db277db 100644 --- a/lib/rmg/Zone.cpp +++ b/lib/rmg/Zone.cpp @@ -138,7 +138,7 @@ void Zone::initFreeTiles() }); dAreaPossible.assign(possibleTiles); - if(dAreaFree.empty()) + if(dAreaFree.empty() && getType() != ETemplateZoneType::SEALED) { // Fixme: This might fail fot water zone, which doesn't need to have a tile in its center of the mass dAreaPossible.erase(pos); @@ -348,6 +348,16 @@ void Zone::fractalize() tilesToIgnore.clear(); } } + else if (type == ETemplateZoneType::SEALED) + { + //Completely block all the tiles in the zone + auto tiles = areaPossible()->getTiles(); + for(const auto & t : tiles) + map.setOccupied(t, ETileType::BLOCKED); + possibleTiles.clear(); + dAreaFree.clear(); + return; + } else { // Handle special case - place Monoliths at the edge of a zone From 9e6cd9b94dcab9999115a227ff66be0f4151aed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Sat, 23 Nov 2024 12:37:30 +0100 Subject: [PATCH 634/726] Add "special" property for secondary skills --- config/schemas/skill.json | 4 ++++ docs/modders/Entities_Format/Secondary_Skill_Format.md | 9 +++++++++ lib/CSkillHandler.cpp | 8 ++++++-- lib/CSkillHandler.h | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/config/schemas/skill.json b/config/schemas/skill.json index 162f84de6..2332f0eaf 100644 --- a/config/schemas/skill.json +++ b/config/schemas/skill.json @@ -94,5 +94,9 @@ "onlyOnWaterMap" : { "type" : "boolean", "description" : "It true, skill won't be available on a map without water" + }, + "special" : { + "type" : "boolean", + "description" : "If true, skill is not available on maps at random" } } diff --git a/docs/modders/Entities_Format/Secondary_Skill_Format.md b/docs/modders/Entities_Format/Secondary_Skill_Format.md index 367ca775f..f157359d4 100644 --- a/docs/modders/Entities_Format/Secondary_Skill_Format.md +++ b/docs/modders/Entities_Format/Secondary_Skill_Format.md @@ -2,6 +2,15 @@ ## Main format +```jsonc +{ + // Skill be only be available on maps with water + "onlyOnWaterMap" : false, + // Skill is not available on maps at random + "special" : true +} +``` + ```jsonc { "skillName": diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index 8207ca558..e5436ba08 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -32,7 +32,9 @@ CSkill::CSkill(const SecondarySkill & id, std::string identifier, bool obligator id(id), identifier(std::move(identifier)), obligatoryMajor(obligatoryMajor), - obligatoryMinor(obligatoryMinor) + obligatoryMinor(obligatoryMinor), + special(false), + onlyOnWaterMap(false) { gainChance[0] = gainChance[1] = 0; //affects CHeroClassHandler::afterLoadFinalization() levels.resize(NSecondarySkill::levels.size() - 1); @@ -216,6 +218,7 @@ std::shared_ptr CSkillHandler::loadFromJson(const std::string & scope, c skill->modScope = scope; skill->onlyOnWaterMap = json["onlyOnWaterMap"].Bool(); + skill->special = json["special"].Bool(); VLC->generaltexth->registerString(scope, skill->getNameTextID(), json["name"]); switch(json["gainChance"].getType()) @@ -275,7 +278,8 @@ std::set CSkillHandler::getDefaultAllowed() const std::set result; for (auto const & skill : objects) - result.insert(skill->getId()); + if (!skill->special) + result.insert(skill->getId()); return result; } diff --git a/lib/CSkillHandler.h b/lib/CSkillHandler.h index e54797268..ae89435eb 100644 --- a/lib/CSkillHandler.h +++ b/lib/CSkillHandler.h @@ -75,6 +75,7 @@ public: void serializeJson(JsonSerializeFormat & handler); bool onlyOnWaterMap; + bool special; friend class CSkillHandler; friend DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill & skill); From 6366175a3c0e383659588d1730ba044e3d65615a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 23 Nov 2024 19:19:07 +0100 Subject: [PATCH 635/726] fix window --- client/adventureMap/CList.cpp | 2 +- client/windows/CCastleInterface.cpp | 2 +- client/windows/InfoWindows.cpp | 26 ++++++++++++-------------- client/windows/InfoWindows.h | 1 - 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/client/adventureMap/CList.cpp b/client/adventureMap/CList.cpp index 121853931..d25a612a3 100644 --- a/client/adventureMap/CList.cpp +++ b/client/adventureMap/CList.cpp @@ -450,7 +450,7 @@ void CTownList::CTownItem::open() void CTownList::CTownItem::showTooltip() { - CRClickPopup::createAndPush(town, GH.getCursorPosition()); + CRClickPopup::createAndPush(town, pos.center()); } void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const Point & finalPosition) diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index eba40eedc..3d917712e 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -456,7 +456,7 @@ void CHeroGSlot::showPopupWindow(const Point & cursorPosition) { if(hero) { - GH.windows().createAndPushWindow(Point(pos.x + 175, pos.y + 100), hero); + GH.windows().createAndPushWindow(pos.center(), hero); } } diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index 2719c08d5..494192e4c 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -266,16 +266,6 @@ void CRClickPopupInt::mouseDraggedPopup(const Point & cursorPosition, const Poin close(); } -Point CInfoBoxPopup::toScreen(Point p) -{ - auto bounds = adventureInt->terrainAreaPixels(); - - vstd::abetween(p.x, bounds.top() + 100, bounds.bottom() - 100); - vstd::abetween(p.y, bounds.left() + 100, bounds.right() - 100); - - return p; -} - void CInfoBoxPopup::mouseDraggedPopup(const Point & cursorPosition, const Point & lastUpdateDistance) { if(!settings["adventure"]["rightButtonDrag"].Bool()) @@ -289,7 +279,7 @@ void CInfoBoxPopup::mouseDraggedPopup(const Point & cursorPosition, const Point CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town) - : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position)) + : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), position) { InfoAboutTown iah; LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentArmy()); //todo: should this be nearest hero? @@ -298,10 +288,12 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town) tooltip = std::make_shared(Point(9, 10), iah); addUsedEvents(DRAG_POPUP); + + fitToScreen(10); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero) - : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("HEROQVBK"), toScreen(position)) + : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("HEROQVBK"), position) { InfoAboutHero iah; LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentArmy()); //todo: should this be nearest hero? @@ -310,10 +302,12 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero) tooltip = std::make_shared(Point(9, 10), iah); addUsedEvents(DRAG_POPUP); + + fitToScreen(10); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr) - : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position)) + : CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), position) { InfoAboutTown iah; LOCPLINT->cb->getTownInfo(garr, iah); @@ -322,15 +316,19 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr) tooltip = std::make_shared(Point(9, 10), iah); addUsedEvents(DRAG_POPUP); + + fitToScreen(10); } CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature) - : CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), toScreen(position)) + : CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), position) { OBJECT_CONSTRUCTION; tooltip = std::make_shared(Point(9, 10), creature); addUsedEvents(DRAG_POPUP); + + fitToScreen(10); } std::shared_ptr diff --git a/client/windows/InfoWindows.h b/client/windows/InfoWindows.h index 81b661339..468088dd2 100644 --- a/client/windows/InfoWindows.h +++ b/client/windows/InfoWindows.h @@ -90,7 +90,6 @@ public: class CInfoBoxPopup : public CWindowObject { std::shared_ptr tooltip; - Point toScreen(Point pos); Point dragDistance; From 005b97194f866adcdaa5269cc0393945283b9c40 Mon Sep 17 00:00:00 2001 From: kdmcser Date: Sun, 24 Nov 2024 14:06:39 +0800 Subject: [PATCH 636/726] fix joystick cursor position in xbrz --- client/eventsSDL/InputSourceGameController.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/eventsSDL/InputSourceGameController.cpp b/client/eventsSDL/InputSourceGameController.cpp index 94e3ac450..9716bdf4a 100644 --- a/client/eventsSDL/InputSourceGameController.cpp +++ b/client/eventsSDL/InputSourceGameController.cpp @@ -18,6 +18,7 @@ #include "../gui/CursorHandler.h" #include "../gui/EventDispatcher.h" #include "../gui/ShortcutHandler.h" +#include "../render/IScreenHandler.h" #include "../../lib/CConfigHandler.h" @@ -198,9 +199,10 @@ void InputSourceGameController::tryToConvertCursor() assert(CCS->curh); if(CCS->curh->getShowType() == Cursor::ShowType::HARDWARE) { + int ScaleFactor = GH.screenHandler().getScalingFactor(); const Point & cursorPosition = GH.getCursorPosition(); CCS->curh->changeCursor(Cursor::ShowType::SOFTWARE); - CCS->curh->cursorMove(cursorPosition.x, cursorPosition.y); + CCS->curh->cursorMove(cursorPosition.x * ScaleFactor, cursorPosition.y * ScaleFactor); GH.input().setCursorPosition(cursorPosition); } } @@ -225,12 +227,13 @@ void InputSourceGameController::doCursorMove(int deltaX, int deltaY) return; const Point & screenSize = GH.screenDimensions(); const Point & cursorPosition = GH.getCursorPosition(); + int ScaleFactor = GH.screenHandler().getScalingFactor(); int newX = std::min(std::max(cursorPosition.x + deltaX, 0), screenSize.x); int newY = std::min(std::max(cursorPosition.y + deltaY, 0), screenSize.y); Point targetPosition{newX, newY}; GH.input().setCursorPosition(targetPosition); if(CCS && CCS->curh) - CCS->curh->cursorMove(GH.getCursorPosition().x, GH.getCursorPosition().y); + CCS->curh->cursorMove(GH.getCursorPosition().x * ScaleFactor, GH.getCursorPosition().y * ScaleFactor); } int InputSourceGameController::getMoveDis(float planDis) From b29d7e8cfdbf16076b039495349221824556dfc4 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sun, 24 Nov 2024 17:57:49 +0100 Subject: [PATCH 637/726] third upgrade for 8th dwelling --- lib/constants/EntityIdentifiers.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/constants/EntityIdentifiers.h b/lib/constants/EntityIdentifiers.h index c4a1f6272..3bc547247 100644 --- a/lib/constants/EntityIdentifiers.h +++ b/lib/constants/EntityIdentifiers.h @@ -298,7 +298,7 @@ public: HORDE_2_UPGR, GRAIL, EXTRA_TOWN_HALL, EXTRA_CITY_HALL, EXTRA_CAPITOL, DWELL_FIRST=30, DWELL_LVL_2, DWELL_LVL_3, DWELL_LVL_4, DWELL_LVL_5, DWELL_LVL_6, DWELL_LAST=36, DWELL_UP_FIRST=37, DWELL_LVL_2_UP, DWELL_LVL_3_UP, DWELL_LVL_4_UP, DWELL_LVL_5_UP, - DWELL_LVL_6_UP, DWELL_UP_LAST=43, DWELL_LVL_8=150, DWELL_LVL_8_UP=151, + DWELL_LVL_6_UP, DWELL_UP_LAST=43, DWELL_LVL_8=150, DWELL_LVL_8_UP=151, //150-154 reserved for 8. creature with potential upgrades DWELL_LVL_1 = DWELL_FIRST, DWELL_LVL_7 = DWELL_LAST, @@ -337,7 +337,10 @@ public: if (it != tmp.end()) return std::distance(tmp.begin(), it); } - return (dwelling - DWELL_FIRST) % (GameConstants::CREATURES_PER_TOWN - 1); + if(dwelling >= BuildingIDBase::DWELL_LVL_8 && dwelling < BuildingIDBase::DWELL_LVL_8 + 5) + return 7; + else + return (dwelling - DWELL_FIRST) % (GameConstants::CREATURES_PER_TOWN - 1); } static int getUpgradedFromDwelling(BuildingIDBase dwelling) @@ -349,15 +352,18 @@ public: if (it != tmp.end()) return i; } - return (dwelling - DWELL_FIRST) / (GameConstants::CREATURES_PER_TOWN - 1); + if(dwelling >= BuildingIDBase::DWELL_LVL_8 && dwelling < BuildingIDBase::DWELL_LVL_8 + 5) + return dwelling - BuildingIDBase::DWELL_LVL_8; + else + 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 + if(dwelling >= BuildingIDBase::DWELL_LVL_8 && dwelling < BuildingIDBase::DWELL_LVL_8 + 5) dwelling.advance(1); + else + dwelling.advance(GameConstants::CREATURES_PER_TOWN - 1); } bool IsSpecialOrGrail() const From 2b692c2606ecdd2dfdbda60d1827f33dbe512c89 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 24 Nov 2024 19:26:06 +0000 Subject: [PATCH 638/726] Fix h3 bug: icons of View Earth and View Air are swapped --- Mods/vcmi/Content/Sprites/spells.json | 7 +++++++ client/render/ImageLocator.cpp | 10 ++++++---- client/renderSDL/RenderHandler.cpp | 8 +++++++- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 Mods/vcmi/Content/Sprites/spells.json diff --git a/Mods/vcmi/Content/Sprites/spells.json b/Mods/vcmi/Content/Sprites/spells.json new file mode 100644 index 000000000..4d6bddeb2 --- /dev/null +++ b/Mods/vcmi/Content/Sprites/spells.json @@ -0,0 +1,7 @@ +{ + "images" : + [ + { "frame" : 3, "defFile" : "spells.def", "defFrame" : 5}, + { "frame" : 5, "defFile" : "spells.def", "defFrame" : 3} + ] +} diff --git a/client/render/ImageLocator.cpp b/client/render/ImageLocator.cpp index aefbc6d30..515e767a0 100644 --- a/client/render/ImageLocator.cpp +++ b/client/render/ImageLocator.cpp @@ -15,15 +15,17 @@ #include "../../lib/json/JsonNode.h" - ImageLocator::ImageLocator(const JsonNode & config) - : image(ImagePath::fromJson(config["file"])) - , defFile(AnimationPath::fromJson(config["defFile"])) - , defFrame(config["defFrame"].Integer()) + : defFrame(config["defFrame"].Integer()) , defGroup(config["defGroup"].Integer()) , verticalFlip(config["verticalFlip"].Bool()) , horizontalFlip(config["horizontalFlip"].Bool()) { + if(!config["file"].isNull()) + image = ImagePath::fromJson(config["file"]); + + if(!config["defFile"].isNull()) + defFile = AnimationPath::fromJson(config["defFile"]); } ImageLocator::ImageLocator(const ImagePath & path) diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index ccfd5bec8..24aa78ebd 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -142,7 +142,13 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c JsonNode toAdd = node; JsonUtils::inherit(toAdd, base); - toAdd["file"].String() = basepath + node["file"].String(); + + if (toAdd.Struct().count("file")) + toAdd["file"].String() = basepath + node["file"].String(); + + if (toAdd.Struct().count("defFile")) + toAdd["defFile"].String() = basepath + node["defFile"].String(); + source[group][frame] = ImageLocator(toAdd); } } From 1b9c6b176361737db42c3383fb15f753d3c7cda9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 24 Nov 2024 19:47:52 +0000 Subject: [PATCH 639/726] Add fix for similarly swapped portraits of Wights and Wraiths --- Mods/vcmi/Content/Sprites/cprsmall.json | 8 ++++++++ Mods/vcmi/Content/Sprites/spells.json | 1 + 2 files changed, 9 insertions(+) create mode 100644 Mods/vcmi/Content/Sprites/cprsmall.json diff --git a/Mods/vcmi/Content/Sprites/cprsmall.json b/Mods/vcmi/Content/Sprites/cprsmall.json new file mode 100644 index 000000000..3eec15da7 --- /dev/null +++ b/Mods/vcmi/Content/Sprites/cprsmall.json @@ -0,0 +1,8 @@ +{ + "images" : + [ + // Fix for swapped in H3 icons of Wight and Wraith + { "frame" : 62, "defFile" : "cprsmall.def", "defFrame" : 63}, + { "frame" : 63, "defFile" : "cprsmall.def", "defFrame" : 62} + ] +} diff --git a/Mods/vcmi/Content/Sprites/spells.json b/Mods/vcmi/Content/Sprites/spells.json index 4d6bddeb2..4c13e5c79 100644 --- a/Mods/vcmi/Content/Sprites/spells.json +++ b/Mods/vcmi/Content/Sprites/spells.json @@ -1,6 +1,7 @@ { "images" : [ + // Fix for swapped in H3 icons of View Earth and View Air { "frame" : 3, "defFile" : "spells.def", "defFrame" : 5}, { "frame" : 5, "defFile" : "spells.def", "defFrame" : 3} ] From 538f8443988040d8abc976da06b4c8caef788d81 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Sun, 24 Nov 2024 22:47:51 +0300 Subject: [PATCH 640/726] [docs] add referenced image to the repo --- docs/players/Installation_iOS.md | 5 ++--- docs/players/images/itunes.jpg | Bin 0 -> 16331 bytes 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 docs/players/images/itunes.jpg diff --git a/docs/players/Installation_iOS.md b/docs/players/Installation_iOS.md index bf71d7225..4dfcd7478 100644 --- a/docs/players/Installation_iOS.md +++ b/docs/players/Installation_iOS.md @@ -8,9 +8,9 @@ The easiest and recommended way to install on a non-jailbroken device is to inst i) Use [AltStore Windows](https://faq.altstore.io/altstore-classic/how-to-install-altstore-windows) or [AltStore macOS](https://faq.altstore.io/altstore-classic/how-to-install-altstore-macos) instructions to install the store depending on the operating system you are using. -If you're having trouble enabling "sync with this iOS device over Wi-Fi" press on the rectangular shape below "Account". Example shown below. +If you're having trouble enabling "sync with this iOS device over Wi-Fi" press on the rectangular shape below "Account". Windows example from iTunes shown below: -![image](https://github.com/user-attachments/assets/74fe2ca2-b55c-4b05-b083-89df604248f3) +![iTunes](images/itunes.jpg) ii) Download the VCMI-iOS.ipa file on your iOS device directly from the [latest releases](https://github.com/vcmi/vcmi/releases/latest). @@ -50,7 +50,6 @@ To run on a non-jailbroken device you need to sign the IPA file, you have the fo The easiest way to install the ipa on your device is to do one of the following: - In AltStore go to >My Apps > press + in the top left corner. Select VCMI-iOS.ipa to install or - - Drag and drop the .ipa file into your iOS device in iTunes Alternatively, to install the signed ipa on your device, you can use Xcode or Apple Configurator (available on the Mac App Store for free). The latter also allows installing ipa from the command line, here's an example that assumes you have only 1 device connected to your Mac and the signed ipa is on your desktop: diff --git a/docs/players/images/itunes.jpg b/docs/players/images/itunes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1301fbf6d213fd4818929b8351b590c5d92f7eca GIT binary patch literal 16331 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 Date: Sun, 24 Nov 2024 20:05:34 +0000 Subject: [PATCH 641/726] Fix Orrin as starting hero on some maps Fixes a bug that led to Orrin being replaced with a different randomly selected hero on maps where starting hero is not generated in town, but pre-placed on map --- lib/gameState/CGameState.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 91a58b796..7df3c63e9 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -62,7 +62,7 @@ boost::shared_mutex CGameState::mutex; HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner) { const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner); - if(ps.hero >= HeroTypeID(0) && !isUsedHero(HeroTypeID(ps.hero))) //we haven't used selected hero + if(ps.hero.isValid() && !isUsedHero(HeroTypeID(ps.hero))) //we haven't used selected hero { return HeroTypeID(ps.hero); } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 7380ef70b..b295d0b16 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -661,8 +661,10 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand) if (ID == Obj::RANDOM_HERO) { + auto selectedHero = cb->gameState()->pickNextHeroType(getOwner()); + ID = Obj::HERO; - subID = cb->gameState()->pickNextHeroType(getOwner()); + subID = selectedHero; randomizeArmy(getHeroClass()->faction); } From f842b5ad916a8e936c5cbcd27a28d0c78fa1fb45 Mon Sep 17 00:00:00 2001 From: mikeiit Date: Sun, 24 Nov 2024 17:07:07 -0800 Subject: [PATCH 642/726] max player count update --- lib/rmg/CMapGenOptions.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 2a9169739..b5607b753 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -124,17 +124,7 @@ si8 CMapGenOptions::getMinPlayersCount(bool withTemplateLimit) const si8 CMapGenOptions::getMaxPlayersCount(bool withTemplateLimit) const { // Max number of players possible with current settings - auto totalPlayers = 0; - si8 humans = getHumanOrCpuPlayerCount(); - si8 cpus = getCompOnlyPlayerCount(); - if (humans == RANDOM_SIZE || cpus == RANDOM_SIZE) - { - totalPlayers = PlayerColor::PLAYER_LIMIT_I; - } - else - { - totalPlayers = humans + cpus; - } + auto totalPlayers = PlayerColor::PLAYER_LIMIT_I; if (withTemplateLimit && mapTemplate) { From 9cef823330645aeab206016f407976bfbf9e6206 Mon Sep 17 00:00:00 2001 From: mikeiit Date: Sun, 24 Nov 2024 18:55:35 -0800 Subject: [PATCH 643/726] Revert "max player count update" This reverts commit f842b5ad916a8e936c5cbcd27a28d0c78fa1fb45. Trying another approach --- lib/rmg/CMapGenOptions.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index b5607b753..2a9169739 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -124,7 +124,17 @@ si8 CMapGenOptions::getMinPlayersCount(bool withTemplateLimit) const si8 CMapGenOptions::getMaxPlayersCount(bool withTemplateLimit) const { // Max number of players possible with current settings - auto totalPlayers = PlayerColor::PLAYER_LIMIT_I; + auto totalPlayers = 0; + si8 humans = getHumanOrCpuPlayerCount(); + si8 cpus = getCompOnlyPlayerCount(); + if (humans == RANDOM_SIZE || cpus == RANDOM_SIZE) + { + totalPlayers = PlayerColor::PLAYER_LIMIT_I; + } + else + { + totalPlayers = humans + cpus; + } if (withTemplateLimit && mapTemplate) { From a4db80671ace70ffcc4c0f3ecf71a977b2b15b05 Mon Sep 17 00:00:00 2001 From: mikeiit Date: Sun, 24 Nov 2024 19:50:47 -0800 Subject: [PATCH 644/726] call getPlayerLimit --- client/lobby/RandomMapTab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index bf25bedbc..a0906e4af 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -263,7 +263,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) humanCountAllowed = tmpl->getHumanPlayers().getNumbers(); // Unused now? } - si8 playerLimit = opts->getMaxPlayersCount(); + si8 playerLimit = opts->getPlayerLimit(); si8 humanOrCpuPlayerCount = opts->getHumanOrCpuPlayerCount(); si8 compOnlyPlayersCount = opts->getCompOnlyPlayerCount(); From b3b525d282ee9646963db577a4e09661c127e18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zieli=C5=84ski?= Date: Mon, 25 Nov 2024 17:34:13 +0100 Subject: [PATCH 645/726] Better docs --- docs/modders/Random_Map_Template.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/modders/Random_Map_Template.md b/docs/modders/Random_Map_Template.md index 1660a2daf..90386f12b 100644 --- a/docs/modders/Random_Map_Template.md +++ b/docs/modders/Random_Map_Template.md @@ -60,7 +60,11 @@ ``` javascript { // Type of this zone. Possible values are: - // "playerStart", "cpuStart", "treasure", "junction", "sealed" + // "playerStart" - Starting zone for a "human or CPU" players + // "cpuStart" - Starting zone for "CPU only" players + // "treasure" - Generic neutral zone + // "junction" - Neutral zone with narrow passages only. The rest of area is filled with obstacles. + // "sealed" - Decorative impassable zone completely filled with obstacles "type" : "playerStart", // relative size of zone From 993a4dc73d6e6c48dabc1fe04d5edaa8624c8e1e Mon Sep 17 00:00:00 2001 From: kdmcser Date: Tue, 26 Nov 2024 01:11:09 +0800 Subject: [PATCH 646/726] crash fix: dismiss hero when pick artifact --- client/widgets/CArtifactsOfHeroBase.cpp | 2 ++ client/widgets/CArtifactsOfHeroMain.cpp | 3 ++- client/windows/CHeroWindow.cpp | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index 7113f114b..36c0690f4 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -140,6 +140,8 @@ void CArtifactsOfHeroBase::gestureArtPlace(CComponentHolder & artPlace, const Po void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero) { curHero = hero; + if (!hero) + return; for(auto slot : artWorn) { diff --git a/client/widgets/CArtifactsOfHeroMain.cpp b/client/widgets/CArtifactsOfHeroMain.cpp index 9ca0ecc23..34458351e 100644 --- a/client/widgets/CArtifactsOfHeroMain.cpp +++ b/client/widgets/CArtifactsOfHeroMain.cpp @@ -28,7 +28,8 @@ CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position) CArtifactsOfHeroMain::~CArtifactsOfHeroMain() { - CArtifactsOfHeroBase::putBackPickedArtifact(); + if(curHero) + CArtifactsOfHeroBase::putBackPickedArtifact(); } void CArtifactsOfHeroMain::keyPressed(EShortcut key) diff --git a/client/windows/CHeroWindow.cpp b/client/windows/CHeroWindow.cpp index 1cd49584f..ad73c3960 100644 --- a/client/windows/CHeroWindow.cpp +++ b/client/windows/CHeroWindow.cpp @@ -312,6 +312,7 @@ void CHeroWindow::dismissCurrent() arts->putBackPickedArtifact(); close(); LOCPLINT->cb->dismissHero(curHero); + arts->setHero(nullptr); }, nullptr); } From 14a3c6ad14cb77ac8b5881f0217f8a272a5b5888 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 25 Nov 2024 17:31:20 +0000 Subject: [PATCH 647/726] Fix freeze on closing server before starting the game Fixes possible freeze that seems to be caused by client shutting down socket before sending its final LobbyClientDisconnected packet, leading to server not processing disconnection of host correctly, which in turn causes client to wait server shutdown forever. Looks like regression from #4722 - Fixes #4912 and its duplicates --- server/CVCMIServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 54fa8a25f..53b6879d8 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -300,7 +300,7 @@ void CVCMIServer::onDisconnected(const std::shared_ptr & con std::shared_ptr c = findConnection(connection); // player may have already disconnected via clientDisconnected call - if (c && gh && getState() == EServerState::GAMEPLAY) + if (c) { LobbyClientDisconnected lcd; lcd.c = c; From c57120f0dd9365d80a523cd89cde3f37a6ddc495 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 16 Oct 2024 12:52:08 +0000 Subject: [PATCH 648/726] Initial support for mod presets system --- launcher/CMakeLists.txt | 2 + launcher/modManager/cmodlist.cpp | 51 +++------ launcher/modManager/cmodlist.h | 10 +- launcher/modManager/cmodlistmodel_moc.cpp | 2 + launcher/modManager/cmodmanager.cpp | 35 +----- launcher/modManager/cmodmanager.h | 5 +- launcher/modManager/modsettingsstorage.cpp | 123 +++++++++++++++++++++ launcher/modManager/modsettingsstorage.h | 37 +++++++ lib/modding/CModHandler.cpp | 33 ++++-- lib/modding/CModHandler.h | 6 +- lib/modding/CModInfo.cpp | 6 +- lib/modding/CModInfo.h | 2 +- 12 files changed, 221 insertions(+), 91 deletions(-) create mode 100644 launcher/modManager/modsettingsstorage.cpp create mode 100644 launcher/modManager/modsettingsstorage.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a56893838..6e407d2f9 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -13,6 +13,7 @@ set(launcher_SRCS modManager/cmodmanager.cpp modManager/imageviewer_moc.cpp modManager/chroniclesextractor.cpp + modManager/modsettingsstorage.cpp settingsView/csettingsview_moc.cpp firstLaunch/firstlaunch_moc.cpp main.cpp @@ -43,6 +44,7 @@ set(launcher_HEADERS modManager/cmodmanager.h modManager/imageviewer_moc.h modManager/chroniclesextractor.h + modManager/modsettingsstorage.h settingsView/csettingsview_moc.h firstLaunch/firstlaunch_moc.h mainwindow_moc.h diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index fad621601..865960a8d 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -33,8 +33,8 @@ QString CModEntry::sizeToString(double size) return QCoreApplication::translate("File size", sizes[index]).arg(QString::number(size, 'f', 1)); } -CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, QVariantMap modSettings, QString modname) - : repository(repository), localData(localData), modSettings(modSettings), modname(modname) +CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname) + : repository(repository), localData(localData), modActive(modActive), modname(modname) { } @@ -46,7 +46,7 @@ bool CModEntry::isEnabled() const if (!isVisible()) return false; - return modSettings["active"].toBool(); + return modActive; } bool CModEntry::isDisabled() const @@ -250,9 +250,9 @@ void CModList::setLocalModList(QVariantMap data) cachedMods.clear(); } -void CModList::setModSettings(QVariant data) +void CModList::setModSettings(std::shared_ptr data) { - modSettings = data.toMap(); + modSettings = data; cachedMods.clear(); } @@ -294,46 +294,23 @@ CModEntry CModList::getModUncached(QString modname) const { QVariantMap repo; QVariantMap local = localModList[modname].toMap(); - QVariantMap settings; QString path = modname; path = "/" + path.replace(".", "/mods/"); - QVariant conf = getValue(modSettings, path); - if(conf.isNull()) + bool modActive = modSettings->isModActive(modname); + + if(modActive) { - settings["active"] = !local.value("keepDisabled").toBool(); - } - else - { - if(!conf.toMap().isEmpty()) - { - settings = conf.toMap(); - if(settings.value("active").isNull()) - settings["active"] = !local.value("keepDisabled").toBool(); - } - else - settings.insert("active", conf); - } - - if(settings["active"].toBool()) - { - QString rootPath = path.section('/', 0, 1); - if(path != rootPath) - { - conf = getValue(modSettings, rootPath); - const auto confMap = conf.toMap(); - if(!conf.isNull() && !confMap["active"].isNull() && !confMap["active"].toBool()) - { - settings = confMap; - } - } + QString rootModName = modname.section('.', 0, 1); + if (!modSettings->isModActive(rootModName)) + modActive = false; // parent mod is inactive -> submod is also inactive } - if(settings.value("active").toBool()) + if(modActive) { if(!::isCompatible(local.value("compatibility").toMap())) - settings["active"] = false; + modActive = false; // mod not compatible with our version of vcmi -> inactive } for(auto entry : repositories) @@ -366,7 +343,7 @@ CModEntry CModList::getModUncached(QString modname) const } } - return CModEntry(repo, local, settings, modname); + return CModEntry(repo, local, modActive, modname); } bool CModList::hasMod(QString modname) const diff --git a/launcher/modManager/cmodlist.h b/launcher/modManager/cmodlist.h index a9c595a08..ccfe253b0 100644 --- a/launcher/modManager/cmodlist.h +++ b/launcher/modManager/cmodlist.h @@ -13,6 +13,8 @@ #include #include +#include "modsettingsstorage.h" + namespace ModStatus { enum EModStatus @@ -30,13 +32,13 @@ class CModEntry // repository contains newest version only (if multiple are available) QVariantMap repository; QVariantMap localData; - QVariantMap modSettings; + bool modActive; QString modname; QVariant getValueImpl(QString value, bool localized) const; public: - CModEntry(QVariantMap repository, QVariantMap localData, QVariantMap modSettings, QString modname); + CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname); // installed and enabled bool isEnabled() const; @@ -80,7 +82,7 @@ class CModList { QVector repositories; QVariantMap localModList; - QVariantMap modSettings; + std::shared_ptr modSettings; mutable QMap cachedMods; @@ -92,7 +94,7 @@ public: virtual void reloadRepositories(); virtual void addRepository(QVariantMap data); virtual void setLocalModList(QVariantMap data); - virtual void setModSettings(QVariant data); + virtual void setModSettings(std::shared_ptr data); virtual void modChanged(QString modID); // returns mod by name. Note: mod MUST exist diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index 258ffbd83..bc24e10d0 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -192,6 +192,8 @@ void CModListModel::resetRepositories() void CModListModel::modChanged(QString modID) { + CModList::modChanged(modID); + int index = modNameToID.indexOf(modID); QModelIndex parent = this->parent(createIndex(0, 0, index)); int row = modIndex[modIndexToName(parent)].indexOf(modID); diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 4288f4a15..c8ae3dfc8 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -69,15 +69,10 @@ CModManager::CModManager(CModList * modList) loadModSettings(); } -QString CModManager::settingsPath() -{ - return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json"); -} - void CModManager::loadModSettings() { - modSettings = JsonUtils::JsonFromFile(settingsPath()).toMap(); - modList->setModSettings(modSettings["activeMods"]); + modSettings = std::make_shared(); + modList->setModSettings(modSettings); } void CModManager::resetRepositories() @@ -248,35 +243,11 @@ bool CModManager::canDisableMod(QString modname) return true; } -static QVariant writeValue(QString path, QVariantMap input, QVariant value) -{ - if(path.size() > 1) - { - - QString entryName = path.section('/', 0, 1); - QString remainder = "/" + path.section('/', 2, -1); - - entryName.remove(0, 1); - input.insert(entryName, writeValue(remainder, input.value(entryName).toMap(), value)); - return input; - } - else - { - return value; - } -} - bool CModManager::doEnableMod(QString mod, bool on) { - QString path = mod; - path = "/activeMods/" + path.replace(".", "/mods/") + "/active"; - - modSettings = writeValue(path, modSettings, QVariant(on)).toMap(); - modList->setModSettings(modSettings["activeMods"]); + modSettings->setModActive(mod, on); modList->modChanged(mod); - JsonUtils::JsonToFile(settingsPath(), modSettings); - return true; } diff --git a/launcher/modManager/cmodmanager.h b/launcher/modManager/cmodmanager.h index 987d1a580..a9b4f5b3e 100644 --- a/launcher/modManager/cmodmanager.h +++ b/launcher/modManager/cmodmanager.h @@ -10,6 +10,7 @@ #pragma once #include "cmodlist.h" +#include "modsettingsstorage.h" class CModManager : public QObject { @@ -17,14 +18,12 @@ class CModManager : public QObject CModList * modList; - QString settingsPath(); - // check-free version of public method bool doEnableMod(QString mod, bool on); bool doInstallMod(QString mod, QString archivePath); bool doUninstallMod(QString mod); - QVariantMap modSettings; + std::shared_ptr modSettings; QVariantMap localMods; QStringList recentErrors; diff --git a/launcher/modManager/modsettingsstorage.cpp b/launcher/modManager/modsettingsstorage.cpp new file mode 100644 index 000000000..6d1e6f9f3 --- /dev/null +++ b/launcher/modManager/modsettingsstorage.cpp @@ -0,0 +1,123 @@ +/* + * modsettignsstorage.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "modsettingsstorage.h" + +#include "../../lib/VCMIDirs.h" +#include "../vcmiqt/jsonutils.h" + +static QVariant writeValue(QString path, QVariantMap input, QVariant value) +{ + if(path.size() > 1) + { + QString entryName = path.section('/', 0, 1); + QString remainder = "/" + path.section('/', 2, -1); + entryName.remove(0, 1); + input.insert(entryName, writeValue(remainder, input.value(entryName).toMap(), value)); + return input; + } + else + { + return value; + } +} + +ModSettingsStorage::ModSettingsStorage() +{ + config = JsonUtils::JsonFromFile(settingsPath()).toMap(); + + // TODO: import from 1.5 format +} + +QString ModSettingsStorage::settingsPath() const +{ + return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json"); +} + +void ModSettingsStorage::setRootModActive(const QString & modName, bool on) +{ + QString presetName = getActivePreset(); + QStringList activeMods = getActiveMods(); + + assert(modName.count('.') == 0); // this method should never be used for submods + + if (on) + activeMods.push_back(modName); + else + activeMods.removeAll(modName); + + config = writeValue("/presets/" + presetName + "/mods", config, activeMods).toMap(); + + JsonUtils::JsonToFile(settingsPath(), config); +} + +void ModSettingsStorage::setModSettingActive(const QString & modName, bool on) +{ + QString presetName = getActivePreset(); + QString rootModName = modName.section('.', 0, 0); + QString settingName = modName.section('.', 1); + QVariantMap modSettings = getModSettings(rootModName); + + assert(modName.count('.') != 0); // this method should only be used for submods + + modSettings.insert(settingName, QVariant(on)); + + config = writeValue("/presets/" + presetName + "/settings/" + rootModName, config, modSettings).toMap(); + + JsonUtils::JsonToFile(settingsPath(), config); +} + +void ModSettingsStorage::setModActive(const QString & modName, bool on) +{ + if (modName.contains('.')) + setModSettingActive(modName, on); + else + setRootModActive(modName, on); +} + +void ModSettingsStorage::setActivePreset(const QString & presetName) +{ + config.insert("activePreset", QVariant(presetName)); +} + +QString ModSettingsStorage::getActivePreset() const +{ + return config["activePreset"].toString(); +} + +bool ModSettingsStorage::isModActive(const QString & modName) const +{ + if (modName.contains('.')) + { + QString rootModName = modName.section('.', 0, 0); + QString settingName = modName.section('.', 1); + + return getModSettings(rootModName)[settingName].toBool(); + } + else + return getActiveMods().contains(modName); +} + +QStringList ModSettingsStorage::getActiveMods() const +{ + return getActivePresetData()["mods"].toStringList(); +} + +QVariantMap ModSettingsStorage::getActivePresetData() const +{ + QString presetName = getActivePreset(); + return config["presets"].toMap()[presetName].toMap(); +} + +QVariantMap ModSettingsStorage::getModSettings(const QString & modName) const +{ + QString presetName = getActivePreset(); + return getActivePresetData()["settings"].toMap()[modName].toMap(); +} diff --git a/launcher/modManager/modsettingsstorage.h b/launcher/modManager/modsettingsstorage.h new file mode 100644 index 000000000..76b793d88 --- /dev/null +++ b/launcher/modManager/modsettingsstorage.h @@ -0,0 +1,37 @@ +/* + * modsettignsstorage.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include + +class ModSettingsStorage +{ + QVariantMap config; + + QString settingsPath() const; + + void setRootModActive(const QString & modName, bool on); + void setModSettingActive(const QString & modName, bool on); + + QVariantMap getActivePresetData() const; + QStringList getActiveMods() const; + QVariantMap getModSettings(const QString & modName) const; + +public: + ModSettingsStorage(); + + void setModActive(const QString & modName, bool on); + void setActivePreset(const QString & presetName); + + QString getActivePreset() const; + bool isModActive(const QString & modName) const; + //QStringList getPresetsList() const; +}; + diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index ab1f375ec..ed98a37bb 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -238,13 +238,13 @@ std::vector CModHandler::getModList(const std::string & path) const -void CModHandler::loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, bool enableMods) +void CModHandler::loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods) { for(const std::string & modName : getModList(path)) - loadOneMod(modName, parent, modSettings, enableMods); + loadOneMod(modName, parent, modSettings, modsToActivate, enableMods); } -void CModHandler::loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods) +void CModHandler::loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods) { boost::to_lower(modName); std::string modFullName = parent.empty() ? modName : parent + '.' + modName; @@ -257,7 +257,8 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName))) { - CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName))); + bool thisModActive = vstd::contains(modsToActivate, modFullName); + CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName)), thisModActive); if (!parent.empty()) // this is submod, add parent to dependencies mod.dependencies.insert(parent); @@ -265,7 +266,7 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co if (mod.isEnabled() && enableMods) activeMods.push_back(modFullName); - loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.isEnabled()); + loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], modsToActivate, enableMods && mod.isEnabled()); } } @@ -274,9 +275,27 @@ void CModHandler::loadMods() JsonNode modConfig; modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); - loadMods("", "", modConfig["activeMods"], true); + const JsonNode & modSettings = modConfig["activeMods"]; + const std::string & currentPresetName = modConfig["activePreset"].String(); + const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; + const JsonNode & modsToActivateJson = currentPreset["mods"]; + std::vector modsToActivate = modsToActivateJson.convertTo>(); - coreMod = std::make_unique(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json"))); + for(const auto & settings : currentPreset["settings"].Struct()) + { + if (!vstd::contains(modsToActivate, settings.first)) + continue; // settings for inactive mod + + for (const auto & submod : settings.second.Struct()) + { + if (submod.second.Bool()) + modsToActivate.push_back(settings.first + '.' + submod.first); + } + } + + loadMods("", "", modSettings, modsToActivate, true); + + coreMod = std::make_unique(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json")), true); } std::vector CModHandler::getAllMods() const diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index e50a70f9f..ccc4bc540 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -42,11 +42,11 @@ class DLL_LINKAGE CModHandler final : boost::noncopyable * @param modsToResolve list of valid mod IDs (checkDependencies returned true - TODO: Clarify it.) * @return a vector of the topologically sorted resolved mods: child nodes (dependent mods) have greater index than parents */ - std::vector validateAndSortDependencies(std::vector modsToResolve) const; + std::vector validateAndSortDependencies(std::vector modsToResolve) const; std::vector getModList(const std::string & path) const; - void loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, bool enableMods); - void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods); + void loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods); + void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods); void loadTranslation(const TModID & modName); CModVersion getModVersion(TModID modName) const; diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp index fb0778f6f..aa39b4e7d 100644 --- a/lib/modding/CModInfo.cpp +++ b/lib/modding/CModInfo.cpp @@ -40,12 +40,12 @@ CModInfo::CModInfo(): } -CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config): +CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive): identifier(identifier), dependencies(readModList(config["depends"])), softDependencies(readModList(config["softDepends"])), conflicts(readModList(config["conflicts"])), - explicitlyEnabled(false), + explicitlyEnabled(isActive), implicitlyEnabled(true), validation(PENDING), config(addMeta(config, identifier)) @@ -110,11 +110,9 @@ void CModInfo::loadLocalData(const JsonNode & data) { bool validated = false; implicitlyEnabled = true; - explicitlyEnabled = !config["keepDisabled"].Bool(); verificationInfo.checksum = 0; if (data.isStruct()) { - explicitlyEnabled = data["active"].Bool(); validated = data["validated"].Bool(); updateChecksum(strtol(data["checksum"].String().c_str(), nullptr, 16)); } diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h index d5c077167..fd0f8319e 100644 --- a/lib/modding/CModInfo.h +++ b/lib/modding/CModInfo.h @@ -55,7 +55,7 @@ public: JsonNode config; CModInfo(); - CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config); + CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive); JsonNode saveLocalData() const; void updateChecksum(ui32 newChecksum); From 915bd0bd35554523026e6d6095cba18bb6e171b6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 16 Oct 2024 18:42:32 +0000 Subject: [PATCH 649/726] Import mod preset from 1.5 data if no preset exists --- launcher/modManager/modsettingsstorage.cpp | 54 +++++++++++++++++++++- launcher/modManager/modsettingsstorage.h | 3 ++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/launcher/modManager/modsettingsstorage.cpp b/launcher/modManager/modsettingsstorage.cpp index 6d1e6f9f3..c5175eec0 100644 --- a/launcher/modManager/modsettingsstorage.cpp +++ b/launcher/modManager/modsettingsstorage.cpp @@ -29,11 +29,63 @@ static QVariant writeValue(QString path, QVariantMap input, QVariant value) } } +void ModSettingsStorage::createInitialPreset() +{ + // TODO: scan mods directory for all its content? + + QStringList modList; + QVariantMap preset; + QVariantMap presetList; + + modList.push_back("vcmi"); + preset.insert("mods", modList); + presetList.insert("default", preset); + config.insert("presets", presetList); +} + +void ModSettingsStorage::importInitialPreset() +{ + QStringList modList; + QMap modSettings; + + QVariantMap activeMods = config["activeMods"].toMap(); + for (QVariantMap::const_iterator modEntry = activeMods.begin(); modEntry != activeMods.end(); ++modEntry) + { + if (modEntry.value().toMap()["active"].toBool()) + modList.push_back(modEntry.key()); + + QVariantMap submods = modEntry.value().toMap()["mods"].toMap(); + for (QVariantMap::const_iterator submodEntry = submods.begin(); submodEntry != submods.end(); ++submodEntry) + modSettings[modEntry.key()].insert(submodEntry.key(), submodEntry.value().toMap()["active"]); + } + + QVariantMap importedPreset; + QVariantMap modSettingsVariant; + QVariantMap presetList; + + for (QMap::const_iterator modEntry = modSettings.begin(); modEntry != modSettings.end(); ++modEntry) + modSettingsVariant.insert(modEntry.key(), modEntry.value()); + + importedPreset.insert("mods", modList); + importedPreset.insert("settings", modSettingsVariant); + presetList.insert("default", importedPreset); + config.insert("presets", presetList); +} + ModSettingsStorage::ModSettingsStorage() { config = JsonUtils::JsonFromFile(settingsPath()).toMap(); - // TODO: import from 1.5 format + if (!config.contains("presets")) + { + config.insert("activePreset", QVariant("default")); + if (config.contains("activeMods")) + importInitialPreset(); // 1.5 format import + else + createInitialPreset(); // new install + + JsonUtils::JsonToFile(settingsPath(), config); + } } QString ModSettingsStorage::settingsPath() const diff --git a/launcher/modManager/modsettingsstorage.h b/launcher/modManager/modsettingsstorage.h index 76b793d88..ddeb5c5e6 100644 --- a/launcher/modManager/modsettingsstorage.h +++ b/launcher/modManager/modsettingsstorage.h @@ -17,6 +17,9 @@ class ModSettingsStorage QString settingsPath() const; + void createInitialPreset(); + void importInitialPreset(); + void setRootModActive(const QString & modName, bool on); void setModSettingActive(const QString & modName, bool on); From 9742f3a110fb8f599b34b0ca77723d65800940f2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 16 Oct 2024 19:17:21 +0000 Subject: [PATCH 650/726] Initialize submods / mods settings on new mod install --- launcher/modManager/cmodmanager.cpp | 6 ++++-- launcher/modManager/modsettingsstorage.cpp | 16 ++++++++++++++++ launcher/modManager/modsettingsstorage.h | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index c8ae3dfc8..69cdb576b 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -65,13 +65,13 @@ QString detectModArchive(QString path, QString modName, std::vector CModManager::CModManager(CModList * modList) : modList(modList) { + modSettings = std::make_shared(); loadMods(); loadModSettings(); } void CModManager::loadModSettings() { - modSettings = std::make_shared(); modList->setModSettings(modSettings); } @@ -116,7 +116,9 @@ void CModManager::loadMods() json["storedLocally"].Bool() = true; mod = JsonUtils::toVariant(json); - localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), mod); + QString modNameQt = QString::fromUtf8(modname.c_str()).toLower(); + localMods.insert(modNameQt, mod); + modSettings->registerNewMod(modNameQt, json["keepDisabled"].Bool()); } } modList->setLocalModList(localMods); diff --git a/launcher/modManager/modsettingsstorage.cpp b/launcher/modManager/modsettingsstorage.cpp index c5175eec0..0e07612c3 100644 --- a/launcher/modManager/modsettingsstorage.cpp +++ b/launcher/modManager/modsettingsstorage.cpp @@ -110,6 +110,22 @@ void ModSettingsStorage::setRootModActive(const QString & modName, bool on) JsonUtils::JsonToFile(settingsPath(), config); } +void ModSettingsStorage::registerNewMod(const QString & modName, bool keepDisabled) +{ + if (!modName.contains('.')) + return; + + QString rootModName = modName.section('.', 0, 0); + QString settingName = modName.section('.', 1); + + QVariantMap modSettings = getModSettings(rootModName); + + if (modSettings.contains(settingName)) + return; + + setModSettingActive(modName, !keepDisabled); +} + void ModSettingsStorage::setModSettingActive(const QString & modName, bool on) { QString presetName = getActivePreset(); diff --git a/launcher/modManager/modsettingsstorage.h b/launcher/modManager/modsettingsstorage.h index ddeb5c5e6..441c1922c 100644 --- a/launcher/modManager/modsettingsstorage.h +++ b/launcher/modManager/modsettingsstorage.h @@ -30,6 +30,8 @@ class ModSettingsStorage public: ModSettingsStorage(); + void registerNewMod(const QString & modName, bool keepDisabled); + void setModActive(const QString & modName, bool on); void setActivePreset(const QString & presetName); From 1120f16d33ca79467519ed070bec1fa6ebed7487 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 16 Oct 2024 19:39:35 +0000 Subject: [PATCH 651/726] Add separate on/off icons for submods with disabled parent mod --- launcher/icons/submod-disabled.png | Bin 0 -> 5794 bytes launcher/icons/submod-enabled.png | Bin 0 -> 3062 bytes launcher/modManager/cmodlistmodel_moc.cpp | 36 +++++++++++++++++----- launcher/resources.qrc | 2 ++ 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 launcher/icons/submod-disabled.png create mode 100644 launcher/icons/submod-enabled.png diff --git a/launcher/icons/submod-disabled.png b/launcher/icons/submod-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7c1a142486b2330e42a577c6acbab5086475d1 GIT binary patch literal 5794 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 literal 0 HcmV?d00001 diff --git a/launcher/icons/submod-enabled.png b/launcher/icons/submod-enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..3eee77480546c9ab1b1bc87f374f31b1a8a49931 GIT binary patch literal 3062 zcmVAIEM!_f;1*4#!13<1l0LhsKVH`i(wvFQ1w(Yy}*~Zv*Y}>Z&tj3;M$4;Kh z)c17fvkM@k1j`{RVF&G(!fs0%=`09y`f z#0&%xIMTY{OI5mnz_%3H7j4+0Dq;%9v&Df&t1jPrcA!jwHQ0QFG)8hf3C;-KT~!P4 z+x$=r#U^wV!$A0tv_H_lYCb`RuJ!Mvu*8w+tQ~97lj6yCpRVgE)o}kolr$dgteKu- ztyqc(#gi583nb6?-0M5wLxmrCa6U>0bzDLd<{}8+;bu)fUhWTo^Sn^ZMjI=tDobc1 z{JN>|!3*}D#QDBADIh) zG|Bts|2^gRL^G$AI9t4gL9~GFlIDBPbGNvmSc_Iw6Y>I0JePwHZbiMM3vfFre;}IC zqADQQsRCNq1j>geBxSzyde<%&6b)!rm5~)F^IRNmxD@-Gw@bMI*D%T-gJzZ!-Po=!S`8P8ZA!LwK#Ssoh$5wGK&?QM=cd6xa^^Fn{KH3mS`Wnx z&PoXLc}($YASQ8e$bK8a^~Nv-J?Z?4$Bc&)nkJ|1J5n3++DFp zdyA9{(2iFFFo3h7!u*Xq7efSE;+OOOPiUYpU=3ob8mt$n=ee2Ckao>Q66XJLf_5b( zj6_t`fOP^Jd9MC{X3a6mku8eC^=#X=ZQHj0J_HpfRp}nvwryt=8|`P?wtBy{l6B9$ zt=mlXJKwnW*{AlYndw_=1;c3p6?VnA8orZuT;+-VcIm#8{)~SwK4IJ(mvPFD)0Eu>%5e*^2$ks-e-6Y+W@AciS{D;+@-C;J+hahTN zA&pV+h)BOMR_@{bh$&Z*M>R4M@}$ecPjkEvE9EcuST`7}h0q05qlT!)Jh(;Pa<=)w z1dpGo1_>TH=>lqqS$syyfa8z*DIRJXs);<($P|~rSqlwr&d=`0TK@;-{)uTeiGC9D zq>I7B{2Sd@l|8=aJlC%-lo%$&Er_=!5XC^a1e{Y^zA(9_v8yA6&Wt2}M$nDp?u*MF zUp>(EE*n>Y1RX~UNC{+6Koyb2_@*m}Ige-(|4i54r7La(sVj$+gdFJt+`0V6AJ>k~ zO-__>A%HxQLb3@dB{o&H(%K$wEg`g6KYkyAD&zU5+6<#1iC)G?J~5 zMNS|9Kb)MuovxCn{YLg@9>nC1M2=X=9lui#EO-2S9gPl@n1C#iMxqVU$O=R-1P+l! z_PvMili}p_1I}=JG%P8a8YoQbPQ=gak58Ry73I7B`yVL(^S|4U67!HI z5{PwxB+>!_RKVVhen`>vxYR36Oyd19rTk^TlZ~5=lt3KO4iInB6TxuUo3NU$C8RzH z(rtl*bvHULHDCeO<83J6!P;gkh;)D$l0r`sGhqjF94cIiN<5;1ZUlAP7b?b-nzey3 z6A4an1>tsyAl{^F1w2JBCpOWa#iS0re>H53->t_h#+2pR08zvR zwqhd2VhwkZ_C^F80Ai;<7aI74$^_A%P&ab&OKH z4(Pb|7{2e0TS1T* zNb^i{mENzH4l->ufit4`v>4m4rAOeNi}|eIO~)z!hq`Yk(_j-g{jd?+1paHc9$@AO zfG`YCZJS}Ex(%zaR#5Y_-aXW|ZQHhOn^A4s_{zy|>uz`7Cp^q%CfZ%tnMAvI7Ivc_ zKL@^{6g=r|jE@FbPprdkgxQtk1MsA|FxXRnZVIM+m5tj5SOAM6EwLjBc*b^gg~4PB zw)8^!ejUuf1Z+@*$z%jHwx5DIt-AI|0}EhLvaG)XI?w!}R;A$1D50HH!E{W*)+Fl>mCdK%uYDGE zzKSrzu?bs}U_Mm#Q7uiOkt!-*6|mk|lj!v~qdU)mI*39irIz|c0SjO;HYduou=Stt z>raRzmQv0rU@E3zQ-ZC74+;|~)biz&S1SlJ3>&a9LB>E~*OZJDf~AwY%RgkW-dKYT z@wNggWLA$tHL0uWe#GEY(k z;idqlVHVazYdi$@K+ZvCs$&7qZV#A%QCJ*2r1Y`8dk@KRVVY>(rr?WnTU;qFB07*qoM6N<$ Eg84Gp<^TWy literal 0 HcmV?d00001 diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index bc24e10d0..ee3638add 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -16,8 +16,10 @@ namespace ModStatus { static const QString iconDelete = ":/icons/mod-delete.png"; static const QString iconDisabled = ":/icons/mod-disabled.png"; +static const QString iconDisabledSubmod = ":/icons/submod-disabled.png"; static const QString iconDownload = ":/icons/mod-download.png"; static const QString iconEnabled = ":/icons/mod-enabled.png"; +static const QString iconEnabledSubmod = ":/icons/submod-enabled.png"; static const QString iconUpdate = ":/icons/mod-update.png"; } @@ -104,15 +106,33 @@ QVariant CModListModel::getText(const CModEntry & mod, int field) const QVariant CModListModel::getIcon(const CModEntry & mod, int field) const { - if(field == ModFields::STATUS_ENABLED && mod.isEnabled()) - return QIcon(ModStatus::iconEnabled); - if(field == ModFields::STATUS_ENABLED && mod.isDisabled()) - return QIcon(ModStatus::iconDisabled); + if (field == ModFields::STATUS_ENABLED) + { + if(mod.isSubmod()) + { + QString toplevelParent = mod.getName().section('.', 0, 0); + if (getMod(toplevelParent).isDisabled()) + { + if (mod.isEnabled()) + return QIcon(ModStatus::iconEnabledSubmod); + if(mod.isDisabled()) + return QIcon(ModStatus::iconDisabledSubmod); + } + } - if(field == ModFields::STATUS_UPDATE && mod.isUpdateable()) - return QIcon(ModStatus::iconUpdate); - if(field == ModFields::STATUS_UPDATE && !mod.isInstalled()) - return QIcon(ModStatus::iconDownload); + if (mod.isEnabled()) + return QIcon(ModStatus::iconEnabled); + if(mod.isDisabled()) + return QIcon(ModStatus::iconDisabled); + } + + if(field == ModFields::STATUS_UPDATE) + { + if (mod.isUpdateable()) + return QIcon(ModStatus::iconUpdate); + if(!mod.isInstalled()) + return QIcon(ModStatus::iconDownload); + } return QVariant(); } diff --git a/launcher/resources.qrc b/launcher/resources.qrc index 139401504..5be9d4f1b 100644 --- a/launcher/resources.qrc +++ b/launcher/resources.qrc @@ -7,8 +7,10 @@ icons/menu-settings.png icons/mod-delete.png icons/mod-disabled.png + icons/submod-disabled.png icons/mod-download.png icons/mod-enabled.png + icons/submod-enabled.png icons/mod-update.png From c22471fd9182909012ff39eef477bb5b4f817cf4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 17 Oct 2024 14:19:59 +0000 Subject: [PATCH 652/726] Display name of parent mod next to the name of submods in dependencies --- launcher/modManager/cmodlist.cpp | 6 ++++++ launcher/modManager/cmodlist.h | 4 ++++ launcher/modManager/cmodlistview_moc.cpp | 23 +++++++++++++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp index 865960a8d..675d598c7 100644 --- a/launcher/modManager/cmodlist.cpp +++ b/launcher/modManager/cmodlist.cpp @@ -149,6 +149,12 @@ QString CModEntry::getName() const return modname; } +QString CModEntry::getTopParentName() const +{ + assert(isSubmod()); + return modname.section('.', 0, 0); +} + QVariant CModEntry::getValue(QString value) const { return getValueImpl(value, true); diff --git a/launcher/modManager/cmodlist.h b/launcher/modManager/cmodlist.h index ccfe253b0..721b36cb2 100644 --- a/launcher/modManager/cmodlist.h +++ b/launcher/modManager/cmodlist.h @@ -66,8 +66,12 @@ public: // see ModStatus enum int getModStatus() const; + // Returns mod name / identifier (not human-readable) QString getName() const; + // For submods only. Returns mod name / identifier of a top-level parent mod + QString getTopParentName() const; + // get value of some field in mod structure. Returns empty optional if value is not present QVariant getValue(QString value) const; QVariant getBaseValue(QString value) const; diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index a0b11b6a3..f86274a7d 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -261,14 +261,25 @@ QStringList CModListView::getModNames(QStringList input) { auto mod = modModel->getMod(modID.toLower()); - QString modName = mod.getValue("name").toString(); + QString displayName = mod.getValue("name").toString(); + if (displayName.isEmpty()) + displayName = modID.toLower(); - if (modName.isEmpty()) - result += modID.toLower(); - else - result += modName; + if (mod.isSubmod()) + { + auto parentModID = mod.getTopParentName(); + auto parentMod = modModel->getMod(parentModID.toLower()); + QString parentDisplayName = parentMod.getValue("name").toString(); + if (parentDisplayName.isEmpty()) + parentDisplayName = parentModID.toLower(); + + if (mod.isInstalled()) + displayName = QString("%1 (%2)").arg(displayName, parentDisplayName); + else + displayName = parentDisplayName; + } + result += displayName; } - return result; } From ba9e3dca9d8f862fef8c9000a62577394cd41db4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Nov 2024 20:29:07 +0000 Subject: [PATCH 653/726] Mod management rework, part 1 - Replaced CModInfo class with constant ModDescription class - Simplified mod loading logic - Extracted some functionality from ModHandler into separate classes for future reuse by Launcher --- client/globalLobby/GlobalLobbyRoomWindow.cpp | 6 +- launcher/modManager/cmodmanager.cpp | 52 +- lib/CMakeLists.txt | 6 +- lib/IGameCallback.cpp | 1 - lib/VCMI_Lib.cpp | 2 - lib/filesystem/Filesystem.cpp | 1 + lib/mapping/CMapService.cpp | 4 +- lib/modding/ActiveModsInSaveList.cpp | 10 +- lib/modding/ActiveModsInSaveList.h | 7 +- lib/modding/CModHandler.cpp | 535 +++++-------------- lib/modding/CModHandler.h | 39 +- lib/modding/CModInfo.cpp | 204 ------- lib/modding/CModInfo.h | 85 --- lib/modding/ContentTypeHandler.cpp | 67 +-- lib/modding/ContentTypeHandler.h | 8 +- lib/modding/ModDescription.cpp | 114 ++++ lib/modding/ModDescription.h | 56 ++ lib/modding/ModManager.cpp | 367 +++++++++++++ lib/modding/ModManager.h | 95 ++++ lib/modding/ModVerificationInfo.cpp | 10 +- mapeditor/mapcontroller.cpp | 2 +- mapeditor/mapcontroller.h | 3 +- mapeditor/mapsettings/modsettings.cpp | 10 +- mapeditor/validator.cpp | 2 +- server/GlobalLobbyProcessor.cpp | 5 +- 25 files changed, 864 insertions(+), 827 deletions(-) delete mode 100644 lib/modding/CModInfo.cpp delete mode 100644 lib/modding/CModInfo.h create mode 100644 lib/modding/ModDescription.cpp create mode 100644 lib/modding/ModDescription.h create mode 100644 lib/modding/ModManager.cpp create mode 100644 lib/modding/ModManager.h diff --git a/client/globalLobby/GlobalLobbyRoomWindow.cpp b/client/globalLobby/GlobalLobbyRoomWindow.cpp index f744dca2d..943737b72 100644 --- a/client/globalLobby/GlobalLobbyRoomWindow.cpp +++ b/client/globalLobby/GlobalLobbyRoomWindow.cpp @@ -27,7 +27,7 @@ #include "../widgets/ObjectLists.h" #include "../../lib/modding/CModHandler.h" -#include "../../lib/modding/CModInfo.h" +#include "../../lib/modding/ModDescription.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/texts/MetaString.h" @@ -128,14 +128,14 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s GlobalLobbyRoomModInfo modInfo; modInfo.status = modEntry.second; if (modEntry.second == ModVerificationStatus::EXCESSIVE) - modInfo.version = CGI->modh->getModInfo(modEntry.first).getVerificationInfo().version.toString(); + modInfo.version = CGI->modh->getModInfo(modEntry.first).getVersion().toString(); else modInfo.version = roomDescription.modList.at(modEntry.first).version.toString(); if (modEntry.second == ModVerificationStatus::NOT_INSTALLED) modInfo.modName = roomDescription.modList.at(modEntry.first).name; else - modInfo.modName = CGI->modh->getModInfo(modEntry.first).getVerificationInfo().name; + modInfo.modName = CGI->modh->getModInfo(modEntry.first).getName(); modVerificationList.push_back(modInfo); } diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 69cdb576b..6b639d733 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -14,7 +14,6 @@ #include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/CZipLoader.h" #include "../../lib/modding/CModHandler.h" -#include "../../lib/modding/CModInfo.h" #include "../../lib/modding/IdentifierStorage.h" #include "../vcmiqt/jsonutils.h" @@ -90,37 +89,32 @@ void CModManager::loadRepositories(QVector repomap) void CModManager::loadMods() { CModHandler handler; - handler.loadMods(); auto installedMods = handler.getAllMods(); localMods.clear(); - for(auto modname : installedMods) - { - auto resID = CModInfo::getModFile(modname); - if(CResourceHandler::get()->existsResource(resID)) - { - //calculate mod size - qint64 total = 0; - ResourcePath resDir(CModInfo::getModDir(modname), EResType::DIRECTORY); - if(CResourceHandler::get()->existsResource(resDir)) - { - for(QDirIterator iter(QString::fromStdString(CResourceHandler::get()->getResourceName(resDir)->string()), QDirIterator::Subdirectories); iter.hasNext(); iter.next()) - total += iter.fileInfo().size(); - } - - boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID); - auto mod = JsonUtils::JsonFromFile(pathToQString(name)); - auto json = JsonUtils::toJson(mod); - json["localSizeBytes"].Float() = total; - if(!name.is_absolute()) - json["storedLocally"].Bool() = true; - - mod = JsonUtils::toVariant(json); - QString modNameQt = QString::fromUtf8(modname.c_str()).toLower(); - localMods.insert(modNameQt, mod); - modSettings->registerNewMod(modNameQt, json["keepDisabled"].Bool()); - } - } +// for(auto modname : installedMods) +// { +// //calculate mod size +// qint64 total = 0; +// ResourcePath resDir(CModInfo::getModDir(modname), EResType::DIRECTORY); +// if(CResourceHandler::get()->existsResource(resDir)) +// { +// for(QDirIterator iter(QString::fromStdString(CResourceHandler::get()->getResourceName(resDir)->string()), QDirIterator::Subdirectories); iter.hasNext(); iter.next()) +// total += iter.fileInfo().size(); +// } +// +// boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID); +// auto mod = JsonUtils::JsonFromFile(pathToQString(name)); +// auto json = JsonUtils::toJson(mod); +// json["localSizeBytes"].Float() = total; +// if(!name.is_absolute()) +// json["storedLocally"].Bool() = true; +// +// mod = JsonUtils::toVariant(json); +// QString modNameQt = QString::fromUtf8(modname.c_str()).toLower(); +// localMods.insert(modNameQt, mod); +// modSettings->registerNewMod(modNameQt, json["keepDisabled"].Bool()); +// } modList->setLocalModList(localMods); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index aca9b5919..85e1c95a8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -157,10 +157,11 @@ set(lib_MAIN_SRCS modding/ActiveModsInSaveList.cpp modding/CModHandler.cpp - modding/CModInfo.cpp modding/CModVersion.cpp modding/ContentTypeHandler.cpp modding/IdentifierStorage.cpp + modding/ModDescription.cpp + modding/ModManager.cpp modding/ModUtility.cpp modding/ModVerificationInfo.cpp @@ -547,11 +548,12 @@ set(lib_MAIN_HEADERS modding/ActiveModsInSaveList.h modding/CModHandler.h - modding/CModInfo.h modding/CModVersion.h modding/ContentTypeHandler.h modding/IdentifierStorage.h + modding/ModDescription.h modding/ModIncompatibility.h + modding/ModManager.h modding/ModScope.h modding/ModUtility.h modding/ModVerificationInfo.h diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 6c7bf82c4..7cfef68cf 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -41,7 +41,6 @@ #include "gameState/QuestInfo.h" #include "mapping/CMap.h" #include "modding/CModHandler.h" -#include "modding/CModInfo.h" #include "modding/IdentifierStorage.h" #include "modding/CModVersion.h" #include "modding/ActiveModsInSaveList.h" diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 91d3da34b..b5146f2e4 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -26,7 +26,6 @@ #include "entities/hero/CHeroHandler.h" #include "texts/CGeneralTextHandler.h" #include "modding/CModHandler.h" -#include "modding/CModInfo.h" #include "modding/IdentifierStorage.h" #include "modding/CModVersion.h" #include "IGameEventsReceiver.h" @@ -157,7 +156,6 @@ void LibClasses::loadModFilesystem() CStopWatch loadTime; modh = std::make_unique(); identifiersHandler = std::make_unique(); - modh->loadMods(); logGlobal->info("\tMod handler: %d ms", loadTime.getDiff()); modh->loadModFilesystems(); diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index e3df56e34..4840b3f87 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -212,6 +212,7 @@ ISimpleResourceLoader * CResourceHandler::get() ISimpleResourceLoader * CResourceHandler::get(const std::string & identifier) { + assert(knownLoaders.count(identifier)); return knownLoaders.at(identifier); } diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 830245d12..61a1958b0 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -17,8 +17,8 @@ #include "../filesystem/CMemoryStream.h" #include "../filesystem/CMemoryBuffer.h" #include "../modding/CModHandler.h" +#include "../modding/ModDescription.h" #include "../modding/ModScope.h" -#include "../modding/CModInfo.h" #include "../VCMI_Lib.h" #include "CMap.h" @@ -99,7 +99,7 @@ ModCompatibilityInfo CMapService::verifyMapHeaderMods(const CMapHeader & map) if(vstd::contains(activeMods, mapMod.first)) { const auto & modInfo = VLC->modh->getModInfo(mapMod.first); - if(modInfo.getVerificationInfo().version.compatible(mapMod.second.version)) + if(modInfo.getVersion().compatible(mapMod.second.version)) continue; } missingMods[mapMod.first] = mapMod.second; diff --git a/lib/modding/ActiveModsInSaveList.cpp b/lib/modding/ActiveModsInSaveList.cpp index 17e8927f0..c4620c170 100644 --- a/lib/modding/ActiveModsInSaveList.cpp +++ b/lib/modding/ActiveModsInSaveList.cpp @@ -11,7 +11,7 @@ #include "ActiveModsInSaveList.h" #include "../VCMI_Lib.h" -#include "CModInfo.h" +#include "ModDescription.h" #include "CModHandler.h" #include "ModIncompatibility.h" @@ -21,13 +21,13 @@ std::vector ActiveModsInSaveList::getActiveGameplayAffectingMods() { std::vector result; for (auto const & entry : VLC->modh->getActiveMods()) - if (VLC->modh->getModInfo(entry).checkModGameplayAffecting()) + if (VLC->modh->getModInfo(entry).affectsGameplay()) result.push_back(entry); return result; } -const ModVerificationInfo & ActiveModsInSaveList::getVerificationInfo(TModID mod) +ModVerificationInfo ActiveModsInSaveList::getVerificationInfo(TModID mod) { return VLC->modh->getModInfo(mod).getVerificationInfo(); } @@ -44,10 +44,10 @@ void ActiveModsInSaveList::verifyActiveMods(const std::mapmodh->getModInfo(compared.first).getVerificationInfo().name); + missingMods.push_back(VLC->modh->getModInfo(compared.first).getName()); if (compared.second == ModVerificationStatus::EXCESSIVE) - excessiveMods.push_back(VLC->modh->getModInfo(compared.first).getVerificationInfo().name); + excessiveMods.push_back(VLC->modh->getModInfo(compared.first).getName()); } if(!missingMods.empty() || !excessiveMods.empty()) diff --git a/lib/modding/ActiveModsInSaveList.h b/lib/modding/ActiveModsInSaveList.h index 0d51ed4f3..d89244788 100644 --- a/lib/modding/ActiveModsInSaveList.h +++ b/lib/modding/ActiveModsInSaveList.h @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class ActiveModsInSaveList { std::vector getActiveGameplayAffectingMods(); - const ModVerificationInfo & getVerificationInfo(TModID mod); + ModVerificationInfo getVerificationInfo(TModID mod); /// Checks whether provided mod list is compatible with current VLC and throws on failure void verifyActiveMods(const std::map & modList); @@ -29,7 +29,10 @@ public: std::vector activeMods = getActiveGameplayAffectingMods(); h & activeMods; for(const auto & m : activeMods) - h & getVerificationInfo(m); + { + ModVerificationInfo info = getVerificationInfo(m); + h & info; + } } else { diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index ed98a37bb..6ca90688a 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -10,316 +10,49 @@ #include "StdInc.h" #include "CModHandler.h" -#include "CModInfo.h" -#include "ModScope.h" #include "ContentTypeHandler.h" #include "IdentifierStorage.h" -#include "ModIncompatibility.h" +#include "ModDescription.h" +#include "ModManager.h" +#include "ModScope.h" -#include "../CCreatureHandler.h" #include "../CConfigHandler.h" -#include "../CStopWatch.h" +#include "../CCreatureHandler.h" #include "../GameSettings.h" -#include "../ScriptHandler.h" -#include "../constants/StringConstants.h" +#include "../VCMI_Lib.h" #include "../filesystem/Filesystem.h" #include "../json/JsonUtils.h" -#include "../spells/CSpellHandler.h" #include "../texts/CGeneralTextHandler.h" #include "../texts/Languages.h" -#include "../VCMI_Lib.h" VCMI_LIB_NAMESPACE_BEGIN -static JsonNode loadModSettings(const JsonPath & path) -{ - if (CResourceHandler::get("local")->existsResource(ResourcePath(path))) - { - return JsonNode(path); - } - // Probably new install. Create initial configuration - CResourceHandler::get("local")->createResource(path.getOriginalName() + ".json"); - return JsonNode(); -} - CModHandler::CModHandler() : content(std::make_shared()) - , coreMod(std::make_unique()) + , modManager(std::make_unique()) { } CModHandler::~CModHandler() = default; -// currentList is passed by value to get current list of depending mods -bool CModHandler::hasCircularDependency(const TModID & modID, std::set currentList) const -{ - const CModInfo & mod = allMods.at(modID); - - // Mod already present? We found a loop - if (vstd::contains(currentList, modID)) - { - logMod->error("Error: Circular dependency detected! Printing dependency list:"); - logMod->error("\t%s -> ", mod.getVerificationInfo().name); - return true; - } - - currentList.insert(modID); - - // recursively check every dependency of this mod - for(const TModID & dependency : mod.dependencies) - { - if (hasCircularDependency(dependency, currentList)) - { - logMod->error("\t%s ->\n", mod.getVerificationInfo().name); // conflict detected, print dependency list - return true; - } - } - return false; -} - -// Returned vector affects the resource loaders call order (see CFilesystemList::load). -// The loaders call order matters when dependent mod overrides resources in its dependencies. -std::vector CModHandler::validateAndSortDependencies(std::vector modsToResolve) const -{ - // Topological sort algorithm. - // TODO: Investigate possible ways to improve performance. - boost::range::sort(modsToResolve); // Sort mods per name - std::vector sortedValidMods; // Vector keeps order of elements (LIFO) - sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation - std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements - std::set notResolvedModIDs(modsToResolve.begin(), modsToResolve.end()); // Use a set for validation for performance reason - - // Mod is resolved if it has no dependencies or all its dependencies are already resolved - auto isResolved = [&](const CModInfo & mod) -> bool - { - if(mod.dependencies.size() > resolvedModIDs.size()) - return false; - - for(const TModID & dependency : mod.dependencies) - { - if(!vstd::contains(resolvedModIDs, dependency)) - return false; - } - - for(const TModID & softDependency : mod.softDependencies) - { - if(vstd::contains(notResolvedModIDs, softDependency)) - return false; - } - - for(const TModID & conflict : mod.conflicts) - { - if(vstd::contains(resolvedModIDs, conflict)) - return false; - } - for(const TModID & reverseConflict : resolvedModIDs) - { - if (vstd::contains(allMods.at(reverseConflict).conflicts, mod.identifier)) - return false; - } - return true; - }; - - while(true) - { - std::set resolvedOnCurrentTreeLevel; - for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree - { - if(isResolved(allMods.at(*it))) - { - resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node children will be resolved on the next iteration - sortedValidMods.push_back(*it); - it = modsToResolve.erase(it); - continue; - } - it++; - } - if(!resolvedOnCurrentTreeLevel.empty()) - { - resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end()); - for(const auto & it : resolvedOnCurrentTreeLevel) - notResolvedModIDs.erase(it); - continue; - } - // If there are no valid mods on the current mods tree level, no more mod can be resolved, should be ended. - break; - } - - modLoadErrors = std::make_unique(); - - auto addErrorMessage = [this](const std::string & textID, const std::string & brokenModID, const std::string & missingModID) - { - modLoadErrors->appendTextID(textID); - - if (allMods.count(brokenModID)) - modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); - else - modLoadErrors->replaceRawString(brokenModID); - - if (allMods.count(missingModID)) - modLoadErrors->replaceRawString(allMods.at(missingModID).getVerificationInfo().name); - else - modLoadErrors->replaceRawString(missingModID); - - }; - - // Left mods have unresolved dependencies, output all to log. - for(const auto & brokenModID : modsToResolve) - { - const CModInfo & brokenMod = allMods.at(brokenModID); - bool showErrorMessage = false; - for(const TModID & dependency : brokenMod.dependencies) - { - if(!vstd::contains(resolvedModIDs, dependency) && brokenMod.config["modType"].String() != "Compatibility") - { - addErrorMessage("vcmi.server.errors.modNoDependency", brokenModID, dependency); - showErrorMessage = true; - } - } - for(const TModID & conflict : brokenMod.conflicts) - { - if(vstd::contains(resolvedModIDs, conflict)) - { - addErrorMessage("vcmi.server.errors.modConflict", brokenModID, conflict); - showErrorMessage = true; - } - } - for(const TModID & reverseConflict : resolvedModIDs) - { - if (vstd::contains(allMods.at(reverseConflict).conflicts, brokenModID)) - { - addErrorMessage("vcmi.server.errors.modConflict", brokenModID, reverseConflict); - showErrorMessage = true; - } - } - - // some mods may in a (soft) dependency loop. - if(!showErrorMessage && brokenMod.config["modType"].String() != "Compatibility") - { - modLoadErrors->appendTextID("vcmi.server.errors.modDependencyLoop"); - if (allMods.count(brokenModID)) - modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); - else - modLoadErrors->replaceRawString(brokenModID); - } - - } - return sortedValidMods; -} - -std::vector CModHandler::getModList(const std::string & path) const -{ - std::string modDir = boost::to_upper_copy(path + "MODS/"); - size_t depth = boost::range::count(modDir, '/'); - - auto list = CResourceHandler::get("initial")->getFilteredFiles([&](const ResourcePath & id) -> bool - { - if (id.getType() != EResType::DIRECTORY) - return false; - if (!boost::algorithm::starts_with(id.getName(), modDir)) - return false; - if (boost::range::count(id.getName(), '/') != depth ) - return false; - return true; - }); - - //storage for found mods - std::vector foundMods; - for(const auto & entry : list) - { - std::string name = entry.getName(); - name.erase(0, modDir.size()); //Remove path prefix - - if (!name.empty()) - foundMods.push_back(name); - } - return foundMods; -} - - - -void CModHandler::loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods) -{ - for(const std::string & modName : getModList(path)) - loadOneMod(modName, parent, modSettings, modsToActivate, enableMods); -} - -void CModHandler::loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods) -{ - boost::to_lower(modName); - std::string modFullName = parent.empty() ? modName : parent + '.' + modName; - - if ( ModScope::isScopeReserved(modFullName)) - { - logMod->error("Can not load mod %s - this name is reserved for internal use!", modFullName); - return; - } - - if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName))) - { - bool thisModActive = vstd::contains(modsToActivate, modFullName); - CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName)), thisModActive); - if (!parent.empty()) // this is submod, add parent to dependencies - mod.dependencies.insert(parent); - - allMods[modFullName] = mod; - if (mod.isEnabled() && enableMods) - activeMods.push_back(modFullName); - - loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], modsToActivate, enableMods && mod.isEnabled()); - } -} - -void CModHandler::loadMods() -{ - JsonNode modConfig; - - modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); - const JsonNode & modSettings = modConfig["activeMods"]; - const std::string & currentPresetName = modConfig["activePreset"].String(); - const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; - const JsonNode & modsToActivateJson = currentPreset["mods"]; - std::vector modsToActivate = modsToActivateJson.convertTo>(); - - for(const auto & settings : currentPreset["settings"].Struct()) - { - if (!vstd::contains(modsToActivate, settings.first)) - continue; // settings for inactive mod - - for (const auto & submod : settings.second.Struct()) - { - if (submod.second.Bool()) - modsToActivate.push_back(settings.first + '.' + submod.first); - } - } - - loadMods("", "", modSettings, modsToActivate, true); - - coreMod = std::make_unique(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json")), true); -} - std::vector CModHandler::getAllMods() const { - std::vector modlist; - modlist.reserve(allMods.size()); - for (auto & entry : allMods) - modlist.push_back(entry.first); - return modlist; + return modManager->getActiveMods();// TODO: currently identical to active } std::vector CModHandler::getActiveMods() const { - return activeMods; + return modManager->getActiveMods(); } std::string CModHandler::getModLoadErrors() const { - return modLoadErrors->toString(); + return ""; // TODO: modLoadErrors->toString(); } -const CModInfo & CModHandler::getModInfo(const TModID & modId) const +const ModDescription & CModHandler::getModInfo(const TModID & modId) const { - return allMods.at(modId); + return modManager->getModDescription(modId); } static JsonNode genDefaultFS() @@ -334,86 +67,95 @@ static JsonNode genDefaultFS() return defaultFS; } +static std::string getModDirectory(const TModID & modName) +{ + std::string result = modName; + boost::to_upper(result); + boost::algorithm::replace_all(result, ".", "/MODS/"); + return "MODS/" + result; +} + static ISimpleResourceLoader * genModFilesystem(const std::string & modName, const JsonNode & conf) { static const JsonNode defaultFS = genDefaultFS(); - if (!conf["filesystem"].isNull()) - return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), conf["filesystem"]); + if (!conf.isNull()) + return CResourceHandler::createFileSystem(getModDirectory(modName), conf); else - return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), defaultFS); + return CResourceHandler::createFileSystem(getModDirectory(modName), defaultFS); } -static ui32 calculateModChecksum(const std::string & modName, ISimpleResourceLoader * filesystem) -{ - boost::crc_32_type modChecksum; - // first - add current VCMI version into checksum to force re-validation on VCMI updates - modChecksum.process_bytes(reinterpret_cast(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size()); - - // second - add mod.json into checksum because filesystem does not contains this file - // FIXME: remove workaround for core mod - if (modName != ModScope::scopeBuiltin()) - { - auto modConfFile = CModInfo::getModFile(modName); - ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32(); - modChecksum.process_bytes(reinterpret_cast(&configChecksum), sizeof(configChecksum)); - } - // third - add all detected text files from this mod into checksum - auto files = filesystem->getFilteredFiles([](const ResourcePath & resID) - { - return (resID.getType() == EResType::TEXT || resID.getType() == EResType::JSON) && - ( boost::starts_with(resID.getName(), "DATA") || boost::starts_with(resID.getName(), "CONFIG")); - }); - - for (const ResourcePath & file : files) - { - ui32 fileChecksum = filesystem->load(file)->calculateCRC32(); - modChecksum.process_bytes(reinterpret_cast(&fileChecksum), sizeof(fileChecksum)); - } - return modChecksum.checksum(); -} +//static ui32 calculateModChecksum(const std::string & modName, ISimpleResourceLoader * filesystem) +//{ +// boost::crc_32_type modChecksum; +// // first - add current VCMI version into checksum to force re-validation on VCMI updates +// modChecksum.process_bytes(reinterpret_cast(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size()); +// +// // second - add mod.json into checksum because filesystem does not contains this file +// // FIXME: remove workaround for core mod +// if (modName != ModScope::scopeBuiltin()) +// { +// auto modConfFile = CModInfo::getModFile(modName); +// ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32(); +// modChecksum.process_bytes(reinterpret_cast(&configChecksum), sizeof(configChecksum)); +// } +// // third - add all detected text files from this mod into checksum +// auto files = filesystem->getFilteredFiles([](const ResourcePath & resID) +// { +// return (resID.getType() == EResType::TEXT || resID.getType() == EResType::JSON) && +// ( boost::starts_with(resID.getName(), "DATA") || boost::starts_with(resID.getName(), "CONFIG")); +// }); +// +// for (const ResourcePath & file : files) +// { +// ui32 fileChecksum = filesystem->load(file)->calculateCRC32(); +// modChecksum.process_bytes(reinterpret_cast(&fileChecksum), sizeof(fileChecksum)); +// } +// return modChecksum.checksum(); +//} void CModHandler::loadModFilesystems() { CGeneralTextHandler::detectInstallParameters(); - activeMods = validateAndSortDependencies(activeMods); + const auto & activeMods = modManager->getActiveMods(); - coreMod->updateChecksum(calculateModChecksum(ModScope::scopeBuiltin(), CResourceHandler::get(ModScope::scopeBuiltin()))); + std::map modFilesystems; - std::map modFilesystems; + for(const TModID & modName : activeMods) + modFilesystems[modName] = genModFilesystem(modName, getModInfo(modName).getFilesystemConfig()); - for(std::string & modName : activeMods) - modFilesystems[modName] = genModFilesystem(modName, allMods[modName].config); - - for(std::string & modName : activeMods) + for(const TModID & modName : activeMods) CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]); if (settings["mods"]["validation"].String() == "full") + checkModFilesystemsConflicts(modFilesystems); +} + +void CModHandler::checkModFilesystemsConflicts(const std::map & modFilesystems) +{ + for(const auto & [leftName, leftFilesystem] : modFilesystems) { - for(std::string & leftModName : activeMods) + for(const auto & [rightName, rightFilesystem] : modFilesystems) { - for(std::string & rightModName : activeMods) + if (leftName == rightName) + continue; + + if (getModDependencies(leftName).count(rightName) || getModDependencies(rightName).count(leftName)) + continue; + + if (getModSoftDependencies(leftName).count(rightName) || getModSoftDependencies(rightName).count(leftName)) + continue; + + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY && path.getType() != EResType::JSON;}; + + std::unordered_set leftResources = leftFilesystem->getFilteredFiles(filter); + std::unordered_set rightResources = rightFilesystem->getFilteredFiles(filter); + + for (auto const & leftFile : leftResources) { - if (leftModName == rightModName) - continue; - - if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) - continue; - - if (getModSoftDependencies(leftModName).count(rightModName) || getModSoftDependencies(rightModName).count(leftModName)) - continue; - - const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY && path.getType() != EResType::JSON;}; - - std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); - std::unordered_set rightResources = modFilesystems[rightModName]->getFilteredFiles(filter); - - for (auto const & leftFile : leftResources) - { - if (rightResources.count(leftFile)) - logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftModName, rightModName, leftFile.getOriginalName()); - } + if (rightResources.count(leftFile)) + logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftName, rightName, leftFile.getOriginalName()); } } } @@ -423,7 +165,8 @@ TModID CModHandler::findResourceOrigin(const ResourcePath & name) const { try { - for(const auto & modID : boost::adaptors::reverse(activeMods)) + auto activeMode = modManager->getActiveMods(); + for(const auto & modID : boost::adaptors::reverse(activeMode)) { if(CResourceHandler::get(modID)->existsResource(name)) return modID; @@ -478,7 +221,7 @@ std::string CModHandler::getModLanguage(const TModID& modId) const return VLC->generaltexth->getInstalledLanguage(); if(modId == "map") return VLC->generaltexth->getPreferredLanguage(); - return allMods.at(modId).baseLanguage; + return getModInfo(modId).getBaseLanguage(); } std::set CModHandler::getModDependencies(const TModID & modId) const @@ -489,11 +232,9 @@ std::set CModHandler::getModDependencies(const TModID & modId) const std::set CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const { - auto it = allMods.find(modId); - isModFound = (it != allMods.end()); - - if(isModFound) - return it->second.dependencies; + isModFound = modManager->isModActive(modId); + if (isModFound) + return modManager->getModDescription(modId).getDependencies(); logMod->error("Mod not found: '%s'", modId); return {}; @@ -501,54 +242,37 @@ std::set CModHandler::getModDependencies(const TModID & modId, bool & is std::set CModHandler::getModSoftDependencies(const TModID & modId) const { - auto it = allMods.find(modId); - if(it != allMods.end()) - return it->second.softDependencies; - logMod->error("Mod not found: '%s'", modId); - return {}; + return modManager->getModDescription(modId).getSoftDependencies(); } std::set CModHandler::getModEnabledSoftDependencies(const TModID & modId) const { std::set softDependencies = getModSoftDependencies(modId); - for (auto it = softDependencies.begin(); it != softDependencies.end();) - { - if (allMods.find(*it) == allMods.end()) - it = softDependencies.erase(it); - else - it++; - } + + vstd::erase_if(softDependencies, [&](const TModID & dependency){ return !modManager->isModActive(dependency);}); + return softDependencies; } void CModHandler::initializeConfig() { - VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"])); - - for(const TModID & modName : activeMods) + for(const TModID & modName : getActiveMods()) { - const auto & mod = allMods[modName]; - if (!mod.config["settings"].isNull()) - VLC->settingsHandler->loadBase(mod.config["settings"]); + const auto & mod = getModInfo(modName); + if (!mod.getConfig()["settings"].isNull()) + VLC->settingsHandler->loadBase(mod.getConfig()["settings"]); } } -CModVersion CModHandler::getModVersion(TModID modName) const -{ - if (allMods.count(modName)) - return allMods.at(modName).getVerificationInfo().version; - return {}; -} - void CModHandler::loadTranslation(const TModID & modName) { - const auto & mod = allMods[modName]; + const auto & mod = getModInfo(modName); std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); - std::string modBaseLanguage = allMods[modName].baseLanguage; + std::string modBaseLanguage = getModInfo(modName).getBaseLanguage(); - JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]); - JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]); + JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.getConfig()["translations"]); + JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.getConfig()[preferredLanguage]["translations"]); VLC->generaltexth->loadTranslationOverrides(modName, modBaseLanguage, baseTranslation); VLC->generaltexth->loadTranslationOverrides(modName, preferredLanguage, extraTranslation); @@ -556,29 +280,22 @@ void CModHandler::loadTranslation(const TModID & modName) void CModHandler::load() { - CStopWatch totalTime; - CStopWatch timer; - - logMod->info("\tInitializing content handler: %d ms", timer.getDiff()); + logMod->info("\tInitializing content handler"); content->init(); - for(const TModID & modName : activeMods) - { - logMod->trace("Generating checksum for %s", modName); - allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName))); - } +// for(const TModID & modName : getActiveMods()) +// { +// logMod->trace("Generating checksum for %s", modName); +// allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName))); +// } - // first - load virtual builtin mod that contains all data - // TODO? move all data into real mods? RoE, AB, SoD, WoG - content->preloadData(*coreMod); - for(const TModID & modName : activeMods) - content->preloadData(allMods[modName]); - logMod->info("\tParsing mod data: %d ms", timer.getDiff()); + for(const TModID & modName : getActiveMods()) + content->preloadData(getModInfo(modName)); + logMod->info("\tParsing mod data"); - content->load(*coreMod); - for(const TModID & modName : activeMods) - content->load(allMods[modName]); + for(const TModID & modName : getActiveMods()) + content->load(getModInfo(modName)); #if SCRIPTING_ENABLED VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load @@ -586,36 +303,36 @@ void CModHandler::load() content->loadCustom(); - for(const TModID & modName : activeMods) + for(const TModID & modName : getActiveMods()) loadTranslation(modName); - logMod->info("\tLoading mod data: %d ms", timer.getDiff()); + logMod->info("\tLoading mod data"); VLC->creh->loadCrExpMod(); VLC->identifiersHandler->finalize(); - logMod->info("\tResolving identifiers: %d ms", timer.getDiff()); + logMod->info("\tResolving identifiers"); content->afterLoadFinalization(); - logMod->info("\tHandlers post-load finalization: %d ms ", timer.getDiff()); - logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff()); + logMod->info("\tHandlers post-load finalization"); + logMod->info("\tAll game content loaded"); } void CModHandler::afterLoad(bool onlyEssential) { - JsonNode modSettings; - for (auto & modEntry : allMods) - { - std::string pointer = "/" + boost::algorithm::replace_all_copy(modEntry.first, ".", "/mods/"); + //JsonNode modSettings; + //for (auto & modEntry : getActiveMods()) + //{ + // std::string pointer = "/" + boost::algorithm::replace_all_copy(modEntry.first, ".", "/mods/"); - modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData(); - } - modSettings[ModScope::scopeBuiltin()] = coreMod->saveLocalData(); - modSettings[ModScope::scopeBuiltin()]["name"].String() = "Original game files"; + // modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData(); + //} + //modSettings[ModScope::scopeBuiltin()] = coreMod->saveLocalData(); + //modSettings[ModScope::scopeBuiltin()]["name"].String() = "Original game files"; - if(!onlyEssential) - { - std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); - file << modSettings.toString(); - } + //if(!onlyEssential) + //{ + // std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); + // file << modSettings.toString(); + //} } VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index ccc4bc540..1ea49daef 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -12,51 +12,26 @@ VCMI_LIB_NAMESPACE_BEGIN class CModHandler; -class CModIdentifier; -class CModInfo; -struct CModVersion; -class JsonNode; -class IHandlerBase; -class CIdentifierStorage; +class ModDescription; class CContentHandler; -struct ModVerificationInfo; class ResourcePath; -class MetaString; +class ModManager; +class ISimpleResourceLoader; using TModID = std::string; class DLL_LINKAGE CModHandler final : boost::noncopyable { - std::map allMods; - std::vector activeMods;//active mods, in order in which they were loaded - std::unique_ptr coreMod; - mutable std::unique_ptr modLoadErrors; + std::unique_ptr modManager; - bool hasCircularDependency(const TModID & mod, std::set currentList = std::set()) const; - - /** - * 1. Set apart mods with resolved dependencies from mods which have unresolved dependencies - * 2. Sort resolved mods using topological algorithm - * 3. Log all problem mods and their unresolved dependencies - * - * @param modsToResolve list of valid mod IDs (checkDependencies returned true - TODO: Clarify it.) - * @return a vector of the topologically sorted resolved mods: child nodes (dependent mods) have greater index than parents - */ - std::vector validateAndSortDependencies(std::vector modsToResolve) const; - - std::vector getModList(const std::string & path) const; - void loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods); - void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods); void loadTranslation(const TModID & modName); - - CModVersion getModVersion(TModID modName) const; + void checkModFilesystemsConflicts(const std::map & modFilesystems); public: - std::shared_ptr content; //(!)Do not serialize FIXME: make private + std::shared_ptr content; //FIXME: make private /// receives list of available mods and trying to load mod.json from all of them void initializeConfig(); - void loadMods(); void loadModFilesystems(); /// returns ID of mod that provides selected file resource @@ -82,7 +57,7 @@ public: /// Returns human-readable string that describes errors encounter during mod loading, such as missing dependencies std::string getModLoadErrors() const; - const CModInfo & getModInfo(const TModID & modId) const; + const ModDescription & getModInfo(const TModID & modId) const; /// load content from all available mods void load(); diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp deleted file mode 100644 index aa39b4e7d..000000000 --- a/lib/modding/CModInfo.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - * CModInfo.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "CModInfo.h" - -#include "../texts/CGeneralTextHandler.h" -#include "../VCMI_Lib.h" -#include "../filesystem/Filesystem.h" - -VCMI_LIB_NAMESPACE_BEGIN - -static JsonNode addMeta(JsonNode config, const std::string & meta) -{ - config.setModScope(meta); - return config; -} - -std::set CModInfo::readModList(const JsonNode & input) -{ - std::set result; - - for (auto const & string : input.convertTo>()) - result.insert(boost::to_lower_copy(string)); - - return result; -} - -CModInfo::CModInfo(): - explicitlyEnabled(false), - implicitlyEnabled(true), - validation(PENDING) -{ - -} - -CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive): - identifier(identifier), - dependencies(readModList(config["depends"])), - softDependencies(readModList(config["softDepends"])), - conflicts(readModList(config["conflicts"])), - explicitlyEnabled(isActive), - implicitlyEnabled(true), - validation(PENDING), - config(addMeta(config, identifier)) -{ - if (!config["name"].String().empty()) - verificationInfo.name = config["name"].String(); - else - verificationInfo.name = identifier; - - verificationInfo.version = CModVersion::fromString(config["version"].String()); - verificationInfo.parent = identifier.substr(0, identifier.find_last_of('.')); - if(verificationInfo.parent == identifier) - verificationInfo.parent.clear(); - - if(!config["compatibility"].isNull()) - { - vcmiCompatibleMin = CModVersion::fromString(config["compatibility"]["min"].String()); - vcmiCompatibleMax = CModVersion::fromString(config["compatibility"]["max"].String()); - } - - if (!config["language"].isNull()) - baseLanguage = config["language"].String(); - else - baseLanguage = "english"; - - loadLocalData(local); -} - -JsonNode CModInfo::saveLocalData() const -{ - std::ostringstream stream; - stream << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << verificationInfo.checksum; - - JsonNode conf; - conf["active"].Bool() = explicitlyEnabled; - conf["validated"].Bool() = validation != FAILED; - conf["checksum"].String() = stream.str(); - return conf; -} - -std::string CModInfo::getModDir(const std::string & name) -{ - return "MODS/" + boost::algorithm::replace_all_copy(name, ".", "/MODS/"); -} - -JsonPath CModInfo::getModFile(const std::string & name) -{ - return JsonPath::builtinTODO(getModDir(name) + "/mod.json"); -} - -void CModInfo::updateChecksum(ui32 newChecksum) -{ - // comment-out next line to force validation of all mods ignoring checksum - if (newChecksum != verificationInfo.checksum) - { - verificationInfo.checksum = newChecksum; - validation = PENDING; - } -} - -void CModInfo::loadLocalData(const JsonNode & data) -{ - bool validated = false; - implicitlyEnabled = true; - verificationInfo.checksum = 0; - if (data.isStruct()) - { - validated = data["validated"].Bool(); - updateChecksum(strtol(data["checksum"].String().c_str(), nullptr, 16)); - } - - //check compatibility - implicitlyEnabled &= (vcmiCompatibleMin.isNull() || CModVersion::GameVersion().compatible(vcmiCompatibleMin, true, true)); - implicitlyEnabled &= (vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(CModVersion::GameVersion(), true, true)); - - if(!implicitlyEnabled) - logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", verificationInfo.name); - - if (config["modType"].String() == "Translation") - { - if (baseLanguage != CGeneralTextHandler::getPreferredLanguage()) - { - if (identifier.find_last_of('.') == std::string::npos) - logGlobal->warn("Translation mod %s was not loaded: language mismatch!", verificationInfo.name); - implicitlyEnabled = false; - } - } - if (config["modType"].String() == "Compatibility") - { - // compatibility mods are always explicitly enabled - // however they may be implicitly disabled - if one of their dependencies is missing - explicitlyEnabled = true; - } - - if (isEnabled()) - validation = validated ? PASSED : PENDING; - else - validation = validated ? PASSED : FAILED; - - verificationInfo.impactsGameplay = checkModGameplayAffecting(); -} - -bool CModInfo::checkModGameplayAffecting() const -{ - if (modGameplayAffecting.has_value()) - return *modGameplayAffecting; - - static const std::vector keysToTest = { - "heroClasses", - "artifacts", - "creatures", - "factions", - "objects", - "heroes", - "spells", - "skills", - "templates", - "scripts", - "battlefields", - "terrains", - "rivers", - "roads", - "obstacles" - }; - - JsonPath modFileResource(CModInfo::getModFile(identifier)); - - if(CResourceHandler::get("initial")->existsResource(modFileResource)) - { - const JsonNode modConfig(modFileResource); - - for(const auto & key : keysToTest) - { - if (!modConfig[key].isNull()) - { - modGameplayAffecting = true; - return *modGameplayAffecting; - } - } - } - modGameplayAffecting = false; - return *modGameplayAffecting; -} - -const ModVerificationInfo & CModInfo::getVerificationInfo() const -{ - assert(!verificationInfo.name.empty()); - return verificationInfo; -} - -bool CModInfo::isEnabled() const -{ - return implicitlyEnabled && explicitlyEnabled; -} - -VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h deleted file mode 100644 index fd0f8319e..000000000 --- a/lib/modding/CModInfo.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * CModInfo.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../json/JsonNode.h" -#include "ModVerificationInfo.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class DLL_LINKAGE CModInfo -{ - /// cached result of checkModGameplayAffecting() call - /// Do not serialize - depends on local mod version, not server/save mod version - mutable std::optional modGameplayAffecting; - - static std::set readModList(const JsonNode & input); -public: - enum EValidationStatus - { - PENDING, - FAILED, - PASSED - }; - - /// identifier, identical to name of folder with mod - std::string identifier; - - /// detailed mod description - std::string description; - - /// Base language of mod, all mod strings are assumed to be in this language - std::string baseLanguage; - - /// vcmi versions compatible with the mod - CModVersion vcmiCompatibleMin, vcmiCompatibleMax; - - /// list of mods that should be loaded before this one - std::set dependencies; - - /// list of mods if they are enabled, should be loaded before this one. this mod will overwrite any conflicting items from its soft dependency mods - std::set softDependencies; - - /// list of mods that can't be used in the same time as this one - std::set conflicts; - - EValidationStatus validation; - - JsonNode config; - - CModInfo(); - CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive); - - JsonNode saveLocalData() const; - void updateChecksum(ui32 newChecksum); - - bool isEnabled() const; - - static std::string getModDir(const std::string & name); - static JsonPath getModFile(const std::string & name); - - /// return true if this mod can affect gameplay, e.g. adds or modifies any game objects - bool checkModGameplayAffecting() const; - - const ModVerificationInfo & getVerificationInfo() const; - -private: - /// true if mod is enabled by user, e.g. in Launcher UI - bool explicitlyEnabled; - - /// true if mod can be loaded - compatible and has no missing deps - bool implicitlyEnabled; - - ModVerificationInfo verificationInfo; - - void loadLocalData(const JsonNode & data); -}; - -VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 2696080fe..04f21b4f1 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -11,7 +11,8 @@ #include "ContentTypeHandler.h" #include "CModHandler.h" -#include "CModInfo.h" +#include "ModDescription.h" +#include "ModManager.h" #include "ModScope.h" #include "../BattleFieldHandler.h" @@ -294,39 +295,43 @@ void CContentHandler::afterLoadFinalization() } } -void CContentHandler::preloadData(CModInfo & mod) +void CContentHandler::preloadData(const ModDescription & mod) { - bool validate = validateMod(mod); + preloadModData(mod.getID(), mod.getConfig(), false); - // print message in format [<8-symbols checksum>] - auto & info = mod.getVerificationInfo(); - logMod->info("\t\t[%08x]%s", info.checksum, info.name); - - if (validate && mod.identifier != ModScope::scopeBuiltin()) - { - if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier)) - mod.validation = CModInfo::FAILED; - } - if (!preloadModData(mod.identifier, mod.config, validate)) - mod.validation = CModInfo::FAILED; +// bool validate = validateMod(mod); +// +// // print message in format [<8-symbols checksum>] +// auto & info = mod.getVerificationInfo(); +// logMod->info("\t\t[%08x]%s", info.checksum, info.name); +// +// if (validate && mod.identifier != ModScope::scopeBuiltin()) +// { +// if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier)) +// mod.validation = CModInfo::FAILED; +// } +// if (!preloadModData(mod.identifier, mod.config, validate)) +// mod.validation = CModInfo::FAILED; } -void CContentHandler::load(CModInfo & mod) +void CContentHandler::load(const ModDescription & mod) { - bool validate = validateMod(mod); + loadMod(mod.getID(), false); - if (!loadMod(mod.identifier, validate)) - mod.validation = CModInfo::FAILED; - - if (validate) - { - if (mod.validation != CModInfo::FAILED) - logMod->info("\t\t[DONE] %s", mod.getVerificationInfo().name); - else - logMod->error("\t\t[FAIL] %s", mod.getVerificationInfo().name); - } - else - logMod->info("\t\t[SKIP] %s", mod.getVerificationInfo().name); +// bool validate = validateMod(mod); +// +// if (!loadMod(mod.identifier, validate)) +// mod.validation = CModInfo::FAILED; +// +// if (validate) +// { +// if (mod.validation != CModInfo::FAILED) +// logMod->info("\t\t[DONE] %s", mod.getVerificationInfo().name); +// else +// logMod->error("\t\t[FAIL] %s", mod.getVerificationInfo().name); +// } +// else +// logMod->info("\t\t[SKIP] %s", mod.getVerificationInfo().name); } const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const @@ -334,13 +339,13 @@ const ContentTypeHandler & CContentHandler::operator[](const std::string & name) return handlers.at(name); } -bool CContentHandler::validateMod(const CModInfo & mod) const +bool CContentHandler::validateMod(const ModDescription & mod) const { if (settings["mods"]["validation"].String() == "full") return true; - if (mod.validation == CModInfo::PASSED) - return false; +// if (mod.validation == CModInfo::PASSED) +// return false; if (settings["mods"]["validation"].String() == "off") return false; diff --git a/lib/modding/ContentTypeHandler.h b/lib/modding/ContentTypeHandler.h index a623cb226..746552389 100644 --- a/lib/modding/ContentTypeHandler.h +++ b/lib/modding/ContentTypeHandler.h @@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN class IHandlerBase; -class CModInfo; +class ModDescription; /// internal type to handle loading of one data type (e.g. artifacts, creatures) class DLL_LINKAGE ContentTypeHandler @@ -58,15 +58,15 @@ class DLL_LINKAGE CContentHandler std::map handlers; - bool validateMod(const CModInfo & mod) const; + bool validateMod(const ModDescription & mod) const; public: void init(); /// preloads all data from fileList as data from modName. - void preloadData(CModInfo & mod); + void preloadData(const ModDescription & mod); /// actually loads data in mod - void load(CModInfo & mod); + void load(const ModDescription & mod); void loadCustom(); diff --git a/lib/modding/ModDescription.cpp b/lib/modding/ModDescription.cpp new file mode 100644 index 000000000..51dfb0f03 --- /dev/null +++ b/lib/modding/ModDescription.cpp @@ -0,0 +1,114 @@ +/* + * ModDescription.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ModDescription.h" + +#include "CModVersion.h" +#include "ModVerificationInfo.h" + +#include "../json/JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +ModDescription::ModDescription(const TModID & fullID, const JsonNode & config) + : identifier(fullID) + , config(std::make_unique(config)) + , dependencies(loadModList(config["depends"])) + , softDependencies(loadModList(config["softDepends"])) + , conflicts(loadModList(config["conflicts"])) +{ + if(getID() != "core") + dependencies.insert("core"); +} + +ModDescription::~ModDescription() = default; + +TModSet ModDescription::loadModList(const JsonNode & configNode) const +{ + TModSet result; + for(const auto & entry : configNode.Vector()) + result.insert(boost::algorithm::to_lower_copy(entry.String())); + return result; +} + +const TModID & ModDescription::getID() const +{ + return identifier; +} + +TModID ModDescription::getParentID() const +{ + size_t dotPos = identifier.find_last_of('.'); + + if(dotPos == std::string::npos) + return {}; + + return identifier.substr(0, dotPos); +} + +const TModSet & ModDescription::getDependencies() const +{ + return dependencies; +} + +const TModSet & ModDescription::getSoftDependencies() const +{ + return softDependencies; +} + +const TModSet & ModDescription::getConflicts() const +{ + return conflicts; +} + +const std::string & ModDescription::getBaseLanguage() const +{ + static const std::string defaultLanguage = "english"; + + return getConfig()["language"].isString() ? getConfig()["language"].String() : defaultLanguage; +} + +const std::string & ModDescription::getName() const +{ + return getConfig()["name"].String(); +} + +const JsonNode & ModDescription::getFilesystemConfig() const +{ + return getConfig()["filesystem"]; +} + +const JsonNode & ModDescription::getConfig() const +{ + return *config; +} + +CModVersion ModDescription::getVersion() const +{ + return CModVersion::fromString(getConfig()["version"].String()); +} + +ModVerificationInfo ModDescription::getVerificationInfo() const +{ + ModVerificationInfo result; + result.name = getName(); + result.version = getVersion(); + result.impactsGameplay = affectsGameplay(); + result.parent = getParentID(); + + return result; +} + +bool ModDescription::affectsGameplay() const +{ + return false; // TODO +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ModDescription.h b/lib/modding/ModDescription.h new file mode 100644 index 000000000..ccb1b5947 --- /dev/null +++ b/lib/modding/ModDescription.h @@ -0,0 +1,56 @@ +/* + * ModDescription.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +struct CModVersion; +struct ModVerificationInfo; +class JsonNode; + +using TModID = std::string; +using TModList = std::vector; +using TModSet = std::set; + +class DLL_LINKAGE ModDescription : boost::noncopyable +{ + TModID identifier; + TModSet dependencies; + TModSet softDependencies; + TModSet conflicts; + + std::unique_ptr config; + + TModSet loadModList(const JsonNode & configNode) const; + +public: + ModDescription(const TModID & fullID, const JsonNode & config); + ~ModDescription(); + + const TModID & getID() const; + TModID getParentID() const; + + const TModSet & getDependencies() const; + const TModSet & getSoftDependencies() const; + const TModSet & getConflicts() const; + + const std::string & getBaseLanguage() const; + const std::string & getName() const; + + const JsonNode & getFilesystemConfig() const; + const JsonNode & getConfig() const; + + CModVersion getVersion() const; + ModVerificationInfo getVerificationInfo() const; + + bool affectsGameplay() const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ModManager.cpp b/lib/modding/ModManager.cpp new file mode 100644 index 000000000..011b3bae9 --- /dev/null +++ b/lib/modding/ModManager.cpp @@ -0,0 +1,367 @@ +/* + * ModManager.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ModManager.h" + +#include "ModDescription.h" +#include "ModScope.h" + +#include "../filesystem/Filesystem.h" +#include "../json/JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +static std::string getModSettingsDirectory(const TModID & modName) +{ + std::string result = modName; + boost::to_upper(result); + boost::algorithm::replace_all(result, ".", "/MODS/"); + return "MODS/" + result + "/MODS/"; +} + +static JsonPath getModDescriptionFile(const TModID & modName) +{ + std::string result = modName; + boost::to_upper(result); + boost::algorithm::replace_all(result, ".", "/MODS/"); + return JsonPath::builtin("MODS/" + result + "/mod"); +} + +ModsState::ModsState() +{ + modList.push_back(ModScope::scopeBuiltin()); + + std::vector testLocations = scanModsDirectory("MODS/"); + + while(!testLocations.empty()) + { + std::string target = testLocations.back(); + testLocations.pop_back(); + modList.push_back(boost::algorithm::to_lower_copy(target)); + + for(const auto & submod : scanModsDirectory(getModSettingsDirectory(target))) + testLocations.push_back(target + '.' + submod); + + // TODO: check that this is vcmi mod and not era mod? + // TODO: check that mod name is not reserved (ModScope::isScopeReserved(modFullName))) + } +} + +TModList ModsState::getAllMods() const +{ + return modList; +} + +std::vector ModsState::scanModsDirectory(const std::string & modDir) const +{ + size_t depth = boost::range::count(modDir, '/'); + + const auto & modScanFilter = [&](const ResourcePath & id) -> bool + { + if(id.getType() != EResType::DIRECTORY) + return false; + if(!boost::algorithm::starts_with(id.getName(), modDir)) + return false; + if(boost::range::count(id.getName(), '/') != depth) + return false; + return true; + }; + + auto list = CResourceHandler::get("initial")->getFilteredFiles(modScanFilter); + + //storage for found mods + std::vector foundMods; + for(const auto & entry : list) + { + std::string name = entry.getName(); + name.erase(0, modDir.size()); //Remove path prefix + + if(name.empty()) + continue; + + if(name.find('.') != std::string::npos) + continue; + + if(!CResourceHandler::get("initial")->existsResource(JsonPath::builtin(entry.getName() + "/MOD"))) + continue; + + foundMods.push_back(name); + } + return foundMods; +} + +/////////////////////////////////////////////////////////////////////////////// + +static JsonNode loadModSettings(const JsonPath & path) +{ + if(CResourceHandler::get("local")->existsResource(ResourcePath(path))) + { + return JsonNode(path); + } + // Probably new install. Create initial configuration + CResourceHandler::get("local")->createResource(path.getOriginalName() + ".json"); + return JsonNode(); +} + +ModsPresetState::ModsPresetState() +{ + modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); + + if(modConfig["presets"].isNull()) + { + modConfig["activePreset"] = JsonNode("default"); + if(modConfig["activeMods"].isNull()) + createInitialPreset(); // new install + else + importInitialPreset(); // 1.5 format import + + saveConfiguration(modConfig); + } +} + +void ModsPresetState::saveConfiguration(const JsonNode & modSettings) +{ + std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); + file << modSettings.toString(); +} + +void ModsPresetState::createInitialPreset() +{ + // TODO: scan mods directory for all its content? Probably unnecessary since this looks like new install, but who knows? + modConfig["presets"]["default"]["mods"].Vector().push_back(JsonNode("vcmi")); +} + +void ModsPresetState::importInitialPreset() +{ + JsonNode preset; + + for(const auto & mod : modConfig["activeMods"].Struct()) + { + if(mod.second["active"].Bool()) + preset["mods"].Vector().push_back(JsonNode(mod.first)); + + for(const auto & submod : mod.second["mods"].Struct()) + preset["settings"][mod.first][submod.first] = submod.second["active"]; + } + modConfig["presets"]["default"] = preset; +} + +std::vector ModsPresetState::getActiveMods() const +{ + const std::string & currentPresetName = modConfig["activePreset"].String(); + const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; + const JsonNode & modsToActivateJson = currentPreset["mods"]; + std::vector modsToActivate = modsToActivateJson.convertTo>(); + + modsToActivate.push_back(ModScope::scopeBuiltin()); + + for(const auto & settings : currentPreset["settings"].Struct()) + { + if(!vstd::contains(modsToActivate, settings.first)) + continue; // settings for inactive mod + + for(const auto & submod : settings.second.Struct()) + if(submod.second.Bool()) + modsToActivate.push_back(settings.first + '.' + submod.first); + } + return modsToActivate; +} + +ModsStorage::ModsStorage(const std::vector & modsToLoad) +{ + JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json")); + coreModConfig.setModScope(ModScope::scopeBuiltin()); + mods.try_emplace(ModScope::scopeBuiltin(), ModScope::scopeBuiltin(), coreModConfig); + + for(auto modID : modsToLoad) + { + if(ModScope::isScopeReserved(modID)) + { + logMod->error("Can not load mod %s - this name is reserved for internal use!", modID); + continue; + } + + JsonNode modConfig(getModDescriptionFile(modID)); + modConfig.setModScope(modID); + + if(modConfig["modType"].isNull()) + { + logMod->error("Can not load mod %s - invalid mod config file!", modID); + continue; + } + + mods.try_emplace(modID, modID, modConfig); + } +} + +const ModDescription & ModsStorage::getMod(const TModID & fullID) const +{ + return mods.at(fullID); +} + +ModManager::ModManager() + : modsState(std::make_unique()) + , modsPreset(std::make_unique()) +{ + std::vector desiredModList = modsPreset->getActiveMods(); + const std::vector & installedModList = modsState->getAllMods(); + + vstd::erase_if(desiredModList, [&](const TModID & mod){ + return !vstd::contains(installedModList, mod); + }); + + modsStorage = std::make_unique(desiredModList); + + generateLoadOrder(desiredModList); +} + +ModManager::~ModManager() = default; + +const ModDescription & ModManager::getModDescription(const TModID & modID) const +{ + return modsStorage->getMod(modID); +} + +bool ModManager::isModActive(const TModID & modID) const +{ + return vstd::contains(activeMods, modID); +} + +const TModList & ModManager::getActiveMods() const +{ + return activeMods; +} + +void ModManager::generateLoadOrder(std::vector modsToResolve) +{ + // Topological sort algorithm. + boost::range::sort(modsToResolve); // Sort mods per name + std::vector sortedValidMods; // Vector keeps order of elements (LIFO) + sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation + std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements + std::set notResolvedModIDs(modsToResolve.begin(), modsToResolve.end()); // Use a set for validation for performance reason + + // Mod is resolved if it has no dependencies or all its dependencies are already resolved + auto isResolved = [&](const ModDescription & mod) -> bool + { + if(mod.getDependencies().size() > resolvedModIDs.size()) + return false; + + for(const TModID & dependency : mod.getDependencies()) + if(!vstd::contains(resolvedModIDs, dependency)) + return false; + + for(const TModID & softDependency : mod.getSoftDependencies()) + if(vstd::contains(notResolvedModIDs, softDependency)) + return false; + + for(const TModID & conflict : mod.getConflicts()) + if(vstd::contains(resolvedModIDs, conflict)) + return false; + + for(const TModID & reverseConflict : resolvedModIDs) + if(vstd::contains(modsStorage->getMod(reverseConflict).getConflicts(), mod.getID())) + return false; + + return true; + }; + + while(true) + { + std::set resolvedOnCurrentTreeLevel; + for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree + { + if(isResolved(modsStorage->getMod(*it))) + { + resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node children will be resolved on the next iteration + sortedValidMods.push_back(*it); + it = modsToResolve.erase(it); + continue; + } + it++; + } + if(!resolvedOnCurrentTreeLevel.empty()) + { + resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end()); + for(const auto & it : resolvedOnCurrentTreeLevel) + notResolvedModIDs.erase(it); + continue; + } + // If there are no valid mods on the current mods tree level, no more mod can be resolved, should be ended. + break; + } + + activeMods = sortedValidMods; + brokenMods = modsToResolve; +} + +// modLoadErrors = std::make_unique(); +// +// auto addErrorMessage = [this](const std::string & textID, const std::string & brokenModID, const std::string & missingModID) +// { +// modLoadErrors->appendTextID(textID); +// +// if (allMods.count(brokenModID)) +// modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); +// else +// modLoadErrors->replaceRawString(brokenModID); +// +// if (allMods.count(missingModID)) +// modLoadErrors->replaceRawString(allMods.at(missingModID).getVerificationInfo().name); +// else +// modLoadErrors->replaceRawString(missingModID); +// +// }; +// +// // Left mods have unresolved dependencies, output all to log. +// for(const auto & brokenModID : modsToResolve) +// { +// const CModInfo & brokenMod = allMods.at(brokenModID); +// bool showErrorMessage = false; +// for(const TModID & dependency : brokenMod.dependencies) +// { +// if(!vstd::contains(resolvedModIDs, dependency) && brokenMod.config["modType"].String() != "Compatibility") +// { +// addErrorMessage("vcmi.server.errors.modNoDependency", brokenModID, dependency); +// showErrorMessage = true; +// } +// } +// for(const TModID & conflict : brokenMod.conflicts) +// { +// if(vstd::contains(resolvedModIDs, conflict)) +// { +// addErrorMessage("vcmi.server.errors.modConflict", brokenModID, conflict); +// showErrorMessage = true; +// } +// } +// for(const TModID & reverseConflict : resolvedModIDs) +// { +// if (vstd::contains(allMods.at(reverseConflict).conflicts, brokenModID)) +// { +// addErrorMessage("vcmi.server.errors.modConflict", brokenModID, reverseConflict); +// showErrorMessage = true; +// } +// } +// +// // some mods may in a (soft) dependency loop. +// if(!showErrorMessage && brokenMod.config["modType"].String() != "Compatibility") +// { +// modLoadErrors->appendTextID("vcmi.server.errors.modDependencyLoop"); +// if (allMods.count(brokenModID)) +// modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); +// else +// modLoadErrors->replaceRawString(brokenModID); +// } +// +// } +// return sortedValidMods; +//} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ModManager.h b/lib/modding/ModManager.h new file mode 100644 index 000000000..67888920d --- /dev/null +++ b/lib/modding/ModManager.h @@ -0,0 +1,95 @@ +/* + * ModManager.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../json/JsonNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class JsonNode; +class ModDescription; +struct CModVersion; + +using TModID = std::string; +using TModList = std::vector; +using TModSet = std::set; + +/// Provides interface to access list of locally installed mods +class ModsState : boost::noncopyable +{ + TModList modList; + + TModList scanModsDirectory(const std::string & modDir) const; + +public: + ModsState(); + + TModList getAllMods() const; +}; + +/// Provides interface to access or change current mod preset +class ModsPresetState : boost::noncopyable +{ + JsonNode modConfig; + + void saveConfiguration(const JsonNode & config); + + void createInitialPreset(); + void importInitialPreset(); + +public: + ModsPresetState(); + + /// Returns true if mod is active in current preset + bool isModActive(const TModID & modName) const; + + void activateModInPreset(const TModID & modName); + void dectivateModInAllPresets(const TModID & modName); + + /// Returns list of all mods active in current preset. Mod order is unspecified + TModList getActiveMods() const; +}; + +/// Provides access to mod properties +class ModsStorage : boost::noncopyable +{ + std::map mods; + +public: + ModsStorage(const TModList & modsToLoad); + + const ModDescription & getMod(const TModID & fullID) const; +}; + +/// Provides public interface to access mod state +class ModManager : boost::noncopyable +{ + /// all currently active mods, in their load order + TModList activeMods; + + /// Mods from current preset that failed to load due to invalid dependencies + TModList brokenMods; + + std::unique_ptr modsState; + std::unique_ptr modsPreset; + std::unique_ptr modsStorage; + + void generateLoadOrder(TModList desiredModList); + +public: + ModManager(); + ~ModManager(); + + const ModDescription & getModDescription(const TModID & modID) const; + const TModList & getActiveMods() const; + bool isModActive(const TModID & modID) const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ModVerificationInfo.cpp b/lib/modding/ModVerificationInfo.cpp index db2c7370a..ab187cd15 100644 --- a/lib/modding/ModVerificationInfo.cpp +++ b/lib/modding/ModVerificationInfo.cpp @@ -10,8 +10,8 @@ #include "StdInc.h" #include "ModVerificationInfo.h" -#include "CModInfo.h" #include "CModHandler.h" +#include "ModDescription.h" #include "ModIncompatibility.h" #include "../json/JsonNode.h" @@ -68,7 +68,7 @@ ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const if(modList.count(m)) continue; - if(VLC->modh->getModInfo(m).checkModGameplayAffecting()) + if(VLC->modh->getModInfo(m).affectsGameplay()) result[m] = ModVerificationStatus::EXCESSIVE; } @@ -88,8 +88,8 @@ ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const continue; } - auto & localModInfo = VLC->modh->getModInfo(remoteModId).getVerificationInfo(); - modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).checkModGameplayAffecting(); + const auto & localVersion = VLC->modh->getModInfo(remoteModId).getVersion(); + modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).affectsGameplay(); // skip it. Such mods should only be present in old saves or if mod changed and no longer affects gameplay if (!modAffectsGameplay) @@ -101,7 +101,7 @@ ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const continue; } - if(remoteModInfo.version != localModInfo.version) + if(remoteModInfo.version != localVersion) { result[remoteModId] = ModVerificationStatus::VERSION_MISMATCH; continue; diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 7b50a9e09..bc10d558d 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -23,7 +23,7 @@ #include "../lib/mapping/CMapEditManager.h" #include "../lib/mapping/ObstacleProxy.h" #include "../lib/modding/CModHandler.h" -#include "../lib/modding/CModInfo.h" +#include "../lib/modding/ModDescription.h" #include "../lib/TerrainHandler.h" #include "../lib/CSkillHandler.h" #include "../lib/spells/CSpellHandler.h" diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 7b8a246eb..55235f05f 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -13,9 +13,8 @@ #include "maphandler.h" #include "mapview.h" -#include "../lib/modding/CModInfo.h" - VCMI_LIB_NAMESPACE_BEGIN +struct ModVerificationInfo; using ModCompatibilityInfo = std::map; class EditorObstaclePlacer; VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/mapsettings/modsettings.cpp b/mapeditor/mapsettings/modsettings.cpp index 330cb9c82..931566fb7 100644 --- a/mapeditor/mapsettings/modsettings.cpp +++ b/mapeditor/mapsettings/modsettings.cpp @@ -11,9 +11,9 @@ #include "modsettings.h" #include "ui_modsettings.h" #include "../mapcontroller.h" +#include "../../lib/modding/ModDescription.h" #include "../../lib/modding/CModHandler.h" #include "../../lib/mapping/CMapService.h" -#include "../../lib/modding/CModInfo.h" void traverseNode(QTreeWidgetItem * item, std::function action) { @@ -45,12 +45,12 @@ void ModSettings::initialize(MapController & c) QSet modsToProcess; ui->treeMods->blockSignals(true); - auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const CModInfo & modInfo) + auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const ModDescription & modInfo) { - auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.getVerificationInfo().name), QString::fromStdString(modInfo.getVerificationInfo().version.toString())}); - item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.identifier))); + auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.getName()), QString::fromStdString(modInfo.getVersion().toString())}); + item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.getID()))); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(0, controller->map()->mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked); + item->setCheckState(0, controller->map()->mods.count(modInfo.getID()) ? Qt::Checked : Qt::Unchecked); //set parent check if(parent && item->checkState(0) == Qt::Checked) parent->setCheckState(0, Qt::Checked); diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index 17c1d2dcc..a2d965e49 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -16,7 +16,7 @@ #include "../lib/mapping/CMap.h" #include "../lib/mapObjects/MapObjects.h" #include "../lib/modding/CModHandler.h" -#include "../lib/modding/CModInfo.h" +#include "../lib/modding/ModDescription.h" #include "../lib/spells/CSpellHandler.h" Validator::Validator(const CMap * map, QWidget *parent) : diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 15fd376f7..418fad4da 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -15,7 +15,8 @@ #include "../lib/json/JsonUtils.h" #include "../lib/VCMI_Lib.h" #include "../lib/modding/CModHandler.h" -#include "../lib/modding/CModInfo.h" +#include "../lib/modding/ModDescription.h" +#include "../lib/modding/ModVerificationInfo.h" GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner) : owner(owner) @@ -161,7 +162,7 @@ JsonNode GlobalLobbyProcessor::getHostModList() const for (auto const & modName : VLC->modh->getActiveMods()) { - if(VLC->modh->getModInfo(modName).checkModGameplayAffecting()) + if(VLC->modh->getModInfo(modName).affectsGameplay()) info[modName] = VLC->modh->getModInfo(modName).getVerificationInfo(); } From 4945370fe322238c3745e18a5174c76ec0882c56 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 11 Nov 2024 11:19:14 +0000 Subject: [PATCH 654/726] Implemented validation of preset - removal of non-existing mods, addition of newly installed mods --- lib/json/JsonNode.h | 2 +- lib/modding/ModDescription.cpp | 17 ++++- lib/modding/ModDescription.h | 3 + lib/modding/ModManager.cpp | 125 +++++++++++++++++++++++++++------ lib/modding/ModManager.h | 18 +++-- 5 files changed, 138 insertions(+), 27 deletions(-) diff --git a/lib/json/JsonNode.h b/lib/json/JsonNode.h index 7ac55f57e..037148e9b 100644 --- a/lib/json/JsonNode.h +++ b/lib/json/JsonNode.h @@ -187,7 +187,7 @@ void convert(std::map & value, const JsonNode & node) { value.clear(); for(const JsonMap::value_type & entry : node.Struct()) - value.insert(entry.first, entry.second.convertTo()); + value.emplace(entry.first, entry.second.convertTo()); } template diff --git a/lib/modding/ModDescription.cpp b/lib/modding/ModDescription.cpp index 51dfb0f03..8378290c1 100644 --- a/lib/modding/ModDescription.cpp +++ b/lib/modding/ModDescription.cpp @@ -106,9 +106,24 @@ ModVerificationInfo ModDescription::getVerificationInfo() const return result; } +bool ModDescription::isCompatibility() const +{ + return getConfig()["modType"].String() == "Compatibility"; +} + +bool ModDescription::isTranslation() const +{ + return getConfig()["modType"].String() == "Translation"; +} + +bool ModDescription::keepDisabled() const +{ + return getConfig()["keepDisabled"].Bool(); +} + bool ModDescription::affectsGameplay() const { - return false; // TODO + return true; // TODO } VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ModDescription.h b/lib/modding/ModDescription.h index ccb1b5947..d70d2d93b 100644 --- a/lib/modding/ModDescription.h +++ b/lib/modding/ModDescription.h @@ -51,6 +51,9 @@ public: ModVerificationInfo getVerificationInfo() const; bool affectsGameplay() const; + bool isCompatibility() const; + bool isTranslation() const; + bool keepDisabled() const; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ModManager.cpp b/lib/modding/ModManager.cpp index 011b3bae9..89934b912 100644 --- a/lib/modding/ModManager.cpp +++ b/lib/modding/ModManager.cpp @@ -15,6 +15,7 @@ #include "../filesystem/Filesystem.h" #include "../json/JsonNode.h" +#include "../texts/CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -153,27 +154,64 @@ void ModsPresetState::importInitialPreset() modConfig["presets"]["default"] = preset; } -std::vector ModsPresetState::getActiveMods() const +const JsonNode & ModsPresetState::getActivePresetConfig() const { const std::string & currentPresetName = modConfig["activePreset"].String(); const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; - const JsonNode & modsToActivateJson = currentPreset["mods"]; + return currentPreset; +} + +TModList ModsPresetState::getActiveRootMods() const +{ + const JsonNode & modsToActivateJson = getActivePresetConfig()["mods"]; std::vector modsToActivate = modsToActivateJson.convertTo>(); - modsToActivate.push_back(ModScope::scopeBuiltin()); - - for(const auto & settings : currentPreset["settings"].Struct()) - { - if(!vstd::contains(modsToActivate, settings.first)) - continue; // settings for inactive mod - - for(const auto & submod : settings.second.Struct()) - if(submod.second.Bool()) - modsToActivate.push_back(settings.first + '.' + submod.first); - } return modsToActivate; } +std::map ModsPresetState::getModSettings(const TModID & modID) const +{ + const JsonNode & modSettingsJson = getActivePresetConfig()["settings"][modID]; + std::map modSettings = modSettingsJson.convertTo>(); + return modSettings; +} + +void ModsPresetState::setSettingActiveInPreset(const TModID & modName, const TModID & settingName, bool isActive) +{ + const std::string & currentPresetName = modConfig["activePreset"].String(); + JsonNode & currentPreset = modConfig["presets"][currentPresetName]; + + currentPreset["settings"][modName][settingName].Bool() = isActive; +} + +void ModsPresetState::eraseModInAllPresets(const TModID & modName) +{ + for (auto & preset : modConfig["presets"].Struct()) + vstd::erase(preset.second["mods"].Vector(), JsonNode(modName)); +} + +void ModsPresetState::eraseModSettingInAllPresets(const TModID & modName, const TModID & settingName) +{ + for (auto & preset : modConfig["presets"].Struct()) + preset.second["settings"][modName].Struct().erase(modName); +} + +std::vector ModsPresetState::getActiveMods() const +{ + TModList activeRootMods = getActiveRootMods(); + TModList allActiveMods; + + for(const auto & activeMod : activeRootMods) + { + activeRootMods.push_back(activeMod); + + for(const auto & submod : getModSettings(activeMod)) + if(submod.second) + allActiveMods.push_back(activeMod + '.' + submod.first); + } + return allActiveMods; +} + ModsStorage::ModsStorage(const std::vector & modsToLoad) { JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json")); @@ -210,15 +248,11 @@ ModManager::ModManager() : modsState(std::make_unique()) , modsPreset(std::make_unique()) { + eraseMissingModsFromPreset(); + addNewModsToPreset(); + std::vector desiredModList = modsPreset->getActiveMods(); - const std::vector & installedModList = modsState->getAllMods(); - - vstd::erase_if(desiredModList, [&](const TModID & mod){ - return !vstd::contains(installedModList, mod); - }); - modsStorage = std::make_unique(desiredModList); - generateLoadOrder(desiredModList); } @@ -239,6 +273,54 @@ const TModList & ModManager::getActiveMods() const return activeMods; } +void ModManager::eraseMissingModsFromPreset() +{ + const TModList & installedMods = modsState->getAllMods(); + const TModList & rootMods = modsPreset->getActiveRootMods(); + + for(const auto & rootMod : rootMods) + { + if(!vstd::contains(installedMods, rootMod)) + { + modsPreset->eraseModInAllPresets(rootMod); + continue; + } + + const auto & modSettings = modsPreset->getModSettings(rootMod); + + for(const auto & modSetting : modSettings) + { + TModID fullModID = rootMod + '.' + modSetting.first; + if(!vstd::contains(installedMods, fullModID)) + { + modsPreset->eraseModSettingInAllPresets(rootMod, modSetting.first); + continue; + } + } + } +} + +void ModManager::addNewModsToPreset() +{ + const TModList & installedMods = modsState->getAllMods(); + + for(const auto & modID : installedMods) + { + size_t dotPos = modID.find('.'); + + if(dotPos == std::string::npos) + continue; // only look up submods aka mod settings + + std::string rootMod = modID.substr(0, dotPos); + std::string settingID = modID.substr(dotPos + 1); + + const auto & modSettings = modsPreset->getModSettings(rootMod); + + if (!modSettings.count(settingID)) + modsPreset->setSettingActiveInPreset(rootMod, settingID, modsStorage->getMod(modID).keepDisabled()); + } +} + void ModManager::generateLoadOrder(std::vector modsToResolve) { // Topological sort algorithm. @@ -251,6 +333,9 @@ void ModManager::generateLoadOrder(std::vector modsToResolve) // Mod is resolved if it has no dependencies or all its dependencies are already resolved auto isResolved = [&](const ModDescription & mod) -> bool { + if (mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage()) + return false; + if(mod.getDependencies().size() > resolvedModIDs.size()) return false; diff --git a/lib/modding/ModManager.h b/lib/modding/ModManager.h index 67888920d..ecd3fd801 100644 --- a/lib/modding/ModManager.h +++ b/lib/modding/ModManager.h @@ -44,17 +44,23 @@ class ModsPresetState : boost::noncopyable void createInitialPreset(); void importInitialPreset(); + const JsonNode & getActivePresetConfig() const; + public: ModsPresetState(); - /// Returns true if mod is active in current preset - bool isModActive(const TModID & modName) const; - - void activateModInPreset(const TModID & modName); - void dectivateModInAllPresets(const TModID & modName); + void setSettingActiveInPreset(const TModID & modName, const TModID & settingName, bool isActive); + void eraseModInAllPresets(const TModID & modName); + void eraseModSettingInAllPresets(const TModID & modName, const TModID & settingName); /// Returns list of all mods active in current preset. Mod order is unspecified TModList getActiveMods() const; + + /// Returns list of currently active root mods (non-submod) + TModList getActiveRootMods() const; + + /// Returns list of all known settings (submods) for a specified mod + std::map getModSettings(const TModID & modID) const; }; /// Provides access to mod properties @@ -82,6 +88,8 @@ class ModManager : boost::noncopyable std::unique_ptr modsStorage; void generateLoadOrder(TModList desiredModList); + void eraseMissingModsFromPreset(); + void addNewModsToPreset(); public: ModManager(); From f8724b95584cce89b89bebd75601b524597baf6f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 12 Nov 2024 20:00:21 +0000 Subject: [PATCH 655/726] Reworked mod handling in Launcher in order to unify code with lib --- launcher/CMakeLists.txt | 16 +- launcher/mainwindow_moc.cpp | 8 +- launcher/mainwindow_moc.h | 2 +- launcher/modManager/cmodlist.cpp | 417 ------------------ launcher/modManager/cmodlist.h | 118 ----- launcher/modManager/cmodlistview_moc.cpp | 241 +++++----- launcher/modManager/cmodlistview_moc.h | 18 +- launcher/modManager/modsettingsstorage.cpp | 191 -------- launcher/modManager/modsettingsstorage.h | 42 -- launcher/modManager/modstate.cpp | 212 +++++++++ launcher/modManager/modstate.h | 71 +++ ...cmodmanager.cpp => modstatecontroller.cpp} | 107 ++--- .../{cmodmanager.h => modstatecontroller.h} | 24 +- ...odel_moc.cpp => modstateitemmodel_moc.cpp} | 99 ++--- ...istmodel_moc.h => modstateitemmodel_moc.h} | 53 ++- launcher/modManager/modstatemodel.cpp | 73 +++ launcher/modManager/modstatemodel.h | 40 ++ lib/VCMI_Lib.cpp | 54 +-- lib/modding/CModHandler.cpp | 8 +- lib/modding/ContentTypeHandler.cpp | 2 +- lib/modding/ModDescription.cpp | 40 +- lib/modding/ModDescription.h | 12 +- lib/modding/ModManager.cpp | 19 +- lib/modding/ModManager.h | 6 +- 24 files changed, 746 insertions(+), 1127 deletions(-) delete mode 100644 launcher/modManager/cmodlist.cpp delete mode 100644 launcher/modManager/cmodlist.h delete mode 100644 launcher/modManager/modsettingsstorage.cpp delete mode 100644 launcher/modManager/modsettingsstorage.h create mode 100644 launcher/modManager/modstate.cpp create mode 100644 launcher/modManager/modstate.h rename launcher/modManager/{cmodmanager.cpp => modstatecontroller.cpp} (78%) rename launcher/modManager/{cmodmanager.h => modstatecontroller.h} (72%) rename launcher/modManager/{cmodlistmodel_moc.cpp => modstateitemmodel_moc.cpp} (70%) rename launcher/modManager/{cmodlistmodel_moc.h => modstateitemmodel_moc.h} (64%) create mode 100644 launcher/modManager/modstatemodel.cpp create mode 100644 launcher/modManager/modstatemodel.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 6e407d2f9..1dad5bc7f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -7,13 +7,13 @@ set(launcher_SRCS StdInc.cpp aboutProject/aboutproject_moc.cpp modManager/cdownloadmanager_moc.cpp - modManager/cmodlist.cpp - modManager/cmodlistmodel_moc.cpp + modManager/modstateitemmodel_moc.cpp modManager/cmodlistview_moc.cpp - modManager/cmodmanager.cpp + modManager/modstatecontroller.cpp + modManager/modstatemodel.cpp + modManager/modstate.cpp modManager/imageviewer_moc.cpp modManager/chroniclesextractor.cpp - modManager/modsettingsstorage.cpp settingsView/csettingsview_moc.cpp firstLaunch/firstlaunch_moc.cpp main.cpp @@ -38,13 +38,13 @@ set(launcher_HEADERS StdInc.h aboutProject/aboutproject_moc.h modManager/cdownloadmanager_moc.h - modManager/cmodlist.h - modManager/cmodlistmodel_moc.h + modManager/modstateitemmodel_moc.h modManager/cmodlistview_moc.h - modManager/cmodmanager.h + modManager/modstatecontroller.h + modManager/modstatemodel.h + modManager/modstate.h modManager/imageviewer_moc.h modManager/chroniclesextractor.h - modManager/modsettingsstorage.h settingsView/csettingsview_moc.h firstLaunch/firstlaunch_moc.h mainwindow_moc.h diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 488d6d73f..d2752f7d5 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -206,10 +206,10 @@ void MainWindow::on_startEditorButton_clicked() startEditor({}); } -const CModList & MainWindow::getModList() const -{ - return ui->modlistView->getModList(); -} +//const CModList & MainWindow::getModList() const +//{ +// return ui->modlistView->getModList(); +//} CModListView * MainWindow::getModView() { diff --git a/launcher/mainwindow_moc.h b/launcher/mainwindow_moc.h index 6581086ff..038460c6a 100644 --- a/launcher/mainwindow_moc.h +++ b/launcher/mainwindow_moc.h @@ -46,7 +46,7 @@ public: explicit MainWindow(QWidget * parent = nullptr); ~MainWindow() override; - const CModList & getModList() const; +// const CModList & getModList() const; CModListView * getModView(); void updateTranslation(); diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp deleted file mode 100644 index 675d598c7..000000000 --- a/launcher/modManager/cmodlist.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* - * cmodlist.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "cmodlist.h" - -#include "../lib/CConfigHandler.h" -#include "../../lib/filesystem/CFileInputStream.h" -#include "../../lib/GameConstants.h" -#include "../../lib/modding/CModVersion.h" - -QString CModEntry::sizeToString(double size) -{ - static const std::array sizes { - QT_TRANSLATE_NOOP("File size", "%1 B"), - QT_TRANSLATE_NOOP("File size", "%1 KiB"), - QT_TRANSLATE_NOOP("File size", "%1 MiB"), - QT_TRANSLATE_NOOP("File size", "%1 GiB"), - QT_TRANSLATE_NOOP("File size", "%1 TiB") - }; - size_t index = 0; - while(size > 1024 && index < sizes.size()) - { - size /= 1024; - index++; - } - return QCoreApplication::translate("File size", sizes[index]).arg(QString::number(size, 'f', 1)); -} - -CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname) - : repository(repository), localData(localData), modActive(modActive), modname(modname) -{ -} - -bool CModEntry::isEnabled() const -{ - if(!isInstalled()) - return false; - - if (!isVisible()) - return false; - - return modActive; -} - -bool CModEntry::isDisabled() const -{ - if(!isInstalled()) - return false; - return !isEnabled(); -} - -bool CModEntry::isAvailable() const -{ - if(isInstalled()) - return false; - return !repository.isEmpty(); -} - -bool CModEntry::isUpdateable() const -{ - if(!isInstalled()) - return false; - - auto installedVer = localData["installedVersion"].toString().toStdString(); - auto availableVer = repository["latestVersion"].toString().toStdString(); - - return (CModVersion::fromString(installedVer) < CModVersion::fromString(availableVer)); -} - -bool isCompatible(const QVariantMap & compatibility) -{ - auto compatibleMin = CModVersion::fromString(compatibility["min"].toString().toStdString()); - auto compatibleMax = CModVersion::fromString(compatibility["max"].toString().toStdString()); - - return (compatibleMin.isNull() || CModVersion::GameVersion().compatible(compatibleMin, true, true)) - && (compatibleMax.isNull() || compatibleMax.compatible(CModVersion::GameVersion(), true, true)); -} - -bool CModEntry::isCompatible() const -{ - return ::isCompatible(localData["compatibility"].toMap()); -} - -bool CModEntry::isEssential() const -{ - return getName() == "vcmi"; -} - -bool CModEntry::isInstalled() const -{ - return !localData.isEmpty(); -} - -bool CModEntry::isVisible() const -{ - if (isCompatibilityPatch()) - { - if (isSubmod()) - return false; - } - - if (isTranslation()) - { - // Do not show not installed translation mods to languages other than player language - if (localData.empty() && getBaseValue("language") != QString::fromStdString(settings["general"]["language"].String()) ) - return false; - } - - return !localData.isEmpty() || (!repository.isEmpty() && !repository.contains("mod")); -} - -bool CModEntry::isTranslation() const -{ - return getBaseValue("modType").toString() == "Translation"; -} - -bool CModEntry::isCompatibilityPatch() const -{ - return getBaseValue("modType").toString() == "Compatibility"; -} - -bool CModEntry::isSubmod() const -{ - return getName().contains('.'); -} - -int CModEntry::getModStatus() const -{ - int status = 0; - if(isEnabled()) - status |= ModStatus::ENABLED; - if(isInstalled()) - status |= ModStatus::INSTALLED; - if(isUpdateable()) - status |= ModStatus::UPDATEABLE; - - return status; -} - -QString CModEntry::getName() const -{ - return modname; -} - -QString CModEntry::getTopParentName() const -{ - assert(isSubmod()); - return modname.section('.', 0, 0); -} - -QVariant CModEntry::getValue(QString value) const -{ - return getValueImpl(value, true); -} - -QStringList CModEntry::getDependencies() const -{ - QStringList result; - for (auto const & entry : getValue("depends").toStringList()) - result.push_back(entry.toLower()); - return result; -} - -QStringList CModEntry::getConflicts() const -{ - QStringList result; - for (auto const & entry : getValue("conflicts").toStringList()) - result.push_back(entry.toLower()); - return result; -} - -QVariant CModEntry::getBaseValue(QString value) const -{ - return getValueImpl(value, false); -} - -QVariant CModEntry::getValueImpl(QString value, bool localized) const - -{ - QString langValue = QString::fromStdString(settings["general"]["language"].String()); - - // Priorities - // 1) data from newest version - // 2) data from preferred language - - bool useRepositoryData = repository.contains(value); - - if(repository.contains(value) && localData.contains(value)) - { - // value is present in both repo and locally installed. Select one from latest version - auto installedVer = localData["installedVersion"].toString().toStdString(); - auto availableVer = repository["latestVersion"].toString().toStdString(); - - useRepositoryData = CModVersion::fromString(installedVer) < CModVersion::fromString(availableVer); - } - - auto & storage = useRepositoryData ? repository : localData; - - if(localized && storage.contains(langValue)) - { - auto langStorage = storage[langValue].toMap(); - if (langStorage.contains(value)) - return langStorage[value]; - } - - if(storage.contains(value)) - return storage[value]; - - return QVariant(); -} - -QVariantMap CModList::copyField(QVariantMap data, QString from, QString to) const -{ - QVariantMap renamed; - - for(auto it = data.begin(); it != data.end(); it++) - { - QVariantMap modConf = it.value().toMap(); - - modConf.insert(to, modConf.value(from)); - renamed.insert(it.key(), modConf); - } - return renamed; -} - -void CModList::reloadRepositories() -{ - cachedMods.clear(); -} - -void CModList::resetRepositories() -{ - repositories.clear(); - cachedMods.clear(); -} - -void CModList::addRepository(QVariantMap data) -{ - for(auto & key : data.keys()) - data[key.toLower()] = data.take(key); - repositories.push_back(copyField(data, "version", "latestVersion")); - - cachedMods.clear(); -} - -void CModList::setLocalModList(QVariantMap data) -{ - localModList = copyField(data, "version", "installedVersion"); - cachedMods.clear(); -} - -void CModList::setModSettings(std::shared_ptr data) -{ - modSettings = data; - cachedMods.clear(); -} - -void CModList::modChanged(QString modID) -{ - cachedMods.clear(); -} - -static QVariant getValue(QVariant input, QString path) -{ - if(path.size() > 1) - { - QString entryName = path.section('/', 0, 1); - QString remainder = "/" + path.section('/', 2, -1); - - entryName.remove(0, 1); - return getValue(input.toMap().value(entryName), remainder); - } - else - { - return input; - } -} - -const CModEntry & CModList::getMod(QString modName) const -{ - modName = modName.toLower(); - - auto it = cachedMods.find(modName); - - if (it != cachedMods.end()) - return it.value(); - - auto itNew = cachedMods.insert(modName, getModUncached(modName)); - return *itNew; -} - -CModEntry CModList::getModUncached(QString modname) const -{ - QVariantMap repo; - QVariantMap local = localModList[modname].toMap(); - - QString path = modname; - path = "/" + path.replace(".", "/mods/"); - - bool modActive = modSettings->isModActive(modname); - - if(modActive) - { - QString rootModName = modname.section('.', 0, 1); - if (!modSettings->isModActive(rootModName)) - modActive = false; // parent mod is inactive -> submod is also inactive - } - - if(modActive) - { - if(!::isCompatible(local.value("compatibility").toMap())) - modActive = false; // mod not compatible with our version of vcmi -> inactive - } - - for(auto entry : repositories) - { - QVariant repoVal = getValue(entry, path); - if(repoVal.isValid()) - { - auto repoValMap = repoVal.toMap(); - if(::isCompatible(repoValMap["compatibility"].toMap())) - { - if(repo.empty() - || CModVersion::fromString(repo["version"].toString().toStdString()) - < CModVersion::fromString(repoValMap["version"].toString().toStdString())) - { - //take valid download link, screenshots and mod size before assignment - auto download = repo.value("download"); - auto screenshots = repo.value("screenshots"); - auto size = repo.value("downloadSize"); - repo = repoValMap; - if(repo.value("download").isNull()) - { - repo["download"] = download; - if(repo.value("screenshots").isNull()) //taking screenshot from the downloadable version - repo["screenshots"] = screenshots; - } - if(repo.value("downloadSize").isNull()) - repo["downloadSize"] = size; - } - } - } - } - - return CModEntry(repo, local, modActive, modname); -} - -bool CModList::hasMod(QString modname) const -{ - if(localModList.contains(modname)) - return true; - - for(auto entry : repositories) - if(entry.contains(modname)) - return true; - - return false; -} - -QStringList CModList::getRequirements(QString modname) -{ - QStringList ret; - - if(hasMod(modname)) - { - auto mod = getMod(modname); - - for(auto entry : mod.getDependencies()) - ret += getRequirements(entry.toLower()); - } - ret += modname; - - return ret; -} - -QVector CModList::getModList() const -{ - QSet knownMods; - QVector modList; - for(auto repo : repositories) - { - for(auto it = repo.begin(); it != repo.end(); it++) - { - knownMods.insert(it.key().toLower()); - } - } - for(auto it = localModList.begin(); it != localModList.end(); it++) - { - knownMods.insert(it.key().toLower()); - } - - for(auto entry : knownMods) - { - modList.push_back(entry); - } - return modList; -} - -QVector CModList::getChildren(QString parent) const -{ - QVector children; - - int depth = parent.count('.') + 1; - for(const QString & mod : getModList()) - { - if(mod.count('.') == depth && mod.startsWith(parent)) - children.push_back(mod); - } - return children; -} diff --git a/launcher/modManager/cmodlist.h b/launcher/modManager/cmodlist.h deleted file mode 100644 index 721b36cb2..000000000 --- a/launcher/modManager/cmodlist.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * cmodlist.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include - -#include "modsettingsstorage.h" - -namespace ModStatus -{ -enum EModStatus -{ - MASK_NONE = 0, - ENABLED = 1, - INSTALLED = 2, - UPDATEABLE = 4, - MASK_ALL = 255 -}; -} - -class CModEntry -{ - // repository contains newest version only (if multiple are available) - QVariantMap repository; - QVariantMap localData; - bool modActive; - - QString modname; - - QVariant getValueImpl(QString value, bool localized) const; -public: - CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname); - - // installed and enabled - bool isEnabled() const; - // installed but disabled - bool isDisabled() const; - // available in any of repositories but not installed - bool isAvailable() const; - // installed and greater version exists in repository - bool isUpdateable() const; - // installed - bool isInstalled() const; - // vcmi essential files - bool isEssential() const; - // checks if version is compatible with vcmi - bool isCompatible() const; - // returns true if mod should be visible in Launcher - bool isVisible() const; - // returns true if mod type is Translation - bool isTranslation() const; - // returns true if mod type is Compatibility - bool isCompatibilityPatch() const; - // returns true if this is a submod - bool isSubmod() const; - - // see ModStatus enum - int getModStatus() const; - - // Returns mod name / identifier (not human-readable) - QString getName() const; - - // For submods only. Returns mod name / identifier of a top-level parent mod - QString getTopParentName() const; - - // get value of some field in mod structure. Returns empty optional if value is not present - QVariant getValue(QString value) const; - QVariant getBaseValue(QString value) const; - - QStringList getDependencies() const; - QStringList getConflicts() const; - - static QString sizeToString(double size); -}; - -class CModList -{ - QVector repositories; - QVariantMap localModList; - std::shared_ptr modSettings; - - mutable QMap cachedMods; - - QVariantMap copyField(QVariantMap data, QString from, QString to) const; - - CModEntry getModUncached(QString modname) const; -public: - virtual void resetRepositories(); - virtual void reloadRepositories(); - virtual void addRepository(QVariantMap data); - virtual void setLocalModList(QVariantMap data); - virtual void setModSettings(std::shared_ptr data); - virtual void modChanged(QString modID); - - // returns mod by name. Note: mod MUST exist - const CModEntry & getMod(QString modname) const; - - // returns list of all mods necessary to run selected one, including mod itself - // order is: first mods in list don't have any dependencies, last mod is modname - // note: may include mods not present in list - QStringList getRequirements(QString modname); - - bool hasMod(QString modname) const; - - // returns list of all available mods - QVector getModList() const; - - QVector getChildren(QString parent) const; -}; diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index f86274a7d..361c04ca6 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -17,8 +17,9 @@ #include #include -#include "cmodlistmodel_moc.h" -#include "cmodmanager.h" +#include "modstatemodel.h" +#include "modstateitemmodel_moc.h" +#include "modstatecontroller.h" #include "cdownloadmanager_moc.h" #include "chroniclesextractor.h" #include "../settingsView/csettingsview_moc.h" @@ -34,15 +35,11 @@ #include -static double mbToBytes(double mb) -{ - return mb * 1024 * 1024; -} - void CModListView::setupModModel() { - modModel = new CModListModel(this); - manager = std::make_unique(modModel); + modStateModel = std::make_shared(); + modModel = new ModStateItemModel(this); + manager = std::make_unique(modStateModel); } void CModListView::changeEvent(QEvent *event) @@ -148,13 +145,7 @@ CModListView::CModListView(QWidget * parent) dlManager = nullptr; if(settings["launcher"]["autoCheckRepositories"].Bool()) - { loadRepositories(); - } - else - { - manager->resetRepositories(); - } #ifdef VCMI_MOBILE for(auto * scrollWidget : { @@ -171,8 +162,6 @@ CModListView::CModListView(QWidget * parent) void CModListView::loadRepositories() { - manager->resetRepositories(); - QStringList repositories; if (settings["launcher"]["defaultRepositoryEnabled"].Bool()) @@ -223,7 +212,7 @@ static QString replaceIfNotEmpty(QStringList value, QString pattern) return ""; } -QString CModListView::genChangelogText(CModEntry & mod) +QString CModListView::genChangelogText(ModState & mod) { QString headerTemplate = "

%1:

"; QString entryBegin = "