1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-05 00:49:09 +02:00

Merge branch 'master' into 'develop'

This commit is contained in:
Ivan Savenko
2025-02-21 13:59:34 +00:00
185 changed files with 10270 additions and 2771 deletions

View File

@ -29,7 +29,7 @@ jobs:
before_install: linux_qt5.sh before_install: linux_qt5.sh
preset: linux-gcc-test preset: linux-gcc-test
- platform: linux - platform: linux
os: ubuntu-20.04 os: ubuntu-22.04
test: 0 test: 0
before_install: linux_qt5.sh before_install: linux_qt5.sh
preset: linux-gcc-debug preset: linux-gcc-debug
@ -246,6 +246,9 @@ jobs:
if [[ ${{matrix.preset}} == linux-gcc-test ]] if [[ ${{matrix.preset}} == linux-gcc-test ]]
then then
cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 --preset ${{ matrix.preset }} cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 --preset ${{ matrix.preset }}
elif [[ ${{matrix.preset}} == linux-gcc-debug ]]
then
cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 --preset ${{ matrix.preset }}
elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]] elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]]
then then
cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily;applicationVariant=daily" --preset ${{ matrix.preset }} cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily;applicationVariant=daily" --preset ${{ matrix.preset }}
@ -355,7 +358,7 @@ jobs:
deploy-src: deploy-src:
if: always() && github.ref == 'refs/heads/master' if: always() && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest runs-on: ubuntu-24.04
defaults: defaults:
run: run:
shell: bash shell: bash

View File

@ -411,6 +411,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
{ {
LOG_TRACE(logAi); LOG_TRACE(logAi);
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
nullkiller->invalidatePathfinderData(); // new hero needs to look around
} }
void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
@ -929,7 +930,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
const CArmedInstance * armies[] = {destinationArmy, source}; const CArmedInstance * armies[] = {destinationArmy, source};
auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source); auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source, myCb->getTile(source->visitablePos())->getTerrainID());
for(auto army : armies) for(auto army : armies)
{ {
@ -983,7 +984,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
&& source->stacksCount() == 1 && source->stacksCount() == 1
&& (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature)) && (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature))
{ {
auto weakest = nullkiller->armyManager->getWeakestCreature(bestArmy); auto weakest = nullkiller->armyManager->getBestUnitForScout(bestArmy, myCb->getTile(source->visitablePos())->getTerrainID());
if(weakest->creature == targetCreature) if(weakest->creature == targetCreature)
{ {

View File

@ -774,9 +774,9 @@ bool townHasFreeTavern(const CGTownInstance * town)
return canMoveVisitingHeroToGarrison; return canMoveVisitingHeroToGarrison;
} }
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy) uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy, int fortLevel)
{ {
auto armyStrength = heroArmy->getArmyStrength(); auto armyStrength = heroArmy->getArmyStrength(fortLevel);
if(hero && hero->commander && hero->commander->alive) if(hero && hero->commander && hero->commander->alive)
{ {

View File

@ -217,7 +217,7 @@ int64_t getArtifactScoreForHero(const CGHeroInstance * hero, const CArtifactInst
int64_t getPotentialArtifactScore(const CArtifact * art); int64_t getPotentialArtifactScore(const CArtifact * art);
bool townHasFreeTavern(const CGTownInstance * town); bool townHasFreeTavern(const CGTownInstance * town);
uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy); uint64_t getHeroArmyStrengthWithCommander(const CGHeroInstance * hero, const CCreatureSet * heroArmy, int fortLevel = 0);
uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start); uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock> start);

View File

@ -13,8 +13,10 @@
#include "../Engine/Nullkiller.h" #include "../Engine/Nullkiller.h"
#include "../../../CCallback.h" #include "../../../CCallback.h"
#include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/mapping/CMapDefines.h"
#include "../../../lib/IGameSettings.h" #include "../../../lib/IGameSettings.h"
#include "../../../lib/GameConstants.h" #include "../../../lib/GameConstants.h"
#include "../../../lib/TerrainHandler.h"
namespace NKAI namespace NKAI
{ {
@ -76,7 +78,7 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
{ {
return howManyReinforcementsCanGet(hero, hero, source); return howManyReinforcementsCanGet(hero, hero, source, ai->cb->getTile(hero->visitablePos())->getTerrainID());
} }
std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
@ -111,17 +113,59 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
return resultingArmy; return resultingArmy;
} }
std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<SlotInfo> & army) const std::vector<SlotInfo>::iterator ArmyManager::getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const
{ {
auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool uint64_t totalPower = 0;
for (const auto & unit : army)
totalPower += unit.power;
int baseMovementCost = cb->getSettings().getInteger(EGameSettings::HEROES_MOVEMENT_COST_BASE);
bool terrainHasPenalty = armyTerrain.hasValue() && armyTerrain.toEntity(VLC)->moveCost != baseMovementCost;
// arbitrary threshold - don't give scout more than specified part of total AI value of our army
uint64_t maxUnitValue = totalPower / 100;
const auto & movementPointsLimits = cb->getSettings().getVector(EGameSettings::HEROES_MOVEMENT_POINTS_LAND);
auto fastest = boost::min_element(army, [&](const SlotInfo & left, const SlotInfo & right) -> bool
{ {
if(left.creature->getLevel() != right.creature->getLevel()) uint64_t leftUnitPower = left.power / left.count;
return left.creature->getLevel() < right.creature->getLevel(); uint64_t rightUnitPower = left.power / left.count;
bool leftUnitIsWeak = leftUnitPower < maxUnitValue || left.creature->getLevel() < 4;
return left.creature->getMovementRange() > right.creature->getMovementRange(); bool rightUnitIsWeak = rightUnitPower < maxUnitValue || right.creature->getLevel() < 4;
if (leftUnitIsWeak != rightUnitIsWeak)
return leftUnitIsWeak;
if (terrainHasPenalty)
{
auto leftNativeTerrain = left.creature->getFactionID().toFaction()->nativeTerrain;
auto rightNativeTerrain = right.creature->getFactionID().toFaction()->nativeTerrain;
if (leftNativeTerrain != rightNativeTerrain)
{
if (leftNativeTerrain == armyTerrain)
return true;
if (rightNativeTerrain == armyTerrain)
return false;
}
}
int leftEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, left.creature->getMovementRange());
int rightEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, right.creature->getMovementRange());
int leftMovementPointsLimit = movementPointsLimits[leftEffectiveMovement];
int rightMovementPointsLimit = movementPointsLimits[rightEffectiveMovement];
if (leftMovementPointsLimit != rightMovementPointsLimit)
return leftMovementPointsLimit > rightMovementPointsLimit;
return leftUnitPower < rightUnitPower;
}); });
return weakest; return fastest;
} }
class TemporaryArmy : public CArmedInstance class TemporaryArmy : public CArmedInstance
@ -134,7 +178,7 @@ public:
} }
}; };
std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
{ {
auto sortedSlots = getSortedSlots(target, source); auto sortedSlots = getSortedSlots(target, source);
@ -218,7 +262,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
&& allowedFactions.size() == alignmentMap.size() && allowedFactions.size() == alignmentMap.size()
&& source->needsLastStack()) && source->needsLastStack())
{ {
auto weakest = getWeakestCreature(resultingArmy); auto weakest = getBestUnitForScout(resultingArmy, armyTerrain);
if(weakest->count == 1) if(weakest->count == 1)
{ {
@ -398,14 +442,14 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
return creaturesInDwellings; return creaturesInDwellings;
} }
ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
{ {
if(source->stacksCount() == 0) if(source->stacksCount() == 0)
{ {
return 0; return 0;
} }
auto bestArmy = getBestArmy(armyCarrier, target, source); auto bestArmy = getBestArmy(armyCarrier, target, source, armyTerrain);
uint64_t newArmy = 0; uint64_t newArmy = 0;
uint64_t oldArmy = target->getArmyStrength(); uint64_t oldArmy = target->getArmyStrength();

View File

@ -53,10 +53,11 @@ public:
virtual ui64 howManyReinforcementsCanGet( virtual ui64 howManyReinforcementsCanGet(
const IBonusBearer * armyCarrier, const IBonusBearer * armyCarrier,
const CCreatureSet * target, const CCreatureSet * target,
const CCreatureSet * source) const = 0; const CCreatureSet * source,
const TerrainId & armyTerrain) const = 0;
virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0; virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const = 0;
virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0; virtual std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const = 0;
virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0; virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
virtual std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0; virtual std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0;
@ -97,9 +98,9 @@ public:
uint8_t turn = 0) const override; uint8_t turn = 0) const override;
ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override; ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override; ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override; std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override; std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const override;
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override; std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override;

View File

@ -291,6 +291,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
prerequisite.baseCreatureID = info.baseCreatureID; prerequisite.baseCreatureID = info.baseCreatureID;
prerequisite.prerequisitesCount++; prerequisite.prerequisitesCount++;
prerequisite.armyCost = info.armyCost; prerequisite.armyCost = info.armyCost;
prerequisite.armyStrength = info.armyStrength;
bool haveSameOrBetterFort = false; bool haveSameOrBetterFort = false;
if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT) if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT)
haveSameOrBetterFort = true; haveSameOrBetterFort = true;

View File

@ -459,6 +459,8 @@ void ObjectClusterizer::clusterizeObject(
continue; continue;
} }
float priority = 0;
if(path.nodes.size() > 1) if(path.nodes.size() > 1)
{ {
auto blocker = getBlocker(path); auto blocker = getBlocker(path);
@ -475,7 +477,10 @@ void ObjectClusterizer::clusterizeObject(
heroesProcessed.insert(path.targetHero); heroesProcessed.insert(path.targetHero);
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER); for (int prio = PriorityEvaluator::PriorityTier::BUILDINGS; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
{
priority = std::max(priority, priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), prio));
}
if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) if(ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
continue; continue;
@ -498,7 +503,10 @@ void ObjectClusterizer::clusterizeObject(
heroesProcessed.insert(path.targetHero); heroesProcessed.insert(path.targetHero);
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), PriorityEvaluator::PriorityTier::HUNTER_GATHER); for (int prio = PriorityEvaluator::PriorityTier::BUILDINGS; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
{
priority = std::max(priority, priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)), prio));
}
if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY) if (ai->settings->isUseFuzzy() && priority < MIN_PRIORITY)
continue; continue;

View File

@ -57,7 +57,8 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
auto reinforcement = ai->armyManager->howManyReinforcementsCanGet( auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
targetHero, targetHero,
targetHero, targetHero,
&*townArmyAvailableToBuy); &*townArmyAvailableToBuy,
TerrainId::NONE);
if(reinforcement) if(reinforcement)
vstd::amin(reinforcement, ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town)); vstd::amin(reinforcement, ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));

View File

@ -214,11 +214,15 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
std::vector<int> pathsToDefend; std::vector<int> pathsToDefend;
std::map<const CGHeroInstance *, std::vector<int>> defferedPaths; std::map<const CGHeroInstance *, std::vector<int>> defferedPaths;
AIPath* closestWay = nullptr;
for(int i = 0; i < paths.size(); i++) for(int i = 0; i < paths.size(); i++)
{ {
auto & path = paths[i]; auto & path = paths[i];
if (!closestWay || path.movementCost() < closestWay->movementCost())
closestWay = &path;
#if NKAI_TRACE_LEVEL >= 1 #if NKAI_TRACE_LEVEL >= 1
logAi->trace( logAi->trace(
"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s", "Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
@ -382,7 +386,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
town->getObjectName()); town->getObjectName());
#endif #endif
sequence.push_back(sptr(ExecuteHeroChain(path, town))); ExecuteHeroChain heroChain = ExecuteHeroChain(path, town);
if (closestWay)
{
heroChain.closestWayRatio = closestWay->movementCost() / heroChain.getPath().movementCost();
}
sequence.push_back(sptr(heroChain));
composition.addNextSequence(sequence); composition.addNextSequence(sequence);
auto firstBlockedAction = path.getFirstBlockedAction(); auto firstBlockedAction = path.getFirstBlockedAction();

View File

@ -300,7 +300,8 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
ai->armyManager->getBestArmy( ai->armyManager->getBestArmy(
path.targetHero, path.targetHero,
path.heroArmy, path.heroArmy,
upgrader->getUpperArmy())); upgrader->getUpperArmy(),
TerrainId::NONE));
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength(); armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();

View File

@ -58,6 +58,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
ai->dangerHitMap->updateHitMap(); ai->dangerHitMap->updateHitMap();
int treasureSourcesCount = 0; int treasureSourcesCount = 0;
int bestClosestThreat = UINT8_MAX;
for(auto town : towns) for(auto town : towns)
{ {
@ -118,6 +119,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
bestScore = score; bestScore = score;
bestHeroToHire = hero; bestHeroToHire = hero;
bestTownToHireFrom = town; bestTownToHireFrom = town;
bestClosestThreat = closestThreat;
} }
} }
} }
@ -128,7 +130,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
{ {
if (ai->cb->getHeroesInfo().size() == 0 if (ai->cb->getHeroesInfo().size() == 0
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5 || treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|| bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 || (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !ai->buildAnalyzer->isGoldPressureHigh()))
|| (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())) || (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
{ {

View File

@ -149,7 +149,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
{ {
if(!startupTown->visitingHero) if(!startupTown->visitingHero)
{ {
if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200) if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero, TerrainId::NONE) > 200)
{ {
auto paths = ai->pathfinder->getPathInfo(startupTown->visitablePos()); auto paths = ai->pathfinder->getPathInfo(startupTown->visitablePos());

View File

@ -127,9 +127,9 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
auto fortLevel = town->fortLevel(); auto fortLevel = town->fortLevel();
if (fortLevel == CGTownInstance::EFortLevel::CASTLE) if (fortLevel == CGTownInstance::EFortLevel::CASTLE)
danger = std::max(danger * 2, danger + 10000); danger += 10000;
else if(fortLevel == CGTownInstance::EFortLevel::CITADEL) else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
danger = std::max(ui64(danger * 1.4), danger + 4000); danger += 4000;
} }
return danger; return danger;

View File

@ -446,7 +446,7 @@ void Nullkiller::makeTurn()
#if NKAI_TRACE_LEVEL >= 1 #if NKAI_TRACE_LEVEL >= 1
int prioOfTask = 0; int prioOfTask = 0;
#endif #endif
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::DEFEND; ++prio) for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
{ {
#if NKAI_TRACE_LEVEL >= 1 #if NKAI_TRACE_LEVEL >= 1
prioOfTask = prio; prioOfTask = prio;
@ -535,7 +535,10 @@ void Nullkiller::makeTurn()
else else
return; return;
} }
hasAnySuccess = true; else
{
hasAnySuccess = true;
}
} }
hasAnySuccess |= handleTrading(); hasAnySuccess |= handleTrading();
@ -721,7 +724,7 @@ bool Nullkiller::handleTrading()
if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources
{ {
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive); cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive);
#if NKAI_TRACE_LEVEL >= 1 #if NKAI_TRACE_LEVEL >= 2
logAi->info("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());
#endif #endif
haveTraded = true; haveTraded = true;

View File

@ -66,7 +66,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai)
isArmyUpgrade(false), isArmyUpgrade(false),
isHero(false), isHero(false),
isEnemy(false), isEnemy(false),
explorePriority(0) explorePriority(0),
powerRatio(0)
{ {
} }
@ -609,9 +610,6 @@ float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target)) ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target))
: 0; : 0;
case Obj::KEYMASTER:
return 0.6f;
default: default:
return 0; return 0;
} }
@ -889,7 +887,14 @@ public:
Goals::StayAtTown& stayAtTown = dynamic_cast<Goals::StayAtTown&>(*task); Goals::StayAtTown& stayAtTown = dynamic_cast<Goals::StayAtTown&>(*task);
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero()); if (stayAtTown.getHero() != nullptr && stayAtTown.getHero()->movementPointsRemaining() < 100)
{
return;
}
if(stayAtTown.town->mageGuildLevel() > 0)
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
if (evaluationContext.armyReward == 0) if (evaluationContext.armyReward == 0)
evaluationContext.isDefend = true; evaluationContext.isDefend = true;
else else
@ -1018,6 +1023,45 @@ public:
if(heroRole == HeroRole::MAIN) if(heroRole == HeroRole::MAIN)
evaluationContext.heroRole = heroRole; evaluationContext.heroRole = heroRole;
if (hero)
{
// Assuming Slots() returns a collection of slots with slot.second->getCreatureID() and slot.second->getPower()
float heroPower = 0;
float totalPower = 0;
// Map to store the aggregated power of creatures by CreatureID
std::map<int, float> totalPowerByCreatureID;
// Calculate hero power and total power by CreatureID
for (auto slot : hero->Slots())
{
int creatureID = slot.second->getCreatureID();
float slotPower = slot.second->getPower();
// Add the power of this slot to the heroPower
heroPower += slotPower;
// Accumulate the total power for the specific CreatureID
if (totalPowerByCreatureID.find(creatureID) == totalPowerByCreatureID.end())
{
// First time encountering this CreatureID, retrieve total creatures' power
totalPowerByCreatureID[creatureID] = ai->armyManager->getTotalCreaturesAvailable(creatureID).power;
}
}
// Calculate total power based on unique CreatureIDs
for (const auto& entry : totalPowerByCreatureID)
{
totalPower += entry.second;
}
// Compute the power ratio if total power is greater than zero
if (totalPower > 0)
{
evaluationContext.powerRatio = heroPower / totalPower;
}
}
if (target) if (target)
{ {
evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero); evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
@ -1030,6 +1074,8 @@ public:
evaluationContext.isHero = true; evaluationContext.isHero = true;
if (target->getOwner().isValidPlayer() && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES) if (target->getOwner().isValidPlayer() && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
evaluationContext.isEnemy = true; evaluationContext.isEnemy = true;
if (target->ID == Obj::TOWN)
evaluationContext.defenseValue = dynamic_cast<const CGTownInstance*>(target)->fortLevel();
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
if(evaluationContext.danger > 0) if(evaluationContext.danger > 0)
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength(); evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
@ -1169,6 +1215,19 @@ public:
evaluationContext.goldCost += cost; evaluationContext.goldCost += cost;
evaluationContext.closestWayRatio = 1; evaluationContext.closestWayRatio = 1;
evaluationContext.buildingCost += bi.buildCostWithPrerequisites; evaluationContext.buildingCost += bi.buildCostWithPrerequisites;
bool alreadyOwn = false;
int highestMageGuildPossible = BuildingID::MAGES_GUILD_3;
for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo())
{
if (town->hasBuilt(bi.id))
alreadyOwn = true;
if (evaluationContext.evaluator.ai->cb->canBuildStructure(town, BuildingID::MAGES_GUILD_5) != EBuildingState::FORBIDDEN)
highestMageGuildPossible = BuildingID::MAGES_GUILD_5;
else if (evaluationContext.evaluator.ai->cb->canBuildStructure(town, BuildingID::MAGES_GUILD_4) != EBuildingState::FORBIDDEN)
highestMageGuildPossible = BuildingID::MAGES_GUILD_4;
}
if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0) if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
evaluationContext.isTradeBuilding = true; evaluationContext.isTradeBuilding = true;
@ -1183,14 +1242,19 @@ public:
if(bi.baseCreatureID == bi.creatureID) if(bi.baseCreatureID == bi.creatureID)
{ {
evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount); evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount);
evaluationContext.armyReward += bi.armyStrength; evaluationContext.armyReward += bi.armyStrength * 1.5;
} }
else else
{ {
auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi); auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount); evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount; if(bi.id.IsDwelling())
evaluationContext.armyReward += bi.armyStrength - evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows);
else //This is for prerequisite-buildings
evaluationContext.armyReward += evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows);
if(alreadyOwn)
evaluationContext.armyReward /= bi.buildCostWithPrerequisites.marketValue();
} }
} }
else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE) else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
@ -1201,9 +1265,14 @@ public:
else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5) else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
{ {
evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1); evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo()) if (!alreadyOwn && evaluationContext.evaluator.ai->cb->canBuildStructure(buildThis.town, highestMageGuildPossible) != EBuildingState::FORBIDDEN)
{ {
evaluationContext.armyInvolvement += hero->getArmyCost(); for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())
{
if(hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + hero->getPrimSkillLevel(PrimarySkill::KNOWLEDGE) > hero->getPrimSkillLevel(PrimarySkill::ATTACK) + hero->getPrimSkillLevel(PrimarySkill::DEFENSE)
&& hero->manaLimit() > 30)
evaluationContext.armyReward += hero->getArmyCost();
}
} }
} }
int sameTownBonus = 0; int sameTownBonus = 0;
@ -1333,18 +1402,35 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
else else
{ {
float score = 0; float score = 0;
const bool amIInDanger = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0); bool currentPositionThreatened = false;
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget(); if (task->hero)
{
auto currentTileThreat = ai->dangerHitMap->getTileThreat(task->hero->visitablePos());
if (currentTileThreat.fastestDanger.turn < 1 && currentTileThreat.fastestDanger.danger > task->hero->getTotalStrength())
currentPositionThreatened = true;
}
if (priorityTier == PriorityTier::FAR_HUNTER_GATHER && currentPositionThreatened == false)
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Skip FAR_HUNTER_GATHER because hero is not threatened.");
#endif
return 0;
}
const bool amIInDanger = ai->cb->getTownsInfo().empty();
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio > 0 ? ai->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio : 1.0;
float dangerThreshold = 1;
dangerThreshold *= evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0;
bool arriveNextWeek = false; bool arriveNextWeek = false;
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL) if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL)
arriveNextWeek = true; arriveNextWeek = true;
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %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 isEnemy: %d arriveNextWeek: %d", logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, maxWillingToLose: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %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, dangerThreshold: %f explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d powerRatio: %f",
priorityTier, priorityTier,
task->toString(), task->toString(),
evaluationContext.armyLossPersentage, evaluationContext.armyLossPersentage,
maxWillingToLose,
(int)evaluationContext.turn, (int)evaluationContext.turn,
evaluationContext.movementCostByRole[HeroRole::MAIN], evaluationContext.movementCostByRole[HeroRole::MAIN],
evaluationContext.movementCostByRole[HeroRole::SCOUT], evaluationContext.movementCostByRole[HeroRole::SCOUT],
@ -1362,23 +1448,27 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
evaluationContext.conquestValue, evaluationContext.conquestValue,
evaluationContext.closestWayRatio, evaluationContext.closestWayRatio,
evaluationContext.enemyHeroDangerRatio, evaluationContext.enemyHeroDangerRatio,
dangerThreshold,
evaluationContext.explorePriority, evaluationContext.explorePriority,
evaluationContext.isDefend, evaluationContext.isDefend,
evaluationContext.isEnemy, evaluationContext.isEnemy,
arriveNextWeek); arriveNextWeek,
evaluationContext.powerRatio);
#endif #endif
switch (priorityTier) switch (priorityTier)
{ {
case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach
{ {
if (evaluationContext.turn > 0) if (evaluationContext.turn > 0 || evaluationContext.isExchange)
return 0; return 0;
if (evaluationContext.movementCost >= 1) if (evaluationContext.movementCost >= 1)
return 0; return 0;
if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0;
if(evaluationContext.conquestValue > 0) if(evaluationContext.conquestValue > 0)
score = evaluationContext.armyInvolvement; score = evaluationContext.armyInvolvement;
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
return 0; return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0; return 0;
@ -1388,23 +1478,47 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
} }
case PriorityTier::INSTADEFEND: //Defend immediately threatened towns case PriorityTier::INSTADEFEND: //Defend immediately threatened towns
{ {
if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) //No point defending if we don't have defensive-structures
score = evaluationContext.armyInvolvement; if (evaluationContext.defenseValue < 2)
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0; return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
if (evaluationContext.closestWayRatio < 1.0)
return 0;
if (evaluationContext.isEnemy && evaluationContext.turn > 0)
return 0;
if (evaluationContext.isDefend && evaluationContext.threatTurns <= evaluationContext.turn)
{
const float OPTIMAL_PERCENTAGE = 0.75f; // We want army to be 75% of the threat
float optimalStrength = evaluationContext.threat * OPTIMAL_PERCENTAGE;
// Calculate how far the army is from optimal strength
float deviation = std::abs(evaluationContext.armyInvolvement - optimalStrength);
// Convert deviation to a percentage of the threat to normalize it
float deviationPercentage = deviation / evaluationContext.threat;
// Calculate score: 1.0 is perfect, decreasing as deviation increases
score = 1.0f / (1.0f + deviationPercentage);
// Apply turn penalty to still prefer earlier moves when scores are close
score = score / (evaluationContext.turn + 1);
}
break; break;
} }
case PriorityTier::KILL: //Take towns / kill heroes that are further away case PriorityTier::KILL: //Take towns / kill heroes that are further away
//FALL_THROUGH //FALL_THROUGH
case PriorityTier::FAR_KILL: case PriorityTier::FAR_KILL:
{ {
if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0;
if (evaluationContext.turn > 0 && evaluationContext.isHero) if (evaluationContext.turn > 0 && evaluationContext.isHero)
return 0; return 0;
if (arriveNextWeek && evaluationContext.isEnemy) if (arriveNextWeek && evaluationContext.isEnemy)
return 0; return 0;
if (evaluationContext.conquestValue > 0) if (evaluationContext.conquestValue > 0)
score = evaluationContext.armyInvolvement; score = evaluationContext.armyInvolvement;
if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
return 0; return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0; return 0;
@ -1413,24 +1527,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
score /= evaluationContext.movementCost; score /= evaluationContext.movementCost;
break; break;
} }
case PriorityTier::UPGRADE:
{
if (!evaluationContext.isArmyUpgrade)
return 0;
if (evaluationContext.enemyHeroDangerRatio > 1)
return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0;
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
return 0;
score = 1000;
if (evaluationContext.movementCost > 0)
score /= evaluationContext.movementCost;
break;
}
case PriorityTier::HIGH_PRIO_EXPLORE: case PriorityTier::HIGH_PRIO_EXPLORE:
{ {
if (evaluationContext.enemyHeroDangerRatio > 1) if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0; return 0;
if (evaluationContext.explorePriority != 1) if (evaluationContext.explorePriority != 1)
return 0; return 0;
@ -1447,17 +1546,15 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
//FALL_THROUGH //FALL_THROUGH
case PriorityTier::FAR_HUNTER_GATHER: case PriorityTier::FAR_HUNTER_GATHER:
{ {
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && !evaluationContext.isDefend && priorityTier != PriorityTier::FAR_HUNTER_GATHER)
return 0; return 0;
if (evaluationContext.buildingCost.marketValue() > 0) if (evaluationContext.buildingCost.marketValue() > 0)
return 0; return 0;
if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0)) if (priorityTier != PriorityTier::FAR_HUNTER_GATHER && evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio > dangerThreshold || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
return 0; return 0;
if (evaluationContext.explorePriority == 3) if (evaluationContext.explorePriority == 3)
return 0; return 0;
if (evaluationContext.isArmyUpgrade) if (priorityTier != PriorityTier::FAR_HUNTER_GATHER && ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > dangerThreshold))
return 0;
if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1)
return 0; return 0;
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
return 0; return 0;
@ -1475,12 +1572,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
score = 1000; score = 1000;
if (evaluationContext.movementCost > 0) if (evaluationContext.movementCost > 0)
score /= evaluationContext.movementCost; score /= evaluationContext.movementCost;
if(priorityTier == PriorityTier::FAR_HUNTER_GATHER && evaluationContext.enemyHeroDangerRatio > 0)
score /= evaluationContext.enemyHeroDangerRatio;
} }
break; break;
} }
case PriorityTier::LOW_PRIO_EXPLORE: case PriorityTier::LOW_PRIO_EXPLORE:
{ {
if (evaluationContext.enemyHeroDangerRatio > 1) if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0; return 0;
if (evaluationContext.explorePriority != 3) if (evaluationContext.explorePriority != 3)
return 0; return 0;
@ -1495,7 +1594,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
} }
case PriorityTier::DEFEND: //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) if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
return 0; return 0;
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade) if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
score = evaluationContext.armyInvolvement; score = evaluationContext.armyInvolvement;
@ -1536,9 +1635,15 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
TResources needed = evaluationContext.buildingCost - resourcesAvailable; TResources needed = evaluationContext.buildingCost - resourcesAvailable;
needed.positive(); needed.positive();
int turnsTo = needed.maxPurchasableCount(income); int turnsTo = needed.maxPurchasableCount(income);
bool haveEverythingButGold = true;
for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
{
if (i != GameResID::GOLD && resourcesAvailable[i] < evaluationContext.buildingCost[i])
haveEverythingButGold = false;
}
if (turnsTo == INT_MAX) if (turnsTo == INT_MAX)
return 0; return 0;
else if (!haveEverythingButGold)
score /= turnsTo; score /= turnsTo;
} }
} }

View File

@ -84,6 +84,7 @@ struct DLL_EXPORT EvaluationContext
bool isHero; bool isHero;
bool isEnemy; bool isEnemy;
int explorePriority; int explorePriority;
float powerRatio;
EvaluationContext(const Nullkiller * ai); EvaluationContext(const Nullkiller * ai);
@ -114,13 +115,13 @@ public:
INSTAKILL, INSTAKILL,
INSTADEFEND, INSTADEFEND,
KILL, KILL,
UPGRADE,
HIGH_PRIO_EXPLORE, HIGH_PRIO_EXPLORE,
HUNTER_GATHER, HUNTER_GATHER,
LOW_PRIO_EXPLORE, LOW_PRIO_EXPLORE,
FAR_KILL, FAR_KILL,
DEFEND,
FAR_HUNTER_GATHER, FAR_HUNTER_GATHER,
DEFEND MAX_PRIORITY_TIER = FAR_HUNTER_GATHER
}; };
private: private:

View File

@ -583,42 +583,28 @@ public:
bool AINodeStorage::calculateHeroChain() bool AINodeStorage::calculateHeroChain()
{ {
std::random_device randomDevice;
std::mt19937 randomEngine(randomDevice());
heroChainPass = EHeroChainPass::CHAIN; heroChainPass = EHeroChainPass::CHAIN;
heroChain.clear(); heroChain.clear();
std::vector<int3> data(committedTiles.begin(), committedTiles.end()); std::vector<int3> data(committedTiles.begin(), committedTiles.end());
if(data.size() > 100) int maxConcurrency = tbb::this_task_arena::max_concurrency();
std::vector<std::vector<CGPathNode *>> results(maxConcurrency);
logAi->trace("Caculating hero chain for %d items", data.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r)
{ {
boost::mutex resultMutex;
std::shuffle(data.begin(), data.end(), randomEngine);
tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r)
{
//auto r = blocked_range<size_t>(0, data.size());
HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
task.execute(r);
{
boost::lock_guard<boost::mutex> resultLock(resultMutex);
task.flushResult(heroChain);
}
});
}
else
{
auto r = tbb::blocked_range<size_t>(0, data.size());
HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn); HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
int ourThread = tbb::this_task_arena::current_thread_index();
task.execute(r); task.execute(r);
task.flushResult(heroChain); task.flushResult(results.at(ourThread));
} });
// FIXME: potentially non-deterministic behavior due to parallel_for
for (const auto & result : results)
vstd::concatenate(heroChain, result);
committedTiles.clear(); committedTiles.clear();
@ -1464,9 +1450,20 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
} }
} }
int fortLevel = 0;
auto visitableObjects = cb->getVisitableObjs(pos);
for (auto obj : visitableObjects)
{
if (objWithID<Obj::TOWN>(obj))
{
auto town = dynamic_cast<const CGTownInstance*>(obj);
fortLevel = town->fortLevel();
}
}
path.targetObjectArmyLoss = evaluateArmyLoss( path.targetObjectArmyLoss = evaluateArmyLoss(
path.targetHero, path.targetHero,
getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy), getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy, fortLevel),
path.targetObjectDanger); path.targetObjectDanger);
path.chainMask = node.actor->chainMask; path.chainMask = node.actor->chainMask;

View File

@ -13,6 +13,7 @@
#include "../Engine/Nullkiller.h" #include "../Engine/Nullkiller.h"
#include "../../../CCallback.h" #include "../../../CCallback.h"
#include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/mapping/CMapDefines.h"
#include "../../../lib/pathfinder/TurnInfo.h" #include "../../../lib/pathfinder/TurnInfo.h"
#include "Actions/BuyArmyAction.h" #include "Actions/BuyArmyAction.h"
@ -394,7 +395,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
{ {
auto * target = new HeroExchangeArmy(); auto * target = new HeroExchangeArmy();
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2); auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2, ai->cb->getTile(actor->hero->visitablePos())->getTerrainID());
for(auto & slotInfo : bestArmy) for(auto & slotInfo : bestArmy)
{ {

View File

@ -109,6 +109,8 @@ include(CMakeDependentOption)
cmake_dependent_option(ENABLE_INNOEXTRACT "Enable innoextract for GOG file extraction in launcher" ON "ENABLE_LAUNCHER" OFF) cmake_dependent_option(ENABLE_INNOEXTRACT "Enable innoextract for GOG file extraction in launcher" ON "ENABLE_LAUNCHER" OFF)
cmake_dependent_option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON "NOT ENABLE_GOLDMASTER" OFF) cmake_dependent_option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON "NOT ENABLE_GOLDMASTER" OFF)
option(VCMI_PORTMASTER "PortMaster build" OFF)
############################################ ############################################
# Miscellaneous options # # Miscellaneous options #
############################################ ############################################

View File

@ -319,6 +319,25 @@
"cacheVariables": { "cacheVariables": {
"ANDROID_GRADLE_PROPERTIES": "applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily;applicationVariant=daily" "ANDROID_GRADLE_PROPERTIES": "applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily;applicationVariant=daily"
} }
},
{
"name": "portmaster-release",
"displayName": "PortMaster",
"description": "VCMI PortMaster",
"inherits": "default-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_INSTALL_PREFIX": ".",
"ENABLE_DEBUG_CONSOLE": "OFF",
"ENABLE_EDITOR": "OFF",
"ENABLE_GITVERSION": "OFF",
"ENABLE_LAUNCHER": "OFF",
"ENABLE_SERVER": "OFF",
"ENABLE_TRANSLATIONS": "OFF",
"FORCE_BUNDLED_FL": "ON",
"ENABLE_GOLDMASTER": "ON",
"VCMI_PORTMASTER": "ON"
}
} }
], ],
"buildPresets": [ "buildPresets": [
@ -447,6 +466,12 @@
"name": "android-daily-release", "name": "android-daily-release",
"configurePreset": "android-daily-release", "configurePreset": "android-daily-release",
"inherits": "android-conan-ninja-release" "inherits": "android-conan-ninja-release"
},
{
"name": "portmaster-release",
"configurePreset": "portmaster-release",
"inherits": "default-release",
"configuration": "Release"
} }
], ],
"testPresets": [ "testPresets": [

View File

@ -1,5 +1,80 @@
# VCMI Project Changelog # VCMI Project Changelog
## 1.6.5 -> 1.6.6
### General
* Game no longer requires local network connection for single player games
* Reduced size of obstacle-filled junction zones in Coldshadow Fantasy template
* Upscaling filter xbrz x2 is now enabled by default on mobile systems
* Fixes failure to import Chronicles on Windows with non-ascii characters in username
* Added support for importing Chronicles using old All-in-One installer from gog.com
* It is now possible to enable portrait mode on mobile systems.
* Fixed grey bar at top of screen when returning to app while in game on Android
### Stability
* Fixed possible crash on opening unit description with unavailable upgrades
* Fixed crash on winning game after last player loses the game due to not controlling a town for 7 days
### Interface
* Pressing Q during hero exchange will now swap both army and artifacts and will no longer trigger a quest log
* Spellbook search is no longer enabled by default, allowing standard h3 shortcuts to work. Search can now be activated by pressing Tab
* Ctrl/Shift + click on arrow buttons below creature slots during hero exchange now works in the familiar way from hd mod
* On mobile systems, clicking on a blocked tile of a visitable object on the adventure map will now build a path to it
* It is now possible to activate the adventure map overlay on the mobile system using the two-finger tap gesture
* Fixed incorrect pinch event calculation that caused problems when zooming with touchscreen gestures
* Game now displays both total cost in movement points and estimated time to arrive in turns when hovering over an accessible location
* Artifact sort buttons in the Hero Backpack window now have correct text describing the sort order
* Fixed non-standard color handling for shadows under selection highlight in creature animations from mods such as HotA's Iron Golem
* Effects such as Bloodlust, Clone, and Petrify will now display correctly when xbrz is in use
* Fixed broken Chronicles campaign screen available with new main menu themes mod
* Fixed empty bonus shown in unit info window when unit is in Necropolis with Cover of Darkness built
* Right-clicking on the difficulty button will now display the difficulty description popup
* Fixed regression causing two minus signs in Fountain of Fortune description
* Added option to upgrade all creatures in the radial menu when in town
* Added option to display remaining unit health in the form of a health bar
* Fixed regression that caused unavailable tiles to be displayed on the left and right sides of the battlefield when hovering with the mouse
* Fixed regression that caused all spells to be displayed as having a duration of 16 rounds
* Scrolling in the lobby window now only happens when hovering over the appropriate item, instead of scrolling all scrollable widgets at once
* Fixed regression that caused black pixels on some hero portraits in mods that use 8-bit palette images
* Fixed memory leak when upscaling images with xbrz filter
* Fixed creature windows text align and buttons background
### Mechanics
* It is no longer possible to attack heroes standing on a visitable object from blocked tiles or from water when the attacker uses Fly
* Fixed regression from 1.6 that caused multiple taverns in towns of the same faction to not be counted towards the level of information available for the thieves' guild
* Fixed regression that caused Cove towns placed on map to be replaced with Castles on HotA maps
* The amount of gold a player can receive from a bonfire is now always equal to the amount of rare resources received multiplied by 100
* Disabled default victory conditions on all Elixir of Life campaign maps that require an artifact to be found, in line with H3
### Nullkiller AI
* Improved scoring of town buildings by the AI
* AI will now prefer to give faster units to its scout heroes to optimize their movement points in future turns
* Fixed AI not constructing prerequisites for town buildings in some cases, like not building Stables when attempting to build Training Grounds
* AI will now avoid recruiting heroes if AI is low on gold or if the town is threatened by an enemy hero
* AI will no longer attempt to use more than one hero to defend a town
* AI will now devalue non-flying units when attacking towns with fortifications to prevent suicides against castles
* Increased the priority of building unupgraded dwellings, as they provide units that can be hired immediately, rather than next week like citadels and castles
* When multiple cities are threatened, the AI will now prefer to defend the one that takes the least number of turns to reach
* Fixed AI attempting to restore mana points in town without a mage guild built
* Reduced AI prioritization of army merging to the same level as general gathering
* AI will now prioritize army merging before attacking enemies
* Increased AI defense prioritization
* AI will no longer leave the defense of a threatened town in order to bring the army to another hero
* AI will no longer send heroes to die outside of towns that already have a garrisoning hero inside, if there's a stronger enemy hero lurking around the town
* AI will no longer focus excessively on reaching Keymaster tents
* AI will no longer rush towns that don't have a citadel or better if there is a strong enemy hero in the area
* AI will no longer try to maximize defenses by using the strongest defender possible, but will instead try to use the most appropriate defender
* Heroes that are currently threatened will be braver and not worry about attacking things that are also threatened if nothing safe is in range
### Launcher
* Added context menu for mod lists that allows disabling, enabling, installing, uninstalling, updating, opening installed mod location, and opening mod repository
## 1.6.4 -> 1.6.5 ## 1.6.4 -> 1.6.5
### General ### General

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -23,8 +23,6 @@
"vcmi.adventureMap.noTownWithTavern" : "没有酒馆可供查看。", "vcmi.adventureMap.noTownWithTavern" : "没有酒馆可供查看。",
"vcmi.adventureMap.spellUnknownProblem" : "无此魔法的信息。", "vcmi.adventureMap.spellUnknownProblem" : "无此魔法的信息。",
"vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s", "vcmi.adventureMap.playerAttacked" : "玩家遭受攻击: %s",
"vcmi.adventureMap.moveCostDetails" : "移动点数 - 花费: %TURNS 轮 + %POINTS 点移动力, 剩余移动力: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "移动点数 - 花费: %POINTS 点移动力, 剩余移动力: %REMAINING",
"vcmi.adventureMap.movementPointsHeroInfo" : "(移动点数: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(移动点数: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉,重放对手行动功能目前暂未实现!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "抱歉,重放对手行动功能目前暂未实现!",
@ -422,11 +420,11 @@
"vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面", "vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面",
"vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物", "vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物",
"vcmi.heroWindow.sortBackpackByCost.hover" : "按价格排序", "vcmi.heroWindow.sortBackpackByCost.hover" : "按价格排序",
"vcmi.heroWindow.sortBackpackByCost.help" : "将行囊里的宝物按价格排序。", "vcmi.heroWindow.sortBackpackByCost.help" : "{按价格排序}\n\n将行囊里的宝物按价格排序。",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "按装备槽排序", "vcmi.heroWindow.sortBackpackBySlot.hover" : "按装备槽排序",
"vcmi.heroWindow.sortBackpackBySlot.help" : "将行囊里的宝物按装备槽排序。", "vcmi.heroWindow.sortBackpackBySlot.help" : "{按装备槽排序}\n\n将行囊里的宝物按装备槽排序。",
"vcmi.heroWindow.sortBackpackByClass.hover" : "按类型排序", "vcmi.heroWindow.sortBackpackByClass.hover" : "按类型排序",
"vcmi.heroWindow.sortBackpackByClass.help" : "将行囊里的宝物按装备槽排序:低级宝物、中级宝物、高级宝物、圣物。", "vcmi.heroWindow.sortBackpackByClass.help" : "{按类型排序}\n\n将行囊里的宝物按装备槽排序:低级宝物、中级宝物、高级宝物、圣物。",
"vcmi.heroWindow.fusingArtifact.fusing" : "你已拥有融合%s所需的全部组件,想现在进行融合吗?{所有组件在融合后将被消耗。}", "vcmi.heroWindow.fusingArtifact.fusing" : "你已拥有融合%s所需的全部组件,想现在进行融合吗?{所有组件在融合后将被消耗。}",
"vcmi.tavernWindow.inviteHero" : "邀请英雄", "vcmi.tavernWindow.inviteHero" : "邀请英雄",

View File

@ -23,9 +23,9 @@
"vcmi.adventureMap.noTownWithTavern" : "Nejsou dostupná žádná města s putykou!", "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.spellUnknownProblem" : "Neznámý problém s tímto kouzlem! Další informace nejsou k dispozici.",
"vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s", "vcmi.adventureMap.playerAttacked" : "Hráč byl napaden: %s",
"vcmi.adventureMap.moveCostDetails" : "Body pohybu - Cena: %TURNS tahů + %POINTS bodů, zbylé body: %REMAINING", "vcmi.adventureMap.moveCostDetails" : "Přesun sem tě bude stát {%TOTAL} bodů (za {%TURNS} tahů a {%POINTS} bodů). Po přesunu ti zbyde {%REMAINING} bodů.",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Body pohybu - Cena: %POINTS bodů, zbylé body: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Přesun sem tě bude stát {%POINTS} bodů. Po přesunu ti zbyde {%REMAINING} bodů.",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Body pohybu: %REMAINING / %POINTS)", "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.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!",
"vcmi.bonusSource.artifact" : "Artefakt", "vcmi.bonusSource.artifact" : "Artefakt",
@ -68,6 +68,7 @@
"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.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou",
"vcmi.radialWheel.heroDismiss" : "Propustit hrdinu", "vcmi.radialWheel.heroDismiss" : "Propustit hrdinu",
"vcmi.radialWheel.upgradeCreatures" : "Vylepšit všechny jednotky",
"vcmi.radialWheel.moveTop" : "Přesunout nahoru", "vcmi.radialWheel.moveTop" : "Přesunout nahoru",
"vcmi.radialWheel.moveUp" : "Posunout výše", "vcmi.radialWheel.moveUp" : "Posunout výše",
@ -86,9 +87,9 @@
"vcmi.spellBook.search" : "Hledat", "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 dalších kouzel.", "vcmi.spellResearch.canNotAfford" : "Nemáš dostatek prostředků na výměnu kouzla {%SPELL1} za {%SPELL2}. Můžeš ho však odstranit a pokračovat ve výzkumu.",
"vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.", "vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vrať se zítra.",
"vcmi.spellResearch.pay" : "Chcete nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu dalších kouzel?", "vcmi.spellResearch.pay" : "Chceš nahradit {%SPELL1} za {%SPELL2}? Nebo zrušit toto kouzlo a pokračovat ve výzkumu dalších kouzel?",
"vcmi.spellResearch.research" : "Prozkoumat toto kouzlo", "vcmi.spellResearch.research" : "Prozkoumat toto kouzlo",
"vcmi.spellResearch.skip" : "Přeskočit toto kouzlo", "vcmi.spellResearch.skip" : "Přeskočit toto kouzlo",
"vcmi.spellResearch.abort" : "Přerušit", "vcmi.spellResearch.abort" : "Přerušit",
@ -168,10 +169,10 @@
"vcmi.lobby.login.as" : "Přihlásit se jako %s", "vcmi.lobby.login.as" : "Přihlásit se jako %s",
"vcmi.lobby.login.spectator" : "Divák", "vcmi.lobby.login.spectator" : "Divák",
"vcmi.lobby.header.rooms" : "Herní místnosti - %d", "vcmi.lobby.header.rooms" : "Herní místnosti - %d",
"vcmi.lobby.header.channels" : "Kanály konverzace", "vcmi.lobby.header.channels" : "Kanály chatu",
"vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name "vcmi.lobby.header.chat.global" : "Globální chat hry - %s", // %s -> language name
"vcmi.lobby.header.chat.match" : "Konverzace předchozí hry %s", // %s -> game start date & time "vcmi.lobby.header.chat.match" : "Chat 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.chat.player" : "Soukromý chat s %s", // %s -> nickname of another player
"vcmi.lobby.header.history" : "Vaše předchozí hry", "vcmi.lobby.header.history" : "Vaše předchozí hry",
"vcmi.lobby.header.players" : "Online hráči - %d", "vcmi.lobby.header.players" : "Online hráči - %d",
"vcmi.lobby.match.solo" : "Hra jednoho hráče", "vcmi.lobby.match.solo" : "Hra jednoho hráče",
@ -185,7 +186,7 @@
"vcmi.lobby.room.description.load" : "Pro start hry načtěte uloženou hru.", "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.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.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.invite.notification" : "Hráč vás pozval do své soukromé místnosti. Nyní se k ní můžete připojit.",
"vcmi.lobby.preview.title" : "Připojit se do herní místnosti", "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.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.version" : "Verze hry:",
@ -216,9 +217,9 @@
"vcmi.lobby.pvp.coin.hover" : "Mince", "vcmi.lobby.pvp.coin.hover" : "Mince",
"vcmi.lobby.pvp.coin.help" : "Hodí mincí", "vcmi.lobby.pvp.coin.help" : "Hodí mincí",
"vcmi.lobby.pvp.randomTown.hover" : "Náhodné město", "vcmi.lobby.pvp.randomTown.hover" : "Náhodné město",
"vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do konvezace", "vcmi.lobby.pvp.randomTown.help" : "Napsat náhodné město do chatu",
"vcmi.lobby.pvp.randomTownVs.hover" : "Náhodné město vs.", "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.randomTownVs.help" : "Napsat 2 náhodná města do chatu",
"vcmi.lobby.pvp.versus" : "vs.", "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.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",
@ -363,6 +364,8 @@
"vcmi.battleOptions.endWithAutocombat.help" : "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.", "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.hover" : "Zobrazit rychlý panel kouzel",
"vcmi.battleOptions.showQuickSpell.help" : "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.", "vcmi.battleOptions.showQuickSpell.help" : "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.",
"vcmi.battleOptions.showHealthBar.hover": "Zobrazit ukazatel zdraví",
"vcmi.battleOptions.showHealthBar.help": "{Zobrazit ukazatel zdraví}\n\nZobrazí ukazatel, který znázorňuje, kolik zdraví zbývá, než jednotka zemře.",
"vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit objekt", "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.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.",
@ -415,6 +418,9 @@
"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.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.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.townWindow.upgradeAll.notAllUpgradable" : "Nemáte dostatek surovin na vylepšení všech jednotek. Chcete vylepšit následující jednotky?",
"vcmi.townWindow.upgradeAll.notUpgradable" : "Nemáte dostatek surovin na vylepšení žádné z jednotek.",
"vcmi.logicalExpressions.anyOf" : "Nějaké 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.allOf" : "Všechny následující:",
"vcmi.logicalExpressions.noneOf" : "Žádné z následujících:", "vcmi.logicalExpressions.noneOf" : "Žádné z následujících:",
@ -424,11 +430,11 @@
"vcmi.heroWindow.openBackpack.hover" : "Otevřít okno s artefakty", "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.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.hover" : "Seřadit podle ceny",
"vcmi.heroWindow.sortBackpackByCost.help" : "Seřadí artefakty v batohu podle ceny.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Seřadit podle ceny}\n\nSeřadí artefakty v batohu podle ceny.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Seřadit podle slotu", "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.sortBackpackBySlot.help" : "{Seřadit podle slotu}\n\nSeřadí artefakty v batohu podle přiřazeného slotu.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Seřadit podle třídy", "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.sortBackpackByClass.help" : "{Seřadit podle třídy}\n\nSeř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.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.tavernWindow.inviteHero" : "Pozvat hrdinu",
@ -807,4 +813,4 @@
"spell.core.strongholdMoatTrigger.name" : "Dřevěné bodce", "spell.core.strongholdMoatTrigger.name" : "Dřevěné bodce",
"spell.core.summonDemons.name" : "Přivolání démonů", "spell.core.summonDemons.name" : "Přivolání démonů",
"spell.core.towerMoat.name" : "Pozemní mina" "spell.core.towerMoat.name" : "Pozemní mina"
} }

View File

@ -23,16 +23,16 @@
"vcmi.adventureMap.noTownWithTavern" : "There are no available towns with taverns!", "vcmi.adventureMap.noTownWithTavern" : "There are no available towns with taverns!",
"vcmi.adventureMap.spellUnknownProblem" : "There is an unknown problem with this spell! No more information is available.", "vcmi.adventureMap.spellUnknownProblem" : "There is an unknown problem with this spell! No more information is available.",
"vcmi.adventureMap.playerAttacked" : "Player has been attacked: %s", "vcmi.adventureMap.playerAttacked" : "Player has been attacked: %s",
"vcmi.adventureMap.moveCostDetails" : "Movement points - Cost: %TURNS turns + %POINTS points, Remaining points: %REMAINING", "vcmi.adventureMap.moveCostDetails" : "Moving here will cost {%TOTAL} points in total ({%TURNS} turns and {%POINTS} points). {%REMAINING} points will remain after moving.",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Movement points - Cost: %POINTS points, Remaining points: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Moving here will cost {%POINTS} points. {%REMAINING} points will remain after moving.",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Movement points: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(Movement points: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sorry, replay opponent turn is not implemented yet!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sorry, replay opponent turn is not implemented yet!",
"vcmi.bonusSource.artifact" : "Artifact", "vcmi.bonusSource.artifact" : "Artifact",
"vcmi.bonusSource.creature" : "Ability", "vcmi.bonusSource.creature" : "Ability",
"vcmi.bonusSource.spell" : "Spell", "vcmi.bonusSource.spell" : "Spell",
"vcmi.bonusSource.hero" : "Hero", "vcmi.bonusSource.hero" : "Hero",
"vcmi.bonusSource.commander" : "Commander", "vcmi.bonusSource.commander" : "Command.",
"vcmi.bonusSource.other" : "Other", "vcmi.bonusSource.other" : "Other",
"vcmi.capitalColors.0" : "Red", "vcmi.capitalColors.0" : "Red",
@ -68,6 +68,7 @@
"vcmi.radialWheel.heroGetArtifacts" : "Get artifacts from other hero", "vcmi.radialWheel.heroGetArtifacts" : "Get artifacts from other hero",
"vcmi.radialWheel.heroSwapArtifacts" : "Swap artifacts with other hero", "vcmi.radialWheel.heroSwapArtifacts" : "Swap artifacts with other hero",
"vcmi.radialWheel.heroDismiss" : "Dismiss hero", "vcmi.radialWheel.heroDismiss" : "Dismiss hero",
"vcmi.radialWheel.upgradeCreatures" : "Upgrade all creatures",
"vcmi.radialWheel.moveTop" : "Move to top", "vcmi.radialWheel.moveTop" : "Move to top",
"vcmi.radialWheel.moveUp" : "Move up", "vcmi.radialWheel.moveUp" : "Move up",
@ -363,6 +364,8 @@
"vcmi.battleOptions.endWithAutocombat.help": "{Ends battle}\n\nAuto-Combat plays battle to end instant", "vcmi.battleOptions.endWithAutocombat.help": "{Ends battle}\n\nAuto-Combat plays battle to end instant",
"vcmi.battleOptions.showQuickSpell.hover": "Show Quickspell panel", "vcmi.battleOptions.showQuickSpell.hover": "Show Quickspell panel",
"vcmi.battleOptions.showQuickSpell.help": "{Show Quickspell panel}\n\nShow panel for quick selecting spells", "vcmi.battleOptions.showQuickSpell.help": "{Show Quickspell panel}\n\nShow panel for quick selecting spells",
"vcmi.battleOptions.showHealthBar.hover": "Show health bar",
"vcmi.battleOptions.showHealthBar.help": "{Show health bar}\n\nShow health bar indicating remaining health before one unit dies.",
"vcmi.adventureMap.revisitObject.hover" : "Revisit Object", "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.revisitObject.help" : "{Revisit Object}\n\nIf a hero currently stands on a Map Object, he can revisit the location.",
@ -415,6 +418,9 @@
"vcmi.townStructure.bank.borrow" : "You enter the bank. A banker sees you and says: \"We have made a special offer for you. You can take a loan of 2500 gold from us for 5 days. You will have to repay 500 gold every day.\"", "vcmi.townStructure.bank.borrow" : "You enter the bank. A banker sees you and says: \"We have made a special offer for you. You can take a loan of 2500 gold from us for 5 days. You will have to repay 500 gold every day.\"",
"vcmi.townStructure.bank.payBack" : "You enter the bank. A banker sees you and says: \"You have already got your loan. Pay it back before taking a new one.\"", "vcmi.townStructure.bank.payBack" : "You enter the bank. A banker sees you and says: \"You have already got your loan. Pay it back before taking a new one.\"",
"vcmi.townWindow.upgradeAll.notAllUpgradable" : "Not enough resources to upgrade all creatures. Do you want to upgrade following creatures?",
"vcmi.townWindow.upgradeAll.notUpgradable" : "Not enough resources to upgrade any creature.",
"vcmi.logicalExpressions.anyOf" : "Any of the following:", "vcmi.logicalExpressions.anyOf" : "Any of the following:",
"vcmi.logicalExpressions.allOf" : "All of the following:", "vcmi.logicalExpressions.allOf" : "All of the following:",
"vcmi.logicalExpressions.noneOf" : "None of the following:", "vcmi.logicalExpressions.noneOf" : "None of the following:",
@ -423,12 +429,12 @@
"vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero.", "vcmi.heroWindow.openCommander.help" : "Shows details about the commander of this hero.",
"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window", "vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
"vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.", "vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management.",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Sort by cost", "vcmi.heroWindow.sortBackpackByCost.hover" : "By value",
"vcmi.heroWindow.sortBackpackByCost.help" : "Sort artifacts in backpack by cost.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Sort by cost}\n\nSort artifacts in backpack by cost.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Sort by slot", "vcmi.heroWindow.sortBackpackBySlot.hover" : "By slot",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Sort artifacts in backpack by equipped slot.", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Sort by slot}\n\nSort artifacts in backpack by equipped slot.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Sort by class", "vcmi.heroWindow.sortBackpackByClass.hover" : "By class",
"vcmi.heroWindow.sortBackpackByClass.help" : "Sort artifacts in backpack by artifact class. Treasure, Minor, Major, Relic", "vcmi.heroWindow.sortBackpackByClass.help" : "{Sort by class}\n\nSort 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.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", "vcmi.tavernWindow.inviteHero" : "Invite hero",

View File

@ -18,8 +18,6 @@
"vcmi.adventureMap.noTownWithTavern" : "Il n'y a pas de villes disponibles avec des tavernes !", "vcmi.adventureMap.noTownWithTavern" : "Il n'y a pas de villes disponibles avec des tavernes !",
"vcmi.adventureMap.spellUnknownProblem" : "Il y a un problème inconnu avec ce sort ! Pas plus d'informations sont disponibles.", "vcmi.adventureMap.spellUnknownProblem" : "Il y a un problème inconnu avec ce sort ! Pas plus d'informations sont disponibles.",
"vcmi.adventureMap.playerAttacked" : "Le joueur a été attaqué : %s", "vcmi.adventureMap.playerAttacked" : "Le joueur a été attaqué : %s",
"vcmi.adventureMap.moveCostDetails" : "Points de mouvement - Coût : %TURNS tours + %POINTS points, Points restants : %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Points de mouvement - Coût : %POINTS points, Points restants : %REMAINING",
"vcmi.capitalColors.0" : "Rouge", "vcmi.capitalColors.0" : "Rouge",
"vcmi.capitalColors.1" : "Bleu", "vcmi.capitalColors.1" : "Bleu",

View File

@ -23,16 +23,16 @@
"vcmi.adventureMap.noTownWithTavern" : "Keine Stadt mit Taverne verfügbar!", "vcmi.adventureMap.noTownWithTavern" : "Keine Stadt mit Taverne verfügbar!",
"vcmi.adventureMap.spellUnknownProblem" : "Unbekanntes Problem mit diesem Zauberspruch, keine weiteren Informationen verfügbar.", "vcmi.adventureMap.spellUnknownProblem" : "Unbekanntes Problem mit diesem Zauberspruch, keine weiteren Informationen verfügbar.",
"vcmi.adventureMap.playerAttacked" : "Spieler wurde attackiert: %s", "vcmi.adventureMap.playerAttacked" : "Spieler wurde attackiert: %s",
"vcmi.adventureMap.moveCostDetails" : "Bewegungspunkte - Kosten: %TURNS Runden + %POINTS Punkte, Verbleibende Punkte: %REMAINING", "vcmi.adventureMap.moveCostDetails" : "Eine Bewegung hierher kostet insgesamt {%TOTAL} Punkte ({%TURNS} Runden und {%POINTS} Punkte). Nach der Bewegung bleiben {%REMAINING} Punkte übrig.",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Bewegungspunkte - Kosten: %POINTS Punkte, Verbleibende Punkte: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Eine Bewegung hierher kostet {%POINTS} Punkte. Nach der Bewegung bleiben {%REMAINING} Punkte übrig.",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Bewegungspunkte: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(Bewegungspunkte: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Das Wiederholen des gegnerischen Zuges ist aktuell noch nicht implementiert!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Das Wiederholen des gegnerischen Zuges ist aktuell noch nicht implementiert!",
"vcmi.bonusSource.artifact" : "Artefakt", "vcmi.bonusSource.artifact" : "Artefakt",
"vcmi.bonusSource.creature" : "Fähigkeit", "vcmi.bonusSource.creature" : "Fähigkeit",
"vcmi.bonusSource.spell" : "Zauber", "vcmi.bonusSource.spell" : "Zauber",
"vcmi.bonusSource.hero" : "Held", "vcmi.bonusSource.hero" : "Held",
"vcmi.bonusSource.commander" : "Commander", "vcmi.bonusSource.commander" : "Command.",
"vcmi.bonusSource.other" : "Anderes", "vcmi.bonusSource.other" : "Anderes",
"vcmi.capitalColors.0" : "Rot", "vcmi.capitalColors.0" : "Rot",
@ -68,6 +68,7 @@
"vcmi.radialWheel.heroGetArtifacts" : "Artefakte von anderen Helden erhalten", "vcmi.radialWheel.heroGetArtifacts" : "Artefakte von anderen Helden erhalten",
"vcmi.radialWheel.heroSwapArtifacts" : "Tausche Artefakte mit anderen Helden", "vcmi.radialWheel.heroSwapArtifacts" : "Tausche Artefakte mit anderen Helden",
"vcmi.radialWheel.heroDismiss" : "Held entlassen", "vcmi.radialWheel.heroDismiss" : "Held entlassen",
"vcmi.radialWheel.upgradeCreatures" : "Alle Kreaturen aufrüsten",
"vcmi.radialWheel.moveTop" : "Ganz nach oben bewegen", "vcmi.radialWheel.moveTop" : "Ganz nach oben bewegen",
"vcmi.radialWheel.moveUp" : "Nach oben bewegen", "vcmi.radialWheel.moveUp" : "Nach oben bewegen",
@ -363,6 +364,8 @@
"vcmi.battleOptions.endWithAutocombat.help": "{Kampf beenden}\n\nAutokampf spielt den Kampf sofort zu Ende", "vcmi.battleOptions.endWithAutocombat.help": "{Kampf beenden}\n\nAutokampf spielt den Kampf sofort zu Ende",
"vcmi.battleOptions.showQuickSpell.hover": "Schnellzauber-Panel anzeigen", "vcmi.battleOptions.showQuickSpell.hover": "Schnellzauber-Panel anzeigen",
"vcmi.battleOptions.showQuickSpell.help": "{Schnellzauber-Panel anzeigen}\n\nZeigt ein Panel, auf dem schnell Zauber ausgewählt werden können", "vcmi.battleOptions.showQuickSpell.help": "{Schnellzauber-Panel anzeigen}\n\nZeigt ein Panel, auf dem schnell Zauber ausgewählt werden können",
"vcmi.battleOptions.showHealthBar.hover": "Gesundheits-Balken anzeigen",
"vcmi.battleOptions.showHealthBar.help": "{Gesundheits-Balken anzeigen}\n\nAnzeige eines Gesundheitsbalkens, der die verbleibende Gesundheit anzeigt, bevor eine Einheit stirbt.",
"vcmi.adventureMap.revisitObject.hover" : "Objekt erneut besuchen", "vcmi.adventureMap.revisitObject.hover" : "Objekt erneut besuchen",
"vcmi.adventureMap.revisitObject.help" : "{Objekt erneut besuchen}\n\nSteht ein Held gerade auf einem Kartenobjekt, kann er den Ort erneut aufsuchen.", "vcmi.adventureMap.revisitObject.help" : "{Objekt erneut besuchen}\n\nSteht ein Held gerade auf einem Kartenobjekt, kann er den Ort erneut aufsuchen.",
@ -415,6 +418,9 @@
"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.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.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.townWindow.upgradeAll.notAllUpgradable" : "Nicht genügend Ressourcen um alle Kreaturen aufzurüsten. Folgende Kreaturen aufrüsten?",
"vcmi.townWindow.upgradeAll.notUpgradable" : "Nicht genügend Ressourcen um mindestens eine Kreatur aufzurüsten.",
"vcmi.logicalExpressions.anyOf" : "Eines der folgenden:", "vcmi.logicalExpressions.anyOf" : "Eines der folgenden:",
"vcmi.logicalExpressions.allOf" : "Alles der folgenden:", "vcmi.logicalExpressions.allOf" : "Alles der folgenden:",
"vcmi.logicalExpressions.noneOf" : "Keines der folgenden:", "vcmi.logicalExpressions.noneOf" : "Keines der folgenden:",
@ -424,11 +430,11 @@
"vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen", "vcmi.heroWindow.openBackpack.hover" : "Artefakt-Rucksack-Fenster öffnen",
"vcmi.heroWindow.openBackpack.help" : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert", "vcmi.heroWindow.openBackpack.help" : "Öffnet ein Fenster, das die Verwaltung des Artefakt-Rucksacks erleichtert",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Nach Kosten sortieren", "vcmi.heroWindow.sortBackpackByCost.hover" : "Nach Kosten sortieren",
"vcmi.heroWindow.sortBackpackByCost.help" : "Artefakte im Rucksack nach Kosten sortieren.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Nach Kosten sortieren}\n\nArtefakte im Rucksack nach Kosten sortieren.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Nach Slot sortieren", "vcmi.heroWindow.sortBackpackBySlot.hover" : "Nach Slot sortieren",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Artefakte im Rucksack nach Ausrüstungsslot sortieren.", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Nach Slot sortieren}\n\nArtefakte im Rucksack nach Ausrüstungsslot sortieren.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Nach Klasse sortieren", "vcmi.heroWindow.sortBackpackByClass.hover" : "Nach Klasse sortieren",
"vcmi.heroWindow.sortBackpackByClass.help" : "Artefakte im Rucksack nach Artefaktklasse sortieren. Schatz, Klein, Groß, Relikt", "vcmi.heroWindow.sortBackpackByClass.help" : "{Nach Klasse sortieren}\n\nArtefakte 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.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", "vcmi.tavernWindow.inviteHero" : "Helden einladen",
@ -608,7 +614,7 @@
"core.seerhut.quest.reachDate.visit.5" : "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.", "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.name": "Doppelschlag",
"core.bonus.ADDITIONAL_ATTACK.description": "Greift zweimal an", "core.bonus.ADDITIONAL_ATTACK.description": "Greift zweimal an",
"core.bonus.ADDITIONAL_RETALIATION.name": "Zusätzliche Vergeltungsmaßnahmen", "core.bonus.ADDITIONAL_RETALIATION.name": "Zusätzliche Vergeltungsmaßnahmen",
@ -763,16 +769,6 @@
"core.bonus.MECHANICAL.description": "Immunität gegen viele Effekte, reparierbar", "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.name": "Prisma-Atem",
"core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Prisma-Atem-Angriff (drei Richtungen)", "core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Prisma-Atem-Angriff (drei Richtungen)",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name": "Zauber-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description": "Immunität gegen alle Zauber-Schulen",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.air": "Luft-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.fire": "Feuer-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.water": "Wasser-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.earth": "Erde-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.air": "Immunität gegen Zauber der Luft-Schule",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.fire": "Immunität gegen Zauber der Feuer-Schule",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.water": "Immunität gegen Zauber der Wasser-Schule",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth": "Immunität gegen Zauber der Erde-Schule",
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Zauberwiderstand", "core.bonus.SPELL_DAMAGE_REDUCTION.name": "Zauberwiderstand",
"core.bonus.SPELL_DAMAGE_REDUCTION.name.air": "Luft-Zauberwiderstand", "core.bonus.SPELL_DAMAGE_REDUCTION.name.air": "Luft-Zauberwiderstand",
"core.bonus.SPELL_DAMAGE_REDUCTION.name.fire": "Feuer-Zauberwiderstand", "core.bonus.SPELL_DAMAGE_REDUCTION.name.fire": "Feuer-Zauberwiderstand",
@ -783,6 +779,38 @@
"core.bonus.SPELL_DAMAGE_REDUCTION.description.fire": "Schaden von Feuer-Zaubern um ${val}% reduziert.", "core.bonus.SPELL_DAMAGE_REDUCTION.description.fire": "Schaden von Feuer-Zaubern um ${val}% reduziert.",
"core.bonus.SPELL_DAMAGE_REDUCTION.description.water": "Schaden von Wasser-Zaubern um ${val}% reduziert.", "core.bonus.SPELL_DAMAGE_REDUCTION.description.water": "Schaden von Wasser-Zaubern um ${val}% reduziert.",
"core.bonus.SPELL_DAMAGE_REDUCTION.description.earth": "Schaden von Erde-Zaubern um ${val}% reduziert.", "core.bonus.SPELL_DAMAGE_REDUCTION.description.earth": "Schaden von Erde-Zaubern um ${val}% reduziert.",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name": "Zauber-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.air": "Luft-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.fire": "Feuer-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.water": "Wasser-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.earth": "Erde-Immunität",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description": "Immunität gegen alle Zauber-Schulen",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.air": "Immunität gegen Zauber der Luft-Schule",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.fire": "Immunität gegen Zauber der Feuer-Schule",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.water": "Immunität gegen Zauber der Wasser-Schule",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth": "Immunität gegen Zauber der Erde-Schule",
"core.bonus.OPENING_BATTLE_SPELL.name": "Startet mit Zauber", "core.bonus.OPENING_BATTLE_SPELL.name": "Startet mit Zauber",
"core.bonus.OPENING_BATTLE_SPELL.description": "Wirkt ${subtype.spell} beim Start des Kampfes" "core.bonus.OPENING_BATTLE_SPELL.description": "Wirkt ${subtype.spell} beim Start des Kampfes",
"spell.core.castleMoat.name" : "Graben",
"spell.core.castleMoatTrigger.name" : "Graben",
"spell.core.catapultShot.name" : "Katapultschuss",
"spell.core.cyclopsShot.name" : "Belagerungsschuss",
"spell.core.dungeonMoat.name" : "Siedeöl",
"spell.core.dungeonMoatTrigger.name" : "Siedeöl",
"spell.core.fireWallTrigger.name" : "Feuerwand",
"spell.core.firstAid.name" : "Erste Hilfe",
"spell.core.fortressMoat.name" : "Siedender Teer",
"spell.core.fortressMoatTrigger.name" : "Siedender Teer",
"spell.core.infernoMoat.name" : "Lava",
"spell.core.infernoMoatTrigger.name" : "Lava",
"spell.core.landMineTrigger.name" : "Landmine",
"spell.core.necropolisMoat.name" : "Knochenplatz",
"spell.core.necropolisMoatTrigger.name" : "Knochenplatz",
"spell.core.rampartMoat.name" : "Brombeeren",
"spell.core.rampartMoatTrigger.name" : "Brombeeren",
"spell.core.strongholdMoat.name" : "Holzspieße",
"spell.core.strongholdMoatTrigger.name" : "Holzspieße",
"spell.core.summonDemons.name" : "Dämonen beschwören",
"spell.core.towerMoat.name" : "Landmine"
} }

View File

@ -23,8 +23,6 @@
"vcmi.adventureMap.noTownWithTavern" : "Nincsenek elérhető kocsmával rendelkező városok!", "vcmi.adventureMap.noTownWithTavern" : "Nincsenek elérhető kocsmával rendelkező városok!",
"vcmi.adventureMap.spellUnknownProblem" : "Ismeretlen probléma van ezzel a varázslattal! További információ nem érhető el.", "vcmi.adventureMap.spellUnknownProblem" : "Ismeretlen probléma van ezzel a varázslattal! További információ nem érhető el.",
"vcmi.adventureMap.playerAttacked" : "A játékost megtámadták: %s", "vcmi.adventureMap.playerAttacked" : "A játékost megtámadták: %s",
"vcmi.adventureMap.moveCostDetails" : "Mozgáspontok - Költség: %TURNS kör + %POINTS pont, Hátralévő pontok: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Mozgáspontok - Költség: %POINTS pont, Hátralévő pontok: %REMAINING",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Mozgáspontok: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(Mozgáspontok: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sajnáljuk, az ellenfél körének visszajátszása még nincs megvalósítva!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Sajnáljuk, az ellenfél körének visszajátszása még nincs megvalósítva!",
@ -424,11 +422,11 @@
"vcmi.heroWindow.openBackpack.hover" : "Műtárgy hátizsák ablak megnyitása", "vcmi.heroWindow.openBackpack.hover" : "Műtárgy hátizsák ablak megnyitása",
"vcmi.heroWindow.openBackpack.help" : "Az ablak megnyitása, amely megkönnyíti a műtárgy hátizsák kezelését.", "vcmi.heroWindow.openBackpack.help" : "Az ablak megnyitása, amely megkönnyíti a műtárgy hátizsák kezelését.",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Rendezés ár szerint", "vcmi.heroWindow.sortBackpackByCost.hover" : "Rendezés ár szerint",
"vcmi.heroWindow.sortBackpackByCost.help" : "A műtárgyak ár szerinti rendezése a hátizsákban.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Rendezés ár szerint}\n\nA műtárgyak ár szerinti rendezése a hátizsákban.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Rendezés nyílás szerint", "vcmi.heroWindow.sortBackpackBySlot.hover" : "Rendezés nyílás szerint",
"vcmi.heroWindow.sortBackpackBySlot.help" : "A műtárgyak nyílás szerinti rendezése a hátizsákban.", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Rendezés nyílás szerint}\n\nA műtárgyak nyílás szerinti rendezése a hátizsákban.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Rendezés osztály szerint", "vcmi.heroWindow.sortBackpackByClass.hover" : "Rendezés osztály szerint",
"vcmi.heroWindow.sortBackpackByClass.help" : "A műtárgyak osztály szerinti rendezése a hátizsákban. Kincs, Kisebb, Nagyobb, Relikvia", "vcmi.heroWindow.sortBackpackByClass.help" : "{Rendezés osztály szerint}\n\nA műtárgyak osztály szerinti rendezése a hátizsákban. Kincs, Kisebb, Nagyobb, Relikvia",
"vcmi.heroWindow.fusingArtifact.fusing" : "Ön birtokában van az összes szükséges komponensnek a(z) %s összeolvasztásához. Szeretné elvégezni az összeolvasztást? {Minden komponens elfogy az összeolvasztás során.}", "vcmi.heroWindow.fusingArtifact.fusing" : "Ön birtokában van az összes szükséges komponensnek a(z) %s összeolvasztásához. Szeretné elvégezni az összeolvasztást? {Minden komponens elfogy az összeolvasztás során.}",
"vcmi.tavernWindow.inviteHero" : "Hős meghívása", "vcmi.tavernWindow.inviteHero" : "Hős meghívása",

View File

@ -0,0 +1,809 @@
{
"vcmi.adventureMap.monsterThreat.title" : "\n\nMinaccia: ",
"vcmi.adventureMap.monsterThreat.levels.0" : "Facile",
"vcmi.adventureMap.monsterThreat.levels.1" : "Molto Debole",
"vcmi.adventureMap.monsterThreat.levels.2" : "Debole",
"vcmi.adventureMap.monsterThreat.levels.3" : "Un po' più debole",
"vcmi.adventureMap.monsterThreat.levels.4" : "Uguale",
"vcmi.adventureMap.monsterThreat.levels.5" : "Un po' più forte",
"vcmi.adventureMap.monsterThreat.levels.6" : "Forte",
"vcmi.adventureMap.monsterThreat.levels.7" : "Molto Forte",
"vcmi.adventureMap.monsterThreat.levels.8" : "Difficile",
"vcmi.adventureMap.monsterThreat.levels.9" : "Schiacciante",
"vcmi.adventureMap.monsterThreat.levels.10" : "Mortale",
"vcmi.adventureMap.monsterThreat.levels.11" : "Impossibile",
"vcmi.adventureMap.monsterLevel" : "\n\nUnità di livello %LEVEL %TOWN %ATTACK_TYPE",
"vcmi.adventureMap.monsterMeleeType" : "corpo a corpo",
"vcmi.adventureMap.monsterRangedType" : "a distanza",
"vcmi.adventureMap.search.hover" : "Cerca oggetto sulla mappa",
"vcmi.adventureMap.search.help" : "Seleziona un oggetto da cercare sulla mappa.",
"vcmi.adventureMap.confirmRestartGame" : "Sei sicuro di voler riavviare il gioco?",
"vcmi.adventureMap.noTownWithMarket" : "Non ci sono mercati disponibili!",
"vcmi.adventureMap.noTownWithTavern" : "Non ci sono città disponibili con taverne!",
"vcmi.adventureMap.spellUnknownProblem" : "C'è un problema sconosciuto con questo incantesimo! Nessuna informazione aggiuntiva disponibile.",
"vcmi.adventureMap.playerAttacked" : "Il giocatore è stato attaccato: %s",
"vcmi.adventureMap.moveCostDetails" : "Punti movimento - Costo: %TURNS turni + %POINTS punti, Punti rimanenti: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Punti movimento - Costo: %POINTS punti, Punti rimanenti: %REMAINING",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Punti movimento: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Spiacente, la riproduzione del turno avversario non è ancora implementata!",
"vcmi.bonusSource.artifact" : "Artefatto",
"vcmi.bonusSource.creature" : "Abilità",
"vcmi.bonusSource.spell" : "Incantesimo",
"vcmi.bonusSource.hero" : "Eroe",
"vcmi.bonusSource.commander" : "Comandante",
"vcmi.bonusSource.other" : "Altro",
"vcmi.capitalColors.0" : "Rosso",
"vcmi.capitalColors.1" : "Blu",
"vcmi.capitalColors.2" : "Marrone",
"vcmi.capitalColors.3" : "Verde",
"vcmi.capitalColors.4" : "Arancione",
"vcmi.capitalColors.5" : "Viola",
"vcmi.capitalColors.6" : "Turchese",
"vcmi.capitalColors.7" : "Rosa",
"vcmi.heroOverview.startingArmy" : "Unità Iniziali",
"vcmi.heroOverview.warMachine" : "Macchine da Guerra",
"vcmi.heroOverview.secondarySkills" : "Abilità Secondarie",
"vcmi.heroOverview.spells" : "Incantesimi",
"vcmi.quickExchange.moveUnit" : "Sposta Unità",
"vcmi.quickExchange.moveAllUnits" : "Sposta Tutte le Unità",
"vcmi.quickExchange.swapAllUnits" : "Scambia Eserciti",
"vcmi.quickExchange.moveAllArtifacts" : "Sposta Tutti gli Artefatti",
"vcmi.quickExchange.swapAllArtifacts" : "Scambia Artefatti",
"vcmi.radialWheel.mergeSameUnit" : "Unisci creature dello stesso tipo",
"vcmi.radialWheel.fillSingleUnit" : "Riempi con creature singole",
"vcmi.radialWheel.splitSingleUnit" : "Dividi una singola creatura",
"vcmi.radialWheel.splitUnitEqually" : "Dividi equamente le creature",
"vcmi.radialWheel.moveUnit" : "Sposta creature in un altro esercito",
"vcmi.radialWheel.splitUnit" : "Dividi creatura in un altro slot",
"vcmi.radialWheel.heroGetArmy" : "Prendi l'esercito da un altro eroe",
"vcmi.radialWheel.heroSwapArmy" : "Scambia l'esercito con un altro eroe",
"vcmi.radialWheel.heroExchange" : "Apri scambio eroe",
"vcmi.radialWheel.heroGetArtifacts" : "Prendi artefatti da un altro eroe",
"vcmi.radialWheel.heroSwapArtifacts" : "Scambia artefatti con un altro eroe",
"vcmi.radialWheel.heroDismiss" : "Congeda l'eroe",
"vcmi.radialWheel.moveTop" : "Sposta in alto",
"vcmi.radialWheel.moveUp" : "Sposta su",
"vcmi.radialWheel.moveDown" : "Sposta giù",
"vcmi.radialWheel.moveBottom" : "Sposta in basso",
"vcmi.randomMap.description" : "Mappa creata dal Generatore di Mappe Casuali.\nIl modello era %s, dimensione %dx%d, livelli %d, giocatori %d, computer %d, acqua %s, mostri %s, mappa VCMI",
"vcmi.randomMap.description.isHuman" : ", %s è un umano",
"vcmi.randomMap.description.townChoice" : ", la scelta della città di %s è %s",
"vcmi.randomMap.description.water.none" : "nessuna",
"vcmi.randomMap.description.water.normal" : "normale",
"vcmi.randomMap.description.water.islands" : "isole",
"vcmi.randomMap.description.monster.weak" : "debole",
"vcmi.randomMap.description.monster.normal" : "normale",
"vcmi.randomMap.description.monster.strong" : "forte",
"vcmi.spellBook.search" : "cerca...",
"vcmi.spellResearch.canNotAfford" : "Non puoi permetterti di sostituire {%SPELL1} con {%SPELL2}. Ma puoi comunque scartare questo incantesimo e continuare la ricerca.",
"vcmi.spellResearch.comeAgain" : "La ricerca è già stata fatta oggi. Torna domani.",
"vcmi.spellResearch.pay" : "Vuoi sostituire {%SPELL1} con {%SPELL2}? Oppure scartare questo incantesimo e continuare la ricerca?",
"vcmi.spellResearch.research" : "Ricerca questo Incantesimo",
"vcmi.spellResearch.skip" : "Salta questo Incantesimo",
"vcmi.spellResearch.abort" : "Annulla",
"vcmi.spellResearch.noMoreSpells" : "Non ci sono più incantesimi disponibili per la ricerca.",
"vcmi.mainMenu.serverConnecting" : "Connessione in corso...",
"vcmi.mainMenu.serverAddressEnter" : "Inserisci indirizzo:",
"vcmi.mainMenu.serverConnectionFailed" : "Connessione fallita",
"vcmi.mainMenu.serverClosing" : "Chiusura in corso...",
"vcmi.mainMenu.hostTCP" : "Ospita una partita TCP/IP",
"vcmi.mainMenu.joinTCP" : "Unisciti a una partita TCP/IP",
"vcmi.lobby.filepath" : "Percorso file",
"vcmi.lobby.creationDate" : "Data di creazione",
"vcmi.lobby.scenarioName" : "Nome dello scenario",
"vcmi.lobby.mapPreview" : "Anteprima mappa",
"vcmi.lobby.noPreview" : "nessuna anteprima",
"vcmi.lobby.noUnderground" : "nessun sotterraneo",
"vcmi.lobby.sortDate" : "Ordina le mappe per data di modifica",
"vcmi.lobby.backToLobby" : "Ritorna alla lobby",
"vcmi.lobby.author" : "Autore",
"vcmi.lobby.handicap" : "Handicap",
"vcmi.lobby.handicap.resource" : "Dà ai giocatori risorse appropriate per iniziare oltre a quelle normali. I valori negativi sono consentiti, ma limitati a 0 (il giocatore non inizia mai con risorse negative).",
"vcmi.lobby.handicap.income" : "Modifica le entrate del giocatore in percentuale. Arrotondato per eccesso.",
"vcmi.lobby.handicap.growth" : "Modifica la crescita delle creature nelle città possedute dal giocatore. Arrotondato per eccesso.",
"vcmi.lobby.deleteUnsupportedSave" : "{Salvataggi non supportati trovati}\n\nVCMI ha trovato %d salvataggi non più supportati, probabilmente a causa di differenze tra le versioni di VCMI.\n\nVuoi eliminarli?",
"vcmi.lobby.deleteSaveGameTitle" : "Seleziona un salvataggio da eliminare",
"vcmi.lobby.deleteMapTitle" : "Seleziona uno scenario da eliminare",
"vcmi.lobby.deleteFile" : "Vuoi eliminare il seguente file?",
"vcmi.lobby.deleteFolder" : "Vuoi eliminare la seguente cartella?",
"vcmi.lobby.deleteMode" : "Passa alla modalità elimina e torna indietro",
"vcmi.broadcast.failedLoadGame" : "Impossibile caricare la partita",
"vcmi.broadcast.command" : "Usa '!help' per elencare i comandi disponibili",
"vcmi.broadcast.simturn.end" : "I turni simultanei sono terminati",
"vcmi.broadcast.simturn.endBetween" : "I turni simultanei tra i giocatori %s e %s sono terminati",
"vcmi.broadcast.serverProblem" : "Il server ha riscontrato un problema",
"vcmi.broadcast.gameTerminated" : "la partita è stata terminata",
"vcmi.broadcast.gameSavedAs" : "partita salvata come",
"vcmi.broadcast.noCheater" : "Nessun baro registrato!",
"vcmi.broadcast.playerCheater" : "Il giocatore %s ha usato i trucchi!",
"vcmi.broadcast.statisticFile" : "I file delle statistiche possono essere trovati nella directory %s",
"vcmi.broadcast.help.commands" : "Comandi disponibili per l'host:",
"vcmi.broadcast.help.exit" : "'!exit' - termina immediatamente la partita in corso",
"vcmi.broadcast.help.kick" : "'!kick <player>' - espelle il giocatore specificato dalla partita",
"vcmi.broadcast.help.save" : "'!save <filename>' - salva la partita con il nome specificato",
"vcmi.broadcast.help.statistic" : "'!statistic' - salva le statistiche della partita come file CSV",
"vcmi.broadcast.help.commandsAll" : "Comandi disponibili per tutti i giocatori:",
"vcmi.broadcast.help.help" : "'!help' - mostra questo aiuto",
"vcmi.broadcast.help.cheaters" : "'!cheaters' - elenca i giocatori che hanno usato trucchi durante la partita",
"vcmi.broadcast.help.vote" : "'!vote' - consente di modificare alcune impostazioni di gioco se tutti i giocatori sono d'accordo",
"vcmi.broadcast.vote.allow" : "'!vote simturns allow X' - consente turni simultanei per un numero specificato di giorni, o fino al contatto",
"vcmi.broadcast.vote.force" : "'!vote simturns force X' - forza turni simultanei per un numero specificato di giorni, bloccando i contatti tra giocatori",
"vcmi.broadcast.vote.abort" : "'!vote simturns abort' - interrompe i turni simultanei alla fine di questo turno",
"vcmi.broadcast.vote.timer" : "'!vote timer prolong X' - prolunga il timer di base per tutti i giocatori del numero specificato di secondi",
"vcmi.broadcast.vote.noActive" : "Nessuna votazione attiva!",
"vcmi.broadcast.vote.yes" : "sì",
"vcmi.broadcast.vote.no" : "no",
"vcmi.broadcast.vote.notRecognized" : "Comando di votazione non riconosciuto!",
"vcmi.broadcast.vote.success.untilContacts" : "Votazione riuscita. I turni simultanei dureranno ancora %s giorni, o fino al contatto",
"vcmi.broadcast.vote.success.contactsBlocked" : "Votazione riuscita. I turni simultanei dureranno ancora %s giorni. I contatti sono bloccati",
"vcmi.broadcast.vote.success.nextDay" : "Votazione riuscita. I turni simultanei termineranno il giorno successivo",
"vcmi.broadcast.vote.success.timer" : "Votazione riuscita. Il timer per tutti i giocatori è stato prolungato di %s secondi",
"vcmi.broadcast.vote.aborted" : "Un giocatore ha votato contro la modifica. Votazione annullata",
"vcmi.broadcast.vote.start.untilContacts" : "Votazione avviata per consentire turni simultanei per altri %s giorni",
"vcmi.broadcast.vote.start.contactsBlocked" : "Votazione avviata per forzare turni simultanei per altri %s giorni",
"vcmi.broadcast.vote.start.nextDay" : "Votazione avviata per terminare i turni simultanei dal giorno successivo",
"vcmi.broadcast.vote.start.timer" : "Votazione avviata per prolungare il timer per tutti i giocatori di %s secondi",
"vcmi.broadcast.vote.hint" : "Digita '!vote yes' per accettare la modifica o '!vote no' per rifiutarla",
"vcmi.lobby.login.title" : "VCMI Lobby Online",
"vcmi.lobby.login.username" : "Nome utente:",
"vcmi.lobby.login.connecting" : "Connessione in corso...",
"vcmi.lobby.login.error" : "Errore di connessione: %s",
"vcmi.lobby.login.create" : "Nuovo Account",
"vcmi.lobby.login.login" : "Accedi",
"vcmi.lobby.login.as" : "Accedi come %s",
"vcmi.lobby.login.spectator" : "Spettatore",
"vcmi.lobby.header.rooms" : "Stanze di gioco - %d",
"vcmi.lobby.header.channels" : "Canali chat",
"vcmi.lobby.header.chat.global" : "Chat globale - %s", // %s -> language name
"vcmi.lobby.header.chat.match" : "Chat dalla partita precedente su %s", // %s -> game start date & time
"vcmi.lobby.header.chat.player" : "Chat privata con %s", // %s -> nickname of another player
"vcmi.lobby.header.history" : "Le tue partite precedenti",
"vcmi.lobby.header.players" : "Giocatori online - %d",
"vcmi.lobby.match.solo" : "Partita in singolo",
"vcmi.lobby.match.duel" : "Partita con %s", // %s -> nickname of another player
"vcmi.lobby.match.multi" : "%d giocatori",
"vcmi.lobby.room.create" : "Crea nuova stanza",
"vcmi.lobby.room.players.limit" : "Limite giocatori",
"vcmi.lobby.room.description.public" : "Qualsiasi giocatore può entrare in una stanza pubblica.",
"vcmi.lobby.room.description.private" : "Solo i giocatori invitati possono entrare in una stanza privata.",
"vcmi.lobby.room.description.new" : "Per avviare la partita, seleziona uno scenario o imposta una mappa casuale.",
"vcmi.lobby.room.description.load" : "Per avviare la partita, usa uno dei tuoi salvataggi.",
"vcmi.lobby.room.description.limit" : "Fino a %d giocatori possono entrare nella tua stanza, incluso te.",
"vcmi.lobby.invite.header" : "Invita giocatori",
"vcmi.lobby.invite.notification" : "Un giocatore ti ha invitato nella sua stanza di gioco. Ora puoi unirti alla sua stanza privata.",
"vcmi.lobby.preview.title" : "Unisciti alla stanza di gioco",
"vcmi.lobby.preview.subtitle" : "Partita su %s, ospitata da %s", //TL Note: 1) name of map or RMG template 2) nickname of game host
"vcmi.lobby.preview.version" : "Versione di gioco:",
"vcmi.lobby.preview.players" : "Giocatori:",
"vcmi.lobby.preview.mods" : "Mod utilizzate:",
"vcmi.lobby.preview.allowed" : "Vuoi unirti alla stanza di gioco?",
"vcmi.lobby.preview.error.header" : "Impossibile unirsi a questa stanza.",
"vcmi.lobby.preview.error.playing" : "Devi prima uscire dalla tua partita attuale.",
"vcmi.lobby.preview.error.full" : "La stanza è già piena.",
"vcmi.lobby.preview.error.busy" : "La stanza non accetta più nuovi giocatori.",
"vcmi.lobby.preview.error.invite" : "Non sei stato invitato a questa stanza.",
"vcmi.lobby.preview.error.mods" : "Stai usando un set di mod diverso.",
"vcmi.lobby.preview.error.version" : "Stai usando una versione diversa di VCMI.",
"vcmi.lobby.room.new" : "Nuova partita",
"vcmi.lobby.room.load" : "Carica partita",
"vcmi.lobby.room.type" : "Tipo di stanza",
"vcmi.lobby.room.mode" : "Modalità di gioco",
"vcmi.lobby.room.state.public" : "Pubblica",
"vcmi.lobby.room.state.private" : "Privata",
"vcmi.lobby.room.state.busy" : "In gioco",
"vcmi.lobby.room.state.invited" : "Invitato",
"vcmi.lobby.mod.state.compatible" : "Compatibile",
"vcmi.lobby.mod.state.disabled" : "Deve essere attivato",
"vcmi.lobby.mod.state.version" : "Versione incompatibile",
"vcmi.lobby.mod.state.excessive" : "Deve essere disattivato",
"vcmi.lobby.mod.state.missing" : "Non installato",
"vcmi.lobby.pvp.coin.hover" : "Lancia la moneta",
"vcmi.lobby.pvp.coin.help" : "Lancia una moneta",
"vcmi.lobby.pvp.randomTown.hover" : "Città casuale",
"vcmi.lobby.pvp.randomTown.help" : "Scrivi una città casuale in chat",
"vcmi.lobby.pvp.randomTownVs.hover" : "Città casuale vs.",
"vcmi.lobby.pvp.randomTownVs.help" : "Scrivi due città casuali in chat",
"vcmi.lobby.pvp.versus" : "vs.",
"vcmi.client.errors.invalidMap" : "{Mappa o campagna non valida}\n\nImpossibile avviare la partita! La mappa o la campagna selezionata potrebbe essere non valida o corrotta. Motivo:\n%s",
"vcmi.client.errors.missingCampaigns" : "{File dati mancanti}\n\nI file dati delle campagne non sono stati trovati! Potresti avere file dati incompleti o corrotti di Heroes 3. Reinstalla i dati del gioco.",
"vcmi.client.errors.modLoadingFailure" : "{Errore di caricamento delle mod}\n\nSono stati rilevati problemi critici durante il caricamento delle mod! Il gioco potrebbe non funzionare correttamente o bloccarsi! Aggiorna o disattiva le seguenti mod:\n\n",
"vcmi.server.errors.disconnected" : "{Errore di rete}\n\nLa connessione al server di gioco è stata persa!",
"vcmi.server.errors.playerLeft" : "{Giocatore disconnesso}\n\nIl giocatore %s si è disconnesso dalla partita!", //%s -> player color
"vcmi.server.errors.existingProcess" : "Un altro processo del server VCMI è in esecuzione. Terminarlo prima di avviare una nuova partita.",
"vcmi.server.errors.modsToEnable" : "{Le seguenti mod sono richieste}",
"vcmi.server.errors.modsToDisable" : "{Le seguenti mod devono essere disattivate}",
"vcmi.server.errors.unknownEntity" : "Impossibile caricare il salvataggio! Entità sconosciuta '%s' trovata nel salvataggio! Il salvataggio potrebbe non essere compatibile con la versione attualmente installata delle mod!",
"vcmi.server.errors.wrongIdentified" : "Sei stato identificato come giocatore %s mentre ci si aspettava %s",
"vcmi.server.errors.notAllowed" : "Non ti è permesso eseguire questa azione!",
"vcmi.dimensionDoor.seaToLandError" : "Non è possibile teletrasportarsi dal mare alla terraferma o viceversa con la Porta Dimensionale.",
"vcmi.settingsMainWindow.generalTab.hover" : "Generale",
"vcmi.settingsMainWindow.generalTab.help" : "Passa alla scheda Opzioni generali, che contiene le impostazioni relative al comportamento generale del client di gioco.",
"vcmi.settingsMainWindow.battleTab.hover" : "Battaglia",
"vcmi.settingsMainWindow.battleTab.help" : "Passa alla scheda Opzioni di battaglia, che consente di configurare il comportamento del gioco durante le battaglie.",
"vcmi.settingsMainWindow.adventureTab.hover" : "Mappa Avventura",
"vcmi.settingsMainWindow.adventureTab.help" : "Passa alla scheda Opzioni Mappa Avventura (la mappa dell'avventura è la sezione del gioco in cui i giocatori controllano i movimenti degli eroi).",
"vcmi.systemOptions.videoGroup" : "Impostazioni video",
"vcmi.systemOptions.audioGroup" : "Impostazioni audio",
"vcmi.systemOptions.otherGroup" : "Altre impostazioni", // unused right now
"vcmi.systemOptions.townsGroup" : "Schermata della città",
"vcmi.statisticWindow.statistics" : "Statistiche",
"vcmi.statisticWindow.tsvCopy" : "Dati negli appunti",
"vcmi.statisticWindow.selectView" : "Seleziona vista",
"vcmi.statisticWindow.value" : "Valore",
"vcmi.statisticWindow.title.overview" : "Panoramica",
"vcmi.statisticWindow.title.resources" : "Risorse",
"vcmi.statisticWindow.title.income" : "Entrate",
"vcmi.statisticWindow.title.numberOfHeroes" : "Numero di eroi",
"vcmi.statisticWindow.title.numberOfTowns" : "Numero di città",
"vcmi.statisticWindow.title.numberOfArtifacts" : "Numero di artefatti",
"vcmi.statisticWindow.title.numberOfDwellings" : "Numero di dimore",
"vcmi.statisticWindow.title.numberOfMines" : "Numero di miniere",
"vcmi.statisticWindow.title.armyStrength" : "Forza dell'esercito",
"vcmi.statisticWindow.title.experience" : "Esperienza",
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Costi dell'esercito",
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Costi degli edifici",
"vcmi.statisticWindow.title.mapExplored" : "Percentuale di mappa esplorata",
"vcmi.statisticWindow.param.playerName" : "Nome giocatore",
"vcmi.statisticWindow.param.daysSurvived" : "Giorni sopravvissuti",
"vcmi.statisticWindow.param.maxHeroLevel" : "Livello massimo dell'eroe",
"vcmi.statisticWindow.param.battleWinRatioHero" : "Tasso di vittoria (vs. eroe)",
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Tasso di vittoria (vs. neutrali)",
"vcmi.statisticWindow.param.battlesHero" : "Battaglie (vs. eroe)",
"vcmi.statisticWindow.param.battlesNeutral" : "Battaglie (vs. neutrali)",
"vcmi.statisticWindow.param.maxArmyStrength" : "Massima forza dell'esercito totale",
"vcmi.statisticWindow.param.tradeVolume" : "Volume di scambio",
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisco visitato",
"vcmi.statisticWindow.icon.townCaptured" : "Città conquistate",
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Eroe più forte dell'avversario sconfitto",
"vcmi.statisticWindow.icon.grailFound" : "Graal trovato",
"vcmi.statisticWindow.icon.defeated" : "Sconfitto",
"vcmi.systemOptions.fullscreenBorderless.hover" : "Schermo intero (senza bordi)",
"vcmi.systemOptions.fullscreenBorderless.help" : "{Schermo intero senza bordi}\n\nSe selezionato, VCMI verrà eseguito in modalità schermo intero senza bordi. In questa modalità, il gioco utilizzerà sempre la stessa risoluzione del desktop, ignorando la risoluzione selezionata.",
"vcmi.systemOptions.fullscreenExclusive.hover" : "Schermo intero (esclusivo)",
"vcmi.systemOptions.fullscreenExclusive.help" : "{Schermo intero}\n\nSe selezionato, VCMI verrà eseguito in modalità schermo intero esclusiva. In questa modalità, il gioco cambierà la risoluzione del monitor con quella selezionata.",
"vcmi.systemOptions.resolutionButton.hover" : "Risoluzione: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Seleziona risoluzione}\n\nModifica la risoluzione dello schermo di gioco.",
"vcmi.systemOptions.resolutionMenu.hover" : "Seleziona risoluzione",
"vcmi.systemOptions.resolutionMenu.help" : "Modifica la risoluzione dello schermo di gioco.",
"vcmi.systemOptions.scalingButton.hover" : "Scala interfaccia: %p%",
"vcmi.systemOptions.scalingButton.help" : "{Scala interfaccia}\n\nModifica la scala dell'interfaccia di gioco.",
"vcmi.systemOptions.scalingMenu.hover" : "Seleziona scala interfaccia",
"vcmi.systemOptions.scalingMenu.help" : "Modifica la scala dell'interfaccia di gioco.",
"vcmi.systemOptions.longTouchButton.hover" : "Intervallo di tocco lungo: %d ms",
"vcmi.systemOptions.longTouchButton.help" : "{Intervallo di tocco lungo}\n\nQuando si utilizza il touchscreen, le finestre popup appariranno dopo aver toccato lo schermo per la durata specificata in millisecondi.",
"vcmi.systemOptions.longTouchMenu.hover" : "Seleziona intervallo di tocco lungo",
"vcmi.systemOptions.longTouchMenu.help" : "Modifica la durata dell'intervallo di tocco lungo.",
"vcmi.systemOptions.longTouchMenu.entry" : "%d millisecondi",
"vcmi.systemOptions.framerateButton.hover" : "Mostra FPS",
"vcmi.systemOptions.framerateButton.help" : "{Mostra FPS}\n\nAttiva o disattiva la visibilità del contatore dei fotogrammi al secondo nell'angolo della finestra di gioco.",
"vcmi.systemOptions.hapticFeedbackButton.hover" : "Feedback aptico",
"vcmi.systemOptions.hapticFeedbackButton.help" : "{Feedback aptico}\n\nAttiva o disattiva il feedback aptico sugli input tattili.",
"vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Miglioramenti interfaccia",
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Miglioramenti interfaccia}\n\nAttiva vari miglioramenti della qualità della vita nell'interfaccia. Ad esempio, un pulsante per lo zaino, ecc. Disabilitalo per un'esperienza più classica.",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Libro degli incantesimi grande",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Libro degli incantesimi grande}\n\nAbilita un libro degli incantesimi più grande che mostra più incantesimi per pagina. L'animazione del cambio pagina non funziona con questa impostazione attivata.",
"vcmi.systemOptions.audioMuteFocus.hover" : "Silenzioso quando inattivo",
"vcmi.systemOptions.audioMuteFocus.help" : "{Silenzioso quando inattivo}\n\nDisattiva l'audio quando la finestra del gioco non è attiva. Fanno eccezione i messaggi di gioco e il suono del nuovo turno.",
"vcmi.adventureOptions.infoBarPick.hover" : "Mostra messaggi nel pannello informazioni",
"vcmi.adventureOptions.infoBarPick.help" : "{Mostra messaggi nel pannello informazioni}\n\nQuando possibile, i messaggi del gioco provenienti dagli oggetti sulla mappa saranno mostrati nel pannello informazioni, invece che comparire in una finestra separata.",
"vcmi.adventureOptions.numericQuantities.hover" : "Quantità di creature numeriche",
"vcmi.adventureOptions.numericQuantities.help" : "{Quantità di creature numeriche}\n\nMostra la quantità approssimativa di creature nemiche nel formato numerico A-B.",
"vcmi.adventureOptions.forceMovementInfo.hover" : "Mostra sempre il costo del movimento",
"vcmi.adventureOptions.forceMovementInfo.help" : "{Mostra sempre il costo del movimento}\n\nMostra sempre i dati dei punti movimento nella barra di stato (invece di visualizzarli solo quando si tiene premuto il tasto ALT).",
"vcmi.adventureOptions.showGrid.hover" : "Mostra griglia",
"vcmi.adventureOptions.showGrid.help" : "{Mostra griglia}\n\nMostra la griglia di sovrapposizione, evidenziando i confini tra le caselle della mappa avventura.",
"vcmi.adventureOptions.borderScroll.hover" : "Scorrimento ai bordi",
"vcmi.adventureOptions.borderScroll.help" : "{Scorrimento ai bordi}\n\nScorri la mappa avventura quando il cursore è vicino al bordo della finestra. Può essere disattivato tenendo premuto il tasto CTRL.",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Gestione creature nel pannello informazioni",
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Gestione creature nel pannello informazioni}\n\nConsente di riordinare le creature nel pannello informazioni invece di alternarle tra i componenti predefiniti.",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Trascinamento con clic sinistro",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Trascinamento con clic sinistro}\n\nQuando attivato, spostando il mouse con il tasto sinistro premuto si trascina la visuale della mappa avventura.",
"vcmi.adventureOptions.rightButtonDrag.hover" : "Trascinamento con clic destro",
"vcmi.adventureOptions.rightButtonDrag.help" : "{Trascinamento con clic destro}\n\nQuando attivato, spostando il mouse con il tasto destro premuto si trascina la visuale della mappa avventura.",
"vcmi.adventureOptions.smoothDragging.hover" : "Trascinamento della mappa fluido",
"vcmi.adventureOptions.smoothDragging.help" : "{Trascinamento della mappa fluido}\n\nQuando attivato, il trascinamento della mappa ha un effetto di scorrimento fluido moderno.",
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Salta effetti di dissolvenza",
"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Salta effetti di dissolvenza}\n\nQuando attivato, salta la dissolvenza degli oggetti e altri effetti simili (raccolta risorse, imbarco su navi, ecc.). Rende l'interfaccia più reattiva in alcuni casi, a scapito dell'estetica. Utile soprattutto nei giochi PvP. Per una velocità di movimento massima, la dissolvenza è sempre saltata indipendentemente da questa impostazione.",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
"vcmi.adventureOptions.mapScrollSpeed1.help": "Imposta la velocità di scorrimento della mappa su molto lenta.",
"vcmi.adventureOptions.mapScrollSpeed5.help": "Imposta la velocità di scorrimento della mappa su molto veloce.",
"vcmi.adventureOptions.mapScrollSpeed6.help": "Imposta la velocità di scorrimento della mappa su istantanea.",
"vcmi.adventureOptions.hideBackground.hover" : "Nascondi Sfondo",
"vcmi.adventureOptions.hideBackground.help" : "{Nascondi Sfondo}\n\nNasconde la mappa dell'avventura nello sfondo e mostra una texture al suo posto.",
"vcmi.battleOptions.queueSizeLabel.hover": "Mostra coda dell'ordine di turno",
"vcmi.battleOptions.queueSizeNoneButton.hover": "SPENTO",
"vcmi.battleOptions.queueSizeAutoButton.hover": "AUTO",
"vcmi.battleOptions.queueSizeSmallButton.hover": "PICCOLO",
"vcmi.battleOptions.queueSizeBigButton.hover": "GRANDE",
"vcmi.battleOptions.queueSizeNoneButton.help": "Non visualizzare la coda dell'ordine di turno.",
"vcmi.battleOptions.queueSizeAutoButton.help": "Regola automaticamente la dimensione della coda dell'ordine di turno in base alla risoluzione del gioco (PICCOLO viene utilizzato se l'altezza della risoluzione è inferiore a 700 pixel, GRANDE viene usato altrimenti).",
"vcmi.battleOptions.queueSizeSmallButton.help": "Imposta la dimensione della coda dell'ordine di turno su PICCOLO.",
"vcmi.battleOptions.queueSizeBigButton.help": "Imposta la dimensione della coda dell'ordine di turno su GRANDE (non supportato se l'altezza della risoluzione del gioco è inferiore a 700 pixel).",
"vcmi.battleOptions.animationsSpeed1.hover": "",
"vcmi.battleOptions.animationsSpeed5.hover": "",
"vcmi.battleOptions.animationsSpeed6.hover": "",
"vcmi.battleOptions.animationsSpeed1.help": "Imposta la velocità dell'animazione su molto lenta.",
"vcmi.battleOptions.animationsSpeed5.help": "Imposta la velocità dell'animazione su molto veloce.",
"vcmi.battleOptions.animationsSpeed6.help": "Imposta la velocità dell'animazione su istantanea.",
"vcmi.battleOptions.movementHighlightOnHover.hover": "Evidenzia il movimento al passaggio del mouse",
"vcmi.battleOptions.movementHighlightOnHover.help": "{Evidenzia il movimento al passaggio del mouse}\n\nEvidenzia il raggio di movimento dell'unità quando ci passi sopra con il cursore.",
"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Mostra limiti di gittata per tiratori",
"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Mostra limiti di gittata per tiratori}\n\nMostra i limiti di gittata del tiratore quando ci passi sopra con il cursore.",
"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Mostra finestre statistiche degli eroi",
"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostra finestre statistiche degli eroi}\n\nAttiva in modo permanente le finestre statistiche degli eroi che mostrano le statistiche primarie e i punti incantesimo.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Salta musica introduttiva",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Salta musica introduttiva}\n\nPermette di compiere azioni durante la musica introduttiva che viene riprodotta all'inizio di ogni battaglia.",
"vcmi.battleOptions.endWithAutocombat.hover": "Termina la battaglia",
"vcmi.battleOptions.endWithAutocombat.help": "{Termina la battaglia}\n\nL'Auto-Combat gioca la battaglia fino alla fine immediatamente.",
"vcmi.battleOptions.showQuickSpell.hover": "Mostra pannello incantesimi rapidi",
"vcmi.battleOptions.showQuickSpell.help": "{Mostra pannello incantesimi rapidi}\n\nMostra il pannello per la selezione rapida degli incantesimi.",
"vcmi.adventureMap.revisitObject.hover" : "Rivisita oggetto",
"vcmi.adventureMap.revisitObject.help" : "{Rivisita oggetto}\n\nSe un eroe si trova su un oggetto della mappa, può rivisitare la posizione.",
"vcmi.battleWindow.pressKeyToSkipIntro" : "Premi un tasto qualsiasi per iniziare immediatamente la battaglia",
"vcmi.battleWindow.damageEstimation.melee" : "Attacca %CREATURE (%DAMAGE).",
"vcmi.battleWindow.damageEstimation.meleeKills" : "Attacca %CREATURE (%DAMAGE, %KILLS).",
"vcmi.battleWindow.damageEstimation.ranged" : "Spara a %CREATURE (%SHOTS, %DAMAGE).",
"vcmi.battleWindow.damageEstimation.rangedKills" : "Spara a %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
"vcmi.battleWindow.damageEstimation.shots" : "%d colpi rimasti",
"vcmi.battleWindow.damageEstimation.shots.1" : "%d colpo rimasto",
"vcmi.battleWindow.damageEstimation.damage" : "%d danni",
"vcmi.battleWindow.damageEstimation.damage.1" : "%d danni",
"vcmi.battleWindow.damageEstimation.kills" : "%d periranno",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d perirà",
"vcmi.battleWindow.damageRetaliation.will" : "Contrattaccherà",
"vcmi.battleWindow.damageRetaliation.may" : "Potrebbe contrattaccare",
"vcmi.battleWindow.damageRetaliation.never" : "Non contrattaccherà.",
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
"vcmi.battleWindow.killed" : "Ucciso",
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s sono stati uccisi da colpi precisi!",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s è stato ucciso con un colpo preciso!",
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s sono stati uccisi da colpi precisi!",
"vcmi.battleWindow.endWithAutocombat" : "Sei sicuro di voler terminare la battaglia con il combattimento automatico?",
"vcmi.battleResultsWindow.applyResultsLabel" : "Accettare il risultato della battaglia?",
"vcmi.tutorialWindow.title" : "Introduzione touchscreen",
"vcmi.tutorialWindow.decription.RightClick" : "Tocca e tieni premuto l'elemento su cui desideri fare clic destro. Tocca un'area libera per chiudere.",
"vcmi.tutorialWindow.decription.MapPanning" : "Tocca e trascina con un dito per spostare la mappa.",
"vcmi.tutorialWindow.decription.MapZooming" : "Pizzica con due dita per modificare lo zoom della mappa.",
"vcmi.tutorialWindow.decription.RadialWheel" : "Scorrendo si apre la ruota radiale per varie azioni, come la gestione delle creature/eroi e l'ordinamento delle città.",
"vcmi.tutorialWindow.decription.BattleDirection" : "Per attaccare da una direzione specifica, scorri nella direzione da cui deve essere effettuato l'attacco.",
"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Il gesto di attacco direzionale può essere annullato se il dito è sufficientemente lontano.",
"vcmi.tutorialWindow.decription.AbortSpell" : "Tocca e tieni premuto per annullare un incantesimo.",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Mostra creature disponibili",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Mostra creature disponibili}\n\nMostra il numero di creature disponibili per l'acquisto invece della loro crescita nel riepilogo della città (angolo in basso a sinistra della schermata della città).",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Mostra crescita settimanale delle creature",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Mostra crescita settimanale delle creature}\n\nMostra la crescita settimanale delle creature invece della quantità disponibile nel riepilogo della città (angolo in basso a sinistra della schermata della città).",
"vcmi.otherOptions.compactTownCreatureInfo.hover": "Info compatta delle creature",
"vcmi.otherOptions.compactTownCreatureInfo.help": "{Info compatta delle creature}\n\nMostra informazioni più piccole per le creature della città nel riepilogo della città (angolo in basso a sinistra della schermata della città).",
"vcmi.townHall.missingBase" : "L'edificio base %s deve essere costruito prima",
"vcmi.townHall.noCreaturesToRecruit" : "Non ci sono creature da reclutare!",
"vcmi.townStructure.bank.borrow" : "Entri in banca. Un banchiere ti vede e dice: \"Abbiamo preparato un'offerta speciale per te. Puoi prendere un prestito di 2500 oro per 5 giorni. Dovrai restituire 500 oro ogni giorno.\"",
"vcmi.townStructure.bank.payBack" : "Entri in banca. Un banchiere ti vede e dice: \"Hai già ottenuto un prestito. Rimborsalo prima di prenderne un altro.\"",
"vcmi.logicalExpressions.anyOf" : "Qualsiasi dei seguenti:",
"vcmi.logicalExpressions.allOf" : "Tutti i seguenti:",
"vcmi.logicalExpressions.noneOf" : "Nessuno dei seguenti:",
"vcmi.heroWindow.openCommander.hover" : "Apri finestra informazioni comandante",
"vcmi.heroWindow.openCommander.help" : "Mostra i dettagli sul comandante di questo eroe.",
"vcmi.heroWindow.openBackpack.hover" : "Apri finestra zaino artefatti",
"vcmi.heroWindow.openBackpack.help" : "Apre una finestra che consente una gestione più semplice dello zaino artefatti.",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Ordina per costo",
"vcmi.heroWindow.sortBackpackByCost.help" : "Ordina gli artefatti nello zaino in base al costo.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Ordina per slot",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Ordina gli artefatti nello zaino in base allo slot equipaggiato.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Ordina per classe",
"vcmi.heroWindow.sortBackpackByClass.help" : "Ordina gli artefatti nello zaino in base alla classe dell'artefatto. Tesoro, Minore, Maggiore, Reliquia",
"vcmi.heroWindow.fusingArtifact.fusing" : "Possiedi tutti i componenti necessari per la fusione del %s. Vuoi eseguire la fusione? {Tutti i componenti saranno consumati durante la fusione.}",
"vcmi.tavernWindow.inviteHero" : "Invita eroe",
"vcmi.commanderWindow.artifactMessage" : "Vuoi restituire questo artefatto all'eroe?",
"vcmi.creatureWindow.showBonuses.hover" : "Passa alla vista bonus",
"vcmi.creatureWindow.showBonuses.help" : "Mostra tutti i bonus attivi del comandante.",
"vcmi.creatureWindow.showSkills.hover" : "Passa alla vista abilità",
"vcmi.creatureWindow.showSkills.help" : "Mostra tutte le abilità apprese del comandante.",
"vcmi.creatureWindow.returnArtifact.hover" : "Restituisci artefatto",
"vcmi.creatureWindow.returnArtifact.help" : "Fai clic su questo pulsante per restituire l'artefatto allo zaino dell'eroe.",
"vcmi.questLog.hideComplete.hover" : "Nascondi missioni completate",
"vcmi.questLog.hideComplete.help" : "Nasconde tutte le missioni completate.",
"vcmi.randomMapTab.widgets.randomTemplate" : "(Casuale)",
"vcmi.randomMapTab.widgets.templateLabel" : "Modello",
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Imposta...",
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Allineamenti squadra",
"vcmi.randomMapTab.widgets.roadTypesLabel" : "Tipi di strade",
"vcmi.optionsTab.turnOptions.hover" : "Opzioni turno",
"vcmi.optionsTab.turnOptions.help" : "Seleziona opzioni timer turno e turni simultanei",
"vcmi.optionsTab.chessFieldBase.hover" : "Timer base",
"vcmi.optionsTab.chessFieldTurn.hover" : "Timer turno",
"vcmi.optionsTab.chessFieldBattle.hover" : "Timer battaglia",
"vcmi.optionsTab.chessFieldUnit.hover" : "Timer unità",
"vcmi.optionsTab.chessFieldBase.help" : "Utilizzato quando {Timer turno} raggiunge 0. Impostato una sola volta all'inizio del gioco. Al raggiungimento dello zero, il turno corrente termina. Qualsiasi combattimento in corso si concluderà con una sconfitta.",
"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Utilizzato fuori dal combattimento o quando {Timer battaglia} scade. Resettato ogni turno. Il tempo residuo viene aggiunto a {Timer base} alla fine del turno.",
"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Utilizzato fuori dal combattimento o quando {Timer battaglia} scade. Resettato ogni turno. Il tempo non speso viene perso.",
"vcmi.optionsTab.chessFieldBattle.help" : "Utilizzato nelle battaglie con IA o nei combattimenti PvP quando {Timer unità} scade. Resettato all'inizio di ogni combattimento.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Utilizzato quando si seleziona l'azione di un'unità nei combattimenti PvP. Il tempo residuo viene aggiunto a {Timer battaglia} alla fine del turno dell'unità.",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Utilizzato quando si seleziona l'azione di un'unità nei combattimenti PvP. Resettato all'inizio del turno di ogni unità. Il tempo non speso viene perso.",
"vcmi.optionsTab.accumulate" : "Accumula",
"vcmi.optionsTab.simturnsTitle" : "Turni simultanei",
"vcmi.optionsTab.simturnsMin.hover" : "Almeno per",
"vcmi.optionsTab.simturnsMax.hover" : "Al massimo per",
"vcmi.optionsTab.simturnsAI.hover" : "(Sperimentale) Turni simultanei IA",
"vcmi.optionsTab.simturnsMin.help" : "Gioca simultaneamente per il numero di giorni specificato. I contatti tra i giocatori durante questo periodo sono bloccati.",
"vcmi.optionsTab.simturnsMax.help" : "Gioca simultaneamente per il numero di giorni specificato o fino al contatto con un altro giocatore.",
"vcmi.optionsTab.simturnsAI.help" : "{Turni simultanei IA}\nOpzione sperimentale. Consente ai giocatori IA di agire contemporaneamente ai giocatori umani quando i turni simultanei sono abilitati.",
"vcmi.optionsTab.turnTime.select" : "Seleziona preset timer turno",
"vcmi.optionsTab.turnTime.unlimited" : "Tempo di turno illimitato",
"vcmi.optionsTab.turnTime.classic.1" : "Timer classico: 1 minuto",
"vcmi.optionsTab.turnTime.classic.2" : "Timer classico: 2 minuti",
"vcmi.optionsTab.turnTime.classic.5" : "Timer classico: 5 minuti",
"vcmi.optionsTab.turnTime.classic.10" : "Timer classico: 10 minuti",
"vcmi.optionsTab.turnTime.classic.20" : "Timer classico: 20 minuti",
"vcmi.optionsTab.turnTime.classic.30" : "Timer classico: 30 minuti",
"vcmi.optionsTab.turnTime.chess.20" : "Scacchi: 20:00 + 10:00 + 02:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.16" : "Scacchi: 16:00 + 08:00 + 01:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.8" : "Scacchi: 08:00 + 04:00 + 01:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.4" : "Scacchi: 04:00 + 02:00 + 00:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.2" : "Scacchi: 02:00 + 01:00 + 00:15 + 00:00",
"vcmi.optionsTab.turnTime.chess.1" : "Scacchi: 01:00 + 01:00 + 00:00 + 00:00",
"vcmi.optionsTab.simturns.select" : "Seleziona preset turni simultanei",
"vcmi.optionsTab.simturns.none" : "Nessun turno simultaneo",
"vcmi.optionsTab.simturns.tillContactMax" : "Turni simultanei: fino al contatto",
"vcmi.optionsTab.simturns.tillContact1" : "Turni simultanei: 1 settimana, interruzione al contatto",
"vcmi.optionsTab.simturns.tillContact2" : "Turni simultanei: 2 settimane, interruzione al contatto",
"vcmi.optionsTab.simturns.tillContact4" : "Turni simultanei: 1 mese, interruzione al contatto",
"vcmi.optionsTab.simturns.blocked1" : "Turni simultanei: 1 settimana, contatti bloccati",
"vcmi.optionsTab.simturns.blocked2" : "Turni simultanei: 2 settimane, contatti bloccati",
"vcmi.optionsTab.simturns.blocked4" : "Turni simultanei: 1 mese, contatti bloccati",
// 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 giorni",
"vcmi.optionsTab.simturns.days.1" : " %d giorno",
"vcmi.optionsTab.simturns.days.2" : " %d giorni",
"vcmi.optionsTab.simturns.weeks.0" : " %d settimane",
"vcmi.optionsTab.simturns.weeks.1" : " %d settimana",
"vcmi.optionsTab.simturns.weeks.2" : " %d settimane",
"vcmi.optionsTab.simturns.months.0" : " %d mesi",
"vcmi.optionsTab.simturns.months.1" : " %d mese",
"vcmi.optionsTab.simturns.months.2" : " %d mesi",
"vcmi.optionsTab.extraOptions.hover" : "Opzioni extra",
"vcmi.optionsTab.extraOptions.help" : "Impostazioni aggiuntive per il gioco",
"vcmi.optionsTab.cheatAllowed.hover" : "Permetti trucchi",
"vcmi.optionsTab.unlimitedReplay.hover" : "Replay battaglia illimitato",
"vcmi.optionsTab.cheatAllowed.help" : "{Permetti trucchi}\nPermette l'inserimento di trucchi durante il gioco.",
"vcmi.optionsTab.unlimitedReplay.help" : "{Replay battaglia illimitato}\nNessun limite di riproduzione delle battaglie.",
// Custom victory conditions for H3 campaigns and HotA maps
"vcmi.map.victoryCondition.daysPassed.toOthers" : "Il nemico è riuscito a sopravvivere fino a questo giorno. La vittoria è sua!",
"vcmi.map.victoryCondition.daysPassed.toSelf" : "Congratulazioni! Sei riuscito a sopravvivere. La vittoria è tua!",
"vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Il nemico ha sconfitto tutti i mostri che infestavano questa terra e reclama la vittoria!",
"vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Congratulazioni! Hai sconfitto tutti i mostri che infestavano questa terra e puoi reclamare la vittoria!",
"vcmi.map.victoryCondition.collectArtifacts.message" : "Acquisisci tre artefatti",
"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Congratulazioni! Hai sconfitto tutti i tuoi nemici e hai l'Alleanza Angelica! La vittoria è tua!",
"vcmi.map.victoryCondition.angelicAlliance.message" : "Sconfiggi tutti i nemici e crea l'Alleanza Angelica",
"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Ahimè, hai perso parte dell'Alleanza Angelica. Tutto è perduto.",
// few strings from WoG used by vcmi
"vcmi.stackExperience.description" : "» Dettagli esperienza truppa «\n\nTipo di creatura ................... : %s\nGrado esperienza ................. : %s (%i)\nPunti esperienza ............... : %i\nPunti esperienza al livello successivo .. : %i\nEsperienza massima per battaglia ... : %i%% (%i)\nNumero di creature nello stack .... : %i\nNumero massimo di nuove reclute\n senza perdere il grado attuale .... : %i\nMoltiplicatore esperienza ........... : %.2f\nMoltiplicatore miglioramento .............. : %.2f\nEsperienza dopo il livello 10 ........ : %i\nNumero massimo di nuove reclute per mantenere il\n grado 10 se a esperienza massima : %i",
"vcmi.stackExperience.rank.0" : "Base",
"vcmi.stackExperience.rank.1" : "Principiante",
"vcmi.stackExperience.rank.2" : "Addestrato",
"vcmi.stackExperience.rank.3" : "Abile",
"vcmi.stackExperience.rank.4" : "Esperto",
"vcmi.stackExperience.rank.5" : "Veterano",
"vcmi.stackExperience.rank.6" : "Maestro",
"vcmi.stackExperience.rank.7" : "Esperto",
"vcmi.stackExperience.rank.8" : "Gran Maestro",
"vcmi.stackExperience.rank.9" : "Campione",
"vcmi.stackExperience.rank.10" : "Asso",
// Strings for HotA Seer Hut / Quest Guards
"core.seerhut.quest.heroClass.complete.0" : "Ah, sei %s. Ecco un regalo per te. Lo accetti?",
"core.seerhut.quest.heroClass.complete.1" : "Ah, sei %s. Ecco un regalo per te. Lo accetti?",
"core.seerhut.quest.heroClass.complete.2" : "Ah, sei %s. Ecco un regalo per te. Lo accetti?",
"core.seerhut.quest.heroClass.complete.3" : "Le guardie notano che sei %s e ti offrono di passare. Accetti?",
"core.seerhut.quest.heroClass.complete.4" : "Le guardie notano che sei %s e ti offrono di passare. Accetti?",
"core.seerhut.quest.heroClass.complete.5" : "Le guardie notano che sei %s e ti offrono di passare. Accetti?",
"core.seerhut.quest.heroClass.description.0" : "Invia %s a %s",
"core.seerhut.quest.heroClass.description.1" : "Invia %s a %s",
"core.seerhut.quest.heroClass.description.2" : "Invia %s a %s",
"core.seerhut.quest.heroClass.description.3" : "Invia %s ad aprire il cancello",
"core.seerhut.quest.heroClass.description.4" : "Invia %s ad aprire il cancello",
"core.seerhut.quest.heroClass.description.5" : "Invia %s ad aprire il cancello",
"core.seerhut.quest.heroClass.hover.0" : "(cerca un eroe della classe %s)",
"core.seerhut.quest.heroClass.hover.1" : "(cerca un eroe della classe %s)",
"core.seerhut.quest.heroClass.hover.2" : "(cerca un eroe della classe %s)",
"core.seerhut.quest.heroClass.hover.3" : "(cerca un eroe della classe %s)",
"core.seerhut.quest.heroClass.hover.4" : "(cerca un eroe della classe %s)",
"core.seerhut.quest.heroClass.hover.5" : "(cerca un eroe della classe %s)",
"core.seerhut.quest.heroClass.receive.0" : "Ho un regalo per %s.",
"core.seerhut.quest.heroClass.receive.1" : "Ho un regalo per %s.",
"core.seerhut.quest.heroClass.receive.2" : "Ho un regalo per %s.",
"core.seerhut.quest.heroClass.receive.3" : "Le guardie qui dicono che lasceranno passare solo %s.",
"core.seerhut.quest.heroClass.receive.4" : "Le guardie qui dicono che lasceranno passare solo %s.",
"core.seerhut.quest.heroClass.receive.5" : "Le guardie qui dicono che lasceranno passare solo %s.",
"core.seerhut.quest.heroClass.visit.0" : "Non sei %s. Non ho niente per te. Vattene!",
"core.seerhut.quest.heroClass.visit.1" : "Non sei %s. Non ho niente per te. Vattene!",
"core.seerhut.quest.heroClass.visit.2" : "Non sei %s. Non ho niente per te. Vattene!",
"core.seerhut.quest.heroClass.visit.3" : "Le guardie qui lasceranno passare solo %s.",
"core.seerhut.quest.heroClass.visit.4" : "Le guardie qui lasceranno passare solo %s.",
"core.seerhut.quest.heroClass.visit.5" : "Le guardie qui lasceranno passare solo %s.",
"core.seerhut.quest.reachDate.complete.0" : "Ora sono libero. Ecco cosa ho per te. Lo accetti?",
"core.seerhut.quest.reachDate.complete.1" : "Ora sono libero. Ecco cosa ho per te. Lo accetti?",
"core.seerhut.quest.reachDate.complete.2" : "Ora sono libero. Ecco cosa ho per te. Lo accetti?",
"core.seerhut.quest.reachDate.complete.3" : "Ora puoi passare. Vuoi attraversare?",
"core.seerhut.quest.reachDate.complete.4" : "Ora puoi passare. Vuoi attraversare?",
"core.seerhut.quest.reachDate.complete.5" : "Ora puoi passare. Vuoi attraversare?",
"core.seerhut.quest.reachDate.description.0" : "Aspetta fino a %s per %s",
"core.seerhut.quest.reachDate.description.1" : "Aspetta fino a %s per %s",
"core.seerhut.quest.reachDate.description.2" : "Aspetta fino a %s per %s",
"core.seerhut.quest.reachDate.description.3" : "Aspetta fino a %s per aprire il cancello",
"core.seerhut.quest.reachDate.description.4" : "Aspetta fino a %s per aprire il cancello",
"core.seerhut.quest.reachDate.description.5" : "Aspetta fino a %s per aprire il cancello",
"core.seerhut.quest.reachDate.hover.0" : "(Non tornare prima di %s)",
"core.seerhut.quest.reachDate.hover.1" : "(Non tornare prima di %s)",
"core.seerhut.quest.reachDate.hover.2" : "(Non tornare prima di %s)",
"core.seerhut.quest.reachDate.hover.3" : "(Non tornare prima di %s)",
"core.seerhut.quest.reachDate.hover.4" : "(Non tornare prima di %s)",
"core.seerhut.quest.reachDate.hover.5" : "(Non tornare prima di %s)",
"core.seerhut.quest.reachDate.receive.0" : "Sono occupato. Torna dopo il %s",
"core.seerhut.quest.reachDate.receive.1" : "Sono occupato. Torna dopo il %s",
"core.seerhut.quest.reachDate.receive.2" : "Sono occupato. Torna dopo il %s",
"core.seerhut.quest.reachDate.receive.3" : "Chiuso fino al %s.",
"core.seerhut.quest.reachDate.receive.4" : "Chiuso fino al %s.",
"core.seerhut.quest.reachDate.receive.5" : "Chiuso fino al %s.",
"core.seerhut.quest.reachDate.visit.0" : "Sono occupato. Torna dopo il %s.",
"core.seerhut.quest.reachDate.visit.1" : "Sono occupato. Torna dopo il %s.",
"core.seerhut.quest.reachDate.visit.2" : "Sono occupato. Torna dopo il %s.",
"core.seerhut.quest.reachDate.visit.3" : "Chiuso fino al %s.",
"core.seerhut.quest.reachDate.visit.4" : "Chiuso fino al %s.",
"core.seerhut.quest.reachDate.visit.5" : "Chiuso fino al %s.",
"mapObject.core.hillFort.object.description" : "Aggiorna le creature. I livelli 1 - 4 sono meno costosi rispetto alla città associata.",
"core.bonus.ADDITIONAL_ATTACK.name": "Doppio colpo",
"core.bonus.ADDITIONAL_ATTACK.description": "Attacca due volte",
"core.bonus.ADDITIONAL_RETALIATION.name": "Ritorsioni aggiuntive",
"core.bonus.ADDITIONAL_RETALIATION.description": "Può contrattaccare ${val} volte in più",
"core.bonus.AIR_IMMUNITY.name": "Immunità all'aria",
"core.bonus.AIR_IMMUNITY.description": "Immune a tutti gli incantesimi della scuola di magia dell'Aria",
"core.bonus.ATTACKS_ALL_ADJACENT.name": "Attacco a 360°",
"core.bonus.ATTACKS_ALL_ADJACENT.description": "Attacca tutti i nemici adiacenti",
"core.bonus.BLOCKS_RETALIATION.name": "Nessuna ritorsione",
"core.bonus.BLOCKS_RETALIATION.description": "Il nemico non può contrattaccare",
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Nessuna ritorsione a distanza",
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Il nemico non può contrattaccare con un attacco a distanza",
"core.bonus.CATAPULT.name": "Catapulta",
"core.bonus.CATAPULT.description": "Attacca le mura d'assedio",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Riduce il costo del lancio (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Riduce il costo del lancio degli incantesimi dell'eroe di ${val}",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Resistenza magica (${val}%)",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Aumenta il costo del lancio degli incantesimi nemici di ${val}",
"core.bonus.CHARGE_IMMUNITY.name": "Immunità alla carica",
"core.bonus.CHARGE_IMMUNITY.description": "Immune alla carica di Cavalieri e Campioni",
"core.bonus.DARKNESS.name": "Oscurità",
"core.bonus.DARKNESS.description": "Crea un velo d'oscurità con raggio ${val}",
"core.bonus.DEATH_STARE.name": "Sguardo della morte (${val}%)",
"core.bonus.DEATH_STARE.description": "Ha una probabilità del ${val}% di uccidere un'unità singola",
"core.bonus.DEFENSIVE_STANCE.name": "Bonus di difesa",
"core.bonus.DEFENSIVE_STANCE.description": "+${val} Difesa quando è in posizione difensiva",
"core.bonus.DESTRUCTION.name": "Distruzione",
"core.bonus.DESTRUCTION.description": "Ha una probabilità del ${val}% di uccidere unità extra dopo l'attacco",
"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Colpo della morte",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Ha una probabilità del ${val}% di infliggere il doppio dei danni base quando attacca",
"core.bonus.DRAGON_NATURE.name": "Drago",
"core.bonus.DRAGON_NATURE.description": "Creatura con Natura del Drago",
"core.bonus.EARTH_IMMUNITY.name": "Immunità alla terra",
"core.bonus.EARTH_IMMUNITY.description": "Immune a tutti gli incantesimi della scuola di magia della Terra",
"core.bonus.ENCHANTER.name": "Incantatore",
"core.bonus.ENCHANTER.description": "Può lanciare l'incantesimo ${subtype.spell} ogni turno",
"core.bonus.ENCHANTED.name": "Incantato",
"core.bonus.ENCHANTED.description": "Sotto effetto permanente di ${subtype.spell}",
"core.bonus.ENEMY_ATTACK_REDUCTION.name": "Ignora attacco (${val}%)",
"core.bonus.ENEMY_ATTACK_REDUCTION.description": "Quando viene attaccata, ignora il ${val}% dell'attacco dell'avversario",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignora difesa (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Quando attacca, ignora il ${val}% della difesa dell'avversario",
"core.bonus.FIRE_IMMUNITY.name": "Immunità al fuoco",
"core.bonus.FIRE_IMMUNITY.description": "Immune a tutti gli incantesimi della scuola di magia del Fuoco",
"core.bonus.FIRE_SHIELD.name": "Scudo di fuoco (${val}%)",
"core.bonus.FIRE_SHIELD.description": "Riflette una parte dei danni da mischia",
"core.bonus.FIRST_STRIKE.name": "Primo colpo",
"core.bonus.FIRST_STRIKE.description": "Questa creatura contrattacca prima di essere attaccata",
"core.bonus.FEAR.name": "Paura",
"core.bonus.FEAR.description": "Provoca paura su una pila nemica",
"core.bonus.FEARLESS.name": "Impavido",
"core.bonus.FEARLESS.description": "Immune all'abilità Paura",
"core.bonus.FEROCITY.name": "Ferocia",
"core.bonus.FEROCITY.description": "Attacca ${val} volte aggiuntive se uccide qualcuno",
"core.bonus.FLYING.name": "Volare",
"core.bonus.FLYING.description": "Si muove volando (ignora gli ostacoli)",
"core.bonus.FREE_SHOOTING.name": "Colpo ravvicinato",
"core.bonus.FREE_SHOOTING.description": "Può usare attacchi a distanza anche in mischia",
"core.bonus.GARGOYLE.name": "Gargoyle",
"core.bonus.GARGOYLE.description": "Non può essere rianimato o curato",
"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Riduzione danno (${val}%)",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Riduce il danno fisico da attacchi a distanza o corpo a corpo",
"core.bonus.HATE.name": "Odia ${subtype.creature}",
"core.bonus.HATE.description": "Infligge ${val}% di danni in più a ${subtype.creature}",
"core.bonus.HEALER.name": "Guaritore",
"core.bonus.HEALER.description": "Cura le unità alleate",
"core.bonus.HP_REGENERATION.name": "Rigenerazione",
"core.bonus.HP_REGENERATION.description": "Cura ${val} punti ferita ogni turno",
"core.bonus.JOUSTING.name": "Carica del Campione",
"core.bonus.JOUSTING.description": "+${val}% danno per ogni esagono percorso",
"core.bonus.KING.name": "Re",
"core.bonus.KING.description": "Vulnerabile a SLAUGHTER di livello ${val} o superiore",
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Immunità agli incantesimi 1-${val}",
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immunità agli incantesimi di livello 1-${val}",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Portata limitata",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Impossibile attaccare unità oltre ${val} esagoni",
"core.bonus.LIFE_DRAIN.name": "Assorbimento vitale (${val}%)",
"core.bonus.LIFE_DRAIN.description": "Drena ${val}% del danno inflitto",
"core.bonus.MANA_CHANNELING.name": "Canale Magico ${val}%",
"core.bonus.MANA_CHANNELING.description": "Fornisce al tuo eroe ${val}% del mana speso dal nemico",
"core.bonus.MANA_DRAIN.name": "Drenaggio di mana",
"core.bonus.MANA_DRAIN.description": "Drena ${val} mana ogni turno",
"core.bonus.MAGIC_MIRROR.name": "Specchio Magico (${val}%)",
"core.bonus.MAGIC_MIRROR.description": "Ha una probabilità del ${val}% di reindirizzare un incantesimo offensivo su un'unità nemica",
"core.bonus.MAGIC_RESISTANCE.name": "Resistenza Magica (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description": "Ha una probabilità del ${val}% di resistere a un incantesimo nemico",
"core.bonus.MIND_IMMUNITY.name": "Immunità agli incantesimi mentali",
"core.bonus.MIND_IMMUNITY.description": "Immune agli incantesimi di tipo mentale",
"core.bonus.NO_DISTANCE_PENALTY.name": "Nessuna penalità a distanza",
"core.bonus.NO_DISTANCE_PENALTY.description": "Infligge il massimo danno a qualsiasi distanza",
"core.bonus.NO_MELEE_PENALTY.name": "Nessuna penalità in mischia",
"core.bonus.NO_MELEE_PENALTY.description": "L'unità non subisce penalità in mischia",
"core.bonus.NO_MORALE.name": "Morale neutrale",
"core.bonus.NO_MORALE.description": "L'unità è immune agli effetti del morale",
"core.bonus.NO_WALL_PENALTY.name": "Nessuna penalità per le mura",
"core.bonus.NO_WALL_PENALTY.description": "Danno pieno durante l'assedio",
"core.bonus.NON_LIVING.name": "Non vivente",
"core.bonus.NON_LIVING.description": "Immunità a molti effetti",
"core.bonus.RANDOM_SPELLCASTER.name": "Random spellcaster",
"core.bonus.RANDOM_SPELLCASTER.description": "Può lanciare un incantesimo casuale",
"core.bonus.RANGED_RETALIATION.name": "Ritorsione a distanza",
"core.bonus.RANGED_RETALIATION.description": "Può effettuare un contrattacco a distanza",
"core.bonus.RECEPTIVE.name": "Ricettivo",
"core.bonus.RECEPTIVE.description": "Nessuna immunità agli incantesimi amichevoli",
"core.bonus.REBIRTH.name": "Rinascita (${val}%)",
"core.bonus.REBIRTH.description": "${val}% della pila risorgerà dopo la morte",
"core.bonus.RETURN_AFTER_STRIKE.name": "Attacco e Ritorno",
"core.bonus.RETURN_AFTER_STRIKE.description": "Ritorna dopo un attacco in mischia",
"core.bonus.REVENGE.name": "Vendetta",
"core.bonus.REVENGE.description": "Infligge danni extra in base alla salute persa dell'attaccante in battaglia",
"core.bonus.SHOOTER.name": "A distanza",
"core.bonus.SHOOTER.description": "L'unità può attaccare a distanza",
"core.bonus.SHOOTS_ALL_ADJACENT.name": "Tiro a raggio totale",
"core.bonus.SHOOTS_ALL_ADJACENT.description": "Gli attacchi a distanza di questa unità colpiscono tutti i bersagli in una piccola area",
"core.bonus.SOUL_STEAL.name": "Furto d'anima",
"core.bonus.SOUL_STEAL.description": "Ottiene ${val} nuove creature per ogni nemico ucciso",
"core.bonus.SPELLCASTER.name": "Incantatore",
"core.bonus.SPELLCASTER.description": "Può lanciare ${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.name": "Lancia Dopo l'Attacco",
"core.bonus.SPELL_AFTER_ATTACK.description": "Ha una probabilità del ${val}% di lanciare ${subtype.spell} dopo l'attacco",
"core.bonus.SPELL_BEFORE_ATTACK.name": "Lancia Prima dell'Attacco",
"core.bonus.SPELL_BEFORE_ATTACK.description": "Ha una probabilità del ${val}% di lanciare ${subtype.spell} prima dell'attacco",
"core.bonus.SPELL_IMMUNITY.name": "Immunità agli incantesimi",
"core.bonus.SPELL_IMMUNITY.description": "Immune a ${subtype.spell}",
"core.bonus.SPELL_LIKE_ATTACK.name": "Attacco simile a un incantesimo",
"core.bonus.SPELL_LIKE_ATTACK.description": "Attacca con ${subtype.spell}",
"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura di Resistenza",
"core.bonus.SPELL_RESISTANCE_AURA.description": "Gli stack vicini ottengono ${val}% di resistenza magica",
"core.bonus.SUMMON_GUARDIANS.name": "Evoca guardiani",
"core.bonus.SUMMON_GUARDIANS.description": "All'inizio della battaglia evoca ${subtype.creature} (${val}%)",
"core.bonus.SYNERGY_TARGET.name": "Sinergizzabile",
"core.bonus.SYNERGY_TARGET.description": "Questa creatura è vulnerabile all'effetto sinergico",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Soffio",
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Attacco a soffio (raggio di 2 esagoni)",
"core.bonus.THREE_HEADED_ATTACK.name": "Attacco a tre teste",
"core.bonus.THREE_HEADED_ATTACK.description": "Attacca tre unità adiacenti",
"core.bonus.TRANSMUTATION.name": "Trasmutazione",
"core.bonus.TRANSMUTATION.description": "${val}% di possibilità di trasformare l'unità attaccata in un altro tipo",
"core.bonus.UNDEAD.name": "Non Morto",
"core.bonus.UNDEAD.description": "L'unità è Non Morta",
"core.bonus.UNLIMITED_RETALIATIONS.name": "Ritorsioni illimitate",
"core.bonus.UNLIMITED_RETALIATIONS.description": "Può contrattaccare un numero illimitato di attacchi",
"core.bonus.WATER_IMMUNITY.name": "Immunità all'acqua",
"core.bonus.WATER_IMMUNITY.description": "Immune a tutti gli incantesimi della scuola di magia dell'Acqua",
"core.bonus.WIDE_BREATH.name": "Soffio ampio",
"core.bonus.WIDE_BREATH.description": "Attacco a soffio ampio (più esagoni)",
"core.bonus.DISINTEGRATE.name": "Disintegrazione",
"core.bonus.DISINTEGRATE.description": "Nessun cadavere rimane dopo la morte",
"core.bonus.INVINCIBLE.name": "Invincibile",
"core.bonus.INVINCIBLE.description": "Non può essere influenzato da nulla",
"core.bonus.MECHANICAL.name": "Meccanico",
"core.bonus.MECHANICAL.description": "Immunità a molti effetti, riparabile",
"core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Soffio Prisma",
"core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Attacco Soffio Prisma (tre direzioni)",
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Resistenza agli incantesimi",
"core.bonus.SPELL_DAMAGE_REDUCTION.name.air": "Resistenza agli incantesimi dell'Aria",
"core.bonus.SPELL_DAMAGE_REDUCTION.name.fire": "Resistenza agli incantesimi di fuoco",
"core.bonus.SPELL_DAMAGE_REDUCTION.name.water": "Resistenza agli incantesimi dell'Acqua",
"core.bonus.SPELL_DAMAGE_REDUCTION.name.earth": "Resistenza agli incantesimi della Terra",
"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Danno da tutti gli incantesimi ridotto del ${val}%.",
"core.bonus.SPELL_DAMAGE_REDUCTION.description.air": "Danno da tutti gli incantesimi dell'Aria ridotto del ${val}%.",
"core.bonus.SPELL_DAMAGE_REDUCTION.description.fire": "Danno da tutti gli incantesimi del Fuoco ridotto del ${val}%.",
"core.bonus.SPELL_DAMAGE_REDUCTION.description.water": "Danno da tutti gli incantesimi dell'Acqua ridotto del ${val}%.",
"core.bonus.SPELL_DAMAGE_REDUCTION.description.earth": "Danno da tutti gli incantesimi della Terra ridotto del ${val}%.",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name": "Immunità agli incantesimi",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.air": "Immunità all'aria",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.fire": "Immunità al fuoco",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.water": "Immunità all'acqua",
"core.bonus.SPELL_SCHOOL_IMMUNITY.name.earth": "Immunità alla terra",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description": "Questa unità è immune a tutti gli incantesimi",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.air": "Questa unità è immune a tutti gli incantesimi della scuola dell'Aria",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.fire": "Questa unità è immune a tutti gli incantesimi della scuola del Fuoco",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.water": "Questa unità è immune a tutti gli incantesimi della scuola dell'Acqua",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth": "Questa unità è immune a tutti gli incantesimi della scuola della Terra",
"core.bonus.OPENING_BATTLE_SPELL.name": "Inizia con incantesimo",
"core.bonus.OPENING_BATTLE_SPELL.description": "Lancia ${subtype.spell} all'inizio della battaglia",
"spell.core.castleMoat.name" : "Fossato",
"spell.core.castleMoatTrigger.name" : "Fossato",
"spell.core.catapultShot.name" : "Colpo di Catapulta",
"spell.core.cyclopsShot.name" : "Colpo d'assedio",
"spell.core.dungeonMoat.name" : "Olio Bollente",
"spell.core.dungeonMoatTrigger.name" : "Olio Bollente",
"spell.core.fireWallTrigger.name" : "Muro di Fuoco",
"spell.core.firstAid.name" : "Pronto Soccorso",
"spell.core.fortressMoat.name" : "Catrame Bollente",
"spell.core.fortressMoatTrigger.name" : "Catrame Bollente",
"spell.core.infernoMoat.name" : "Lava",
"spell.core.infernoMoatTrigger.name" : "Lava",
"spell.core.landMineTrigger.name" : "Mina Terrestre",
"spell.core.necropolisMoat.name" : "Cimitero d'ossa",
"spell.core.necropolisMoatTrigger.name" : "Cimitero di ossa",
"spell.core.rampartMoat.name" : "Cimitero di ossa",
"spell.core.rampartMoatTrigger.name" : "Rovi",
"spell.core.strongholdMoat.name" : "Rovi",
"spell.core.strongholdMoatTrigger.name" : "Spuntoni di legno",
"spell.core.summonDemons.name" : "Spuntoni di legno",
"spell.core.towerMoat.name" : "Mina terrestre"
}

View File

@ -23,8 +23,8 @@
"vcmi.adventureMap.noTownWithTavern" : "Brak dostępnego miasta z karczmą!", "vcmi.adventureMap.noTownWithTavern" : "Brak dostępnego miasta z karczmą!",
"vcmi.adventureMap.spellUnknownProblem" : "Nieznany problem z zaklęciem, brak dodatkowych informacji.", "vcmi.adventureMap.spellUnknownProblem" : "Nieznany problem z zaklęciem, brak dodatkowych informacji.",
"vcmi.adventureMap.playerAttacked" : "Gracz został zaatakowany: %s", "vcmi.adventureMap.playerAttacked" : "Gracz został zaatakowany: %s",
"vcmi.adventureMap.moveCostDetails" : "Punkty ruchu - Koszt: %TURNS tury + %POINTS punktów, Pozostanie: %REMAINING punktów", "vcmi.adventureMap.moveCostDetails" : "Ruch tutaj będzie kosztował łącznie {%TOTAL} punktów ({%TURNS} tur i {%POINTS} punktów). {%REMAINING} punktów zostanie po ruchu.",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Punkty ruchu - Koszt: %POINTS punktów, Pozostanie: %REMAINING punktów", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Ruch tutaj będzie kosztował {%POINTS} punktów. {%REMAINING} punktów zostanie po ruchu.",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Punkty ruchu: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(Punkty ruchu: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Wybacz, powtórka ruchu wroga nie została jeszcze zaimplementowana!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Wybacz, powtórka ruchu wroga nie została jeszcze zaimplementowana!",
@ -106,6 +106,7 @@
"vcmi.radialWheel.heroGetArtifacts" : "Weź artefakty z innego bohatera", "vcmi.radialWheel.heroGetArtifacts" : "Weź artefakty z innego bohatera",
"vcmi.radialWheel.heroSwapArtifacts" : "Zamień artefakty z innym bohaterem", "vcmi.radialWheel.heroSwapArtifacts" : "Zamień artefakty z innym bohaterem",
"vcmi.radialWheel.heroDismiss" : "Dymisja bohatera", "vcmi.radialWheel.heroDismiss" : "Dymisja bohatera",
"vcmi.radialWheel.upgradeCreatures" : "Ulepsz wszystkie stworzenia",
"vcmi.radialWheel.moveTop" : "Przenieś na początek", "vcmi.radialWheel.moveTop" : "Przenieś na początek",
"vcmi.radialWheel.moveUp" : "Przenieś w górę", "vcmi.radialWheel.moveUp" : "Przenieś w górę",
@ -212,12 +213,12 @@
"vcmi.lobby.pvp.randomTownVs.hover" : "Wylosuj 2 miasta", "vcmi.lobby.pvp.randomTownVs.hover" : "Wylosuj 2 miasta",
"vcmi.lobby.pvp.randomTownVs.help" : "Wyświetli nazwę 2 wylosowanych miast na czacie, które nie zostały zablokowane na liście", "vcmi.lobby.pvp.randomTownVs.help" : "Wyświetli nazwę 2 wylosowanych miast na czacie, które nie zostały zablokowane na liście",
"vcmi.lobby.pvp.versus" : "vs.", "vcmi.lobby.pvp.versus" : "vs.",
"vcmi.lobby.deleteFile" : "Czy chcesz usunąć ten plik ?", "vcmi.lobby.deleteFile" : "Czy chcesz usunąć ten plik?",
"vcmi.lobby.deleteFolder" : "Czy chcesz usunąć ten folder ?", "vcmi.lobby.deleteFolder" : "Czy chcesz usunąć ten folder?",
"vcmi.lobby.deleteMapTitle" : "Wskaż tytuł, który chcesz usunąć", "vcmi.lobby.deleteMapTitle" : "Wskaż tytuł, który chcesz usunąć",
"vcmi.lobby.deleteMode" : "Przełącza tryb na usuwanie i spowrotem", "vcmi.lobby.deleteMode" : "Przełącza tryb na usuwanie i spowrotem",
"vcmi.lobby.deleteSaveGameTitle" : "Wskaż zapis gry do usunięcia", "vcmi.lobby.deleteSaveGameTitle" : "Wskaż zapis gry do usunięcia",
"vcmi.lobby.deleteUnsupportedSave" : "{Znaleziono niekompatybilne zapisy gry}\n\nVCMI wykrył %d zapisów gry, które nie są już wspierane. Prawdopodobnie ze względu na różne wersje gry.\n\nCzy chcesz je usunąć ?", "vcmi.lobby.deleteUnsupportedSave" : "{Znaleziono niekompatybilne zapisy gry}\n\nVCMI wykrył %d zapisów gry, które nie są już wspierane. Prawdopodobnie ze względu na różne wersje gry.\n\nCzy chcesz je usunąć?",
"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.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.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.",
@ -422,11 +423,11 @@
"vcmi.heroWindow.openBackpack.hover" : "Otwórz okno sakwy", "vcmi.heroWindow.openBackpack.hover" : "Otwórz okno sakwy",
"vcmi.heroWindow.openBackpack.help" : "Otwiera okno pozwalające łatwiej zarządzać artefaktami w sakwie", "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.hover" : "Sortuj wg. wartości",
"vcmi.heroWindow.sortBackpackByCost.help" : "Sortuj artefakty w sakwie według wartości", "vcmi.heroWindow.sortBackpackByCost.help" : "{Sortuj wg. wartości}\n\nSortuj artefakty w sakwie według wartości",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Sortuj wg. miejsc", "vcmi.heroWindow.sortBackpackBySlot.hover" : "Sortuj wg. miejsc",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Sortuj artefakty w sakwie według umiejscowienia na ciele", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Sortuj wg. miejsc}\n\nSortuj artefakty w sakwie według umiejscowienia na ciele",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Sortuj wg. jakości", "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.sortBackpackByClass.help" : "{Sortuj wg. jakości}\n\nSortuj 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.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", "vcmi.tavernWindow.inviteHero" : "Zaproś bohatera",

View File

@ -23,8 +23,6 @@
"vcmi.adventureMap.noTownWithTavern" : "Não há cidades disponíveis com tavernas!", "vcmi.adventureMap.noTownWithTavern" : "Não há cidades disponíveis com tavernas!",
"vcmi.adventureMap.spellUnknownProblem" : "Há um problema desconhecido com este feitiço! Não há mais informações disponíveis.", "vcmi.adventureMap.spellUnknownProblem" : "Há um problema desconhecido com este feitiço! Não há mais informações disponíveis.",
"vcmi.adventureMap.playerAttacked" : "O jogador foi atacado: %s", "vcmi.adventureMap.playerAttacked" : "O jogador foi atacado: %s",
"vcmi.adventureMap.moveCostDetails" : "Pontos de movimento - Custo: %TURNS turnos + %POINTS pontos, Pontos restantes: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Pontos de movimento - Custo: %POINTS pontos, Pontos restantes: %REMAINING",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Pontos de movimento: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(Pontos de movimento: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Desculpe, a repetição do turno do oponente ainda não está implementada!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Desculpe, a repetição do turno do oponente ainda não está implementada!",
@ -424,11 +422,11 @@
"vcmi.heroWindow.openBackpack.hover" : "Abrir janela da mochila de artefatos", "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.openBackpack.help" : "Abre a janela que facilita o gerenciamento da mochila de artefatos.",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Ordenar por custo", "vcmi.heroWindow.sortBackpackByCost.hover" : "Ordenar por custo",
"vcmi.heroWindow.sortBackpackByCost.help" : "Ordena artefatos na mochila por custo.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Ordenar por custo}\n\nOrdena artefatos na mochila por custo.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Ordenar por espaço", "vcmi.heroWindow.sortBackpackBySlot.hover" : "Ordenar por espaço",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Ordena artefatos na mochila por espaço equipado.", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Ordenar por espaço}\n\nOrdena artefatos na mochila por espaço equipado.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Ordenar por classe", "vcmi.heroWindow.sortBackpackByClass.hover" : "Ordenar por classe",
"vcmi.heroWindow.sortBackpackByClass.help" : "Ordena artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia.", "vcmi.heroWindow.sortBackpackByClass.help" : "{Ordenar por classe}\n\nOrdena 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.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", "vcmi.tavernWindow.inviteHero" : "Convidar herói",

View File

@ -159,7 +159,7 @@
}, },
"17": "17":
{ {
"type" : "junction", "size" : 30, "type" : "junction", "size" : 15,
"terrainTypeLikeZone" : 9, "terrainTypeLikeZone" : 9,
"allowedTowns" : ["neutral"], "allowedTowns" : ["neutral"],
"monsters" : "strong", "monsters" : "strong",
@ -172,7 +172,7 @@
}, },
"18": "18":
{ {
"type" : "junction", "size" : 30, "type" : "junction", "size" : 15,
"terrainTypeLikeZone" : 9, "terrainTypeLikeZone" : 9,
"allowedTowns" : ["neutral"], "allowedTowns" : ["neutral"],
"monsters" : "strong", "monsters" : "strong",
@ -181,7 +181,7 @@
}, },
"19": "19":
{ {
"type" : "junction", "size" : 30, "type" : "junction", "size" : 15,
"terrainTypeLikeZone" : 9, "terrainTypeLikeZone" : 9,
"allowedTowns" : ["neutral"], "allowedTowns" : ["neutral"],
"monsters" : "strong", "monsters" : "strong",
@ -190,7 +190,7 @@
}, },
"20": "20":
{ {
"type" : "junction", "size" : 30, "type" : "junction", "size" : 15,
"terrainTypeLikeZone" : 9, "terrainTypeLikeZone" : 9,
"allowedTowns" : ["neutral"], "allowedTowns" : ["neutral"],
"monsters" : "strong", "monsters" : "strong",

View File

@ -18,8 +18,6 @@
"vcmi.adventureMap.noTownWithTavern" : "Нет союзных городов с тавернами!", "vcmi.adventureMap.noTownWithTavern" : "Нет союзных городов с тавернами!",
"vcmi.adventureMap.spellUnknownProblem" : "Неизвестная проблема с заклинанием, дополнительная информация недоступна.", "vcmi.adventureMap.spellUnknownProblem" : "Неизвестная проблема с заклинанием, дополнительная информация недоступна.",
"vcmi.adventureMap.playerAttacked" : "Игрок атакован: %s", "vcmi.adventureMap.playerAttacked" : "Игрок атакован: %s",
"vcmi.adventureMap.moveCostDetails" : "Очки движения - Стоимость: %TURNS ходов + %POINTS очков, Останется: %REMAINING очков",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Очки движения - Стоимость: %POINTS очков, Останется: %REMAINING очков",
"vcmi.capitalColors.0" : "Красный", "vcmi.capitalColors.0" : "Красный",
"vcmi.capitalColors.1" : "Синий", "vcmi.capitalColors.1" : "Синий",

View File

@ -18,8 +18,6 @@
"vcmi.adventureMap.noTownWithTavern" : "¡No hay pueblo disponible con taberna!", "vcmi.adventureMap.noTownWithTavern" : "¡No hay pueblo disponible con taberna!",
"vcmi.adventureMap.spellUnknownProblem" : "Problema desconocido con este hechizo, no hay más información disponible.", "vcmi.adventureMap.spellUnknownProblem" : "Problema desconocido con este hechizo, no hay más información disponible.",
"vcmi.adventureMap.playerAttacked" : "El jugador ha sido atacado: %s", "vcmi.adventureMap.playerAttacked" : "El jugador ha sido atacado: %s",
"vcmi.adventureMap.moveCostDetails" : "Puntos de movimiento - Coste: %TURNS turnos + %POINTS puntos, Puntos restantes: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Puntos de movimiento - Coste: %POINTS puntos, Puntos restantes: %REMAINING",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Disculpe, la repetición del turno del oponente aún no está implementada.", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Disculpe, la repetición del turno del oponente aún no está implementada.",
"vcmi.capitalColors.0" : "Rojo", "vcmi.capitalColors.0" : "Rojo",

View File

@ -23,8 +23,8 @@
"vcmi.adventureMap.noTownWithTavern" : "Немає доступного міста з таверною!", "vcmi.adventureMap.noTownWithTavern" : "Немає доступного міста з таверною!",
"vcmi.adventureMap.spellUnknownProblem" : "Невідома проблема з цим заклинанням, більше інформації немає.", "vcmi.adventureMap.spellUnknownProblem" : "Невідома проблема з цим заклинанням, більше інформації немає.",
"vcmi.adventureMap.playerAttacked" : "Гравця атаковано: %s", "vcmi.adventureMap.playerAttacked" : "Гравця атаковано: %s",
"vcmi.adventureMap.moveCostDetails" : "Очки руху - Вартість: %TURNS ходів + %POINTS очок. Залишок очок: %REMAINING", "vcmi.adventureMap.moveCostDetails" : "Переміщення сюди коштуватиме {%TOTAL} очок загалом ({%TURNS} ходів і {%POINTS} очок). Після переміщення залишиться {%REMAINING} очок.",
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Очки руху - Вартість: %POINTS очок, Залишок очок: %REMAINING", "vcmi.adventureMap.moveCostDetailsNoTurns" : "Переміщення сюди коштуватиме {%TOTAL} очок. Після переміщення залишиться {%REMAINING} очок.",
"vcmi.adventureMap.movementPointsHeroInfo" : "(Очки руху: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo" : "(Очки руху: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Вибачте, функція повтору ходу суперника ще не реалізована!", "vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Вибачте, функція повтору ходу суперника ще не реалізована!",
@ -68,6 +68,7 @@
"vcmi.radialWheel.heroGetArtifacts" : "Отримати артефакти іншого героя", "vcmi.radialWheel.heroGetArtifacts" : "Отримати артефакти іншого героя",
"vcmi.radialWheel.heroSwapArtifacts" : "Обміняти артефакти героїв", "vcmi.radialWheel.heroSwapArtifacts" : "Обміняти артефакти героїв",
"vcmi.radialWheel.heroDismiss" : "Звільнити цього героя", "vcmi.radialWheel.heroDismiss" : "Звільнити цього героя",
"vcmi.radialWheel.upgradeCreatures" : "Покращити усіх істот",
"vcmi.radialWheel.moveTop" : "Перемістити на початок", "vcmi.radialWheel.moveTop" : "Перемістити на початок",
"vcmi.radialWheel.moveUp" : "Перемістити вгору", "vcmi.radialWheel.moveUp" : "Перемістити вгору",
@ -363,6 +364,8 @@
"vcmi.battleOptions.endWithAutocombat.help": "{Завершує бій}\n\nАвто-бій миттєво завершує бій", "vcmi.battleOptions.endWithAutocombat.help": "{Завершує бій}\n\nАвто-бій миттєво завершує бій",
"vcmi.battleOptions.showQuickSpell.hover": "Панель швидкого чарування", "vcmi.battleOptions.showQuickSpell.hover": "Панель швидкого чарування",
"vcmi.battleOptions.showQuickSpell.help": "{Панель швидкого чарування}\n\nПоказати панель для швидкого вибору заклять.", "vcmi.battleOptions.showQuickSpell.help": "{Панель швидкого чарування}\n\nПоказати панель для швидкого вибору заклять.",
"vcmi.battleOptions.showHealthBar.hover": "Показувати шкалу здоров'я",
"vcmi.battleOptions.showHealthBar.help": "{Показувати шкалу здоров'я}\n\nПоказувати шкалу здоров'я, яка вказує на решту здоров'я до смерті однієї істоти.",
"vcmi.adventureMap.revisitObject.hover" : "Відвідати Об'єкт", "vcmi.adventureMap.revisitObject.hover" : "Відвідати Об'єкт",
"vcmi.adventureMap.revisitObject.help" : "{Відвідати Об'єкт}\n\nЯкщо герой в даний момент стоїть на об'єкті мапи, він може знову відвідати цю локацію.", "vcmi.adventureMap.revisitObject.help" : "{Відвідати Об'єкт}\n\nЯкщо герой в даний момент стоїть на об'єкті мапи, він може знову відвідати цю локацію.",
@ -415,6 +418,9 @@
"vcmi.townStructure.bank.borrow" : "Ви заходите в банк. Вас бачить банкір і каже: 'Ми зробили для вас спеціальну пропозицію. Ви можете взяти у нас позику в розмірі 2500 золотих на 5 днів. Але щодня ви повинні будете повертати по 500 золотих'.", "vcmi.townStructure.bank.borrow" : "Ви заходите в банк. Вас бачить банкір і каже: 'Ми зробили для вас спеціальну пропозицію. Ви можете взяти у нас позику в розмірі 2500 золотих на 5 днів. Але щодня ви повинні будете повертати по 500 золотих'.",
"vcmi.townStructure.bank.payBack" : "Ви заходите в банк. Банкір бачить вас і каже: 'Ви вже отримали позику. Погасіть її, перш ніж брати нову позику'.", "vcmi.townStructure.bank.payBack" : "Ви заходите в банк. Банкір бачить вас і каже: 'Ви вже отримали позику. Погасіть її, перш ніж брати нову позику'.",
"vcmi.townWindow.upgradeAll.notAllUpgradable" : "Недостатньо коштів, щоб покращити всіх істот. Чи бажаєте ви покращити наступних істот?",
"vcmi.townWindow.upgradeAll.notUpgradable" : "Недостатньо коштів, щоб покращити будь-яку істоту.",
"vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:", "vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:",
"vcmi.logicalExpressions.allOf" : "Все з перерахованого:", "vcmi.logicalExpressions.allOf" : "Все з перерахованого:",
"vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:", "vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:",
@ -423,12 +429,12 @@
"vcmi.heroWindow.openCommander.help" : "Показує інформацію про командира героя", "vcmi.heroWindow.openCommander.help" : "Показує інформацію про командира героя",
"vcmi.heroWindow.openBackpack.hover" : "Відкрити вікно рюкзака з артефактами", "vcmi.heroWindow.openBackpack.hover" : "Відкрити вікно рюкзака з артефактами",
"vcmi.heroWindow.openBackpack.help" : "Відкриває вікно, що дозволяє легше керувати рюкзаком артефактів", "vcmi.heroWindow.openBackpack.help" : "Відкриває вікно, що дозволяє легше керувати рюкзаком артефактів",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Сортувати за вартістю", "vcmi.heroWindow.sortBackpackByCost.hover" : "За вартістю",
"vcmi.heroWindow.sortBackpackByCost.help" : "Сортувати артефакти в рюкзаку за вартістю.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Сортування за вартістю}\n\nСортувати артефакти в рюкзаку за вартістю.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Сортувати за типом", "vcmi.heroWindow.sortBackpackBySlot.hover" : "За слотом",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Сортувати артефакти в рюкзаку за слотом, в який цей артефакт може бути екіпірований", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Сортування за слотом}\n\nСортувати артефакти в рюкзаку за слотом, в який цей артефакт може бути екіпірований",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Сортування за рідкістю", "vcmi.heroWindow.sortBackpackByClass.hover" : "За рідкістю",
"vcmi.heroWindow.sortBackpackByClass.help" : "Сортувати артефакти в рюкзаку за класом рідкісності артефакту. Скарб, Малий, Великий, Реліквія", "vcmi.heroWindow.sortBackpackByClass.help" : "{Сортування за рідкістю}\n\nСортувати артефакти в рюкзаку за класом рідкісності артефакту. Скарб, Малий, Великий, Реліквія",
"vcmi.heroWindow.fusingArtifact.fusing" : "Ви володієте всіма компонентами, необхідними для злиття %s. Ви бажаєте виконати злиття? {Всі компоненти буде спожито під час злиття.}", "vcmi.heroWindow.fusingArtifact.fusing" : "Ви володієте всіма компонентами, необхідними для злиття %s. Ви бажаєте виконати злиття? {Всі компоненти буде спожито під час злиття.}",
"vcmi.tavernWindow.inviteHero" : "Запросити героя", "vcmi.tavernWindow.inviteHero" : "Запросити героя",
@ -464,7 +470,7 @@
"vcmi.optionsTab.chessFieldBattle.help" : "Використовується у боях з ШІ чи у боях з гравцями якщо {таймер загону} вичерпується. Встановлюється на початку кожного бою.", "vcmi.optionsTab.chessFieldBattle.help" : "Використовується у боях з ШІ чи у боях з гравцями якщо {таймер загону} вичерпується. Встановлюється на початку кожного бою.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку дії. Залишок додається до {таймеру битви}", "vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку дії. Залишок додається до {таймеру битви}",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку дії. Залишок часу буде втрачено.", "vcmi.optionsTab.chessFieldUnitDiscard.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку дії. Залишок часу буде втрачено.",
"vcmi.optionsTab.accumulate" : "Накопичувати", "vcmi.optionsTab.accumulate" : "Накопичувати",
"vcmi.optionsTab.simturnsTitle" : "Одночасні ходи", "vcmi.optionsTab.simturnsTitle" : "Одночасні ходи",
@ -740,7 +746,7 @@
"core.bonus.TRANSMUTATION.name" : "Трансмутація", "core.bonus.TRANSMUTATION.name" : "Трансмутація",
"core.bonus.TRANSMUTATION.description" : "${val}% шанс перетворити атакованого юніта в інший тип", "core.bonus.TRANSMUTATION.description" : "${val}% шанс перетворити атакованого юніта в інший тип",
"core.bonus.UNDEAD.name" : "Нежить", "core.bonus.UNDEAD.name" : "Нежить",
"core.bonus.UNDEAD.description" : "Істота є нежить", "core.bonus.UNDEAD.description" : "Істота є нежиттю",
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Необмежена кількість ударів у відповідь", "core.bonus.UNLIMITED_RETALIATIONS.name" : "Необмежена кількість ударів у відповідь",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Відбиває будь-яку кількість атак", "core.bonus.UNLIMITED_RETALIATIONS.description" : "Відбиває будь-яку кількість атак",
"core.bonus.WATER_IMMUNITY.name" : "Імунітет до води", "core.bonus.WATER_IMMUNITY.name" : "Імунітет до води",
@ -782,5 +788,27 @@
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.water": "На цей загін не діють жодні закляття школи Води", "core.bonus.SPELL_SCHOOL_IMMUNITY.description.water": "На цей загін не діють жодні закляття школи Води",
"core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth": "На цей загін не діють жодні закляття школи Землі", "core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth": "На цей загін не діють жодні закляття школи Землі",
"core.bonus.REVENGE.description" : "Завдає додаткової шкоди залежно від втраченого здоров'я в бою", "core.bonus.REVENGE.description" : "Завдає додаткової шкоди залежно від втраченого здоров'я в бою",
"core.bonus.REVENGE.name" : "Помста" "core.bonus.REVENGE.name" : "Помста",
"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" : "Наземна міна"
} }

View File

@ -23,8 +23,6 @@
"vcmi.adventureMap.noTownWithTavern": "Thành không có sẵn quán rượu!", "vcmi.adventureMap.noTownWithTavern": "Thành không có sẵn quán rượu!",
"vcmi.adventureMap.spellUnknownProblem": "Phép này có lỗi! Không có thông tin nào khác.", "vcmi.adventureMap.spellUnknownProblem": "Phép này có lỗi! Không có thông tin nào khác.",
"vcmi.adventureMap.playerAttacked": "Người chơi bị tấn công: %s", "vcmi.adventureMap.playerAttacked": "Người chơi bị tấn công: %s",
"vcmi.adventureMap.moveCostDetails": "Điểm di chuyển - Cần: %TURNS lượt + %POINTS điểm, Còn lại: %REMAINING",
"vcmi.adventureMap.moveCostDetailsNoTurns": "Điểm di chuyển - Cần: %POINTS điểm, Còn lại: %REMAINING",
"vcmi.adventureMap.movementPointsHeroInfo": "(Điểm di chuyển: %REMAINING / %POINTS)", "vcmi.adventureMap.movementPointsHeroInfo": "(Điểm di chuyển: %REMAINING / %POINTS)",
"vcmi.adventureMap.replayOpponentTurnNotImplemented": "Xin lỗi, lượt chơi lại của đối thủ vẫn chưa được triển khai!", "vcmi.adventureMap.replayOpponentTurnNotImplemented": "Xin lỗi, lượt chơi lại của đối thủ vẫn chưa được triển khai!",
@ -423,11 +421,11 @@
"vcmi.heroWindow.openBackpack.hover" : "Mở cửa sổ ba lô báu vật", "vcmi.heroWindow.openBackpack.hover" : "Mở cửa sổ ba lô báu vật",
"vcmi.heroWindow.openBackpack.help" : "Mở cửa sổ để quản lý ba lô báu vật dễ dàng hơn.", "vcmi.heroWindow.openBackpack.help" : "Mở cửa sổ để quản lý ba lô báu vật dễ dàng hơn.",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Sắp xếp theo giá", "vcmi.heroWindow.sortBackpackByCost.hover" : "Sắp xếp theo giá",
"vcmi.heroWindow.sortBackpackByCost.help" : "Sắp xếp các báu vật trong ba lô theo giá.", "vcmi.heroWindow.sortBackpackByCost.help" : "{Sắp xếp theo giá}\n\nSắp xếp các báu vật trong ba lô theo giá.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Sắp xếp theo vị trí", "vcmi.heroWindow.sortBackpackBySlot.hover" : "Sắp xếp theo vị trí",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Sắp xếp báu vật trong ba lô theo ô được trang bị.", "vcmi.heroWindow.sortBackpackBySlot.help" : "{Sắp xếp theo vị trí}\n\nSắp xếp báu vật trong ba lô theo ô được trang bị.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Sắp xếp theo loại", "vcmi.heroWindow.sortBackpackByClass.hover" : "Sắp xếp theo loại",
"vcmi.heroWindow.sortBackpackByClass.help" : "Sắp xếp các báu vật trong ba lô theo loại: Chính, Phụ, Cổ đại và Quý hiếm.", "vcmi.heroWindow.sortBackpackByClass.help" : "{Sắp xếp theo loại}\n\nSắp xếp các báu vật trong ba lô theo loại: Chính, Phụ, Cổ đại và Quý hiếm.",
"vcmi.heroWindow.fusingArtifact.fusing" : "Bạn đã sở hữu tất cả các món đồ cần thiết để hợp nhất %s. Bạn có muốn hợp nhất không? {Tất cả các món đồ sẽ được sử dụng khi hợp nhất.}", "vcmi.heroWindow.fusingArtifact.fusing" : "Bạn đã sở hữu tất cả các món đồ cần thiết để hợp nhất %s. Bạn có muốn hợp nhất không? {Tất cả các món đồ sẽ được sử dụng khi hợp nhất.}",
"vcmi.tavernWindow.inviteHero" : "Mới thêm tướng", "vcmi.tavernWindow.inviteHero" : "Mới thêm tướng",

View File

@ -52,6 +52,17 @@
] ]
}, },
"italian" : {
"name" : "VCMI - File di base",
"description" : "File di base necessari per il corretto funzionamento di VCMI",
"author" : "Team VCMI",
"skipValidation" : true,
"translations" : [
"config/italian.json"
]
},
"polish" : { "polish" : {
"name" : "Podstawowe pliki VCMI", "name" : "Podstawowe pliki VCMI",
"description" : "Dodatkowe pliki wymagane do prawidłowego działania VCMI", "description" : "Dodatkowe pliki wymagane do prawidłowego działania VCMI",

View File

@ -81,7 +81,7 @@
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="sensorLandscape" /> android:screenOrientation="fullSensor" />
<service <service
android:name=".ServerService" android:name=".ServerService"

View File

@ -107,6 +107,8 @@ public class VcmiSDLActivity extends SDLActivity
mLayout = layout; mLayout = layout;
setContentView(outerLayout); setContentView(outerLayout);
VcmiSDLActivity.this.setWindowStyle(true); // set fullscreen
} }
@Override @Override

View File

@ -493,5 +493,9 @@ if (ffmpeg_INCLUDE_DIRS)
) )
endif() endif()
if(VCMI_PORTMASTER)
target_compile_definitions(vcmiclientcommon PRIVATE VCMI_PORTMASTER)
endif()
vcmi_set_output_dir(vcmiclientcommon "") vcmi_set_output_dir(vcmiclientcommon "")
enable_pch(vcmiclientcommon) enable_pch(vcmiclientcommon)

View File

@ -491,6 +491,7 @@ void CPlayerInterface::heroSecondarySkillChanged(const CGHeroInstance * hero, in
cuw->updateSecondarySkills(); cuw->updateSecondarySkills();
localState->verifyPath(hero); localState->verifyPath(hero);
adventureInt->onHeroChanged(hero);// secondary skill can change primary skill / mana limit
} }
void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero) void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero)
@ -505,6 +506,7 @@ void CPlayerInterface::heroMovePointsChanged(const CGHeroInstance * hero)
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
if (makingTurn && hero->tempOwner == playerID) if (makingTurn && hero->tempOwner == playerID)
adventureInt->onHeroChanged(hero); adventureInt->onHeroChanged(hero);
invalidatePaths();
} }
void CPlayerInterface::receivedResource() void CPlayerInterface::receivedResource()
{ {

View File

@ -186,9 +186,9 @@ void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
si->difficulty = lastDifficulty.Integer(); si->difficulty = lastDifficulty.Integer();
logNetwork->trace("\tStarting local server"); logNetwork->trace("\tStarting local server");
uint16_t srvport = serverRunner->start(getLocalPort(), connectToLobby, si); serverRunner->start(loadMode == ELoadMode::MULTI, connectToLobby, si);
logNetwork->trace("\tConnecting to local server"); logNetwork->trace("\tConnecting to local server");
connectToServer(getLocalHostname(), srvport); connectToServer(getLocalHostname(), getLocalPort());
logNetwork->trace("\tWaiting for connection"); logNetwork->trace("\tWaiting for connection");
} }
@ -206,9 +206,13 @@ void CServerHandler::connectToServer(const std::string & addr, const ui16 port)
Settings remotePort = settings.write["server"]["remotePort"]; Settings remotePort = settings.write["server"]["remotePort"];
remotePort->Integer() = port; remotePort->Integer() = port;
}
networkHandler->connectToRemote(*this, addr, port); networkHandler->connectToRemote(*this, addr, port);
}
else
{
serverRunner->connect(*networkHandler, *this);
}
} }
void CServerHandler::onConnectionFailed(const std::string & errorMessage) void CServerHandler::onConnectionFailed(const std::string & errorMessage)
@ -245,7 +249,7 @@ void CServerHandler::onTimer()
} }
assert(isServerLocal()); assert(isServerLocal());
networkHandler->connectToRemote(*this, getLocalHostname(), getLocalPort()); serverRunner->connect(*networkHandler, *this);
} }
void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection) void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection)

View File

@ -13,6 +13,8 @@
#include "../lib/VCMIDirs.h" #include "../lib/VCMIDirs.h"
#include "../lib/CThreadHelper.h" #include "../lib/CThreadHelper.h"
#include "../lib/network/NetworkInterface.h"
#include "../lib/CConfigHandler.h"
#include "../server/CVCMIServer.h" #include "../server/CVCMIServer.h"
#ifdef ENABLE_SERVER_PROCESS #ifdef ENABLE_SERVER_PROCESS
@ -33,10 +35,11 @@
ServerThreadRunner::ServerThreadRunner() = default; ServerThreadRunner::ServerThreadRunner() = default;
ServerThreadRunner::~ServerThreadRunner() = default; ServerThreadRunner::~ServerThreadRunner() = default;
uint16_t ServerThreadRunner::start(uint16_t cfgport, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) void ServerThreadRunner::start(bool listenForConnections, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
{ {
// cfgport may be 0 -- the real port is returned after calling prepare() // cfgport may be 0 -- the real port is returned after calling prepare()
server = std::make_unique<CVCMIServer>(cfgport, true); uint16_t port = settings["server"]["localPort"].Integer();
server = std::make_unique<CVCMIServer>(port, true);
if (startingInfo) if (startingInfo)
{ {
@ -45,18 +48,16 @@ uint16_t ServerThreadRunner::start(uint16_t cfgport, bool connectToLobby, std::s
std::promise<uint16_t> promise; std::promise<uint16_t> promise;
threadRunLocalServer = boost::thread([this, connectToLobby, &promise]{ threadRunLocalServer = boost::thread([this, connectToLobby, listenForConnections, &promise]{
setThreadName("runServer"); setThreadName("runServer");
uint16_t port = server->prepare(connectToLobby); uint16_t port = server->prepare(connectToLobby, listenForConnections);
promise.set_value(port); promise.set_value(port);
server->run(); server->run();
}); });
logNetwork->trace("Waiting for server port..."); logNetwork->trace("Waiting for server port...");
auto srvport = promise.get_future().get(); serverPort = promise.get_future().get();
logNetwork->debug("Server port: %d", srvport); logNetwork->debug("Server port: %d", serverPort);
return srvport;
} }
void ServerThreadRunner::shutdown() void ServerThreadRunner::shutdown()
@ -74,6 +75,11 @@ int ServerThreadRunner::exitCode()
return 0; return 0;
} }
void ServerThreadRunner::connect(INetworkHandler & network, INetworkClientListener & listener)
{
network.createInternalConnection(listener, server->getNetworkServer());
}
#ifdef ENABLE_SERVER_PROCESS #ifdef ENABLE_SERVER_PROCESS
ServerProcessRunner::ServerProcessRunner() = default; ServerProcessRunner::ServerProcessRunner() = default;
@ -94,8 +100,9 @@ int ServerProcessRunner::exitCode()
return child->exit_code(); return child->exit_code();
} }
uint16_t ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) void ServerProcessRunner::start(bool listenForConnections, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo)
{ {
uint16_t port = settings["server"]["localPort"].Integer();
boost::filesystem::path serverPath = VCMIDirs::get().serverPath(); boost::filesystem::path serverPath = VCMIDirs::get().serverPath();
boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt"; boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "server_log.txt";
std::vector<std::string> args; std::vector<std::string> args;
@ -109,8 +116,14 @@ uint16_t ServerProcessRunner::start(uint16_t port, bool connectToLobby, std::sha
if (ec) if (ec)
throw std::runtime_error("Failed to start server! Reason: " + ec.message()); throw std::runtime_error("Failed to start server! Reason: " + ec.message());
}
return port; void ServerProcessRunner::connect(INetworkHandler & network, INetworkClientListener & listener)
{
std::string host = settings["server"]["localHostname"].String();
uint16_t port = settings["server"]["localPort"].Integer();
network.connectToRemote(listener, host, port);
} }
#endif #endif

View File

@ -12,6 +12,8 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
struct StartInfo; struct StartInfo;
class INetworkHandler;
class INetworkClientListener;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
@ -20,25 +22,31 @@ class CVCMIServer;
class IServerRunner class IServerRunner
{ {
public: public:
virtual uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) = 0; virtual void start(bool listenForConnections, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) = 0;
virtual void shutdown() = 0; virtual void shutdown() = 0;
virtual void wait() = 0; virtual void wait() = 0;
virtual int exitCode() = 0; virtual int exitCode() = 0;
virtual void connect(INetworkHandler & network, INetworkClientListener & listener) = 0;
virtual ~IServerRunner() = default; virtual ~IServerRunner() = default;
}; };
/// Class that runs server instance as a thread of client process /// Class that runs server instance as a thread of client process
class ServerThreadRunner : public IServerRunner, boost::noncopyable class ServerThreadRunner final : public IServerRunner, boost::noncopyable
{ {
std::unique_ptr<CVCMIServer> server; std::unique_ptr<CVCMIServer> server;
boost::thread threadRunLocalServer; boost::thread threadRunLocalServer;
uint16_t serverPort = 0;
public: public:
uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override; void start(bool listenForConnections, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
void shutdown() override; void shutdown() override;
void wait() override; void wait() override;
int exitCode() override; int exitCode() override;
void connect(INetworkHandler & network, INetworkClientListener & listener) override;
ServerThreadRunner(); ServerThreadRunner();
~ServerThreadRunner(); ~ServerThreadRunner();
}; };
@ -64,16 +72,18 @@ class child;
/// Class that runs server instance as a child process /// Class that runs server instance as a child process
/// Available only on desktop systems where process management is allowed /// Available only on desktop systems where process management is allowed
class ServerProcessRunner : public IServerRunner, boost::noncopyable class ServerProcessRunner final : public IServerRunner, boost::noncopyable
{ {
std::unique_ptr<boost::process::child> child; std::unique_ptr<boost::process::child> child;
public: public:
uint16_t start(uint16_t port, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override; void start(bool listenForConnections, bool connectToLobby, std::shared_ptr<StartInfo> startingInfo) override;
void shutdown() override; void shutdown() override;
void wait() override; void wait() override;
int exitCode() override; int exitCode() override;
void connect(INetworkHandler & network, INetworkClientListener & listener) override;
ServerProcessRunner(); ServerProcessRunner();
~ServerProcessRunner(); ~ServerProcessRunner();
}; };

View File

@ -46,6 +46,7 @@
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapping/CMapDefines.h" #include "../../lib/mapping/CMapDefines.h"
#include "../../lib/pathfinder/CGPathNode.h" #include "../../lib/pathfinder/CGPathNode.h"
#include "../../lib/pathfinder/TurnInfo.h"
#include "../../lib/spells/ISpellMechanics.h" #include "../../lib/spells/ISpellMechanics.h"
#include "../../lib/spells/Problem.h" #include "../../lib/spells/Problem.h"
@ -527,7 +528,6 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID; bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner) != PlayerRelations::ENEMIES; canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner) != PlayerRelations::ENEMIES;
bool isHero = false;
if(LOCPLINT->localState->getCurrentArmy()->ID != Obj::HERO) //hero is not selected (presumably town) if(LOCPLINT->localState->getCurrentArmy()->ID != Obj::HERO) //hero is not selected (presumably town)
{ {
if(LOCPLINT->localState->getCurrentArmy() == topBlocking) //selected town clicked if(LOCPLINT->localState->getCurrentArmy() == topBlocking) //selected town clicked
@ -537,9 +537,10 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
} }
else if(const CGHeroInstance * currentHero = LOCPLINT->localState->getCurrentHero()) //hero is selected else if(const CGHeroInstance * currentHero = LOCPLINT->localState->getCurrentHero()) //hero is selected
{ {
isHero = true;
const CGPathNode *pn = LOCPLINT->getPathsInfo(currentHero)->getPathInfo(targetPosition); const CGPathNode *pn = LOCPLINT->getPathsInfo(currentHero)->getPathInfo(targetPosition);
const auto shipyard = dynamic_cast<const IShipyard *>(topBlocking);
if(currentHero == topBlocking) //clicked selected hero if(currentHero == topBlocking) //clicked selected hero
{ {
LOCPLINT->openHeroWindow(currentHero); LOCPLINT->openHeroWindow(currentHero);
@ -550,10 +551,19 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking)); LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
return; return;
} }
else if(shipyard != nullptr && pn->turns == 255 && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner) != PlayerRelations::ENEMIES)
{
LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
}
else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
{ {
int3 destinationTile = targetPosition;
if(topBlocking && topBlocking->isVisitable() && !topBlocking->visitableAt(destinationTile) && settings["gameTweaks"]["simpleObjectSelection"].Bool())
destinationTile = topBlocking->visitablePos();
if(LOCPLINT->localState->hasPath(currentHero) && if(LOCPLINT->localState->hasPath(currentHero) &&
LOCPLINT->localState->getPath(currentHero).endPos() == targetPosition && LOCPLINT->localState->getPath(currentHero).endPos() == destinationTile &&
!GH.isKeyboardShiftDown())//we'll be moving !GH.isKeyboardShiftDown())//we'll be moving
{ {
assert(!CGI->mh->hasOngoingAnimations()); assert(!CGI->mh->hasOngoingAnimations());
@ -570,7 +580,7 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
} }
else //remove old path and find a new one if we clicked on accessible tile else //remove old path and find a new one if we clicked on accessible tile
{ {
LOCPLINT->localState->setPath(currentHero, targetPosition); LOCPLINT->localState->setPath(currentHero, destinationTile);
onHeroChanged(currentHero); onHeroChanged(currentHero);
} }
} }
@ -580,12 +590,6 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
{ {
throw std::runtime_error("Nothing is selected..."); throw std::runtime_error("Nothing is selected...");
} }
const auto shipyard = ourInaccessibleShipyard(topBlocking);
if(isHero && shipyard != nullptr)
{
LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
}
} }
void AdventureMapInterface::onTileHovered(const int3 &targetPosition) void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
@ -686,6 +690,28 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
showMoveDetailsInStatusbar(*hero, *pathNode); showMoveDetailsInStatusbar(*hero, *pathNode);
} }
if (objAtTile && pathNode->action == EPathNodeAction::UNKNOWN)
{
if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
{
CCS->curh->set(Cursor::Map::TOWN);
return;
}
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
{
CCS->curh->set(Cursor::Map::HERO);
return;
}
else if (objAtTile->ID == Obj::SHIPYARD && objRelations != PlayerRelations::ENEMIES)
{
CCS->curh->set(Cursor::Map::T1_SAIL);
return;
}
if(objAtTile->isVisitable() && !objAtTile->visitableAt(targetPosition) && settings["gameTweaks"]["simpleObjectSelection"].Bool())
pathNode = LOCPLINT->getPathsInfo(hero)->getPathInfo(objAtTile->visitablePos());
}
int turns = pathNode->turns; int turns = pathNode->turns;
vstd::amin(turns, 3); vstd::amin(turns, 3);
switch(pathNode->action) switch(pathNode->action)
@ -737,38 +763,36 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
break; break;
default: default:
if(objAtTile && objRelations != PlayerRelations::ENEMIES)
{
if(objAtTile->ID == Obj::TOWN)
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->set(Cursor::Map::POINTER); CCS->curh->set(Cursor::Map::POINTER);
break; break;
} }
} }
if(ourInaccessibleShipyard(objAtTile))
{
CCS->curh->set(Cursor::Map::T1_SAIL);
}
} }
void AdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode) void AdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
{ {
const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.movementPointsLimit(pathNode.layer == EPathfindingLayer::LAND) : hero.movementPointsRemaining(); const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.movementPointsLimit(pathNode.layer == EPathfindingLayer::LAND) : hero.movementPointsRemaining();
const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains; const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0; const int remainingPointsAfterMove = pathNode.moveRemains;
int totalMovementCost = 0;
for (int i = 0; i <= pathNode.turns; ++i)
{
auto turnInfo = hero.getTurnInfo(i);
if (pathNode.layer == EPathfindingLayer::SAIL)
totalMovementCost += turnInfo->getMovePointsLimitWater();
else
totalMovementCost += turnInfo->getMovePointsLimitLand();
}
totalMovementCost -= pathNode.moveRemains;
std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns"); std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns");
boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns)); boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns));
boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost)); boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost));
boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove)); boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove));
boost::replace_first(result, "%TOTAL", std::to_string(totalMovementCost));
GH.statusbar()->write(result); GH.statusbar()->write(result);
} }
@ -844,18 +868,6 @@ Rect AdventureMapInterface::terrainAreaPixels() const
return widget->getMapView()->pos; return widget->getMapView()->pos;
} }
const IShipyard * AdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const
{
const auto *ret = dynamic_cast<const IShipyard *>(obj);
if(!ret ||
obj->tempOwner != currentPlayerID ||
(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
return nullptr;
return ret;
}
void AdventureMapInterface::hotkeyExitWorldView() void AdventureMapInterface::hotkeyExitWorldView()
{ {
setState(EAdventureState::MAKING_TURN); setState(EAdventureState::MAKING_TURN);

View File

@ -75,9 +75,6 @@ private:
/// updates active state of game window whenever game state changes /// updates active state of game window whenever game state changes
void adjustActiveness(); void adjustActiveness();
/// checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const;
/// check and if necessary reacts on scrolling by moving cursor to screen edge /// check and if necessary reacts on scrolling by moving cursor to screen edge
void handleMapScrollingUpdate(uint32_t msPassed); void handleMapScrollingUpdate(uint32_t msPassed);

View File

@ -594,16 +594,19 @@ void ColorTransformAnimation::tick(uint32_t msPassed)
if (index == timePoints.size()) if (index == timePoints.size())
{ {
//end of animation. Apply ColorShifter using final values and die //end of animation. Apply ColorShifter using final values and die
const auto & shifter = steps[index - 1]; const auto & lastColor = effectColors[index - 1];
owner.stacksController->setStackColorFilter(shifter, stack, spell, false); const auto & lastAlpha = transparency[index - 1];
owner.stacksController->setStackColorFilter(lastColor, lastAlpha, stack, spell, false);
delete this; delete this;
return; return;
} }
assert(index != 0); assert(index != 0);
const auto & prevShifter = steps[index - 1]; const auto & prevColor = effectColors[index - 1];
const auto & nextShifter = steps[index]; const auto & nextColor = effectColors[index];
const auto & prevAlpha = transparency[index - 1];
const auto & nextAlpha = transparency[index];
float prevPoint = timePoints[index-1]; float prevPoint = timePoints[index-1];
float nextPoint = timePoints[index]; float nextPoint = timePoints[index];
@ -611,9 +614,10 @@ void ColorTransformAnimation::tick(uint32_t msPassed)
float stepDuration = (nextPoint - prevPoint); float stepDuration = (nextPoint - prevPoint);
float factor = localProgress / stepDuration; float factor = localProgress / stepDuration;
auto shifter = ColorFilter::genInterpolated(prevShifter, nextShifter, factor); const auto & currColor = vstd::lerp(prevColor, nextColor, factor);
const auto & currAlpha = vstd::lerp(prevAlpha, nextAlpha, factor);
owner.stacksController->setStackColorFilter(shifter, stack, spell, true); owner.stacksController->setStackColorFilter(currColor, currAlpha, stack, spell, true);
} }
ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell): ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell):
@ -622,10 +626,11 @@ ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const
totalProgress(0.f) totalProgress(0.f)
{ {
auto effect = owner.effectsController->getMuxerEffect(colorFilterName); auto effect = owner.effectsController->getMuxerEffect(colorFilterName);
steps = effect.filters; effectColors = effect.effectColors;
transparency = effect.transparency;
timePoints = effect.timePoints; timePoints = effect.timePoints;
assert(!steps.empty() && steps.size() == timePoints.size()); assert(!effectColors.empty() && effectColors.size() == timePoints.size());
logAnim->debug("Created ColorTransformAnimation for %s", stack->getName()); logAnim->debug("Created ColorTransformAnimation for %s", stack->getName());
} }

View File

@ -11,6 +11,7 @@
#include "../../lib/battle/BattleHexArray.h" #include "../../lib/battle/BattleHexArray.h"
#include "../../lib/filesystem/ResourcePath.h" #include "../../lib/filesystem/ResourcePath.h"
#include "../../lib/Color.h"
#include "BattleConstants.h" #include "BattleConstants.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -113,8 +114,10 @@ public:
class ColorTransformAnimation : public BattleStackAnimation class ColorTransformAnimation : public BattleStackAnimation
{ {
std::vector<ColorFilter> steps; std::vector<ColorRGBA> effectColors;
std::vector<float> transparency;
std::vector<float> timePoints; std::vector<float> timePoints;
const CSpell * spell; const CSpell * spell;
float totalProgress; float totalProgress;

View File

@ -143,7 +143,8 @@ void BattleEffectsController::loadColorMuxers()
for (const JsonNode & entry : muxer.second.Vector() ) for (const JsonNode & entry : muxer.second.Vector() )
{ {
effect.timePoints.push_back(entry["time"].Float()); effect.timePoints.push_back(entry["time"].Float());
effect.filters.push_back(ColorFilter::genFromJson(entry)); effect.effectColors.push_back(ColorRGBA(255*entry["color"][0].Float(), 255*entry["color"][1].Float(), 255*entry["color"][2].Float(), 255*entry["color"][3].Float()));
effect.transparency.push_back(entry["alpha"].Float() * 255);
} }
colorMuxerEffects[identifier] = effect; colorMuxerEffects[identifier] = effect;
} }

View File

@ -11,6 +11,7 @@
#include "../../lib/battle/BattleHex.h" #include "../../lib/battle/BattleHex.h"
#include "../../lib/Point.h" #include "../../lib/Point.h"
#include "../../lib/Color.h"
#include "../../lib/filesystem/ResourcePath.h" #include "../../lib/filesystem/ResourcePath.h"
#include "BattleConstants.h" #include "BattleConstants.h"
@ -21,13 +22,19 @@ struct BattleTriggerEffect;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
struct ColorMuxerEffect;
class CAnimation; class CAnimation;
class Canvas; class Canvas;
class BattleInterface; class BattleInterface;
class BattleRenderer; class BattleRenderer;
class EffectAnimation; class EffectAnimation;
struct ColorMuxerEffect
{
std::vector<ColorRGBA> effectColors;
std::vector<float> transparency;
std::vector<float> timePoints;
};
/// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,... /// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,...
struct BattleEffect struct BattleEffect
{ {

View File

@ -533,7 +533,7 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
BattleHexArray hoveredMoveHexes = getHighlightedHexesForMovementTarget(); BattleHexArray hoveredMoveHexes = getHighlightedHexesForMovementTarget();
BattleHex hoveredHex = getHoveredHex(); BattleHex hoveredHex = getHoveredHex();
BattleHexArray hoveredMouseHex = hoveredHex.isValid() ? BattleHexArray({ hoveredHex }) : BattleHexArray(); BattleHexArray hoveredMouseHex = hoveredHex.isAvailable() ? BattleHexArray({ hoveredHex }) : BattleHexArray();
const CStack * hoveredStack = getHoveredStack(); const CStack * hoveredStack = getHoveredStack();
if(!hoveredStack && hoveredHex == BattleHex::INVALID) if(!hoveredStack && hoveredHex == BattleHex::INVALID)

View File

@ -636,7 +636,7 @@ void StackInfoBasicPanel::initializeData(const CStack * stack)
auto attack = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getAttack(stack->isShooter())) + "(" + std::to_string(stack->getAttack(stack->isShooter())) + ")"; auto attack = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getAttack(stack->isShooter())) + "(" + std::to_string(stack->getAttack(stack->isShooter())) + ")";
auto defense = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getDefense(stack->isShooter())) + "(" + std::to_string(stack->getDefense(stack->isShooter())) + ")"; auto defense = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getDefense(stack->isShooter())) + "(" + std::to_string(stack->getDefense(stack->isShooter())) + ")";
auto damage = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getMinDamage(stack->isShooter())) + "-" + std::to_string(stack->getMaxDamage(stack->isShooter())); auto damage = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getMinDamage(stack->isShooter())) + "-" + std::to_string(stack->getMaxDamage(stack->isShooter()));
auto health = CGI->creatures()->getByIndex(stack->creatureIndex())->getMaxHealth(); auto health = stack->getMaxHealth();
auto morale = stack->moraleVal(); auto morale = stack->moraleVal();
auto luck = stack->luckVal(); auto luck = stack->luckVal();
@ -691,7 +691,7 @@ void StackInfoBasicPanel::initializeData(const CStack * stack)
if (spellBonuses->empty()) if (spellBonuses->empty())
throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey()); throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey());
int duration = spellBonuses->front()->duration; int duration = spellBonuses->front()->turnsRemain;
icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed)); icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
if(settings["general"]["enableUiEnhancements"].Bool()) if(settings["general"]["enableUiEnhancements"].Bool())
@ -890,7 +890,7 @@ BattleResultResources BattleResultWindow::getResources(const BattleResult & br)
if (ourHero) if (ourHero)
{ {
resources.resultText.appendTextID("core.genrltxt.305"); resources.resultText.appendTextID("core.genrltxt.305");
resources.resultText.replaceTextID(ourHero->getNameTranslated()); resources.resultText.replaceTextID(ourHero->getNameTextID());
resources.resultText.replaceNumber(br.exp[weAreAttacker ? BattleSide::ATTACKER : BattleSide::DEFENDER]); resources.resultText.replaceNumber(br.exp[weAreAttacker ? BattleSide::ATTACKER : BattleSide::DEFENDER]);
} }
} }

View File

@ -38,6 +38,7 @@
#include "../../lib/battle/BattleAction.h" #include "../../lib/battle/BattleAction.h"
#include "../../lib/battle/BattleHex.h" #include "../../lib/battle/BattleHex.h"
#include "../../lib/texts/TextOperations.h" #include "../../lib/texts/TextOperations.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CRandomGenerator.h" #include "../../lib/CRandomGenerator.h"
#include "../../lib/CStack.h" #include "../../lib/CStack.h"
@ -204,8 +205,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
if (!instant) if (!instant)
{ {
// immediately make stack transparent, giving correct shifter time to start // immediately make stack transparent, giving correct shifter time to start
auto shifterFade = ColorFilter::genAlphaShifter(0); setStackColorFilter(Colors::TRANSPARENCY, 0, stack, nullptr, true);
setStackColorFilter(shifterFade, stack, nullptr, true);
owner.addToAnimationStage(EAnimationEvents::HIT, [=]() owner.addToAnimationStage(EAnimationEvents::HIT, [=]()
{ {
@ -318,20 +318,33 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
Point textPosition = Point(amountBG->dimensions().x/2 + boxPosition.x, boxPosition.y + amountBG->dimensions().y/2); Point textPosition = Point(amountBG->dimensions().x/2 + boxPosition.x, boxPosition.y + amountBG->dimensions().y/2);
if(settings["battle"]["showHealthBar"].Bool())
{
float health = stack->getMaxHealth();
float healthRemaining = std::max(stack->getAvailableHealth() - (stack->getCount() - 1) * health, .0f);
Rect r(boxPosition.x, boxPosition.y - 3, amountBG->width(), 4);
canvas.drawColor(r, Colors::RED);
canvas.drawColor(Rect(r.x, r.y, (r.w / health) * healthRemaining, r.h), Colors::GREEN);
canvas.drawBorder(r, Colors::YELLOW);
}
canvas.draw(amountBG, boxPosition); canvas.draw(amountBG, boxPosition);
canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, TextOperations::formatMetric(stack->getCount(), 4)); canvas.drawText(textPosition, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, TextOperations::formatMetric(stack->getCount(), 4));
} }
void BattleStacksController::showStack(Canvas & canvas, const CStack * stack) void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
{ {
ColorFilter fullFilter = ColorFilter::genEmptyShifter(); ColorRGBA effectColor = Colors::TRANSPARENCY;
uint8_t transparency = 255;
for(const auto & filter : stackFilterEffects) for(const auto & filter : stackFilterEffects)
{ {
if (filter.target == stack) if (filter.target == stack)
fullFilter = ColorFilter::genCombined(fullFilter, filter.effect); {
effectColor = filter.effectColor;
transparency = static_cast<int>(filter.transparency) * transparency / 255;
}
} }
stackAnimation[stack->unitId()]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit stackAnimation[stack->unitId()]->nextFrame(canvas, effectColor, transparency, facingRight(stack)); // do actual blit
} }
void BattleStacksController::tick(uint32_t msPassed) void BattleStacksController::tick(uint32_t msPassed)
@ -769,18 +782,19 @@ Point BattleStacksController::getStackPositionAtHex(const BattleHex & hexNum, co
return ret; return ret;
} }
void BattleStacksController::setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell * source, bool persistent) void BattleStacksController::setStackColorFilter(const ColorRGBA & effectColor, uint8_t transparency, const CStack * target, const CSpell * source, bool persistent)
{ {
for (auto & filter : stackFilterEffects) for (auto & filter : stackFilterEffects)
{ {
if (filter.target == target && filter.source == source) if (filter.target == target && filter.source == source)
{ {
filter.effect = effect; filter.effectColor = effectColor;
filter.transparency = transparency;
filter.persistent = persistent; filter.persistent = persistent;
return; return;
} }
} }
stackFilterEffects.push_back({ effect, target, source, persistent }); stackFilterEffects.push_back({ target, source, effectColor, transparency, persistent });
} }
void BattleStacksController::removeExpiredColorFilters() void BattleStacksController::removeExpiredColorFilters()
@ -791,7 +805,7 @@ void BattleStacksController::removeExpiredColorFilters()
{ {
if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id)), Selector::all)) if (filter.source && !filter.target->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(filter.source->id)), Selector::all))
return true; return true;
if (filter.effect == ColorFilter::genEmptyShifter()) if (filter.effectColor == Colors::TRANSPARENCY && filter.transparency == 255)
return true; return true;
} }
return false; return false;

View File

@ -9,7 +9,7 @@
*/ */
#pragma once #pragma once
#include "../render/ColorFilter.h" #include "../../lib/Color.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -37,9 +37,10 @@ class IImage;
struct BattleStackFilterEffect struct BattleStackFilterEffect
{ {
ColorFilter effect;
const CStack * target; const CStack * target;
const CSpell * source; const CSpell * source;
ColorRGBA effectColor;
uint8_t transparency;
bool persistent; bool persistent;
}; };
@ -134,7 +135,7 @@ public:
/// Adds new color filter effect targeting stack /// Adds new color filter effect targeting stack
/// Effect will last as long as stack is affected by specified spell (unless effect is persistent) /// Effect will last as long as stack is affected by specified spell (unless effect is persistent)
/// If effect from same (target, source) already exists, it will be updated /// If effect from same (target, source) already exists, it will be updated
void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); void setStackColorFilter(const ColorRGBA & effect, uint8_t transparency, const CStack * target, const CSpell *source, bool persistent);
void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims
const CStack* getActiveStack() const; const CStack* getActiveStack() const;

View File

@ -24,11 +24,6 @@ static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 };
static const ColorRGBA creatureGoldBorder = { 255, 255, 0, 255 }; static const ColorRGBA creatureGoldBorder = { 255, 255, 0, 255 };
static const ColorRGBA creatureNoBorder = { 0, 0, 0, 0 }; static const ColorRGBA creatureNoBorder = { 0, 0, 0, 0 };
static ColorRGBA genShadow(ui8 alpha)
{
return ColorRGBA(0, 0, 0, alpha);
}
ColorRGBA AnimationControls::getBlueBorder() ColorRGBA AnimationControls::getBlueBorder()
{ {
return creatureBlueBorder; return creatureBlueBorder;
@ -192,7 +187,6 @@ void CreatureAnimation::setType(ECreatureAnimType type)
CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedController controller) CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedController controller)
: name(name_), : name(name_),
speed(0.1f), speed(0.1f),
shadowAlpha(128),
currentFrame(0), currentFrame(0),
animationEnd(-1), animationEnd(-1),
elapsedTime(0), elapsedTime(0),
@ -200,8 +194,15 @@ CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedControll
speedController(controller), speedController(controller),
once(false) once(false)
{ {
forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_OVERLAY);
reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_OVERLAY); forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_SELECTION);
reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::WITH_SHADOW_AND_SELECTION);
if (forward->size(size_t(ECreatureAnimType::DEATH)) == 0)
throw std::runtime_error("Animation '" + name_.getOriginalName() + "' has empty death animation!");
if (forward->size(size_t(ECreatureAnimType::HOLDING)) == 0)
throw std::runtime_error("Animation '" + name_.getOriginalName() + "' has empty holding animation!");
// if necessary, add one frame into vcmi-only group DEAD // if necessary, add one frame into vcmi-only group DEAD
if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0) if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)
@ -324,11 +325,8 @@ static ColorRGBA genBorderColor(ui8 alpha, const ColorRGBA & base)
return ColorRGBA(base.r, base.g, base.b, ui8(base.a * alpha / 256)); return ColorRGBA(base.r, base.g, base.b, ui8(base.a * alpha / 256));
} }
void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight) void CreatureAnimation::nextFrame(Canvas & canvas, const ColorRGBA & effectColor, uint8_t transparency, bool facingRight)
{ {
ColorRGBA shadowTest = shifter.shiftColor(genShadow(128));
shadowAlpha = shadowTest.a;
size_t frame = static_cast<size_t>(floor(currentFrame)); size_t frame = static_cast<size_t>(floor(currentFrame));
std::shared_ptr<IImage> image; std::shared_ptr<IImage> image;
@ -345,7 +343,8 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
else else
image->setOverlayColor(Colors::TRANSPARENCY); image->setOverlayColor(Colors::TRANSPARENCY);
image->adjustPalette(shifter, 0); image->setEffectColor(effectColor);
image->setAlpha(transparency);
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h)); canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
} }

View File

@ -94,9 +94,6 @@ private:
///type of animation being displayed ///type of animation being displayed
ECreatureAnimType type; ECreatureAnimType type;
/// current value of shadow transparency
uint8_t shadowAlpha;
/// border color, disabled if alpha = 0 /// border color, disabled if alpha = 0
ColorRGBA border; ColorRGBA border;
@ -127,7 +124,7 @@ public:
/// returns currently rendered type of animation /// returns currently rendered type of animation
ECreatureAnimType getType() const; ECreatureAnimType getType() const;
void nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight); void nextFrame(Canvas & canvas, const ColorRGBA & effectColor, uint8_t transparency, bool facingRight);
/// should be called every frame, return true when animation was reset to beginning /// should be called every frame, return true when animation was reset to beginning
bool incrementFrame(float timePassed); bool incrementFrame(float timePassed);

View File

@ -234,6 +234,14 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
GH.onScreenResize(false); GH.onScreenResize(false);
} }
#endif
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
#ifdef VCMI_ANDROID
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
GH.onScreenResize(true);
}
#endif #endif
break; break;
case SDL_WINDOWEVENT_FOCUS_GAINED: case SDL_WINDOWEVENT_FOCUS_GAINED:
@ -389,6 +397,13 @@ bool InputHandler::hasTouchInputDevice() const
return fingerHandler->hasTouchInputDevice(); return fingerHandler->hasTouchInputDevice();
} }
int InputHandler::getNumTouchFingers() const
{
if(currentInputMode != InputMode::TOUCH)
return 0;
return fingerHandler->getNumTouchFingers();
}
void InputHandler::dispatchMainThread(const std::function<void()> & functor) void InputHandler::dispatchMainThread(const std::function<void()> & functor)
{ {
auto heapFunctor = new std::function<void()>(functor); auto heapFunctor = new std::function<void()>(functor);

View File

@ -90,6 +90,9 @@ public:
/// returns true if system has active touchscreen /// returns true if system has active touchscreen
bool hasTouchInputDevice() const; bool hasTouchInputDevice() const;
/// returns number of fingers on touchscreen
int getNumTouchFingers() const;
/// Calls provided functor in main thread on next execution frame /// Calls provided functor in main thread on next execution frame
void dispatchMainThread(const std::function<void()> & functor); void dispatchMainThread(const std::function<void()> & functor);

View File

@ -20,6 +20,7 @@
#include "../gui/EventDispatcher.h" #include "../gui/EventDispatcher.h"
#include "../gui/MouseButton.h" #include "../gui/MouseButton.h"
#include "../gui/WindowHandler.h" #include "../gui/WindowHandler.h"
#include "../render/IScreenHandler.h"
#include "../CServerHandler.h" #include "../CServerHandler.h"
#include "../globalLobby/GlobalLobbyClient.h" #include "../globalLobby/GlobalLobbyClient.h"
@ -34,7 +35,7 @@
#include <SDL_timer.h> #include <SDL_timer.h>
InputSourceTouch::InputSourceTouch() InputSourceTouch::InputSourceTouch()
: lastTapTimeTicks(0), lastLeftClickTimeTicks(0) : lastTapTimeTicks(0), lastLeftClickTimeTicks(0), numTouchFingers(0)
{ {
params.useRelativeMode = settings["general"]["userRelativePointer"].Bool(); params.useRelativeMode = settings["general"]["userRelativePointer"].Bool();
params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float(); params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
@ -65,6 +66,7 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin
case TouchState::RELATIVE_MODE: case TouchState::RELATIVE_MODE:
{ {
Point screenSize = GH.screenDimensions(); Point screenSize = GH.screenDimensions();
int scalingFactor = GH.screenHandler().getScalingFactor();
Point moveDistance { Point moveDistance {
static_cast<int>(screenSize.x * params.relativeModeSpeedFactor * tfinger.dx), static_cast<int>(screenSize.x * params.relativeModeSpeedFactor * tfinger.dx),
@ -73,7 +75,7 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin
GH.input().moveCursorPosition(moveDistance); GH.input().moveCursorPosition(moveDistance);
if (CCS && CCS->curh) if (CCS && CCS->curh)
CCS->curh->cursorMove(GH.getCursorPosition().x, GH.getCursorPosition().y); CCS->curh->cursorMove(GH.getCursorPosition().x * scalingFactor, GH.getCursorPosition().y * scalingFactor);
break; break;
} }
@ -114,6 +116,8 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin
void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger) void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
{ {
numTouchFingers = SDL_GetNumTouchFingers(tfinger.touchId);
// FIXME: better place to update potentially changed settings? // FIXME: better place to update potentially changed settings?
params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float(); params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool(); params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool();
@ -172,6 +176,8 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge
void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger) void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
{ {
numTouchFingers = SDL_GetNumTouchFingers(tfinger.touchId);
switch(state) switch(state)
{ {
case TouchState::RELATIVE_MODE: case TouchState::RELATIVE_MODE:
@ -280,6 +286,11 @@ bool InputSourceTouch::hasTouchInputDevice() const
return SDL_GetNumTouchDevices() > 0; return SDL_GetNumTouchDevices() > 0;
} }
int InputSourceTouch::getNumTouchFingers() const
{
return numTouchFingers;
}
void InputSourceTouch::emitPanningEvent(const SDL_TouchFingerEvent & tfinger) void InputSourceTouch::emitPanningEvent(const SDL_TouchFingerEvent & tfinger)
{ {
Point distance = convertTouchToMouse(-tfinger.dx, -tfinger.dy); Point distance = convertTouchToMouse(-tfinger.dx, -tfinger.dy);
@ -324,8 +335,8 @@ void InputSourceTouch::emitPinchEvent(const SDL_TouchFingerEvent & tfinger)
float newX = thisX - otherX; float newX = thisX - otherX;
float newY = thisY - otherY; float newY = thisY - otherY;
double distanceOld = std::sqrt(oldX * oldX + oldY + oldY); double distanceOld = std::sqrt(oldX * oldX + oldY * oldY);
double distanceNew = std::sqrt(newX * newX + newY + newY); double distanceNew = std::sqrt(newX * newX + newY * newY);
if (distanceOld > params.pinchSensitivityThreshold) if (distanceOld > params.pinchSensitivityThreshold)
GH.events().dispatchGesturePinch(lastTapPosition, distanceNew / distanceOld); GH.events().dispatchGesturePinch(lastTapPosition, distanceNew / distanceOld);

View File

@ -108,6 +108,7 @@ class InputSourceTouch
uint32_t lastLeftClickTimeTicks; uint32_t lastLeftClickTimeTicks;
Point lastLeftClickPosition; Point lastLeftClickPosition;
int numTouchFingers;
Point convertTouchToMouse(const SDL_TouchFingerEvent & current); Point convertTouchToMouse(const SDL_TouchFingerEvent & current);
Point convertTouchToMouse(float x, float y); Point convertTouchToMouse(float x, float y);
@ -127,4 +128,6 @@ public:
void handleUpdate(); void handleUpdate();
bool hasTouchInputDevice() const; bool hasTouchInputDevice() const;
int getNumTouchFingers() const;
}; };

View File

@ -28,6 +28,7 @@
#include "../widgets/Images.h" #include "../widgets/Images.h"
#include "../widgets/MiscWidgets.h" #include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h" #include "../widgets/ObjectLists.h"
#include "../widgets/Slider.h"
#include "../widgets/TextControls.h" #include "../widgets/TextControls.h"
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
@ -126,6 +127,15 @@ std::shared_ptr<CIntObject> GlobalLobbyWidget::buildItemList(const JsonNode & co
auto result = std::make_shared<CListBox>(callback, position, itemOffset, visibleAmount, totalAmount, initialPos, sliderMode, Rect(sliderPosition, sliderSize)); auto result = std::make_shared<CListBox>(callback, position, itemOffset, visibleAmount, totalAmount, initialPos, sliderMode, Rect(sliderPosition, sliderSize));
if (result->getSlider())
{
Point scrollBoundsDimensions(sliderPosition.x + result->getSlider()->pos.w, result->getSlider()->pos.h);
Point scrollBoundsOffset = -sliderPosition;
result->getSlider()->setScrollBounds(Rect(scrollBoundsOffset, scrollBoundsDimensions));
result->getSlider()->setPanningStep(itemOffset.length());
}
result->setRedrawParent(true); result->setRedrawParent(true);
return result; return result;
} }

View File

@ -24,7 +24,7 @@
std::unique_ptr<ICursor> CursorHandler::createCursor() std::unique_ptr<ICursor> CursorHandler::createCursor()
{ {
#if defined(VCMI_MOBILE) #if defined(VCMI_MOBILE) || defined(VCMI_PORTMASTER)
if (settings["general"]["userRelativePointer"].Bool()) if (settings["general"]["userRelativePointer"].Bool())
return std::make_unique<CursorSoftware>(); return std::make_unique<CursorSoftware>();
#endif #endif

View File

@ -295,6 +295,7 @@ enum class EShortcut
// Spellbook screen // Spellbook screen
SPELLBOOK_TAB_ADVENTURE, SPELLBOOK_TAB_ADVENTURE,
SPELLBOOK_TAB_COMBAT, SPELLBOOK_TAB_COMBAT,
SPELLBOOK_SEARCH_FOCUS,
LIST_HERO_UP, LIST_HERO_UP,
LIST_HERO_DOWN, LIST_HERO_DOWN,

View File

@ -277,6 +277,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"heroCostumeLoad9", EShortcut::HERO_COSTUME_LOAD_9 }, {"heroCostumeLoad9", EShortcut::HERO_COSTUME_LOAD_9 },
{"spellbookTabAdventure", EShortcut::SPELLBOOK_TAB_ADVENTURE }, {"spellbookTabAdventure", EShortcut::SPELLBOOK_TAB_ADVENTURE },
{"spellbookTabCombat", EShortcut::SPELLBOOK_TAB_COMBAT }, {"spellbookTabCombat", EShortcut::SPELLBOOK_TAB_COMBAT },
{"spellbookSearchFocus", EShortcut::SPELLBOOK_SEARCH_FOCUS },
{"listHeroUp", EShortcut::LIST_HERO_UP }, {"listHeroUp", EShortcut::LIST_HERO_UP },
{"listHeroDown", EShortcut::LIST_HERO_DOWN }, {"listHeroDown", EShortcut::LIST_HERO_DOWN },
{"listHeroTop", EShortcut::LIST_HERO_TOP }, {"listHeroTop", EShortcut::LIST_HERO_TOP },

View File

@ -114,6 +114,7 @@ CBonusSelection::CBonusSelection()
for(size_t b = 0; b < difficultyIcons.size(); ++b) for(size_t b = 0; b < difficultyIcons.size(); ++b)
{ {
difficultyIcons[b] = std::make_shared<CAnimImage>(AnimationPath::builtinTODO("GSPBUT" + std::to_string(b + 3) + ".DEF"), 0, 0, 709, settings["general"]["enableUiEnhancements"].Bool() ? 480 : 455); difficultyIcons[b] = std::make_shared<CAnimImage>(AnimationPath::builtinTODO("GSPBUT" + std::to_string(b + 3) + ".DEF"), 0, 0, 709, settings["general"]["enableUiEnhancements"].Bool() ? 480 : 455);
difficultyIconAreas[b] = std::make_shared<LRClickableArea>(difficultyIcons[b]->pos - pos.topLeft(), nullptr, [b]() { CRClickPopup::createAndPush(CGI->generaltexth->zelp[24 + b].second); });
} }
if(getCampaign()->playerSelectedDifficulty()) if(getCampaign()->playerSelectedDifficulty())
@ -377,9 +378,16 @@ void CBonusSelection::updateAfterStateChange()
for(size_t i = 0; i < difficultyIcons.size(); i++) for(size_t i = 0; i < difficultyIcons.size(); i++)
{ {
if(i == CSH->si->difficulty) if(i == CSH->si->difficulty)
{
difficultyIcons[i]->enable(); difficultyIcons[i]->enable();
difficultyIconAreas[i]->enable();
}
else else
{
difficultyIcons[i]->disable(); difficultyIcons[i]->disable();
difficultyIconAreas[i]->disable();
}
} }
flagbox->recreate(); flagbox->recreate();
createBonusesIcons(); createBonusesIcons();

View File

@ -31,6 +31,7 @@ class ISelectionScreenInfo;
class ExtraOptionsTab; class ExtraOptionsTab;
class VideoWidgetOnce; class VideoWidgetOnce;
class CBonusSelection; class CBonusSelection;
class LRClickableArea;
/// Campaign screen where you can choose one out of three starting bonuses /// Campaign screen where you can choose one out of three starting bonuses
@ -93,6 +94,7 @@ public:
std::shared_ptr<CToggleGroup> groupBonuses; std::shared_ptr<CToggleGroup> groupBonuses;
std::shared_ptr<CLabel> labelDifficulty; std::shared_ptr<CLabel> labelDifficulty;
std::array<std::shared_ptr<CAnimImage>, 5> difficultyIcons; std::array<std::shared_ptr<CAnimImage>, 5> difficultyIcons;
std::array<std::shared_ptr<LRClickableArea>, 5> difficultyIconAreas;
std::shared_ptr<CButton> buttonDifficultyLeft; std::shared_ptr<CButton> buttonDifficultyLeft;
std::shared_ptr<CButton> buttonDifficultyRight; std::shared_ptr<CButton> buttonDifficultyRight;
std::shared_ptr<CAnimImage> iconsMapSizes; std::shared_ptr<CAnimImage> iconsMapSizes;

View File

@ -457,7 +457,7 @@ void SelectionTab::showPopupWindow(const Point & cursorPosition)
} }
GH.windows().createAndPushWindow<CMapOverview>( GH.windows().createAndPushWindow<CMapOverview>(
curItems[py]->getNameTranslated(), curItems[py]->name,
curItems[py]->fullFileURI, curItems[py]->fullFileURI,
creationDateTime, creationDateTime,
author, author,

View File

@ -45,8 +45,6 @@ CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
pos = center(Rect(0, 0, 800, 600)); pos = center(Rect(0, 0, 800, 600));
backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, GH.screenDimensions().x, GH.screenDimensions().y));
addHighScores(); addHighScores();
addButtons(); addButtons();
} }
@ -174,6 +172,12 @@ void CHighScoreScreen::buttonExitClick()
CMM->playMusic(); CMM->playMusic();
} }
void CHighScoreScreen::showAll(Canvas & to)
{
to.fillTexture(GH.renderHandler().loadImage(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE));
CWindowObject::showAll(to);
}
CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic) CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc, const StatisticDataSet & statistic)
: CWindowObject(BORDERED), won(won), calc(calc), stat(statistic) : CWindowObject(BORDERED), won(won), calc(calc), stat(statistic)
{ {
@ -182,7 +186,6 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
pos = center(Rect(0, 0, 800, 600)); pos = center(Rect(0, 0, 800, 600));
backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(-pos.x, -pos.y, GH.screenDimensions().x, GH.screenDimensions().y));
background = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), Colors::BLACK); background = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), Colors::BLACK);
if(won) if(won)
@ -272,6 +275,12 @@ void CHighScoreInputScreen::show(Canvas & to)
CWindowObject::showAll(to); CWindowObject::showAll(to);
} }
void CHighScoreInputScreen::showAll(Canvas & to)
{
to.fillTexture(GH.renderHandler().loadImage(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE));
CWindowObject::showAll(to);
}
void CHighScoreInputScreen::clickPressed(const Point & cursorPosition) void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
{ {
if(statisticButton && statisticButton->pos.isInside(cursorPosition)) if(statisticButton && statisticButton->pos.isInside(cursorPosition))

View File

@ -39,11 +39,11 @@ private:
void buttonExitClick(); void buttonExitClick();
void showPopupWindow(const Point & cursorPosition) override; void showPopupWindow(const Point & cursorPosition) override;
void showAll(Canvas & to) override;
HighScorePage highscorepage; HighScorePage highscorepage;
std::shared_ptr<CPicture> background; std::shared_ptr<CPicture> background;
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
std::vector<std::shared_ptr<CButton>> buttons; std::vector<std::shared_ptr<CButton>> buttons;
std::vector<std::shared_ptr<CLabel>> texts; std::vector<std::shared_ptr<CLabel>> texts;
std::vector<std::shared_ptr<CAnimImage>> images; std::vector<std::shared_ptr<CAnimImage>> images;
@ -77,7 +77,6 @@ class CHighScoreInputScreen : public CWindowObject, public IVideoHolder
std::shared_ptr<CHighScoreInput> input; std::shared_ptr<CHighScoreInput> input;
std::shared_ptr<TransparentFilledRectangle> background; std::shared_ptr<TransparentFilledRectangle> background;
std::shared_ptr<VideoWidgetBase> videoPlayer; std::shared_ptr<VideoWidgetBase> videoPlayer;
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
std::shared_ptr<CButton> statisticButton; std::shared_ptr<CButton> statisticButton;
@ -95,4 +94,5 @@ public:
void clickPressed(const Point & cursorPosition) override; void clickPressed(const Point & cursorPosition) override;
void keyPressed(EShortcut key) override; void keyPressed(EShortcut key) override;
void show(Canvas & to) override; void show(Canvas & to) override;
void showAll(Canvas & to) override;
}; };

View File

@ -407,7 +407,7 @@ std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const AnimationPath
if(it != animations.end()) if(it != animations.end())
return it->second; return it->second;
auto ret = GH.renderHandler().loadAnimation(filename, enableOverlay ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::WITH_SHADOW); auto ret = GH.renderHandler().loadAnimation(filename, enableOverlay ? EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR: EImageBlitMode::WITH_SHADOW);
animations[filename] = ret; animations[filename] = ret;
if(generateMovementGroups) if(generateMovementGroups)

View File

@ -278,6 +278,9 @@ std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) c
if (!tile.visitable()) if (!tile.visitable())
return {}; return {};
if ( tile.visitableObjects.back()->ID == Obj::EVENT)
return {};
return tile.visitableObjects.back()->getObjectName(); return tile.visitableObjects.back()->getObjectName();
} }

View File

@ -224,7 +224,7 @@ void MapViewController::updateState()
adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool(); adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool();
adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool(); adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool();
adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool(); adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool();
adventureContext->settingTextOverlay = GH.isKeyboardAltDown(); adventureContext->settingTextOverlay = GH.isKeyboardAltDown() || GH.input().getNumTouchFingers() == 2;
} }
} }

View File

@ -22,8 +22,6 @@
#include "../../lib/TerrainHandler.h" #include "../../lib/TerrainHandler.h"
#include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/Filesystem.h"
#include <SDL_mixer.h>
void CMusicHandler::onVolumeChange(const JsonNode & volumeNode) void CMusicHandler::onVolumeChange(const JsonNode & volumeNode)
{ {
setVolume(volumeNode.Integer()); setVolume(volumeNode.Integer());

View File

@ -14,8 +14,7 @@
#include "../lib/CConfigHandler.h" #include "../lib/CConfigHandler.h"
struct _Mix_Music; #include <SDL_mixer.h>
using Mix_Music = struct _Mix_Music;
class CMusicHandler; class CMusicHandler;

View File

@ -211,7 +211,7 @@ AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground()
auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE); auto locator = ImageLocator(ImagePath::builtin("CAMPBACK"), EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(200, 116), CanvasScalingPolicy::IGNORE); auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas(); Canvas canvas = image->getCanvas();
canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600)); canvas.draw(img, Point(0, 0), Rect(0, 0, 800, 600));
@ -247,11 +247,11 @@ AssetGenerator::CanvasPtr AssetGenerator::createCampaignBackground()
AssetGenerator::CanvasPtr AssetGenerator::createChroniclesCampaignImages(int chronicle) AssetGenerator::CanvasPtr AssetGenerator::createChroniclesCampaignImages(int chronicle)
{ {
auto imgPathBg = ImagePath::builtin("data/chronicles_" + std::to_string(chronicle) + "/GamSelBk"); auto imgPathBg = ImagePath::builtin("chronicles_" + std::to_string(chronicle) + "/GamSelBk");
auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE); auto locator = ImageLocator(imgPathBg, EImageBlitMode::OPAQUE);
std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator); std::shared_ptr<IImage> img = GH.renderHandler().loadImage(locator);
auto image = GH.renderHandler().createImage(Point(800, 600), CanvasScalingPolicy::IGNORE); auto image = GH.renderHandler().createImage(Point(200, 116), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas(); Canvas canvas = image->getCanvas();
std::array sourceRect = { std::array sourceRect = {

View File

@ -25,6 +25,11 @@ CanvasImage::CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy)
{ {
} }
CanvasImage::~CanvasImage()
{
SDL_FreeSurface(surface);
}
void CanvasImage::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const void CanvasImage::draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const
{ {
if(src) if(src)

View File

@ -16,6 +16,7 @@ class CanvasImage : public IImage
{ {
public: public:
CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy); CanvasImage(const Point & size, CanvasScalingPolicy scalingPolicy);
~CanvasImage();
Canvas getCanvas(); Canvas getCanvas();
@ -31,6 +32,7 @@ public:
void setAlpha(uint8_t value) override{}; void setAlpha(uint8_t value) override{};
void playerColored(const PlayerColor & player) override{}; void playerColored(const PlayerColor & player) override{};
void setOverlayColor(const ColorRGBA & color) override{}; void setOverlayColor(const ColorRGBA & color) override{};
void setEffectColor(const ColorRGBA & color) override{};
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{}; void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override{};
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{}; void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override{};

View File

@ -129,40 +129,3 @@ ColorFilter ColorFilter::genCombined(const ColorFilter & left, const ColorFilter
float a = left.a * right.a; float a = left.a * right.a;
return genMuxerShifter(r,g,b,a); return genMuxerShifter(r,g,b,a);
} }
ColorFilter ColorFilter::genFromJson(const JsonNode & entry)
{
ChannelMuxer r{ 1.f, 0.f, 0.f, 0.f };
ChannelMuxer g{ 0.f, 1.f, 0.f, 0.f };
ChannelMuxer b{ 0.f, 0.f, 1.f, 0.f };
float a{ 1.0};
if (!entry["red"].isNull())
{
r.r = entry["red"].Vector()[0].Float();
r.g = entry["red"].Vector()[1].Float();
r.b = entry["red"].Vector()[2].Float();
r.a = entry["red"].Vector()[3].Float();
}
if (!entry["green"].isNull())
{
g.r = entry["green"].Vector()[0].Float();
g.g = entry["green"].Vector()[1].Float();
g.b = entry["green"].Vector()[2].Float();
g.a = entry["green"].Vector()[3].Float();
}
if (!entry["blue"].isNull())
{
b.r = entry["blue"].Vector()[0].Float();
b.g = entry["blue"].Vector()[1].Float();
b.b = entry["blue"].Vector()[2].Float();
b.a = entry["blue"].Vector()[3].Float();
}
if (!entry["alpha"].isNull())
a = entry["alpha"].Float();
return genMuxerShifter(r,g,b,a);
}

View File

@ -54,13 +54,4 @@ public:
/// Scales down strength of a shifter to a specified factor /// Scales down strength of a shifter to a specified factor
static ColorFilter genInterpolated(const ColorFilter & left, const ColorFilter & right, float power); static ColorFilter genInterpolated(const ColorFilter & left, const ColorFilter & right, float power);
/// Generates object using supplied Json config
static ColorFilter genFromJson(const JsonNode & entry);
};
struct ColorMuxerEffect
{
std::vector<ColorFilter> filters;
std::vector<float> timePoints;
}; };

View File

@ -47,19 +47,24 @@ enum class EImageBlitMode : uint8_t
WITH_SHADOW, WITH_SHADOW,
/// RGBA, may consist from 3 separate parts: base, shadow, and overlay /// RGBA, may consist from 3 separate parts: base, shadow, and overlay
WITH_SHADOW_AND_OVERLAY, WITH_SHADOW_AND_SELECTION,
WITH_SHADOW_AND_FLAG_COLOR,
/// RGBA, contains only body, with shadow and overlay disabled /// RGBA, contains only body, with shadow and overlay disabled
ONLY_BODY, GRAYSCALE_BODY_HIDE_SELECTION,
ONLY_BODY_HIDE_SELECTION,
ONLY_BODY_HIDE_FLAG_COLOR,
/// RGBA, contains only body, with shadow disabled and overlay treated as part of body /// RGBA, contains only body, with shadow disabled and overlay treated as part of body
ONLY_BODY_IGNORE_OVERLAY, ONLY_BODY_IGNORE_OVERLAY,
/// RGBA, contains only shadow /// RGBA, contains only shadow
ONLY_SHADOW, ONLY_SHADOW_HIDE_SELECTION,
ONLY_SHADOW_HIDE_FLAG_COLOR,
/// RGBA, contains only overlay /// RGBA, contains only overlay
ONLY_OVERLAY, ONLY_SELECTION,
ONLY_FLAG_COLOR,
}; };
enum class EScalingAlgorithm : int8_t enum class EScalingAlgorithm : int8_t
@ -100,8 +105,8 @@ public:
virtual void setAlpha(uint8_t value) = 0; virtual void setAlpha(uint8_t value) = 0;
//only indexed bitmaps with 7 special colors
virtual void setOverlayColor(const ColorRGBA & color) = 0; virtual void setOverlayColor(const ColorRGBA & color) = 0;
virtual void setEffectColor(const ColorRGBA & color) = 0;
virtual ~IImage() = default; virtual ~IImage() = default;
}; };

View File

@ -74,9 +74,12 @@ bool FontChain::bitmapFontsPrioritized(const std::string & bitmapFontName) const
return true; // else - use original bitmap fonts return true; // else - use original bitmap fonts
} }
void FontChain::addTrueTypeFont(const JsonNode & trueTypeConfig) void FontChain::addTrueTypeFont(const JsonNode & trueTypeConfig, bool begin)
{ {
chain.insert(chain.begin(), std::make_unique<CTrueTypeFont>(trueTypeConfig)); if(begin)
chain.insert(chain.begin(), std::make_unique<CTrueTypeFont>(trueTypeConfig));
else
chain.push_back(std::make_unique<CTrueTypeFont>(trueTypeConfig));
} }
void FontChain::addBitmapFont(const std::string & bitmapFilename) void FontChain::addBitmapFont(const std::string & bitmapFilename)

View File

@ -33,7 +33,7 @@ class FontChain final : public IFont
public: public:
FontChain() = default; FontChain() = default;
void addTrueTypeFont(const JsonNode & trueTypeConfig); void addTrueTypeFont(const JsonNode & trueTypeConfig, bool begin);
void addBitmapFont(const std::string & bitmapFilename); void addBitmapFont(const std::string & bitmapFilename);
size_t getLineHeightScaled() const override; size_t getLineHeightScaled() const override;

View File

@ -230,6 +230,7 @@ std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const Ima
if (generated) if (generated)
return generated; return generated;
logGlobal->error("Failed to load image %s", locator.image->getOriginalName());
return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT")); return std::make_shared<SDLImageShared>(ImagePath::builtin("DEFAULT"));
} }
@ -292,9 +293,9 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato
std::string imagePathString = pathToLoad.getName(); std::string imagePathString = pathToLoad.getName();
if(locator.layer == EImageBlitMode::ONLY_OVERLAY) if(locator.layer == EImageBlitMode::ONLY_FLAG_COLOR || locator.layer == EImageBlitMode::ONLY_SELECTION)
imagePathString += "-OVERLAY"; imagePathString += "-OVERLAY";
if(locator.layer == EImageBlitMode::ONLY_SHADOW) if(locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION || locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR)
imagePathString += "-SHADOW"; imagePathString += "-SHADOW";
if(locator.playerColored.isValidPlayer()) if(locator.playerColored.isValidPlayer())
imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]); imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]);
@ -347,7 +348,10 @@ std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int
if (!locator.empty()) if (!locator.empty())
return loadImage(locator); return loadImage(locator);
else else
{
logGlobal->error("Failed to load non-existing image");
return loadImage(ImageLocator(ImagePath::builtin("DEFAULT"), mode)); return loadImage(ImageLocator(ImagePath::builtin("DEFAULT"), mode));
}
} }
std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode) std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
@ -375,7 +379,7 @@ void RenderHandler::addImageListEntries(const EntityService * service)
if (imageName.empty()) if (imageName.empty())
return; return;
auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::SIMPLE); auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName), 1, EImageBlitMode::COLORKEY);
JsonNode entry; JsonNode entry;
entry["file"].String() = imageName; entry["file"].String() = imageName;
@ -413,8 +417,8 @@ static void detectOverlappingBuildings(RenderHandler * renderHandler, const Fact
if (left->pos.z != right->pos.z) if (left->pos.z != right->pos.z)
continue; // buildings already have different z-index and have well-defined overlap logic continue; // buildings already have different z-index and have well-defined overlap logic
auto leftImage = renderHandler->loadImage(left->defName, 0, 0, EImageBlitMode::SIMPLE); auto leftImage = renderHandler->loadImage(left->defName, 0, 0, EImageBlitMode::COLORKEY);
auto rightImage = renderHandler->loadImage(right->defName, 0, 0, EImageBlitMode::SIMPLE); auto rightImage = renderHandler->loadImage(right->defName, 0, 0, EImageBlitMode::COLORKEY);
Rect leftRect( left->pos.x, left->pos.y, leftImage->width(), leftImage->height()); Rect leftRect( left->pos.x, left->pos.y, leftImage->width(), leftImage->height());
Rect rightRect( right->pos.x, right->pos.y, rightImage->width(), rightImage->height()); Rect rightRect( right->pos.x, right->pos.y, rightImage->width(), rightImage->height());
@ -490,7 +494,7 @@ std::shared_ptr<const IFont> RenderHandler::loadFont(EFonts font)
bitmapPath = bmpConf[index].String(); bitmapPath = bmpConf[index].String();
if (!ttfConf[bitmapPath].isNull()) if (!ttfConf[bitmapPath].isNull())
loadedFont->addTrueTypeFont(ttfConf[bitmapPath]); loadedFont->addTrueTypeFont(ttfConf[bitmapPath], !config["lowPriority"].Bool());
} }
loadedFont->addBitmapFont(bitmapPath); loadedFont->addBitmapFont(bitmapPath);

View File

@ -97,47 +97,67 @@ void ScalableImageParameters::preparePalette(const SDL_Palette * originalPalette
{ {
switch(blitMode) switch(blitMode)
{ {
case EImageBlitMode::ONLY_SHADOW: case EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR:
case EImageBlitMode::ONLY_OVERLAY: case EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION:
case EImageBlitMode::ONLY_FLAG_COLOR:
case EImageBlitMode::ONLY_SELECTION:
adjustPalette(originalPalette, blitMode, ColorFilter::genAlphaShifter(0), 0); adjustPalette(originalPalette, blitMode, ColorFilter::genAlphaShifter(0), 0);
break; break;
case EImageBlitMode::GRAYSCALE_BODY_HIDE_SELECTION:
adjustPalette(originalPalette, blitMode, ColorFilter::genMuxerShifter( { 0.299, 0.587, 0.114, 0}, { 0.299, 0.587, 0.114, 0}, { 0.299, 0.587, 0.114, 0}, 1), 0);
break;
} }
switch(blitMode) switch(blitMode)
{ {
case EImageBlitMode::SIMPLE: case EImageBlitMode::SIMPLE:
case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_SHADOW: case EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION:
case EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR:
case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
setShadowTransparency(originalPalette, 1.0); setShadowTransparency(originalPalette, 1.0);
break; break;
case EImageBlitMode::ONLY_BODY: case EImageBlitMode::ONLY_BODY_HIDE_SELECTION:
case EImageBlitMode::ONLY_BODY_HIDE_FLAG_COLOR:
case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY:
case EImageBlitMode::ONLY_OVERLAY: case EImageBlitMode::ONLY_FLAG_COLOR:
case EImageBlitMode::ONLY_SELECTION:
case EImageBlitMode::GRAYSCALE_BODY_HIDE_SELECTION:
setShadowTransparency(originalPalette, 0.0); setShadowTransparency(originalPalette, 0.0);
break; break;
} }
switch(blitMode) switch(blitMode)
{ {
case EImageBlitMode::ONLY_OVERLAY: case EImageBlitMode::ONLY_FLAG_COLOR:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR:
setOverlayColor(originalPalette, Colors::WHITE_TRUE); setOverlayColor(originalPalette, Colors::WHITE_TRUE, false);
break; break;
case EImageBlitMode::ONLY_SHADOW: case EImageBlitMode::ONLY_SELECTION:
case EImageBlitMode::ONLY_BODY: case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
setOverlayColor(originalPalette, Colors::TRANSPARENCY); setOverlayColor(originalPalette, Colors::WHITE_TRUE, true);
break;
case EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR:
case EImageBlitMode::ONLY_BODY_HIDE_FLAG_COLOR:
setOverlayColor(originalPalette, Colors::TRANSPARENCY, false);
break;
case EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION:
case EImageBlitMode::ONLY_BODY_HIDE_SELECTION:
case EImageBlitMode::GRAYSCALE_BODY_HIDE_SELECTION:
setOverlayColor(originalPalette, Colors::TRANSPARENCY, true);
break; break;
} }
} }
void ScalableImageParameters::setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color) void ScalableImageParameters::setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color, bool includeShadow)
{ {
palette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color)); palette->colors[5] = CSDL_Ext::toSDL(addColors(targetPalette[5], color));
for (int i : {6,7}) if (includeShadow)
{ {
if (colorsSimilar(originalPalette->colors[i], sourcePalette[i])) for (int i : {6,7})
palette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color)); palette->colors[i] = CSDL_Ext::toSDL(addColors(targetPalette[i], color));
} }
} }
@ -183,7 +203,7 @@ void ScalableImageParameters::setShadowTransparency(const SDL_Palette * original
void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask) void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask)
{ {
// If shadow is enabled, following colors must be skipped unconditionally // If shadow is enabled, following colors must be skipped unconditionally
if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_OVERLAY) if (blitMode == EImageBlitMode::WITH_SHADOW || blitMode == EImageBlitMode::WITH_SHADOW_AND_SELECTION || blitMode == EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR)
colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4); colorsToSkipMask |= (1 << 0) + (1 << 1) + (1 << 4);
// Note: here we skip first colors in the palette that are predefined in H3 images // Note: here we skip first colors in the palette that are predefined in H3 images
@ -259,11 +279,15 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re
bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading(); bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading();
bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading(); bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading();
bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading(); bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading();
bool grayscaleLoading = scaled.at(scalingFactor).bodyGrayscale.at(0) && scaled.at(scalingFactor).bodyGrayscale.at(0)->isLoading();
bool playerLoading = parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->isLoading(); bool playerLoading = parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->isLoading();
if (shadowLoading || bodyLoading || overlayLoading || playerLoading) if (shadowLoading || bodyLoading || overlayLoading || playerLoading || grayscaleLoading)
{ {
getFlippedImage(scaled[1].body)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer); getFlippedImage(scaled[1].body)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer);
if (parameters.effectColorMultiplier.a != ColorRGBA::ALPHA_TRANSPARENT)
getFlippedImage(scaled[1].body)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.effectColorMultiplier, parameters.alphaValue, locator.layer);
return; return;
} }
@ -278,6 +302,9 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re
{ {
if (scaled.at(scalingFactor).body.at(0)) if (scaled.at(scalingFactor).body.at(0))
flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue); flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue);
if (scaled.at(scalingFactor).bodyGrayscale.at(0) && parameters.effectColorMultiplier.a != ColorRGBA::ALPHA_TRANSPARENT)
flipAndDraw(scaled.at(scalingFactor).bodyGrayscale, parameters.effectColorMultiplier, parameters.alphaValue);
} }
if (scaled.at(scalingFactor).overlay.at(0)) if (scaled.at(scalingFactor).overlay.at(0))
@ -353,7 +380,25 @@ void ScalableImageInstance::setOverlayColor(const ColorRGBA & color)
parameters.ovelayColorMultiplier = color; parameters.ovelayColorMultiplier = color;
if (parameters.palette) if (parameters.palette)
parameters.setOverlayColor(image->getPalette(), color); parameters.setOverlayColor(image->getPalette(), color, blitMode == EImageBlitMode::WITH_SHADOW_AND_SELECTION);
}
void ScalableImageInstance::setEffectColor(const ColorRGBA & color)
{
parameters.effectColorMultiplier = color;
if (parameters.palette)
{
const auto grayscaleFilter = ColorFilter::genMuxerShifter( { 0.299, 0.587, 0.114, 0}, { 0.299, 0.587, 0.114, 0}, { 0.299, 0.587, 0.114, 0}, 1);
const auto effectStrengthFilter = ColorFilter::genRangeShifter( 0, 0, 0, color.r / 255.f, color.g / 255.f, color.b / 255.f);
const auto effectFilter = ColorFilter::genCombined(grayscaleFilter, effectStrengthFilter);
const auto effectiveFilter = ColorFilter::genInterpolated(ColorFilter::genEmptyShifter(), effectFilter, color.a / 255.f);
parameters.adjustPalette(image->getPalette(), blitMode, effectiveFilter, 0);
}
if (color.a != ColorRGBA::ALPHA_TRANSPARENT)
image->prepareEffectImage();
} }
void ScalableImageInstance::playerColored(const PlayerColor & player) void ScalableImageInstance::playerColored(const PlayerColor & player)
@ -410,11 +455,19 @@ std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EIm
if (loadedImage) if (loadedImage)
return loadedImage; return loadedImage;
// optional images for 1x resolution - only try load them, don't attempt to generate
bool optionalImage =
mode == EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR ||
mode == EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION ||
mode == EImageBlitMode::ONLY_FLAG_COLOR ||
mode == EImageBlitMode::ONLY_SELECTION ||
mode == EImageBlitMode::GRAYSCALE_BODY_HIDE_SELECTION ||
color != PlayerColor::CANNOT_DETERMINE;
if (scalingFactor == 1) if (scalingFactor == 1)
{ {
// optional images for 1x resolution - only try load them, don't attempt to generate
// this block should never be called for 'body' layer - that image is loaded unconditionally before construction // this block should never be called for 'body' layer - that image is loaded unconditionally before construction
assert(mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE); assert(optionalImage);
return nullptr; return nullptr;
} }
@ -427,7 +480,7 @@ std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EIm
{ {
if (scaling == 1) if (scaling == 1)
{ {
if (mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE) if (optionalImage)
{ {
ScalableImageParameters parameters(getPalette(), mode); ScalableImageParameters parameters(getPalette(), mode);
return loadedImage->scaleInteger(scalingFactor, parameters.palette, mode); return loadedImage->scaleInteger(scalingFactor, parameters.palette, mode);
@ -464,9 +517,13 @@ void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor col
scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]);
break; break;
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
case EImageBlitMode::ONLY_BODY: case EImageBlitMode::ONLY_BODY_HIDE_SELECTION:
scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_HIDE_SELECTION, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]);
break;
case EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR:
case EImageBlitMode::ONLY_BODY_HIDE_FLAG_COLOR:
scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_HIDE_FLAG_COLOR, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]);
break; break;
case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::WITH_SHADOW:
@ -486,9 +543,13 @@ void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor col
scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]);
break; break;
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
case EImageBlitMode::ONLY_BODY: case EImageBlitMode::ONLY_BODY_HIDE_SELECTION:
scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_HIDE_SELECTION, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]);
break;
case EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR:
case EImageBlitMode::ONLY_BODY_HIDE_FLAG_COLOR:
scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_HIDE_FLAG_COLOR, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]);
break; break;
case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::WITH_SHADOW:
@ -503,9 +564,13 @@ void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor col
switch(locator.layer) switch(locator.layer)
{ {
case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::WITH_SHADOW:
case EImageBlitMode::ONLY_SHADOW: case EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].shadow[0]); scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].shadow[0]);
break;
case EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR:
case EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR:
scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].shadow[0]);
break; break;
default: default:
break; break;
@ -516,9 +581,13 @@ void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor col
{ {
switch(locator.layer) switch(locator.layer)
{ {
case EImageBlitMode::ONLY_OVERLAY: case EImageBlitMode::ONLY_FLAG_COLOR:
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::WITH_SHADOW_AND_FLAG_COLOR:
scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].overlay[0]); scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_FLAG_COLOR, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].overlay[0]);
break;
case EImageBlitMode::ONLY_SELECTION:
case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SELECTION, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].overlay[0]);
break; break;
default: default:
break; break;
@ -530,3 +599,20 @@ void ScalableImageShared::preparePlayerColoredImage(PlayerColor color)
{ {
loadScaledImages(GH.screenHandler().getScalingFactor(), color); loadScaledImages(GH.screenHandler().getScalingFactor(), color);
} }
void ScalableImageShared::prepareEffectImage()
{
int scalingFactor = GH.screenHandler().getScalingFactor();
if (scaled[scalingFactor].bodyGrayscale[0] == nullptr)
{
switch(locator.layer)
{
case EImageBlitMode::WITH_SHADOW_AND_SELECTION:
scaled[scalingFactor].bodyGrayscale[0] = loadOrGenerateImage(EImageBlitMode::GRAYSCALE_BODY_HIDE_SELECTION, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].bodyGrayscale[0]);
break;
default:
break;
}
}
}

View File

@ -26,6 +26,7 @@ struct ScalableImageParameters : boost::noncopyable
ColorRGBA colorMultiplier = Colors::WHITE_TRUE; ColorRGBA colorMultiplier = Colors::WHITE_TRUE;
ColorRGBA ovelayColorMultiplier = Colors::WHITE_TRUE; ColorRGBA ovelayColorMultiplier = Colors::WHITE_TRUE;
ColorRGBA effectColorMultiplier = Colors::TRANSPARENCY;
PlayerColor player = PlayerColor::CANNOT_DETERMINE; PlayerColor player = PlayerColor::CANNOT_DETERMINE;
uint8_t alphaValue = 255; uint8_t alphaValue = 255;
@ -39,7 +40,7 @@ struct ScalableImageParameters : boost::noncopyable
void setShadowTransparency(const SDL_Palette * originalPalette, float factor); void setShadowTransparency(const SDL_Palette * originalPalette, float factor);
void shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove); void shiftPalette(const SDL_Palette * originalPalette, uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove);
void playerColored(PlayerColor player); void playerColored(PlayerColor player);
void setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color); void setOverlayColor(const SDL_Palette * originalPalette, const ColorRGBA & color, bool includeShadow);
void preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode); void preparePalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode);
void adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask); void adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask);
}; };
@ -64,6 +65,9 @@ class ScalableImageShared final : public std::enable_shared_from_this<ScalableIm
/// Upscaled overlay (player color, selection highlight) of our image, may be null /// Upscaled overlay (player color, selection highlight) of our image, may be null
FlippedImages overlay; FlippedImages overlay;
/// Upscaled grayscale version of body, for special effects in combat (e.g clone / petrify / berserk)
FlippedImages bodyGrayscale;
/// player-colored images of this particular scale, mostly for UI. These are never flipped in h3 /// player-colored images of this particular scale, mostly for UI. These are never flipped in h3
PlayerColoredImages playerColored; PlayerColoredImages playerColored;
}; };
@ -91,6 +95,7 @@ public:
std::shared_ptr<ScalableImageInstance> createImageReference(); std::shared_ptr<ScalableImageInstance> createImageReference();
void prepareEffectImage();
void preparePlayerColoredImage(PlayerColor color); void preparePlayerColoredImage(PlayerColor color);
}; };
@ -115,6 +120,7 @@ public:
void setAlpha(uint8_t value) override; void setAlpha(uint8_t value) override;
void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const override; void draw(SDL_Surface * where, const Point & pos, const Rect * src, int scalingFactor) const override;
void setOverlayColor(const ColorRGBA & color) override; void setOverlayColor(const ColorRGBA & color) override;
void setEffectColor(const ColorRGBA & color) override;
void playerColored(const PlayerColor & player) override; void playerColored(const PlayerColor & player) override;
void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override; void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override; void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;

View File

@ -208,6 +208,10 @@ ScreenHandler::ScreenHandler()
// NOTE: requires SDL 2.24. // NOTE: requires SDL 2.24.
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor"); SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor");
#endif #endif
if(settings["video"]["allowPortrait"].Bool())
SDL_SetHint(SDL_HINT_ORIENTATIONS, "Portrait PortraitUpsideDown LandscapeLeft LandscapeRight");
else
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER)) if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER))
{ {
@ -354,34 +358,23 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const
return filter; return filter;
// else - autoselect // else - autoselect
#ifdef VCMI_MOBILE
// to help with performance - only if player explicitly enabled xbrz
return EUpscalingFilter::NONE;
#else
Point outputResolution = getRenderResolution(); Point outputResolution = getRenderResolution();
Point logicalResolution = getPreferredLogicalResolution(); Point logicalResolution = getPreferredLogicalResolution();
float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x; float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x;
float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x; float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x;
float scaling = std::min(scaleX, scaleY); float scaling = std::min(scaleX, scaleY);
int systemMemoryMb = SDL_GetSystemRAM();
if (scaling <= 1.001f) if (scaling <= 1.001f)
return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz
else
return EUpscalingFilter::XBRZ_2;
#endif
#if 0 if (systemMemoryMb < 2048)
// Old version, most optimal, but rather performance-heavy return EUpscalingFilter::NONE; // xbrz2 may use ~1.0 - 1.5 Gb of RAM and has notable CPU cost - avoid on low-spec hardware
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 // Only using xbrz2 for autoselection.
#endif // Higher options may have high system requirements and should be only selected explicitly by player
return EUpscalingFilter::XBRZ_2;
} }
void ScreenHandler::selectUpscalingFilter() void ScreenHandler::selectUpscalingFilter()
@ -478,7 +471,7 @@ SDL_Window * ScreenHandler::createWindow()
#endif #endif
#ifdef VCMI_ANDROID #ifdef VCMI_ANDROID
return createWindowImpl(Point(), SDL_WINDOW_FULLSCREEN, false); return createWindowImpl(Point(), SDL_WINDOW_RESIZABLE, false);
#endif #endif
} }

View File

@ -110,6 +110,22 @@ void CExchangeController::moveStack(bool leftToRight, SlotID sourceSlot)
} }
} }
void CExchangeController::moveSingleStackCreature(bool leftToRight, SlotID sourceSlot, bool forceEmptySlotTarget)
{
const auto source = leftToRight ? left : right;
const auto target = leftToRight ? right : left;
auto creature = source->getCreature(sourceSlot);
if(creature == nullptr || source->stacksCount() == 1)
return;
SlotID targetSlot = forceEmptySlotTarget ? target->getFreeSlot() : target->getSlotFor(creature);
if(targetSlot.validSlot())
{
LOCPLINT->cb->splitStack(source, target, sourceSlot, targetSlot, target->getStackCount(targetSlot) + 1);
}
}
void CExchangeController::swapArtifacts(bool equipped, bool baclpack) void CExchangeController::swapArtifacts(bool equipped, bool baclpack)
{ {
LOCPLINT->cb->bulkMoveArtifacts(left->id, right->id, true, equipped, baclpack); LOCPLINT->cb->bulkMoveArtifacts(left->id, right->id, true, equipped, baclpack);

View File

@ -20,6 +20,7 @@ public:
void swapArmy(); void swapArmy();
void moveArmy(bool leftToRight, std::optional<SlotID> heldSlot); void moveArmy(bool leftToRight, std::optional<SlotID> heldSlot);
void moveStack(bool leftToRight, SlotID sourceSlot); void moveStack(bool leftToRight, SlotID sourceSlot);
void moveSingleStackCreature(bool leftToRight, SlotID sourceSlot, bool forceEmptySlotTarget);
void swapArtifacts(bool equipped, bool baclpack); void swapArtifacts(bool equipped, bool baclpack);
void moveArtifacts(bool leftToRight, bool equipped, bool baclpack); void moveArtifacts(bool leftToRight, bool equipped, bool baclpack);

View File

@ -32,6 +32,16 @@
#include "../../lib/texts/CGeneralTextHandler.h" //for Unicode related stuff #include "../../lib/texts/CGeneralTextHandler.h" //for Unicode related stuff
#include "../../lib/CRandomGenerator.h" #include "../../lib/CRandomGenerator.h"
static EImageBlitMode getModeForFlags( uint8_t flags)
{
if (flags & CCreatureAnim::CREATURE_MODE)
return EImageBlitMode::WITH_SHADOW_AND_SELECTION;
if (flags & CCreatureAnim::MAP_OBJECT_MODE)
return EImageBlitMode::WITH_SHADOW;
return EImageBlitMode::COLORKEY;
}
CPicture::CPicture(std::shared_ptr<IImage> image, const Point & position) CPicture::CPicture(std::shared_ptr<IImage> image, const Point & position)
: bg(image) : bg(image)
, needRefresh(false) , needRefresh(false)
@ -195,12 +205,12 @@ CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, size_t Group, i
{ {
pos.x += x; pos.x += x;
pos.y += y; pos.y += y;
anim = GH.renderHandler().loadAnimation(name, (Flags & CCreatureAnim::CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY); anim = GH.renderHandler().loadAnimation(name, getModeForFlags(Flags));
init(); init();
} }
CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags): CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags):
anim(GH.renderHandler().loadAnimation(name, (Flags & CCreatureAnim::CREATURE_MODE) ? EImageBlitMode::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY)), anim(GH.renderHandler().loadAnimation(name, getModeForFlags(Flags))),
frame(Frame), frame(Frame),
group(Group), group(Group),
flags(Flags), flags(Flags),
@ -318,7 +328,7 @@ bool CAnimImage::isPlayerColored() const
} }
CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha): 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::WITH_SHADOW_AND_OVERLAY : EImageBlitMode::COLORKEY)), anim(GH.renderHandler().loadAnimation(name, getModeForFlags(Flags))),
group(Group), group(Group),
frame(0), frame(0),
first(0), first(0),

View File

@ -151,7 +151,8 @@ public:
BASE=1, //base frame will be blitted before current one BASE=1, //base frame will be blitted before current one
HORIZONTAL_FLIP=2, //TODO: will be displayed rotated HORIZONTAL_FLIP=2, //TODO: will be displayed rotated
VERTICAL_FLIP=4, //TODO: will be displayed rotated VERTICAL_FLIP=4, //TODO: will be displayed rotated
CREATURE_MODE=8, // use alpha channel for images with palette. Required for creatures in battle and map objects CREATURE_MODE=8, // use alpha channel for images with palette. Required for creatures in battle
MAP_OBJECT_MODE=16, // use alpha channel for images with palette. Required for map objects
PLAY_ONCE=32 //play animation only once and stop at last frame PLAY_ONCE=32 //play animation only once and stop at last frame
}; };
protected: protected:

View File

@ -550,6 +550,15 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature)
tooltipTextbox = std::make_shared<CTextBox>(textContent, Rect(15, 95, 230, 150), 0, FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE); tooltipTextbox = std::make_shared<CTextBox>(textContent, Rect(15, 95, 230, 150), 0, FONT_SMALL, ETextAlignment::TOPCENTER, Colors::WHITE);
} }
void CreatureTooltip::show(Canvas & to)
{
// fixes scrolling of textbox (#5076)
setRedrawParent(true);
redraw();
CIntObject::show(to);
}
void MoraleLuckBox::set(const AFactionMember * node) void MoraleLuckBox::set(const AFactionMember * node)
{ {
OBJECT_CONSTRUCTION; OBJECT_CONSTRUCTION;
@ -615,9 +624,9 @@ void MoraleLuckBox::set(const AFactionMember * node)
imageName = morale ? "IMRL42" : "ILCK42"; imageName = morale ? "IMRL42" : "ILCK42";
image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), *component.value + 3); image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), *component.value + 3);
image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2)); //center icon
if(settings["general"]["enableUiEnhancements"].Bool()) if(settings["general"]["enableUiEnhancements"].Bool())
label = std::make_shared<CLabel>(small ? 30 : 42, small ? 20 : 38, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(modifierList->totalValue())); label = std::make_shared<CLabel>((image->pos.topLeft() - pos.topLeft()).x + (small ? 28 : 40), (image->pos.topLeft() - pos.topLeft()).y + (small ? 20 : 38), EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(modifierList->totalValue()));
} }
MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small) MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)

View File

@ -166,6 +166,7 @@ class CreatureTooltip : public CIntObject
std::shared_ptr<CAnimImage> creatureImage; std::shared_ptr<CAnimImage> creatureImage;
std::shared_ptr<CTextBox> tooltipTextbox; std::shared_ptr<CTextBox> tooltipTextbox;
void show(Canvas & to) override;
public: public:
CreatureTooltip(Point pos, const CGCreature * creature); CreatureTooltip(Point pos, const CGCreature * creature);
}; };

View File

@ -168,6 +168,11 @@ std::shared_ptr<CIntObject> CListBox::getItem(size_t which)
return std::shared_ptr<CIntObject>(); return std::shared_ptr<CIntObject>();
} }
std::shared_ptr<CSlider> CListBox::getSlider()
{
return slider;
}
size_t CListBox::getIndexOf(std::shared_ptr<CIntObject> item) size_t CListBox::getIndexOf(std::shared_ptr<CIntObject> item)
{ {
size_t i=first; size_t i=first;

View File

@ -91,6 +91,8 @@ public:
//return item with index which or null if not present //return item with index which or null if not present
std::shared_ptr<CIntObject> getItem(size_t which); std::shared_ptr<CIntObject> getItem(size_t which);
std::shared_ptr<CSlider> getSlider();
//return currently active items //return currently active items
const std::list<std::shared_ptr<CIntObject>> & getItems(); const std::list<std::shared_ptr<CIntObject>> & getItems();

View File

@ -101,6 +101,9 @@ std::shared_ptr<RadialMenuItem> RadialMenu::findNearestItem(const Point & cursor
} }
} }
if (bestDistance > 2 * requiredDistanceFromCenter)
return nullptr;
assert(bestItem); assert(bestItem);
return bestItem; return bestItem;
} }

View File

@ -20,6 +20,7 @@
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../PlayerLocalState.h" #include "../PlayerLocalState.h"
#include "../eventsSDL/InputHandler.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h" #include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h" #include "../gui/WindowHandler.h"
@ -51,6 +52,7 @@
#include "../../lib/IGameSettings.h" #include "../../lib/IGameSettings.h"
#include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/CSpellHandler.h"
#include "../../lib/GameConstants.h" #include "../../lib/GameConstants.h"
#include "../../lib/gameState/UpgradeInfo.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/campaign/CampaignState.h" #include "../../lib/campaign/CampaignState.h"
#include "../../lib/entities/building/CBuilding.h" #include "../../lib/entities/building/CBuilding.h"
@ -175,7 +177,7 @@ void CBuildingRect::show(Canvas & to)
{ {
uint32_t stageDelay = BUILDING_APPEAR_TIMEPOINT; uint32_t stageDelay = BUILDING_APPEAR_TIMEPOINT;
bool showTextOverlay = GH.isKeyboardAltDown(); bool showTextOverlay = GH.isKeyboardAltDown() || GH.input().getNumTouchFingers() == 2;
if(stateTimeCounter < BUILDING_APPEAR_TIMEPOINT) if(stateTimeCounter < BUILDING_APPEAR_TIMEPOINT)
{ {
@ -337,17 +339,92 @@ CHeroGSlot::CHeroGSlot(int x, int y, int updown, const CGHeroInstance * h, HeroS
CHeroGSlot::~CHeroGSlot() = default; CHeroGSlot::~CHeroGSlot() = default;
auto CHeroGSlot::getUpgradableSlots(const CArmedInstance *obj)
{
struct result { bool isCreatureUpgradePossible; bool canAffordAny; bool canAffordAll; TResources totalCosts; std::vector<std::pair<SlotID, UpgradeInfo>> upgradeInfos; };
auto slots = std::map<SlotID, const CStackInstance*>(obj->Slots().begin(), obj->Slots().end());
std::vector<std::pair<SlotID, UpgradeInfo>> upgradeInfos;
for(auto & slot : slots)
{
auto upgradeInfo = std::make_pair(slot.first, UpgradeInfo(slot.second->getCreatureID()));
LOCPLINT->cb->fillUpgradeInfo(slot.second->armyObj, slot.first, upgradeInfo.second);
bool canUpgrade = obj->tempOwner == LOCPLINT->playerID && upgradeInfo.second.canUpgrade();
if(canUpgrade)
upgradeInfos.push_back(upgradeInfo);
}
std::sort(upgradeInfos.begin(), upgradeInfos.end(), [&](const std::pair<SlotID, UpgradeInfo> & lhs, const std::pair<SlotID, UpgradeInfo> & rhs) {
return lhs.second.oldID.toCreature()->getLevel() > rhs.second.oldID.toCreature()->getLevel();
});
bool creaturesToUpgrade = static_cast<bool>(upgradeInfos.size());
TResources costs = TResources();
std::vector<SlotID> slotInfosToDelete;
for(auto & upgradeInfo : upgradeInfos)
{
TResources upgradeCosts = upgradeInfo.second.getUpgradeCosts() * slots[upgradeInfo.first]->getCount();
if(LOCPLINT->cb->getResourceAmount().canAfford(costs + upgradeCosts))
costs += upgradeCosts;
else
slotInfosToDelete.push_back(upgradeInfo.first);
}
upgradeInfos.erase(std::remove_if(upgradeInfos.begin(), upgradeInfos.end(), [&slotInfosToDelete](const auto& item) {
return std::count(slotInfosToDelete.begin(), slotInfosToDelete.end(), item.first);
}), upgradeInfos.end());
return result { creaturesToUpgrade, static_cast<bool>(upgradeInfos.size()), !slotInfosToDelete.size(), costs, upgradeInfos };
}
void CHeroGSlot::gesture(bool on, const Point & initialPosition, const Point & finalPosition) void CHeroGSlot::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
{ {
if(!on) if(!on)
return; return;
if(!hero) const CArmedInstance *obj = hero;
if(upg == 0 && !obj)
obj = owner->town->getUpperArmy();
if(!obj)
return; return;
auto upgradableSlots = getUpgradableSlots(obj);
auto upgradeAll = [upgradableSlots, obj](){
if(!upgradableSlots.canAffordAny)
{
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.townWindow.upgradeAll.notUpgradable"));
return;
}
std::vector<std::shared_ptr<CComponent>> resComps;
for(TResources::nziterator i(upgradableSlots.totalCosts); i.valid(); i++)
resComps.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i->resType, i->resVal));
resComps.back()->newLine = true;
for(auto & upgradeInfo : upgradableSlots.upgradeInfos)
resComps.push_back(std::make_shared<CComponent>(ComponentType::CREATURE, upgradeInfo.second.getUpgrade(), obj->Slots().at(upgradeInfo.first)->count));
std::string textID = upgradableSlots.canAffordAll ? "core.genrltxt.207" : "vcmi.townWindow.upgradeAll.notAllUpgradable";
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate(textID), [upgradableSlots, obj](){
for(auto & upgradeInfo : upgradableSlots.upgradeInfos)
LOCPLINT->cb->upgradeCreature(obj, upgradeInfo.first, upgradeInfo.second.getUpgrade());
}, nullptr, resComps);
};
if (!settings["input"]["radialWheelGarrisonSwipe"].Bool()) if (!settings["input"]["radialWheelGarrisonSwipe"].Bool())
return; return;
if(!hero)
{
if(upgradableSlots.isCreatureUpgradePossible)
{
std::vector<RadialMenuConfig> menuElements = {
{ RadialMenuConfig::ITEM_WW, true, "upgradeCreatures", "vcmi.radialWheel.upgradeCreatures", [upgradeAll](){ upgradeAll(); } },
};
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements);
}
return;
}
std::shared_ptr<CHeroGSlot> other = upg ? owner->garrisonedHero : owner->visitingHero; std::shared_ptr<CHeroGSlot> other = upg ? owner->garrisonedHero : owner->visitingHero;
bool twoHeroes = hero && other->hero; bool twoHeroes = hero && other->hero;
@ -360,14 +437,15 @@ void CHeroGSlot::gesture(bool on, const Point & initialPosition, const Point & f
{ RadialMenuConfig::ITEM_NE, twoHeroes, "stackSplitDialog", "vcmi.radialWheel.heroSwapArmy", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).swapArmy();} }, { RadialMenuConfig::ITEM_NE, twoHeroes, "stackSplitDialog", "vcmi.radialWheel.heroSwapArmy", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).swapArmy();} },
{ RadialMenuConfig::ITEM_EE, twoHeroes, "tradeHeroes", "vcmi.radialWheel.heroExchange", [heroId, heroOtherId](){LOCPLINT->showHeroExchange(heroId, heroOtherId);} }, { RadialMenuConfig::ITEM_EE, twoHeroes, "tradeHeroes", "vcmi.radialWheel.heroExchange", [heroId, heroOtherId](){LOCPLINT->showHeroExchange(heroId, heroOtherId);} },
{ RadialMenuConfig::ITEM_SW, twoHeroes, "moveArtifacts", "vcmi.radialWheel.heroGetArtifacts", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).moveArtifacts(false, true, true);} }, { RadialMenuConfig::ITEM_SW, twoHeroes, "moveArtifacts", "vcmi.radialWheel.heroGetArtifacts", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).moveArtifacts(false, true, true);} },
{ RadialMenuConfig::ITEM_SE, twoHeroes, "swapArtifacts", "vcmi.radialWheel.heroSwapArtifacts", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).swapArtifacts(true, true);} }, { RadialMenuConfig::ITEM_SE, twoHeroes, "swapArtifacts", "vcmi.radialWheel.heroSwapArtifacts", [heroId, heroOtherId](){CExchangeController(heroId, heroOtherId).swapArtifacts(true, true);} }
{ RadialMenuConfig::ITEM_WW, true, "dismissHero", "vcmi.radialWheel.heroDismiss", [this]()
{
CFunctionList<void()> ony = [=](){ };
ony += [=](){ LOCPLINT->cb->dismissHero(hero); };
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], ony, nullptr);
} },
}; };
RadialMenuConfig upgradeSlot = { RadialMenuConfig::ITEM_WW, true, "upgradeCreatures", "vcmi.radialWheel.upgradeCreatures", [upgradeAll](){ upgradeAll(); } };
RadialMenuConfig dismissSlot = { RadialMenuConfig::ITEM_WW, true, "dismissHero", "vcmi.radialWheel.heroDismiss", [this](){ LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], [=](){ LOCPLINT->cb->dismissHero(hero); }, nullptr); } };
if(upgradableSlots.isCreatureUpgradePossible)
menuElements.push_back(upgradeSlot);
else
menuElements.push_back(dismissSlot);
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements); GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements);
} }
@ -692,7 +770,7 @@ void CCastleBuildings::show(Canvas & to)
{ {
CIntObject::show(to); CIntObject::show(to);
bool showTextOverlay = GH.isKeyboardAltDown(); bool showTextOverlay = GH.isKeyboardAltDown() || GH.input().getNumTouchFingers() == 2;
if(showTextOverlay) if(showTextOverlay)
drawOverlays(to, buildings); drawOverlays(to, buildings);
} }

View File

@ -103,6 +103,8 @@ class CHeroGSlot : public CIntObject
const CGHeroInstance * hero; const CGHeroInstance * hero;
int upg; //0 - up garrison, 1 - down garrison int upg; //0 - up garrison, 1 - down garrison
auto getUpgradableSlots(const CArmedInstance *obj);
public: public:
CHeroGSlot(int x, int y, int updown, const CGHeroInstance *h, HeroSlots * Owner); CHeroGSlot(int x, int y, int updown, const CGHeroInstance *h, HeroSlots * Owner);
~CHeroGSlot(); ~CHeroGSlot();

View File

@ -238,7 +238,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
if (spellBonuses->empty()) if (spellBonuses->empty())
throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey()); throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey());
int duration = spellBonuses->front()->duration; int duration = spellBonuses->front()->turnsRemain;
boost::replace_first(spellText, "%d", std::to_string(duration)); boost::replace_first(spellText, "%d", std::to_string(duration));
spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed)); spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
@ -671,7 +671,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
expArea->text = parent->generateStackExpDescription(); expArea->text = parent->generateStackExpDescription();
} }
expLabel = std::make_shared<CLabel>( expLabel = std::make_shared<CLabel>(
pos.x + 21, pos.y + 52, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, pos.x + 21, pos.y + 55, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
TextOperations::formatMetric(stack->experience, 6)); TextOperations::formatMetric(stack->experience, 6));
} }
@ -776,9 +776,13 @@ CStackWindow::CStackWindow(const CStackInstance * stack, std::function<void()> d
info->creature = stack->getCreature(); info->creature = stack->getCreature();
info->creatureCount = stack->count; info->creatureCount = stack->count;
info->upgradeInfo = std::make_optional(UnitView::StackUpgradeInfo(upgradeInfo)); if(upgradeInfo.canUpgrade())
{
info->upgradeInfo = std::make_optional(UnitView::StackUpgradeInfo(upgradeInfo));
info->upgradeInfo->callback = callback;
}
info->dismissInfo = std::make_optional(UnitView::StackDismissInfo()); info->dismissInfo = std::make_optional(UnitView::StackDismissInfo());
info->upgradeInfo->callback = callback;
info->dismissInfo->callback = dismiss; info->dismissInfo->callback = dismiss;
info->owner = dynamic_cast<const CGHeroInstance *> (stack->armyObj); info->owner = dynamic_cast<const CGHeroInstance *> (stack->armyObj);
init(); init();

Some files were not shown because too many files have changed in this diff Show More