mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-15 01:24:45 +02:00
@ -167,14 +167,12 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
|||||||
|
|
||||||
result = evaluator.selectStackAction(stack);
|
result = evaluator.selectStackAction(stack);
|
||||||
|
|
||||||
if(autobattlePreferences.enableSpellsUsage && !skipCastUntilNextBattle && evaluator.canCastSpell())
|
if(autobattlePreferences.enableSpellsUsage && evaluator.canCastSpell())
|
||||||
{
|
{
|
||||||
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
auto spelCasted = evaluator.attemptCastingSpell(stack);
|
||||||
|
|
||||||
if(spelCasted)
|
if(spelCasted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
skipCastUntilNextBattle = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
|
||||||
@ -256,8 +254,6 @@ void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1
|
|||||||
{
|
{
|
||||||
LOG_TRACE(logAi);
|
LOG_TRACE(logAi);
|
||||||
side = Side;
|
side = Side;
|
||||||
|
|
||||||
skipCastUntilNextBattle = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleAI::print(const std::string &text) const
|
void CBattleAI::print(const std::string &text) const
|
||||||
|
@ -62,7 +62,6 @@ class CBattleAI : public CBattleGameInterface
|
|||||||
bool wasWaitingForRealize;
|
bool wasWaitingForRealize;
|
||||||
bool wasUnlockingGs;
|
bool wasUnlockingGs;
|
||||||
int movesSkippedByDefense;
|
int movesSkippedByDefense;
|
||||||
bool skipCastUntilNextBattle;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CBattleAI();
|
CBattleAI();
|
||||||
|
@ -119,6 +119,14 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BattleEvaluator::hasWorkingTowers() const
|
||||||
|
{
|
||||||
|
bool keepIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
|
||||||
|
bool upperIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
|
||||||
|
bool bottomIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
|
||||||
|
return keepIntact || upperIntact || bottomIntact;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
||||||
{
|
{
|
||||||
//TODO: faerie dragon type spell should be selected by server
|
//TODO: faerie dragon type spell should be selected by server
|
||||||
@ -161,6 +169,14 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
|
|
||||||
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
||||||
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||||
|
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||||
|
{
|
||||||
|
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||||
|
});
|
||||||
|
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||||
|
&& !stack->canShoot()
|
||||||
|
&& hasWorkingTowers()
|
||||||
|
&& !enemyMellee.empty();
|
||||||
|
|
||||||
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
||||||
{
|
{
|
||||||
@ -174,7 +190,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
|
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb, siegeDefense);
|
||||||
auto & bestAttack = evaluationResult.bestAttack;
|
auto & bestAttack = evaluationResult.bestAttack;
|
||||||
|
|
||||||
cachedAttack.ap = bestAttack;
|
cachedAttack.ap = bestAttack;
|
||||||
@ -227,15 +243,10 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
|||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit * u) -> bool
|
bool isTargetOutsideFort = !hb->battleIsInsideWalls(bestAttack.from);
|
||||||
{
|
|
||||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
|
||||||
});
|
|
||||||
|
|
||||||
bool isTargetOutsideFort = bestAttack.dest.getY() < GameConstants::BFIELD_WIDTH - 4;
|
|
||||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||||
&& !bestAttack.attack.shooting
|
&& !bestAttack.attack.shooting
|
||||||
&& hb->battleGetFortifications().hasMoat
|
&& hasWorkingTowers()
|
||||||
&& !enemyMellee.empty()
|
&& !enemyMellee.empty()
|
||||||
&& isTargetOutsideFort;
|
&& isTargetOutsideFort;
|
||||||
|
|
||||||
@ -349,6 +360,22 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
|||||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||||
|
|
||||||
|
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||||
|
{
|
||||||
|
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||||
|
});
|
||||||
|
|
||||||
|
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||||
|
&& hasWorkingTowers()
|
||||||
|
&& !enemyMellee.empty();
|
||||||
|
|
||||||
|
if (siegeDefense)
|
||||||
|
{
|
||||||
|
vstd::erase_if(avHexes, [&](const BattleHex& hex) {
|
||||||
|
return !cb->getBattle(battleID)->battleIsInsideWalls(hex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
||||||
{
|
{
|
||||||
return BattleAction::makeDefend(stack);
|
return BattleAction::makeDefend(stack);
|
||||||
|
@ -53,6 +53,7 @@ public:
|
|||||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||||
|
bool hasWorkingTowers() const;
|
||||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||||
void print(const std::string & text) const;
|
void print(const std::string & text) const;
|
||||||
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "BattleExchangeVariant.h"
|
#include "BattleExchangeVariant.h"
|
||||||
|
#include "BattleEvaluator.h"
|
||||||
#include "../../lib/CStack.h"
|
#include "../../lib/CStack.h"
|
||||||
|
|
||||||
AttackerValue::AttackerValue()
|
AttackerValue::AttackerValue()
|
||||||
@ -213,7 +214,8 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|||||||
const battle::Unit * activeStack,
|
const battle::Unit * activeStack,
|
||||||
PotentialTargets & targets,
|
PotentialTargets & targets,
|
||||||
DamageCache & damageCache,
|
DamageCache & damageCache,
|
||||||
std::shared_ptr<HypotheticBattle> hb)
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
|
bool siegeDefense)
|
||||||
{
|
{
|
||||||
EvaluationResult result(targets.bestAction());
|
EvaluationResult result(targets.bestAction());
|
||||||
|
|
||||||
@ -231,6 +233,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|||||||
|
|
||||||
for(auto & ap : targets.possibleAttacks)
|
for(auto & ap : targets.possibleAttacks)
|
||||||
{
|
{
|
||||||
|
if (siegeDefense && !hb->battleIsInsideWalls(ap.from))
|
||||||
|
continue;
|
||||||
|
|
||||||
float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
||||||
|
|
||||||
if(score > result.score)
|
if(score > result.score)
|
||||||
@ -263,6 +268,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|||||||
|
|
||||||
for(auto & ap : targets.possibleAttacks)
|
for(auto & ap : targets.possibleAttacks)
|
||||||
{
|
{
|
||||||
|
if (siegeDefense && !hb->battleIsInsideWalls(ap.from))
|
||||||
|
continue;
|
||||||
|
|
||||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||||
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
||||||
|
|
||||||
@ -350,11 +358,32 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|||||||
if(distance <= speed)
|
if(distance <= speed)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
float penaltyMultiplier = 1.0f; // Default multiplier, no penalty
|
||||||
|
float closestAllyDistance = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
for (const battle::Unit* ally : hb->battleAliveUnits()) {
|
||||||
|
if (ally == activeStack)
|
||||||
|
continue;
|
||||||
|
if (ally->unitSide() != activeStack->unitSide())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float allyDistance = dists.distToNearestNeighbour(ally, enemy);
|
||||||
|
if (allyDistance < closestAllyDistance)
|
||||||
|
{
|
||||||
|
closestAllyDistance = allyDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an ally is closer to the enemy, compute the penaltyMultiplier
|
||||||
|
if (closestAllyDistance < distance) {
|
||||||
|
penaltyMultiplier = closestAllyDistance / distance; // Ratio of distances
|
||||||
|
}
|
||||||
|
|
||||||
auto turnsToRich = (distance - 1) / speed + 1;
|
auto turnsToRich = (distance - 1) / speed + 1;
|
||||||
auto hexes = enemy->getSurroundingHexes();
|
auto hexes = enemy->getSurroundingHexes();
|
||||||
auto enemySpeed = enemy->getMovementRange();
|
auto enemySpeed = enemy->getMovementRange();
|
||||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
|
||||||
|
|
||||||
for(auto & hex : hexes)
|
for(auto & hex : hexes)
|
||||||
{
|
{
|
||||||
|
@ -159,7 +159,8 @@ public:
|
|||||||
const battle::Unit * activeStack,
|
const battle::Unit * activeStack,
|
||||||
PotentialTargets & targets,
|
PotentialTargets & targets,
|
||||||
DamageCache & damageCache,
|
DamageCache & damageCache,
|
||||||
std::shared_ptr<HypotheticBattle> hb);
|
std::shared_ptr<HypotheticBattle> hb,
|
||||||
|
bool siegeDefense = false);
|
||||||
|
|
||||||
float evaluateExchange(
|
float evaluateExchange(
|
||||||
const AttackPossibility & ap,
|
const AttackPossibility & ap,
|
||||||
|
@ -309,6 +309,8 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
|||||||
? dynamic_cast<const CGTownInstance *>(dwelling)
|
? dynamic_cast<const CGTownInstance *>(dwelling)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
|
std::set<SlotID> alreadyDisbanded;
|
||||||
|
|
||||||
for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
|
for(int i = dwelling->creatures.size() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
auto ci = infoFromDC(dwelling->creatures[i]);
|
auto ci = infoFromDC(dwelling->creatures[i]);
|
||||||
@ -322,18 +324,71 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
|||||||
|
|
||||||
if(!ci.count) continue;
|
if(!ci.count) continue;
|
||||||
|
|
||||||
|
// Calculate the market value of the new stack
|
||||||
|
TResources newStackValue = ci.creID.toCreature()->getFullRecruitCost() * ci.count;
|
||||||
|
|
||||||
SlotID dst = hero->getSlotFor(ci.creID);
|
SlotID dst = hero->getSlotFor(ci.creID);
|
||||||
|
|
||||||
|
// Keep track of the least valuable slot in the hero's army
|
||||||
|
SlotID leastValuableSlot;
|
||||||
|
TResources leastValuableStackValue;
|
||||||
|
leastValuableStackValue[6] = std::numeric_limits<int>::max();
|
||||||
|
bool shouldDisband = false;
|
||||||
if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
|
if(!hero->hasStackAtSlot(dst)) //need another new slot for this stack
|
||||||
{
|
{
|
||||||
if(!freeHeroSlots) //no more place for stacks
|
if(!freeHeroSlots) // No free slots; consider replacing
|
||||||
|
{
|
||||||
|
// Check for the least valuable existing stack
|
||||||
|
for (auto& slot : hero->Slots())
|
||||||
|
{
|
||||||
|
if (alreadyDisbanded.find(slot.first) != alreadyDisbanded.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if(slot.second->getCreatureID() != CreatureID::NONE)
|
||||||
|
{
|
||||||
|
TResources currentStackValue = slot.second->getCreatureID().toCreature()->getFullRecruitCost() * slot.second->getCount();
|
||||||
|
|
||||||
|
if (town && slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(currentStackValue.marketValue() < leastValuableStackValue.marketValue())
|
||||||
|
{
|
||||||
|
leastValuableStackValue = currentStackValue;
|
||||||
|
leastValuableSlot = slot.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide whether to replace the least valuable stack
|
||||||
|
if(newStackValue.marketValue() <= leastValuableStackValue.marketValue())
|
||||||
|
{
|
||||||
|
continue; // Skip if the new stack isn't worth replacing
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
shouldDisband = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
freeHeroSlots--; //new slot will be occupied
|
freeHeroSlots--; //new slot will be occupied
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
vstd::amin(ci.count, availableRes / ci.creID.toCreature()->getFullRecruitCost()); //max count we can afford
|
||||||
|
|
||||||
if(!ci.count) continue;
|
int disbandMalus = 0;
|
||||||
|
|
||||||
|
if (shouldDisband)
|
||||||
|
{
|
||||||
|
disbandMalus = leastValuableStackValue / ci.creID.toCreature()->getFullRecruitCost();
|
||||||
|
alreadyDisbanded.insert(leastValuableSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.count -= disbandMalus;
|
||||||
|
|
||||||
|
if(ci.count <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
ci.level = i; //this is important for Dungeon Summoning Portal
|
ci.level = i; //this is important for Dungeon Summoning Portal
|
||||||
creaturesInDwellings.push_back(ci);
|
creaturesInDwellings.push_back(ci);
|
||||||
|
@ -505,7 +505,7 @@ void ObjectClusterizer::clusterizeObject(
|
|||||||
else if (priority <= 0)
|
else if (priority <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
bool interestingObject = path.turn() <= 2 || priority > (ai->settings->isUseFuzzy() ? 0.5f : 0);
|
||||||
|
|
||||||
if(interestingObject)
|
if(interestingObject)
|
||||||
{
|
{
|
||||||
|
@ -64,7 +64,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
|||||||
|
|
||||||
if(reinforcement)
|
if(reinforcement)
|
||||||
{
|
{
|
||||||
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(5)));
|
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(reinforcement)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,6 @@ Goals::TGoalVec DefenceBehavior::decompose(const Nullkiller * ai) const
|
|||||||
for(auto town : ai->cb->getTownsInfo())
|
for(auto town : ai->cb->getTownsInfo())
|
||||||
{
|
{
|
||||||
evaluateDefence(tasks, town, ai);
|
evaluateDefence(tasks, town, ai);
|
||||||
//Let's do only one defence-task per pass since otherwise it can try to hire the same hero twice
|
|
||||||
if (!tasks.empty())
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
@ -422,6 +419,21 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
|||||||
if(hero->getTotalStrength() < threat.danger)
|
if(hero->getTotalStrength() < threat.danger)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
bool heroAlreadyHiredInOtherTown = false;
|
||||||
|
for (const auto& task : tasks)
|
||||||
|
{
|
||||||
|
if (auto recruitGoal = dynamic_cast<Goals::RecruitHero*>(task.get()))
|
||||||
|
{
|
||||||
|
if (recruitGoal->getHero() == hero)
|
||||||
|
{
|
||||||
|
heroAlreadyHiredInOtherTown = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (heroAlreadyHiredInOtherTown)
|
||||||
|
continue;
|
||||||
|
|
||||||
auto myHeroes = ai->cb->getHeroesInfo();
|
auto myHeroes = ai->cb->getHeroesInfo();
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
|
@ -124,6 +124,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
|
||||||
|| (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()))
|
||||||
{
|
{
|
||||||
|
@ -397,7 +397,12 @@ void Nullkiller::makeTurn()
|
|||||||
if(!executeTask(bestTask))
|
if(!executeTask(bestTask))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateAiState(i, true);
|
bool fastUpdate = true;
|
||||||
|
|
||||||
|
if (bestTask->getHero() != nullptr)
|
||||||
|
fastUpdate = false;
|
||||||
|
|
||||||
|
updateAiState(i, fastUpdate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1006,6 +1006,9 @@ public:
|
|||||||
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
|
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
|
||||||
const AIPath & path = chain.getPath();
|
const AIPath & path = chain.getPath();
|
||||||
|
|
||||||
|
if (vstd::isAlmostZero(path.movementCost()))
|
||||||
|
return;
|
||||||
|
|
||||||
vstd::amax(evaluationContext.danger, path.getTotalDanger());
|
vstd::amax(evaluationContext.danger, path.getTotalDanger());
|
||||||
evaluationContext.movementCost += path.movementCost();
|
evaluationContext.movementCost += path.movementCost();
|
||||||
evaluationContext.closestWayRatio = chain.closestWayRatio;
|
evaluationContext.closestWayRatio = chain.closestWayRatio;
|
||||||
@ -1019,12 +1022,20 @@ public:
|
|||||||
evaluationContext.involvesSailing = true;
|
evaluationContext.involvesSailing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float highestCostForSingleHero = 0;
|
||||||
for(auto pair : costsPerHero)
|
for(auto pair : costsPerHero)
|
||||||
{
|
{
|
||||||
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
|
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
|
||||||
|
|
||||||
evaluationContext.movementCostByRole[role] += pair.second;
|
evaluationContext.movementCostByRole[role] += pair.second;
|
||||||
|
if (pair.second > highestCostForSingleHero)
|
||||||
|
highestCostForSingleHero = pair.second;
|
||||||
}
|
}
|
||||||
|
if (highestCostForSingleHero > 1 && costsPerHero.size() > 1)
|
||||||
|
{
|
||||||
|
//Chains that involve more than 1 hero doing something for more than a turn are too expensive in my book. They often involved heroes doing nothing just standing there waiting to fulfill their part of the chain.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
evaluationContext.movementCost *= costsPerHero.size(); //further deincentivise chaining as it often involves bringing back the army afterwards
|
||||||
|
|
||||||
auto hero = task->hero;
|
auto hero = task->hero;
|
||||||
bool checkGold = evaluationContext.danger == 0;
|
bool checkGold = evaluationContext.danger == 0;
|
||||||
@ -1046,13 +1057,13 @@ public:
|
|||||||
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
|
||||||
if (target->ID == Obj::HERO)
|
if (target->ID == Obj::HERO)
|
||||||
evaluationContext.isHero = true;
|
evaluationContext.isHero = true;
|
||||||
if (target->getOwner() != PlayerColor::NEUTRAL && 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;
|
||||||
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
|
||||||
evaluationContext.armyInvolvement += army->getArmyCost();
|
|
||||||
if(evaluationContext.danger > 0)
|
if(evaluationContext.danger > 0)
|
||||||
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
|
||||||
}
|
}
|
||||||
|
evaluationContext.armyInvolvement += army->getArmyCost();
|
||||||
|
|
||||||
vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
|
vstd::amax(evaluationContext.armyLossPersentage, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
|
||||||
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
|
||||||
@ -1353,17 +1364,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget();
|
||||||
|
|
||||||
bool arriveNextWeek = false;
|
bool arriveNextWeek = false;
|
||||||
if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7)
|
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, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d",
|
logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, 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",
|
||||||
priorityTier,
|
priorityTier,
|
||||||
task->toString(),
|
task->toString(),
|
||||||
evaluationContext.armyLossPersentage,
|
evaluationContext.armyLossPersentage,
|
||||||
(int)evaluationContext.turn,
|
(int)evaluationContext.turn,
|
||||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||||
|
evaluationContext.armyInvolvement,
|
||||||
goldRewardPerTurn,
|
goldRewardPerTurn,
|
||||||
evaluationContext.goldCost,
|
evaluationContext.goldCost,
|
||||||
evaluationContext.armyReward,
|
evaluationContext.armyReward,
|
||||||
@ -1378,7 +1390,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
evaluationContext.closestWayRatio,
|
evaluationContext.closestWayRatio,
|
||||||
evaluationContext.enemyHeroDangerRatio,
|
evaluationContext.enemyHeroDangerRatio,
|
||||||
evaluationContext.explorePriority,
|
evaluationContext.explorePriority,
|
||||||
evaluationContext.isDefend);
|
evaluationContext.isDefend,
|
||||||
|
evaluationContext.isEnemy,
|
||||||
|
arriveNextWeek);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (priorityTier)
|
switch (priorityTier)
|
||||||
@ -1387,13 +1401,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
{
|
{
|
||||||
if (evaluationContext.turn > 0)
|
if (evaluationContext.turn > 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (evaluationContext.movementCost >= 1)
|
||||||
|
return 0;
|
||||||
if(evaluationContext.conquestValue > 0)
|
if(evaluationContext.conquestValue > 0)
|
||||||
score = 1000;
|
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 > 1 && (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;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
@ -1404,17 +1419,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
score = evaluationContext.armyInvolvement;
|
score = evaluationContext.armyInvolvement;
|
||||||
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
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
|
||||||
|
case PriorityTier::FAR_KILL:
|
||||||
{
|
{
|
||||||
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 = 1000;
|
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 > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
|
||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
@ -1432,8 +1448,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
@ -1446,13 +1463,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
|
case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
|
||||||
|
//FALL_THROUGH
|
||||||
|
case PriorityTier::FAR_HUNTER_GATHER:
|
||||||
{
|
{
|
||||||
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
|
||||||
return 0;
|
return 0;
|
||||||
@ -1468,6 +1488,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (vstd::isAlmostZero(evaluationContext.armyLossPersentage) && evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score += evaluationContext.strategicalValue * 1000;
|
score += evaluationContext.strategicalValue * 1000;
|
||||||
score += evaluationContext.goldReward;
|
score += evaluationContext.goldReward;
|
||||||
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
|
||||||
@ -1478,7 +1500,6 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
if (score > 0)
|
if (score > 0)
|
||||||
{
|
{
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
}
|
}
|
||||||
@ -1492,8 +1513,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
return 0;
|
return 0;
|
||||||
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
if (evaluationContext.closestWayRatio < 1.0)
|
||||||
|
return 0;
|
||||||
score = 1000;
|
score = 1000;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
if (evaluationContext.movementCost > 0)
|
if (evaluationContext.movementCost > 0)
|
||||||
score /= evaluationContext.movementCost;
|
score /= evaluationContext.movementCost;
|
||||||
break;
|
break;
|
||||||
@ -1503,8 +1525,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
|
||||||
return 0;
|
return 0;
|
||||||
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
|
||||||
score = 1000;
|
score = evaluationContext.armyInvolvement;
|
||||||
score *= evaluationContext.closestWayRatio;
|
|
||||||
score /= (evaluationContext.turn + 1);
|
score /= (evaluationContext.turn + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1563,13 +1584,14 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, result %f",
|
logAi->trace("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, result %f",
|
||||||
priorityTier,
|
priorityTier,
|
||||||
task->toString(),
|
task->toString(),
|
||||||
evaluationContext.armyLossPersentage,
|
evaluationContext.armyLossPersentage,
|
||||||
(int)evaluationContext.turn,
|
(int)evaluationContext.turn,
|
||||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||||
|
evaluationContext.armyInvolvement,
|
||||||
goldRewardPerTurn,
|
goldRewardPerTurn,
|
||||||
evaluationContext.goldCost,
|
evaluationContext.goldCost,
|
||||||
evaluationContext.armyReward,
|
evaluationContext.armyReward,
|
||||||
|
@ -118,6 +118,8 @@ public:
|
|||||||
HIGH_PRIO_EXPLORE,
|
HIGH_PRIO_EXPLORE,
|
||||||
HUNTER_GATHER,
|
HUNTER_GATHER,
|
||||||
LOW_PRIO_EXPLORE,
|
LOW_PRIO_EXPLORE,
|
||||||
|
FAR_KILL,
|
||||||
|
FAR_HUNTER_GATHER,
|
||||||
DEFEND
|
DEFEND
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,8 +57,37 @@ void BuyArmy::accept(AIGateway * ai)
|
|||||||
vstd::amin(ci.count, res / ci.creID.toCreature()->getFullRecruitCost());
|
vstd::amin(ci.count, res / ci.creID.toCreature()->getFullRecruitCost());
|
||||||
|
|
||||||
if(ci.count)
|
if(ci.count)
|
||||||
|
{
|
||||||
|
if (town->getUpperArmy()->stacksCount() == GameConstants::ARMY_SIZE)
|
||||||
|
{
|
||||||
|
SlotID lowestValueSlot;
|
||||||
|
int lowestValue = std::numeric_limits<int>::max();
|
||||||
|
for (auto slot : town->getUpperArmy()->Slots())
|
||||||
|
{
|
||||||
|
if (slot.second->getCreatureID() != CreatureID::NONE)
|
||||||
|
{
|
||||||
|
int currentStackMarketValue =
|
||||||
|
slot.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * slot.second->getCount();
|
||||||
|
|
||||||
|
if (slot.second->getCreatureID().toCreature()->getFactionID() == town->getFactionID())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (currentStackMarketValue < lowestValue)
|
||||||
|
{
|
||||||
|
lowestValue = currentStackMarketValue;
|
||||||
|
lowestValueSlot = slot.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowestValueSlot.validSlot())
|
||||||
|
{
|
||||||
|
cb->dismissCreature(town->getUpperArmy(), lowestValueSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (town->getUpperArmy()->stacksCount() < GameConstants::ARMY_SIZE || town->getUpperArmy()->getSlotFor(ci.creID).validSlot()) //It is possible we don't scrap despite we wanted to due to not scrapping stacks that fit our faction
|
||||||
{
|
{
|
||||||
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
cb->recruitCreatures(town, town->getUpperArmy(), ci.creID, ci.count, ci.level);
|
||||||
|
}
|
||||||
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
|
valueBought += ci.count * ci.creID.toCreature()->getAIValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,12 +374,14 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
|||||||
for(auto & creatureToBuy : buyArmy)
|
for(auto & creatureToBuy : buyArmy)
|
||||||
{
|
{
|
||||||
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
auto targetSlot = target->getSlotFor(creatureToBuy.creID.toCreature());
|
||||||
|
if (targetSlot.validSlot())
|
||||||
|
{
|
||||||
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
target->addToSlot(targetSlot, creatureToBuy.creID, creatureToBuy.count);
|
||||||
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
target->armyCost += creatureToBuy.creID.toCreature()->getFullRecruitCost() * creatureToBuy.count;
|
||||||
target->requireBuyArmy = true;
|
target->requireBuyArmy = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(target->getArmyStrength() <= army->getArmyStrength())
|
if(target->getArmyStrength() <= army->getArmyStrength())
|
||||||
{
|
{
|
||||||
|
@ -567,56 +567,62 @@ FunctionEnd
|
|||||||
;Languages
|
;Languages
|
||||||
|
|
||||||
!insertmacro MUI_LANGUAGE "English" ;first language is the default language
|
!insertmacro MUI_LANGUAGE "English" ;first language is the default language
|
||||||
!insertmacro MUI_LANGUAGE "Albanian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Arabic"
|
|
||||||
!insertmacro MUI_LANGUAGE "Basque"
|
|
||||||
!insertmacro MUI_LANGUAGE "Belarusian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Bosnian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Breton"
|
|
||||||
!insertmacro MUI_LANGUAGE "Bulgarian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Croatian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Czech"
|
!insertmacro MUI_LANGUAGE "Czech"
|
||||||
!insertmacro MUI_LANGUAGE "Danish"
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
!insertmacro MUI_LANGUAGE "Dutch"
|
|
||||||
!insertmacro MUI_LANGUAGE "Estonian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Farsi"
|
|
||||||
!insertmacro MUI_LANGUAGE "Finnish"
|
!insertmacro MUI_LANGUAGE "Finnish"
|
||||||
!insertmacro MUI_LANGUAGE "French"
|
!insertmacro MUI_LANGUAGE "French"
|
||||||
!insertmacro MUI_LANGUAGE "German"
|
!insertmacro MUI_LANGUAGE "German"
|
||||||
!insertmacro MUI_LANGUAGE "Greek"
|
|
||||||
!insertmacro MUI_LANGUAGE "Hebrew"
|
|
||||||
!insertmacro MUI_LANGUAGE "Hungarian"
|
!insertmacro MUI_LANGUAGE "Hungarian"
|
||||||
!insertmacro MUI_LANGUAGE "Icelandic"
|
|
||||||
!insertmacro MUI_LANGUAGE "Indonesian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Irish"
|
|
||||||
!insertmacro MUI_LANGUAGE "Italian"
|
!insertmacro MUI_LANGUAGE "Italian"
|
||||||
!insertmacro MUI_LANGUAGE "Japanese"
|
|
||||||
!insertmacro MUI_LANGUAGE "Korean"
|
!insertmacro MUI_LANGUAGE "Korean"
|
||||||
!insertmacro MUI_LANGUAGE "Kurdish"
|
|
||||||
!insertmacro MUI_LANGUAGE "Latvian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Lithuanian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Luxembourgish"
|
|
||||||
!insertmacro MUI_LANGUAGE "Macedonian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Malay"
|
|
||||||
!insertmacro MUI_LANGUAGE "Mongolian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Norwegian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Polish"
|
!insertmacro MUI_LANGUAGE "Polish"
|
||||||
!insertmacro MUI_LANGUAGE "Portuguese"
|
!insertmacro MUI_LANGUAGE "Portuguese"
|
||||||
!insertmacro MUI_LANGUAGE "PortugueseBR"
|
|
||||||
!insertmacro MUI_LANGUAGE "Romanian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Russian"
|
!insertmacro MUI_LANGUAGE "Russian"
|
||||||
!insertmacro MUI_LANGUAGE "Serbian"
|
|
||||||
!insertmacro MUI_LANGUAGE "SerbianLatin"
|
|
||||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
|
||||||
!insertmacro MUI_LANGUAGE "Slovak"
|
|
||||||
!insertmacro MUI_LANGUAGE "Slovenian"
|
|
||||||
!insertmacro MUI_LANGUAGE "Spanish"
|
!insertmacro MUI_LANGUAGE "Spanish"
|
||||||
!insertmacro MUI_LANGUAGE "Swedish"
|
!insertmacro MUI_LANGUAGE "Swedish"
|
||||||
!insertmacro MUI_LANGUAGE "Thai"
|
|
||||||
!insertmacro MUI_LANGUAGE "TradChinese"
|
|
||||||
!insertmacro MUI_LANGUAGE "Turkish"
|
!insertmacro MUI_LANGUAGE "Turkish"
|
||||||
!insertmacro MUI_LANGUAGE "Ukrainian"
|
!insertmacro MUI_LANGUAGE "Ukrainian"
|
||||||
!insertmacro MUI_LANGUAGE "Welsh"
|
!insertmacro MUI_LANGUAGE "Vietnamese"
|
||||||
|
|
||||||
|
;!insertmacro MUI_LANGUAGE "Albanian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Arabic"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Basque"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Belarusian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Bosnian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Breton"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Bulgarian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Croatian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Danish"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Dutch"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Estonian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Farsi"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Greek"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Hebrew"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Icelandic"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Indonesian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Irish"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Japanese"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Kurdish"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Latvian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Lithuanian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Luxembourgish"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Macedonian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Malay"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Mongolian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Norwegian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "PortugueseBR"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Romanian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Serbian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "SerbianLatin"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Slovak"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Slovenian"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Thai"
|
||||||
|
;!insertmacro MUI_LANGUAGE "TradChinese"
|
||||||
|
;!insertmacro MUI_LANGUAGE "Welsh"
|
||||||
|
|
||||||
|
|
||||||
|
; Language Selection Dialog
|
||||||
|
!define MUI_LANGDLL_DISPLAY
|
||||||
|
|
||||||
|
|
||||||
;--------------------------------
|
;--------------------------------
|
||||||
@ -899,6 +905,9 @@ SectionEnd
|
|||||||
; "Program Files" for AllUsers, "My Documents" for JustMe...
|
; "Program Files" for AllUsers, "My Documents" for JustMe...
|
||||||
|
|
||||||
Function .onInit
|
Function .onInit
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGDLL_DISPLAY
|
||||||
|
|
||||||
StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst
|
StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst
|
||||||
|
|
||||||
ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString"
|
ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString"
|
||||||
|
13
ChangeLog.md
13
ChangeLog.md
@ -1,10 +1,11 @@
|
|||||||
# VCMI Project Changelog
|
# VCMI Project Changelog
|
||||||
|
|
||||||
## 1.5.7 -> 1.6.0 (in development)
|
## 1.5.7 -> 1.6.0
|
||||||
|
|
||||||
### Major changes
|
### Major changes
|
||||||
|
|
||||||
* Greatly improved decision-making of NullkillerAI
|
* Greatly improved decision-making of NullkillerAI
|
||||||
|
* Implemented support for multiple mod presets allowing player to quickly switch between them in Launcher
|
||||||
* Implemented handicap system, with options to reduce income and growth in addition to starting resources restriction
|
* Implemented handicap system, with options to reduce income and growth in addition to starting resources restriction
|
||||||
* Game will now show statistics after scenario completion, such as resources or army strength over time
|
* Game will now show statistics after scenario completion, such as resources or army strength over time
|
||||||
* Implemented spell quick selection panel in combat
|
* Implemented spell quick selection panel in combat
|
||||||
@ -66,6 +67,7 @@
|
|||||||
* Mutare and Mutare Drake are now Overlord and not Warlock
|
* Mutare and Mutare Drake are now Overlord and not Warlock
|
||||||
* Elixir of Life no longer affects siege machines
|
* Elixir of Life no longer affects siege machines
|
||||||
* Banned skills known by hero now have minimal chance (1) instead of 0 to appear on levelup
|
* Banned skills known by hero now have minimal chance (1) instead of 0 to appear on levelup
|
||||||
|
* The Transport Artifact victory condition fulfilled by the enemy AI will no longer trigger a victory for human players if "standard victory" is enabled on the map
|
||||||
|
|
||||||
### Video / Audio
|
### Video / Audio
|
||||||
|
|
||||||
@ -178,6 +180,12 @@
|
|||||||
|
|
||||||
### Launcher
|
### Launcher
|
||||||
|
|
||||||
|
* Implemented support for multiple mod presets allowing player to quickly switch between them
|
||||||
|
* Added new Start Game page to Launcher which is now used when starting the game
|
||||||
|
* Added option to create empty mod preset to quickly disable all mods
|
||||||
|
* Added button to update all installed mods to Start Game page
|
||||||
|
* Added diagnostics to detect common issues with Heroes III data files
|
||||||
|
* Added built-in help descriptions for functionalities such as data files import to better explain them to players
|
||||||
* It is now always possible to disable or uninstall a mod. Any mods that depend on this mod will be automatically disabled
|
* It is now always possible to disable or uninstall a mod. Any mods that depend on this mod will be automatically disabled
|
||||||
* It is now always possible to update a mod, even if there are mods that depend on this mod.
|
* It is now always possible to update a mod, even if there are mods that depend on this mod.
|
||||||
* It is now possible to enable mod that conflicts with already active mod. Conflicting mods will be automatically disabled
|
* It is now possible to enable mod that conflicts with already active mod. Conflicting mods will be automatically disabled
|
||||||
@ -188,6 +196,8 @@
|
|||||||
* Launcher will now correctly show conflicts on both sides - if mod A is marked as conflicting with B, then information on this conflict will be shown in description of both mod A and mod B (instead of only in mod B)
|
* Launcher will now correctly show conflicts on both sides - if mod A is marked as conflicting with B, then information on this conflict will be shown in description of both mod A and mod B (instead of only in mod B)
|
||||||
* Added Swedish translation
|
* Added Swedish translation
|
||||||
* Added better diagnostics for gog installer extraction errors
|
* Added better diagnostics for gog installer extraction errors
|
||||||
|
* It is no longer possible to start installation or update for a mod that is already being downloaded
|
||||||
|
* Fixed detection of existing Heroes III Complete or Shadow of Death data files during import
|
||||||
|
|
||||||
### Map Editor
|
### Map Editor
|
||||||
|
|
||||||
@ -205,6 +215,7 @@
|
|||||||
* Fixed duplicated list of spells in Mage Guild in copy-pasted towns
|
* Fixed duplicated list of spells in Mage Guild in copy-pasted towns
|
||||||
* Removed separate versioning of map editor. Map editor now has same version as VCMI
|
* Removed separate versioning of map editor. Map editor now has same version as VCMI
|
||||||
* Timed events interfaces now counts days from 1, instead of from 0
|
* Timed events interfaces now counts days from 1, instead of from 0
|
||||||
|
* Added Recent Files to File Menu and Toolbar
|
||||||
* Fixed crash on attempting to save map with random dwelling
|
* Fixed crash on attempting to save map with random dwelling
|
||||||
|
|
||||||
### Modding
|
### Modding
|
||||||
|
@ -121,6 +121,44 @@
|
|||||||
"vcmi.lobby.deleteFolder" : "你确定要删除下列文件夹?",
|
"vcmi.lobby.deleteFolder" : "你确定要删除下列文件夹?",
|
||||||
"vcmi.lobby.deleteMode" : "切换删除模式并返回",
|
"vcmi.lobby.deleteMode" : "切换删除模式并返回",
|
||||||
|
|
||||||
|
"vcmi.broadcast.failedLoadGame" : "加载游戏失败",
|
||||||
|
"vcmi.broadcast.command" : "输入'!help'来列举可用命令",
|
||||||
|
"vcmi.broadcast.simturn.end" : "同步回合已结束",
|
||||||
|
"vcmi.broadcast.simturn.endBetween" : "在玩家 %s和%s之间的同步回合已结束",
|
||||||
|
"vcmi.broadcast.serverProblem" : "服务器遇到了一个问题",
|
||||||
|
"vcmi.broadcast.gameTerminated" : "游戏已终止",
|
||||||
|
"vcmi.broadcast.gameSavedAs" : "游戏另存为",
|
||||||
|
"vcmi.broadcast.noCheater" : "没有注册作弊者!",
|
||||||
|
"vcmi.broadcast.playerCheater" : "玩家%s是作弊者!",
|
||||||
|
"vcmi.broadcast.statisticFile" : "统计文件可以在目录%s中找到",
|
||||||
|
"vcmi.broadcast.help.commands" : "主机可用命令:",
|
||||||
|
"vcmi.broadcast.help.exit" : "'!exit' - 立即结束当前游戏",
|
||||||
|
"vcmi.broadcast.help.kick" : "'!kick <玩家>' - 从游戏中踢除特定玩家",
|
||||||
|
"vcmi.broadcast.help.save" : "'!save <文件名>' - 以指定文件名保存游戏",
|
||||||
|
"vcmi.broadcast.help.statistic" : "'!statistic' - 将游戏统计信息保存为csv文件",
|
||||||
|
"vcmi.broadcast.help.commandsAll" : "所有玩家可用命令:",
|
||||||
|
"vcmi.broadcast.help.help" : "'!help' - 显示此帮助",
|
||||||
|
"vcmi.broadcast.help.cheaters" : "'!cheaters' - 列出在游戏中使用作弊命令的玩家",
|
||||||
|
"vcmi.broadcast.help.vote" : "'!vote' - 如果所有玩家投票通过,允许更改一些游戏设置",
|
||||||
|
"vcmi.broadcast.vote.allow" : "'!vote simturns allow X' - 允许进行指定天数的同步回合,发生接触解除",
|
||||||
|
"vcmi.broadcast.vote.force" : "'!vote simturns force X' - 强制进行指定天数的同步回合,阻止玩家接触",
|
||||||
|
"vcmi.broadcast.vote.abort" : "'!vote simturns abort' - 本回合结束后中止同步回合",
|
||||||
|
"vcmi.broadcast.vote.timer" : "'!vote timer prolong X' - 将所有玩家的基础计时器延长指定的秒数",
|
||||||
|
"vcmi.broadcast.vote.noActive" : "没有正在进行的投票!",
|
||||||
|
"vcmi.broadcast.vote.yes" : "是",
|
||||||
|
"vcmi.broadcast.vote.no" : "否",
|
||||||
|
"vcmi.broadcast.vote.notRecognized" : "投票命令无法识别!",
|
||||||
|
"vcmi.broadcast.vote.success.untilContacts" : "投票成功,同步回合将继续进行%s天,发生接触解除",
|
||||||
|
"vcmi.broadcast.vote.success.contactsBlocked" : "投票成功,同步回合将继续进行%s天,阻止玩家接触",
|
||||||
|
"vcmi.broadcast.vote.success.nextDay" : "投票成功,将于第二天结束同步回合",
|
||||||
|
"vcmi.broadcast.vote.success.timer" : "投票成功,所有玩家的计时器已延长 %s 秒。",
|
||||||
|
"vcmi.broadcast.vote.aborted" : "玩家投票反对更改,投票已中止。",
|
||||||
|
"vcmi.broadcast.vote.start.untilContacts" : "开始投票,允许同步回合再进行 %s 天",
|
||||||
|
"vcmi.broadcast.vote.start.contactsBlocked" : "开始投票, 允许同步回合再强制进行 %s 天",
|
||||||
|
"vcmi.broadcast.vote.start.nextDay" : "开始投票,从第二天起结束同布回合",
|
||||||
|
"vcmi.broadcast.vote.start.timer" : "开始投票,将所有玩家的计时器延长 %s 秒。",
|
||||||
|
"vcmi.broadcast.vote.hint" : "输入'!vote yes'来同意这项改动或输入'!vote no'来投票反对它。",
|
||||||
|
|
||||||
"vcmi.lobby.login.title" : "VCMI大厅",
|
"vcmi.lobby.login.title" : "VCMI大厅",
|
||||||
"vcmi.lobby.login.username" : "用户名:",
|
"vcmi.lobby.login.username" : "用户名:",
|
||||||
"vcmi.lobby.login.connecting" : "连接中...",
|
"vcmi.lobby.login.connecting" : "连接中...",
|
||||||
@ -128,6 +166,7 @@
|
|||||||
"vcmi.lobby.login.create" : "新账号",
|
"vcmi.lobby.login.create" : "新账号",
|
||||||
"vcmi.lobby.login.login" : "登录",
|
"vcmi.lobby.login.login" : "登录",
|
||||||
"vcmi.lobby.login.as" : "以 %s 身份登录",
|
"vcmi.lobby.login.as" : "以 %s 身份登录",
|
||||||
|
"vcmi.lobby.login.spectator" : "旁观者",
|
||||||
"vcmi.lobby.header.rooms" : "游戏房间 - %d",
|
"vcmi.lobby.header.rooms" : "游戏房间 - %d",
|
||||||
"vcmi.lobby.header.channels" : "聊天频道",
|
"vcmi.lobby.header.channels" : "聊天频道",
|
||||||
"vcmi.lobby.header.chat.global" : "全局游戏聊天 - %s", // %s -> language name
|
"vcmi.lobby.header.chat.global" : "全局游戏聊天 - %s", // %s -> language name
|
||||||
@ -189,6 +228,8 @@
|
|||||||
"vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}",
|
"vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}",
|
||||||
"vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}",
|
"vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}",
|
||||||
"vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!",
|
"vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!",
|
||||||
|
"vcmi.server.errors.wrongIdentified" : "你被识别为玩家%s,但预期是玩家%s。",
|
||||||
|
"vcmi.server.errors.notAllowed" : "你无权执行此操作!",
|
||||||
|
|
||||||
"vcmi.dimensionDoor.seaToLandError" : "无法在陆地与海洋之间使用异次元之门传送。",
|
"vcmi.dimensionDoor.seaToLandError" : "无法在陆地与海洋之间使用异次元之门传送。",
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
"vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky",
|
"vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky",
|
||||||
"vcmi.quickExchange.swapAllUnits" : "Vyměnit armády",
|
"vcmi.quickExchange.swapAllUnits" : "Vyměnit armády",
|
||||||
"vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty",
|
"vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty",
|
||||||
"vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt",
|
"vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakty",
|
||||||
|
|
||||||
"vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky",
|
"vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky",
|
||||||
"vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou",
|
"vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou",
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
"vcmi.quickExchange.moveAllUnits" : "Move All Units",
|
"vcmi.quickExchange.moveAllUnits" : "Move All Units",
|
||||||
"vcmi.quickExchange.swapAllUnits" : "Swap Armies",
|
"vcmi.quickExchange.swapAllUnits" : "Swap Armies",
|
||||||
"vcmi.quickExchange.moveAllArtifacts" : "Move All Artifacts",
|
"vcmi.quickExchange.moveAllArtifacts" : "Move All Artifacts",
|
||||||
"vcmi.quickExchange.swapAllArtifacts" : "Swap Artifact",
|
"vcmi.quickExchange.swapAllArtifacts" : "Swap Artifacts",
|
||||||
|
|
||||||
"vcmi.radialWheel.mergeSameUnit" : "Merge same creatures",
|
"vcmi.radialWheel.mergeSameUnit" : "Merge same creatures",
|
||||||
"vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures",
|
"vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures",
|
||||||
|
@ -121,7 +121,43 @@
|
|||||||
"vcmi.lobby.deleteFolder" : "Möchtet Ihr folgenden Ordner löschen?",
|
"vcmi.lobby.deleteFolder" : "Möchtet Ihr folgenden Ordner löschen?",
|
||||||
"vcmi.lobby.deleteMode" : "In den Löschmodus wechseln und zurück",
|
"vcmi.lobby.deleteMode" : "In den Löschmodus wechseln und zurück",
|
||||||
|
|
||||||
|
"vcmi.broadcast.failedLoadGame" : "Spiel konnte nicht geladen werden",
|
||||||
"vcmi.broadcast.command" : "Benutze '!help' um alle verfügbaren Befehle aufzulisten",
|
"vcmi.broadcast.command" : "Benutze '!help' um alle verfügbaren Befehle aufzulisten",
|
||||||
|
"vcmi.broadcast.simturn.end" : "Simultane Züge wurden beendet",
|
||||||
|
"vcmi.broadcast.simturn.endBetween" : "Simultane Züge zwischen den Spielern %s und %s wurden beendet",
|
||||||
|
"vcmi.broadcast.serverProblem" : "Server hat ein Problem festgestellt",
|
||||||
|
"vcmi.broadcast.gameTerminated" : "Spiel wurde abgebrochen",
|
||||||
|
"vcmi.broadcast.gameSavedAs" : "Spiel gespeichert als",
|
||||||
|
"vcmi.broadcast.noCheater" : "Keine Betrüger registriert!",
|
||||||
|
"vcmi.broadcast.playerCheater" : "Spieler %s ist ein Betrüger!",
|
||||||
|
"vcmi.broadcast.statisticFile" : "Die Statistikdateien befinden sich im Verzeichnis %s",
|
||||||
|
"vcmi.broadcast.help.commands" : "Verfügbare Befehle für den Host:",
|
||||||
|
"vcmi.broadcast.help.exit" : "'!exit' - beendet sofort das aktuelle Spiel",
|
||||||
|
"vcmi.broadcast.help.kick" : "'!kick <player>' - den angegebenen Spieler aus dem Spiel werfen",
|
||||||
|
"vcmi.broadcast.help.save" : "'!save <filename>' - Spiel unter dem angegebenen Dateinamen speichern",
|
||||||
|
"vcmi.broadcast.help.statistic" : "'!statistic' - Spielstatistiken als csv-Datei speichern",
|
||||||
|
"vcmi.broadcast.help.commandsAll" : "Verfügbare Befehle für alle Spieler:",
|
||||||
|
"vcmi.broadcast.help.help" : "'!help' - diese Hilfe anzeigen",
|
||||||
|
"vcmi.broadcast.help.cheaters" : "'!cheaters' - Liste der Spieler, die während des Spiels einen Cheat-Befehl eingegeben haben",
|
||||||
|
"vcmi.broadcast.help.vote" : "'!vote' - erlaubt es, einige Spieleinstellungen zu ändern, wenn alle Spieler dafür stimmen",
|
||||||
|
"vcmi.broadcast.vote.allow" : "'!vote simturns allow X' - erlaubt simultane Züge für eine bestimmte Anzahl von Tagen oder bis zum Kontakt",
|
||||||
|
"vcmi.broadcast.vote.force" : "'!vote simturns force X' - erzwingt simultane Züge für die angegebene Anzahl von Tagen und blockiert Spielerkontakte",
|
||||||
|
"vcmi.broadcast.vote.abort" : "'!vote simturns abort' - Simultane Züge abbrechen, sobald dieser Zug endet",
|
||||||
|
"vcmi.broadcast.vote.timer" : "'!vote timer prolong X' - verlängert den Basis-Timer für alle Spieler um die angegebene Anzahl von Sekunden",
|
||||||
|
"vcmi.broadcast.vote.noActive" : "Keine aktive Abstimmung!",
|
||||||
|
"vcmi.broadcast.vote.yes" : "ja",
|
||||||
|
"vcmi.broadcast.vote.no" : "nein",
|
||||||
|
"vcmi.broadcast.vote.notRecognized" : "Abstimmungsbefehl nicht erkannt!",
|
||||||
|
"vcmi.broadcast.vote.success.untilContacts" : "Abstimmung erfolgreich. Simultane Züge laufen für %s weitere Tage, oder bis zum Kontakt",
|
||||||
|
"vcmi.broadcast.vote.success.contactsBlocked" : "Abstimmung erfolgreich. Simultane Züge werden für %s weitere Tage laufen. Kontakte sind blockiert",
|
||||||
|
"vcmi.broadcast.vote.success.nextDay" : "Abstimmung erfolgreich. Simultane Züge werden am nächsten Tag beendet",
|
||||||
|
"vcmi.broadcast.vote.success.timer" : "Abstimmung erfolgreich. Der Timer für alle Spieler wurde um %s Sekunden verlängert.",
|
||||||
|
"vcmi.broadcast.vote.aborted" : "Spieler haben gegen die Änderung gestimmt. Abstimmung abgebrochen",
|
||||||
|
"vcmi.broadcast.vote.start.untilContacts" : "Abstimmung gestartet, um simultane Züge für weitere %s Tage zu erlauben",
|
||||||
|
"vcmi.broadcast.vote.start.contactsBlocked" : "Abstimmung über die Erzwingung simultaner Züge für weitere %s-Tage eingeleitet",
|
||||||
|
"vcmi.broadcast.vote.start.nextDay" : "Beginn der Abstimmung zur Beendigung der simultanen Züge ab dem nächsten Tag",
|
||||||
|
"vcmi.broadcast.vote.start.timer" : "Abstimmung gestartet, um den Timer für alle Spieler um %s Sekunden zu verlängern",
|
||||||
|
"vcmi.broadcast.vote.hint" : "Gib '!vote yes' ein, um dieser Änderung zuzustimmen oder '!vote no', um dagegen zu stimmen",
|
||||||
|
|
||||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||||
"vcmi.lobby.login.username" : "Benutzername:",
|
"vcmi.lobby.login.username" : "Benutzername:",
|
||||||
@ -130,6 +166,7 @@
|
|||||||
"vcmi.lobby.login.create" : "Neuer Account",
|
"vcmi.lobby.login.create" : "Neuer Account",
|
||||||
"vcmi.lobby.login.login" : "Login",
|
"vcmi.lobby.login.login" : "Login",
|
||||||
"vcmi.lobby.login.as" : "Login als %s",
|
"vcmi.lobby.login.as" : "Login als %s",
|
||||||
|
"vcmi.lobby.login.spectator" : "Beobachter",
|
||||||
"vcmi.lobby.header.rooms" : "Spielräume - %d",
|
"vcmi.lobby.header.rooms" : "Spielräume - %d",
|
||||||
"vcmi.lobby.header.channels" : "Chat Kanäle",
|
"vcmi.lobby.header.channels" : "Chat Kanäle",
|
||||||
"vcmi.lobby.header.chat.global" : "Globaler Spiele-Chat - %s", // %s -> language name
|
"vcmi.lobby.header.chat.global" : "Globaler Spiele-Chat - %s", // %s -> language name
|
||||||
@ -190,8 +227,9 @@
|
|||||||
"vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
|
"vcmi.server.errors.existingProcess" : "Es läuft ein weiterer vcmiserver-Prozess, bitte beendet diesen zuerst",
|
||||||
"vcmi.server.errors.modsToEnable" : "{Erforderliche Mods um das Spiel zu laden}",
|
"vcmi.server.errors.modsToEnable" : "{Erforderliche Mods um das Spiel zu laden}",
|
||||||
"vcmi.server.errors.modsToDisable" : "{Folgende Mods müssen deaktiviert werden}",
|
"vcmi.server.errors.modsToDisable" : "{Folgende Mods müssen deaktiviert werden}",
|
||||||
"vcmi.server.errors.modDependencyLoop" : "Mod {'%s'} konnte nicht geladen werden.!\n Möglicherweise befindet sie sich in einer (weichen) Abhängigkeitsschleife.",
|
|
||||||
"vcmi.server.errors.unknownEntity" : "Spielstand konnte nicht geladen werden! Unbekannte Entität '%s' im gespeicherten Spiel gefunden! Der Spielstand ist möglicherweise nicht mit der aktuell installierten Version der Mods kompatibel!",
|
"vcmi.server.errors.unknownEntity" : "Spielstand konnte nicht geladen werden! Unbekannte Entität '%s' im gespeicherten Spiel gefunden! Der Spielstand ist möglicherweise nicht mit der aktuell installierten Version der Mods kompatibel!",
|
||||||
|
"vcmi.server.errors.wrongIdentified" : "Ihr wurdet als Spieler %s identifiziert, während %s erwartet wurde",
|
||||||
|
"vcmi.server.errors.notAllowed" : "Ihr dürft diese Aktion nicht durchführen!",
|
||||||
|
|
||||||
"vcmi.dimensionDoor.seaToLandError" : "Es ist nicht möglich, mit einer Dimensionstür vom Meer zum Land oder umgekehrt zu teleportieren.",
|
"vcmi.dimensionDoor.seaToLandError" : "Es ist nicht möglich, mit einer Dimensionstür vom Meer zum Land oder umgekehrt zu teleportieren.",
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
"vcmi.adventureMap.monsterThreat.levels.9" : "Przytłaczający",
|
"vcmi.adventureMap.monsterThreat.levels.9" : "Przytłaczający",
|
||||||
"vcmi.adventureMap.monsterThreat.levels.10" : "Śmiertelny",
|
"vcmi.adventureMap.monsterThreat.levels.10" : "Śmiertelny",
|
||||||
"vcmi.adventureMap.monsterThreat.levels.11" : "Nie do pokonania",
|
"vcmi.adventureMap.monsterThreat.levels.11" : "Nie do pokonania",
|
||||||
"vcmi.adventureMap.monsterLevel" : "\n\nJednostka %ATTACK_TYPE %LEVEL poziomu z miasta %TOWN",
|
"vcmi.adventureMap.monsterLevel" : "\n\nJednostka %ATTACK_TYPE %LEVEL-go poziomu z miasta %TOWN",
|
||||||
"vcmi.adventureMap.monsterMeleeType" : "Walcząca wręcz",
|
"vcmi.adventureMap.monsterMeleeType" : "walcząca wręcz",
|
||||||
"vcmi.adventureMap.monsterRangedType" : "Dystansowa",
|
"vcmi.adventureMap.monsterRangedType" : "dystansowa",
|
||||||
"vcmi.adventureMap.search.hover" : "Wyszukiwarka obiektów",
|
"vcmi.adventureMap.search.hover" : "Wyszukiwarka obiektów",
|
||||||
"vcmi.adventureMap.search.help" : "Wybierz obiekt który chcesz znaleźć na mapie.",
|
"vcmi.adventureMap.search.help" : "Wybierz obiekt który chcesz znaleźć na mapie.",
|
||||||
|
|
||||||
@ -35,6 +35,44 @@
|
|||||||
"vcmi.bonusSource.commander" : "Dowódca",
|
"vcmi.bonusSource.commander" : "Dowódca",
|
||||||
"vcmi.bonusSource.other" : "Inne",
|
"vcmi.bonusSource.other" : "Inne",
|
||||||
|
|
||||||
|
"vcmi.broadcast.command" : "Wpisz '!help' aby zobaczyć listę dostępnych komend.",
|
||||||
|
"vcmi.broadcast.failedLoadGame" : "Nie udało się wczytać gry",
|
||||||
|
"vcmi.broadcast.gameSavedAs" : "gra zapisana jako",
|
||||||
|
"vcmi.broadcast.gameTerminated" : "gra została zamknięta",
|
||||||
|
"vcmi.broadcast.help.cheaters" : "'!cheaters' - wyświetla listę graczy, którzy użyli 'kodów' w trakcie gry",
|
||||||
|
"vcmi.broadcast.help.commands" : "Dostępne komendy dla hosta:",
|
||||||
|
"vcmi.broadcast.help.commandsAll" : "Dostępne komendy dla wszystkich graczy:",
|
||||||
|
"vcmi.broadcast.help.exit" : "'!exit' - natychmiast kończy bieżącą grę",
|
||||||
|
"vcmi.broadcast.help.help" : "'!help' - wyświetla tę pomoc",
|
||||||
|
"vcmi.broadcast.help.kick" : "'!kick <player>' - wyrzuca określonego gracza z gry",
|
||||||
|
"vcmi.broadcast.help.save" : "'!save <filename>' - zapisz grę pod określoną nazwą",
|
||||||
|
"vcmi.broadcast.help.statistic" : "'!statistic' - zapisz statystyki gry do pliku csv",
|
||||||
|
"vcmi.broadcast.help.vote" : "'!vote' - pozwala na zmianę ustawień gry jeśli wszyscy gracze zagłosują 'za'",
|
||||||
|
"vcmi.broadcast.noCheater" : "Nie zarejestrowano oszustw!",
|
||||||
|
"vcmi.broadcast.playerCheater" : "Gracz %s to oszust!",
|
||||||
|
"vcmi.broadcast.serverProblem" : "Serwer napotkał problem",
|
||||||
|
"vcmi.broadcast.simturn.end" : "Tury symultaniczne zostały zakończone",
|
||||||
|
"vcmi.broadcast.simturn.endBetween" : "Tury symultaniczne pomiędzy graczami %s i %s dobiegły końca",
|
||||||
|
"vcmi.broadcast.statisticFile" : "Pliki statystyk są dostępne w folderze %s",
|
||||||
|
"vcmi.broadcast.vote.abort" : "'!vote simturns abort' - przerwanie tur symultanicznych po zakończeniu tej tury",
|
||||||
|
"vcmi.broadcast.vote.aborted" : "Gracz zagłosował przeciwko. Głosowanie anulowane.",
|
||||||
|
"vcmi.broadcast.vote.allow" : "'!vote simturns allow X' - tury symultaniczne przez określoną ilość dni, albo do pierwszego kontaktu",
|
||||||
|
"vcmi.broadcast.vote.force" : "'!vote simturns force X' - tury symultaniczne przez określoną ilość dni, niezależnie od kontaktu",
|
||||||
|
"vcmi.broadcast.vote.hint" : "Wpisz '!vote yes' żeby zagłosować na tak lub '!vote no' - przeciwko tej zmianie",
|
||||||
|
"vcmi.broadcast.vote.no" : "nie",
|
||||||
|
"vcmi.broadcast.vote.noActive" : "Żadne głosowanie nie jest aktywne!",
|
||||||
|
"vcmi.broadcast.vote.notRecognized" : "Nieznana komenda do głosowania!",
|
||||||
|
"vcmi.broadcast.vote.start.contactsBlocked" : "Rozpoczęto głosowanie: tury symultaniczne przez %s kolejnych dni",
|
||||||
|
"vcmi.broadcast.vote.start.nextDay" : "Rozpoczęto głosowanie: zakończenie tur symultanicznych od następnego dnia",
|
||||||
|
"vcmi.broadcast.vote.start.timer" : "Rozpoczęto głosowanie: wydłużenie czasu dla wszystkich graczy o %s sekund",
|
||||||
|
"vcmi.broadcast.vote.start.untilContacts" : "Rozpoczęto głosowanie: przedłużenie trwania tur symultanicznych o kolejne %s dni",
|
||||||
|
"vcmi.broadcast.vote.success.contactsBlocked" : "Głosowanie zakończone pomyślnie. Tury symultaniczne będą trwały jeszcze przez kolejne %s dni, niezależnie od kontaktu.",
|
||||||
|
"vcmi.broadcast.vote.success.nextDay" : "Głosowanie zakończone pomyślnie. Tury symultaniczne zostaną wyłączone w kolejnym dniu",
|
||||||
|
"vcmi.broadcast.vote.success.timer" : "Głosowanie zakończone pomyślnie. Czas dla wszystkich graczy został wydłużony o %s sekund",
|
||||||
|
"vcmi.broadcast.vote.success.untilContacts" : "Głosowanie zakończone pomyślnie. Tury symultaniczne będą trwały jeszcze przez kolejne %s dni lub do pierwszego kontaktu.",
|
||||||
|
"vcmi.broadcast.vote.timer" : "'!vote timer prolong X' - wydłużenie bazowego czasu dla wszystkich graczy o X sekund",
|
||||||
|
"vcmi.broadcast.vote.yes" : "tak",
|
||||||
|
|
||||||
"vcmi.capitalColors.0" : "Czerwony",
|
"vcmi.capitalColors.0" : "Czerwony",
|
||||||
"vcmi.capitalColors.1" : "Niebieski",
|
"vcmi.capitalColors.1" : "Niebieski",
|
||||||
"vcmi.capitalColors.2" : "Brązowy",
|
"vcmi.capitalColors.2" : "Brązowy",
|
||||||
@ -174,6 +212,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.deleteFolder" : "Czy chcesz usunąć ten folder ?",
|
||||||
|
"vcmi.lobby.deleteMapTitle" : "Wskaż tytuł, który chcesz usunąć",
|
||||||
|
"vcmi.lobby.deleteMode" : "Przełącza tryb na usuwanie i spowrotem",
|
||||||
|
"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.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.",
|
||||||
@ -183,7 +227,9 @@
|
|||||||
"vcmi.server.errors.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}",
|
"vcmi.server.errors.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}",
|
||||||
"vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}",
|
"vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}",
|
||||||
"vcmi.server.errors.modDependencyLoop" : "Nie udało się wczytać moda {'%s'}!\n Być może znajduje się w pętli zależności",
|
"vcmi.server.errors.modDependencyLoop" : "Nie udało się wczytać moda {'%s'}!\n Być może znajduje się w pętli zależności",
|
||||||
|
"vcmi.server.errors.notAllowed" : "To działanie nie jest dozwolone!",
|
||||||
"vcmi.server.errors.unknownEntity" : "Nie udało się wczytać zapisu! Nieznany element '%s' znaleziony w pliku zapisu! Zapis może nie być zgodny z aktualnie zainstalowaną wersją modów!",
|
"vcmi.server.errors.unknownEntity" : "Nie udało się wczytać zapisu! Nieznany element '%s' znaleziony w pliku zapisu! Zapis może nie być zgodny z aktualnie zainstalowaną wersją modów!",
|
||||||
|
"vcmi.server.errors.wrongIdentified" : "Zostałeś zidentyfikowany jako gracz %s natomiast powinieneś być %s",
|
||||||
|
|
||||||
"vcmi.dimensionDoor.seaToLandError" : "Nie jest możliwa teleportacja przez drzwi wymiarów z wód na ląd i na odwrót.",
|
"vcmi.dimensionDoor.seaToLandError" : "Nie jest możliwa teleportacja przez drzwi wymiarów z wód na ląd i na odwrót.",
|
||||||
|
|
||||||
@ -228,7 +274,7 @@
|
|||||||
"vcmi.statisticWindow.param.obeliskVisited" : "Lb. obelisków",
|
"vcmi.statisticWindow.param.obeliskVisited" : "Lb. obelisków",
|
||||||
"vcmi.statisticWindow.icon.townCaptured" : "Miasto zdobyte",
|
"vcmi.statisticWindow.icon.townCaptured" : "Miasto zdobyte",
|
||||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Najsilniejszy bohater przeciwnika pokonany",
|
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Najsilniejszy bohater przeciwnika pokonany",
|
||||||
"vcmi.statisticWindow.icon.grailFound" : "Gral znaleziony",
|
"vcmi.statisticWindow.icon.grailFound" : "Graal znaleziony",
|
||||||
"vcmi.statisticWindow.icon.defeated" : "Pokonany",
|
"vcmi.statisticWindow.icon.defeated" : "Pokonany",
|
||||||
|
|
||||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Pełny ekran (bez ramek)",
|
"vcmi.systemOptions.fullscreenBorderless.hover" : "Pełny ekran (bez ramek)",
|
||||||
@ -491,11 +537,33 @@
|
|||||||
"vcmi.stackExperience.rank.4" : "Udowodniony",
|
"vcmi.stackExperience.rank.4" : "Udowodniony",
|
||||||
"vcmi.stackExperience.rank.5" : "Weteran",
|
"vcmi.stackExperience.rank.5" : "Weteran",
|
||||||
"vcmi.stackExperience.rank.6" : "Adept",
|
"vcmi.stackExperience.rank.6" : "Adept",
|
||||||
"vcmi.stackExperience.rank.7" : "Expert",
|
"vcmi.stackExperience.rank.7" : "Ekspert",
|
||||||
"vcmi.stackExperience.rank.8" : "Elitarny",
|
"vcmi.stackExperience.rank.8" : "Elitarny",
|
||||||
"vcmi.stackExperience.rank.9" : "Master",
|
"vcmi.stackExperience.rank.9" : "Mistrz",
|
||||||
"vcmi.stackExperience.rank.10" : "As",
|
"vcmi.stackExperience.rank.10" : "As",
|
||||||
|
|
||||||
|
"spell.core.castleMoat.name" : "Fosa",
|
||||||
|
"spell.core.castleMoatTrigger.name" : "Fosa",
|
||||||
|
"spell.core.catapultShot.name" : "Strzał z katapulty",
|
||||||
|
"spell.core.cyclopsShot.name" : "Strzał oblężniczy",
|
||||||
|
"spell.core.dungeonMoat.name" : "Wrzący olej",
|
||||||
|
"spell.core.dungeonMoatTrigger.name" : "Wrzący olej",
|
||||||
|
"spell.core.fireWallTrigger.name" : "Ściana Ognia",
|
||||||
|
"spell.core.firstAid.name" : "Pierwsza pomoc",
|
||||||
|
"spell.core.fortressMoat.name" : "Wrząca smoła",
|
||||||
|
"spell.core.fortressMoatTrigger.name" : "Wrząca smoła",
|
||||||
|
"spell.core.infernoMoat.name" : "Lawa",
|
||||||
|
"spell.core.infernoMoatTrigger.name" : "Lawa",
|
||||||
|
"spell.core.landMineTrigger.name" : "Mina",
|
||||||
|
"spell.core.necropolisMoat.name" : "Fosa z kości",
|
||||||
|
"spell.core.necropolisMoatTrigger.name" : "Fosa z kości",
|
||||||
|
"spell.core.rampartMoat.name" : "Ciernisko",
|
||||||
|
"spell.core.rampartMoatTrigger.name" : "Ciernisko",
|
||||||
|
"spell.core.strongholdMoat.name" : "Palisada obronna",
|
||||||
|
"spell.core.strongholdMoatTrigger.name" : "Palisada obronna",
|
||||||
|
"spell.core.summonDemons.name" : "Przyzwanie Demonów",
|
||||||
|
"spell.core.towerMoat.name" : "Pole minowe",
|
||||||
|
|
||||||
// Strings for HotA Seer Hut / Quest Guards
|
// Strings for HotA Seer Hut / Quest Guards
|
||||||
"core.seerhut.quest.heroClass.complete.0" : "Ah, ty jesteś %s. Oto prezent dla ciebie. Czy go przyjmiesz?",
|
"core.seerhut.quest.heroClass.complete.0" : "Ah, ty jesteś %s. Oto prezent dla ciebie. Czy go przyjmiesz?",
|
||||||
"core.seerhut.quest.heroClass.complete.1" : "Ah, ty jesteś %s. Oto prezent dla ciebie. Czy go przyjmiesz?",
|
"core.seerhut.quest.heroClass.complete.1" : "Ah, ty jesteś %s. Oto prezent dla ciebie. Czy go przyjmiesz?",
|
||||||
|
@ -12,6 +12,11 @@
|
|||||||
"vcmi.adventureMap.monsterThreat.levels.9" : "Нездоланна",
|
"vcmi.adventureMap.monsterThreat.levels.9" : "Нездоланна",
|
||||||
"vcmi.adventureMap.monsterThreat.levels.10" : "Смертельна",
|
"vcmi.adventureMap.monsterThreat.levels.10" : "Смертельна",
|
||||||
"vcmi.adventureMap.monsterThreat.levels.11" : "Неможлива",
|
"vcmi.adventureMap.monsterThreat.levels.11" : "Неможлива",
|
||||||
|
"vcmi.adventureMap.monsterLevel" : "\n\n%TOWN, істота%ATTACK_TYPE, %LEVELго рівня",
|
||||||
|
"vcmi.adventureMap.monsterMeleeType" : " ближнього бою",
|
||||||
|
"vcmi.adventureMap.monsterRangedType" : "-стрілок",
|
||||||
|
"vcmi.adventureMap.search.hover" : "Шукати об'єкт мапи",
|
||||||
|
"vcmi.adventureMap.search.help" : "Оберіть об'єкт для пошуку на мапі.",
|
||||||
|
|
||||||
"vcmi.adventureMap.confirmRestartGame" : "Ви впевнені, що хочете перезапустити гру?",
|
"vcmi.adventureMap.confirmRestartGame" : "Ви впевнені, що хочете перезапустити гру?",
|
||||||
"vcmi.adventureMap.noTownWithMarket" : "Немає доступних ринків!",
|
"vcmi.adventureMap.noTownWithMarket" : "Немає доступних ринків!",
|
||||||
@ -20,8 +25,16 @@
|
|||||||
"vcmi.adventureMap.playerAttacked" : "Гравця атаковано: %s",
|
"vcmi.adventureMap.playerAttacked" : "Гравця атаковано: %s",
|
||||||
"vcmi.adventureMap.moveCostDetails" : "Очки руху - Вартість: %TURNS ходів + %POINTS очок. Залишок очок: %REMAINING",
|
"vcmi.adventureMap.moveCostDetails" : "Очки руху - Вартість: %TURNS ходів + %POINTS очок. Залишок очок: %REMAINING",
|
||||||
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Очки руху - Вартість: %POINTS очок, Залишок очок: %REMAINING",
|
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Очки руху - Вартість: %POINTS очок, Залишок очок: %REMAINING",
|
||||||
|
"vcmi.adventureMap.movementPointsHeroInfo" : "(Очки руху: %REMAINING / %POINTS)",
|
||||||
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Вибачте, функція повтору ходу суперника ще не реалізована!",
|
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Вибачте, функція повтору ходу суперника ще не реалізована!",
|
||||||
|
|
||||||
|
"vcmi.bonusSource.artifact" : "Артифакт",
|
||||||
|
"vcmi.bonusSource.creature" : "Здібність",
|
||||||
|
"vcmi.bonusSource.spell" : "Закляття",
|
||||||
|
"vcmi.bonusSource.hero" : "Герой",
|
||||||
|
"vcmi.bonusSource.commander" : "Командир",
|
||||||
|
"vcmi.bonusSource.other" : "Інше",
|
||||||
|
|
||||||
"vcmi.capitalColors.0" : "Червоний",
|
"vcmi.capitalColors.0" : "Червоний",
|
||||||
"vcmi.capitalColors.1" : "Синій",
|
"vcmi.capitalColors.1" : "Синій",
|
||||||
"vcmi.capitalColors.2" : "Сірий",
|
"vcmi.capitalColors.2" : "Сірий",
|
||||||
@ -36,6 +49,12 @@
|
|||||||
"vcmi.heroOverview.secondarySkills" : "Навички",
|
"vcmi.heroOverview.secondarySkills" : "Навички",
|
||||||
"vcmi.heroOverview.spells" : "Закляття",
|
"vcmi.heroOverview.spells" : "Закляття",
|
||||||
|
|
||||||
|
"vcmi.quickExchange.moveUnit" : "Перемістити загін",
|
||||||
|
"vcmi.quickExchange.moveAllUnits" : "Перемістити усі загони",
|
||||||
|
"vcmi.quickExchange.swapAllUnits" : "Обміняти армії",
|
||||||
|
"vcmi.quickExchange.moveAllArtifacts" : "Перемістити усі артефакти",
|
||||||
|
"vcmi.quickExchange.swapAllArtifacts" : "Обміняти усі артефакти",
|
||||||
|
|
||||||
"vcmi.radialWheel.mergeSameUnit" : "Об'єднати однакових істот",
|
"vcmi.radialWheel.mergeSameUnit" : "Об'єднати однакових істот",
|
||||||
"vcmi.radialWheel.fillSingleUnit" : "Заповнити одиничними істотами",
|
"vcmi.radialWheel.fillSingleUnit" : "Заповнити одиничними істотами",
|
||||||
"vcmi.radialWheel.splitSingleUnit" : "Відділити одну істоту",
|
"vcmi.radialWheel.splitSingleUnit" : "Відділити одну істоту",
|
||||||
@ -55,8 +74,26 @@
|
|||||||
"vcmi.radialWheel.moveDown" : "Перемістити вниз",
|
"vcmi.radialWheel.moveDown" : "Перемістити вниз",
|
||||||
"vcmi.radialWheel.moveBottom" : "Перемістити у кінець",
|
"vcmi.radialWheel.moveBottom" : "Перемістити у кінець",
|
||||||
|
|
||||||
|
"vcmi.randomMap.description" : "Мапа створена генератором випадкових мап.\nШаблон був %s, розмір %dx%d, рівнів %d, гравців %d, гравців-ШІ %d, %s, %s монстри, мапа VCMI",
|
||||||
|
"vcmi.randomMap.description.isHuman" : ", %s людина",
|
||||||
|
"vcmi.randomMap.description.townChoice" : ", %s обрав замок %s",
|
||||||
|
"vcmi.randomMap.description.water.none" : "води немає",
|
||||||
|
"vcmi.randomMap.description.water.normal" : "вода присутня",
|
||||||
|
"vcmi.randomMap.description.water.islands" : "острівна",
|
||||||
|
"vcmi.randomMap.description.monster.weak" : "слабкі",
|
||||||
|
"vcmi.randomMap.description.monster.normal" : "звичайні",
|
||||||
|
"vcmi.randomMap.description.monster.strong" : "сильні",
|
||||||
|
|
||||||
"vcmi.spellBook.search" : "шукати...",
|
"vcmi.spellBook.search" : "шукати...",
|
||||||
|
|
||||||
|
"vcmi.spellResearch.canNotAfford" : "Ви не можете дозволити собі замінити {%SPELL1} на {%SPELL2}. Але ви все одно можете відкинути це закляття і продовжити дослідження заклять.",
|
||||||
|
"vcmi.spellResearch.comeAgain" : "Сьогодні дослідження вже зроблено. Приходьте завтра..",
|
||||||
|
"vcmi.spellResearch.pay" : "Ви хочете замінити {%SPELL1} на {%SPELL2}? Або відкинути це закляття і продовжити дослідження заклять??",
|
||||||
|
"vcmi.spellResearch.research" : "Дослідити це закляття",
|
||||||
|
"vcmi.spellResearch.skip" : "Пропустити це закляття",
|
||||||
|
"vcmi.spellResearch.abort" : "Припинити",
|
||||||
|
"vcmi.spellResearch.noMoreSpells" : "Більше немає жодного закляття, доступного для дослідження.",
|
||||||
|
|
||||||
"vcmi.mainMenu.serverConnecting" : "Підключення...",
|
"vcmi.mainMenu.serverConnecting" : "Підключення...",
|
||||||
"vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:",
|
"vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:",
|
||||||
"vcmi.mainMenu.serverConnectionFailed" : "Помилка з'єднання",
|
"vcmi.mainMenu.serverConnectionFailed" : "Помилка з'єднання",
|
||||||
@ -73,6 +110,56 @@
|
|||||||
"vcmi.lobby.sortDate" : "Сортувати мапи за датою зміни",
|
"vcmi.lobby.sortDate" : "Сортувати мапи за датою зміни",
|
||||||
"vcmi.lobby.backToLobby" : "Назад до лобі",
|
"vcmi.lobby.backToLobby" : "Назад до лобі",
|
||||||
|
|
||||||
|
"vcmi.lobby.author" : "Автор",
|
||||||
|
"vcmi.lobby.handicap" : "Гандикап",
|
||||||
|
"vcmi.lobby.handicap.resource" : "Дає гравцям відповідні ресурси для початку гри на додаток до звичних стартових ресурсів. Від'ємні значення дозволені, але обмежені загальним значенням 0 (гравець ніколи не починає з від'ємними ресурсами).",
|
||||||
|
"vcmi.lobby.handicap.income" : "Змінює різні доходи гравця на певний відсоток. Округлюється у більшу сторону.",
|
||||||
|
"vcmi.lobby.handicap.growth" : "Змінює рівень приросту істот у містах, якими володіє гравець. Округлюється в більшу сторону.",
|
||||||
|
"vcmi.lobby.deleteUnsupportedSave" : "{Знайдено непідтримувані збереження}\n\nVCMI знайдено %d збережених ігор, які більше не підтримуються, найімовірніше, через невідповідність версій VCMI.\n\nЧи бажаєте вилучити їх?",
|
||||||
|
"vcmi.lobby.deleteSaveGameTitle" : "Виберіть гру для видалення",
|
||||||
|
"vcmi.lobby.deleteMapTitle" : "Виберіть сценарій для видалення",
|
||||||
|
"vcmi.lobby.deleteFile" : "Чи бажаєте вилучити цей файл?",
|
||||||
|
"vcmi.lobby.deleteFolder" : "Чи бажаєте вилучити цю теку?",
|
||||||
|
"vcmi.lobby.deleteMode" : "Перехід у режим видалення та назад",
|
||||||
|
|
||||||
|
"vcmi.broadcast.failedLoadGame" : "Не вдалося завантажити гру",
|
||||||
|
"vcmi.broadcast.command" : "Введіть '!help' у чаті гри, щоб переглянути список доступних команд",
|
||||||
|
"vcmi.broadcast.simturn.end" : "Одночасні ходи закінчилися",
|
||||||
|
"vcmi.broadcast.simturn.endBetween" : "Одночасні ходи між гравцями %s та %s завершилися",
|
||||||
|
"vcmi.broadcast.serverProblem" : "Сервер зіткнувся з проблемою",
|
||||||
|
"vcmi.broadcast.gameTerminated" : "гру було завершено",
|
||||||
|
"vcmi.broadcast.gameSavedAs" : "гру збережено як",
|
||||||
|
"vcmi.broadcast.noCheater" : "Читерів не зареєстровано!",
|
||||||
|
"vcmi.broadcast.playerCheater" : "Гравець %s - шахрай!",
|
||||||
|
"vcmi.broadcast.statisticFile" : "Файли статистики можна знайти в каталозі %s",
|
||||||
|
"vcmi.broadcast.help.commands" : "Команди доступні для хоста:",
|
||||||
|
"vcmi.broadcast.help.exit" : "'!exit' - негайно завершує поточну гру",
|
||||||
|
"vcmi.broadcast.help.kick" : "'!kick <player>' - вигнати вказаного гравця з гри",
|
||||||
|
"vcmi.broadcast.help.save" : "'!save <filename>' - зберегти гру під вказаним ім'ям",
|
||||||
|
"vcmi.broadcast.help.statistic" : "'!statistic' - зберегти статистику гри у форматі csv",
|
||||||
|
"vcmi.broadcast.help.commandsAll" : "Команди доступні всім гравцям:",
|
||||||
|
"vcmi.broadcast.help.help" : "'!help' - відобразити цю довідку",
|
||||||
|
"vcmi.broadcast.help.cheaters" : "'!cheaters' - список гравців, які вводили чит-команду під час гри",
|
||||||
|
"vcmi.broadcast.help.vote" : "'!vote' - дозволяє змінити деякі налаштування гри, якщо всі гравці проголосують за це",
|
||||||
|
"vcmi.broadcast.vote.allow" : "'!vote simturns allow X' - дозволяти одночасні ходи на визначену кількість днів або до контакту",
|
||||||
|
"vcmi.broadcast.vote.force" : "'!vote simturns force X' - увімкнути одночасні ходи на визначену кількість днів, блокуючи контакти гравців",
|
||||||
|
"vcmi.broadcast.vote.abort" : "'!vote simturns abort' - завершити одночасні ходи, як тільки цей хід закінчиться",
|
||||||
|
"vcmi.broadcast.vote.timer" : "'!vote timer prolong X' - подовжити базовий таймер для всіх гравців на вказану кількість секунд",
|
||||||
|
"vcmi.broadcast.vote.noActive" : "Активне голосування відсутнє!",
|
||||||
|
"vcmi.broadcast.vote.yes" : "так",
|
||||||
|
"vcmi.broadcast.vote.no" : "ні",
|
||||||
|
"vcmi.broadcast.vote.notRecognized" : "Команда для голосування не розпізнана!",
|
||||||
|
"vcmi.broadcast.vote.success.untilContacts" : "Голосування пройшло успішно. Одночасні ходи триватимуть ще %s днів, або до контакту",
|
||||||
|
"vcmi.broadcast.vote.success.contactsBlocked" : "Голосування пройшло успішно. Одночасні ходи триватимуть ще %s днів. Контакти між гравцями заблоковані",
|
||||||
|
"vcmi.broadcast.vote.success.nextDay" : "Голосування пройшло успішно. Одночасні ходи закінчаться на наступний день",
|
||||||
|
"vcmi.broadcast.vote.success.timer" : "Голосування пройшло успішно. Таймер для всіх гравців було подовжено на %s секунд",
|
||||||
|
"vcmi.broadcast.vote.aborted" : "Гравець проголосував проти змін. Голосування перервано",
|
||||||
|
"vcmi.broadcast.vote.start.untilContacts" : "Розпочато голосування, за одночасні ходи на %s більше днів або до контакту гравців",
|
||||||
|
"vcmi.broadcast.vote.start.contactsBlocked" : "Розпочато голосування за безумовні одночасні ходи на %s більше днів",
|
||||||
|
"vcmi.broadcast.vote.start.nextDay" : "Розпочато голосування за припинення одночасних ходів з наступного дня",
|
||||||
|
"vcmi.broadcast.vote.start.timer" : "Розпочато голосування за продовження таймера для всіх гравців на %s секунд",
|
||||||
|
"vcmi.broadcast.vote.hint" : "Введіть \"!vote yes\", щоб погодитися з цією зміною, або \"!vote no\", щоб проголосувати проти неї",
|
||||||
|
|
||||||
"vcmi.lobby.login.title" : "Онлайн лобі VCMI",
|
"vcmi.lobby.login.title" : "Онлайн лобі VCMI",
|
||||||
"vcmi.lobby.login.username" : "Логін:",
|
"vcmi.lobby.login.username" : "Логін:",
|
||||||
"vcmi.lobby.login.connecting" : "Підключення...",
|
"vcmi.lobby.login.connecting" : "Підключення...",
|
||||||
@ -80,6 +167,7 @@
|
|||||||
"vcmi.lobby.login.create" : "Створити акаунт",
|
"vcmi.lobby.login.create" : "Створити акаунт",
|
||||||
"vcmi.lobby.login.login" : "Увійти",
|
"vcmi.lobby.login.login" : "Увійти",
|
||||||
"vcmi.lobby.login.as" : "Увійти як %s",
|
"vcmi.lobby.login.as" : "Увійти як %s",
|
||||||
|
"vcmi.lobby.login.spectator" : "Спостерігач",
|
||||||
"vcmi.lobby.header.rooms" : "Активні кімнати - %d",
|
"vcmi.lobby.header.rooms" : "Активні кімнати - %d",
|
||||||
"vcmi.lobby.header.channels" : "Канали чату",
|
"vcmi.lobby.header.channels" : "Канали чату",
|
||||||
"vcmi.lobby.header.chat.global" : "Глобальний ігровий чат - %s", // %s -> language name
|
"vcmi.lobby.header.chat.global" : "Глобальний ігровий чат - %s", // %s -> language name
|
||||||
@ -135,11 +223,14 @@
|
|||||||
|
|
||||||
"vcmi.client.errors.invalidMap" : "{Пошкоджена карта або кампанія}\n\nНе вдалося запустити гру! Вибрана карта або кампанія може бути невірною або пошкодженою. Причина:\n%s",
|
"vcmi.client.errors.invalidMap" : "{Пошкоджена карта або кампанія}\n\nНе вдалося запустити гру! Вибрана карта або кампанія може бути невірною або пошкодженою. Причина:\n%s",
|
||||||
"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
|
"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
|
||||||
|
"vcmi.server.errors.disconnected" : "{Помилка мережі}\n\nВтрачено зв'язок з сервером гри!",
|
||||||
|
"vcmi.server.errors.playerLeft" : "{Гравець покинув гру}\n\n%s гравець від'єднався від гри!", //%s -> player color
|
||||||
"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
|
"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
|
||||||
"vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}",
|
"vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}",
|
||||||
"vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}",
|
"vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}",
|
||||||
"vcmi.server.confirmReconnect" : "Підключитися до минулої сесії?",
|
|
||||||
"vcmi.server.errors.unknownEntity" : "Не вдалося завантажити гру! У збереженій грі знайдено невідомий об'єкт '%s'! Це збереження може бути несумісним зі встановленою версією модифікацій!",
|
"vcmi.server.errors.unknownEntity" : "Не вдалося завантажити гру! У збереженій грі знайдено невідомий об'єкт '%s'! Це збереження може бути несумісним зі встановленою версією модифікацій!",
|
||||||
|
"vcmi.server.errors.wrongIdentified" : "Ви були ідентифіковані як гравець %s, хоча очікували %s",
|
||||||
|
"vcmi.server.errors.notAllowed" : "Ви не можете виконати цю дію!",
|
||||||
|
|
||||||
"vcmi.dimensionDoor.seaToLandError" : "Неможливо телепортуватися з моря на сушу або навпаки за допомогою просторової брами",
|
"vcmi.dimensionDoor.seaToLandError" : "Неможливо телепортуватися з моря на сушу або навпаки за допомогою просторової брами",
|
||||||
|
|
||||||
@ -155,6 +246,38 @@
|
|||||||
"vcmi.systemOptions.otherGroup" : "Інші налаштування",
|
"vcmi.systemOptions.otherGroup" : "Інші налаштування",
|
||||||
"vcmi.systemOptions.townsGroup" : "Екран міста",
|
"vcmi.systemOptions.townsGroup" : "Екран міста",
|
||||||
|
|
||||||
|
"vcmi.statisticWindow.statistics" : "Статистика",
|
||||||
|
"vcmi.statisticWindow.tsvCopy" : "Дані до буфера обміну",
|
||||||
|
"vcmi.statisticWindow.selectView" : "Оберіть представлення",
|
||||||
|
"vcmi.statisticWindow.value" : "Цінність",
|
||||||
|
"vcmi.statisticWindow.title.overview" : "Загальний огляд",
|
||||||
|
"vcmi.statisticWindow.title.resources" : "Ресурси",
|
||||||
|
"vcmi.statisticWindow.title.income" : "Прибуток",
|
||||||
|
"vcmi.statisticWindow.title.numberOfHeroes" : "К-сть героїв",
|
||||||
|
"vcmi.statisticWindow.title.numberOfTowns" : "К-сть міст",
|
||||||
|
"vcmi.statisticWindow.title.numberOfArtifacts" : "К-сть артефактів",
|
||||||
|
"vcmi.statisticWindow.title.numberOfDwellings" : "К-сть помешкань",
|
||||||
|
"vcmi.statisticWindow.title.numberOfMines" : "К-сть шахт",
|
||||||
|
"vcmi.statisticWindow.title.armyStrength" : "Сила армії",
|
||||||
|
"vcmi.statisticWindow.title.experience" : "Досвід",
|
||||||
|
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Витрати на армію",
|
||||||
|
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Витрати на будівництво",
|
||||||
|
"vcmi.statisticWindow.title.mapExplored" : "Ступінь вивченості карти",
|
||||||
|
"vcmi.statisticWindow.param.playerName" : "Ім'я гравця",
|
||||||
|
"vcmi.statisticWindow.param.daysSurvived" : "Прожиті дні",
|
||||||
|
"vcmi.statisticWindow.param.maxHeroLevel" : "Макс. рівень героя",
|
||||||
|
"vcmi.statisticWindow.param.battleWinRatioHero" : "Частка перемог (проти героя)",
|
||||||
|
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Частка перемог (проти нейтральних)",
|
||||||
|
"vcmi.statisticWindow.param.battlesHero" : "Боїв (проти героя)",
|
||||||
|
"vcmi.statisticWindow.param.battlesNeutral" : "Боїв (проти нейтральних)",
|
||||||
|
"vcmi.statisticWindow.param.maxArmyStrength" : "Макс. сила армії",
|
||||||
|
"vcmi.statisticWindow.param.tradeVolume" : "Обсяг торгівлі",
|
||||||
|
"vcmi.statisticWindow.param.obeliskVisited" : "Відвідано обеліск",
|
||||||
|
"vcmi.statisticWindow.icon.townCaptured" : "Місто захоплено",
|
||||||
|
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Перемогли сильного героя суперника",
|
||||||
|
"vcmi.statisticWindow.icon.grailFound" : "Грааль знайдено",
|
||||||
|
"vcmi.statisticWindow.icon.defeated" : "Переможений",
|
||||||
|
|
||||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "На весь екран (безрамкове вікно)",
|
"vcmi.systemOptions.fullscreenBorderless.hover" : "На весь екран (безрамкове вікно)",
|
||||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{На весь екран (безрамкове вікно)}\n\nЯкщо обрано, VCMI працюватиме у режимі безрамкового вікна на весь екран. У цьому режимі гра завжди використовує ту саму роздільну здатність, що й робочий стіл, ігноруючи вибрану роздільну здатність",
|
"vcmi.systemOptions.fullscreenBorderless.help" : "{На весь екран (безрамкове вікно)}\n\nЯкщо обрано, VCMI працюватиме у режимі безрамкового вікна на весь екран. У цьому режимі гра завжди використовує ту саму роздільну здатність, що й робочий стіл, ігноруючи вибрану роздільну здатність",
|
||||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "На весь екран (ексклюзивний режим)",
|
"vcmi.systemOptions.fullscreenExclusive.hover" : "На весь екран (ексклюзивний режим)",
|
||||||
@ -195,8 +318,10 @@
|
|||||||
"vcmi.adventureOptions.borderScroll.help" : "{{Прокрутка по краю}\n\nПрокручувати мапу пригод, коли курсор знаходиться біля краю вікна. Цю функцію можна вимкнути, утримуючи клавішу CTRL.",
|
"vcmi.adventureOptions.borderScroll.help" : "{{Прокрутка по краю}\n\nПрокручувати мапу пригод, коли курсор знаходиться біля краю вікна. Цю функцію можна вимкнути, утримуючи клавішу CTRL.",
|
||||||
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Керування істотами у вікні статусу",
|
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Керування істотами у вікні статусу",
|
||||||
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Керування істотами у вікні статусу}\n\nДозволяє впорядковувати істот у вікні статусу замість циклічного перемикання між типовими компонентами",
|
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Керування істотами у вікні статусу}\n\nДозволяє впорядковувати істот у вікні статусу замість циклічного перемикання між типовими компонентами",
|
||||||
"vcmi.adventureOptions.leftButtonDrag.hover" : "Переміщення мапи лівою кнопкою",
|
"vcmi.adventureOptions.leftButtonDrag.hover" : "Переміщення мапи ЛКМ",
|
||||||
"vcmi.adventureOptions.leftButtonDrag.help" : "{Переміщення мапи лівою кнопкою}\n\nЯкщо увімкнено, переміщення миші з натиснутою лівою кнопкою буде перетягувати мапу пригод",
|
"vcmi.adventureOptions.leftButtonDrag.help" : "{Переміщення мапи ЛКМ}\n\nЯкщо увімкнено, переміщення миші з натиснутою лівою кнопкою буде перетягувати мапу пригод",
|
||||||
|
"vcmi.adventureOptions.rightButtonDrag.hover" : "Переміщення мапи ПКМ",
|
||||||
|
"vcmi.adventureOptions.rightButtonDrag.help" : "{Переміщення мапи ПКМ}\n\nЯкщо увімкнено, переміщення миші з натиснутою правою кнопкою буде перетягувати мапу пригод",
|
||||||
"vcmi.adventureOptions.smoothDragging.hover" : "Плавне перетягування мапи",
|
"vcmi.adventureOptions.smoothDragging.hover" : "Плавне перетягування мапи",
|
||||||
"vcmi.adventureOptions.smoothDragging.help" : "{Плавне перетягування мапи}\n\nЯкщо увімкнено, перетягування мапи має сучасний ефект завершення.",
|
"vcmi.adventureOptions.smoothDragging.help" : "{Плавне перетягування мапи}\n\nЯкщо увімкнено, перетягування мапи має сучасний ефект завершення.",
|
||||||
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Вимкнути ефекти зникнення",
|
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Вимкнути ефекти зникнення",
|
||||||
@ -235,6 +360,8 @@
|
|||||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.",
|
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.",
|
||||||
"vcmi.battleOptions.endWithAutocombat.hover": "Завершує бій",
|
"vcmi.battleOptions.endWithAutocombat.hover": "Завершує бій",
|
||||||
"vcmi.battleOptions.endWithAutocombat.help": "{Завершує бій}\n\nАвто-бій миттєво завершує бій",
|
"vcmi.battleOptions.endWithAutocombat.help": "{Завершує бій}\n\nАвто-бій миттєво завершує бій",
|
||||||
|
"vcmi.battleOptions.showQuickSpell.hover": "Панель швидкого чарування",
|
||||||
|
"vcmi.battleOptions.showQuickSpell.help": "{Панель швидкого чарування}\n\nПоказати панель для швидкого вибору заклять.",
|
||||||
|
|
||||||
"vcmi.adventureMap.revisitObject.hover" : "Відвідати Об'єкт",
|
"vcmi.adventureMap.revisitObject.hover" : "Відвідати Об'єкт",
|
||||||
"vcmi.adventureMap.revisitObject.help" : "{Відвідати Об'єкт}\n\nЯкщо герой в даний момент стоїть на об'єкті мапи, він може знову відвідати цю локацію.",
|
"vcmi.adventureMap.revisitObject.help" : "{Відвідати Об'єкт}\n\nЯкщо герой в даний момент стоїть на об'єкті мапи, він може знову відвідати цю локацію.",
|
||||||
@ -284,6 +411,9 @@
|
|||||||
"vcmi.townHall.missingBase" : "Спочатку необхідно звести початкову будівлю: %s",
|
"vcmi.townHall.missingBase" : "Спочатку необхідно звести початкову будівлю: %s",
|
||||||
"vcmi.townHall.noCreaturesToRecruit" : "Немає істот, яких можна завербувати!",
|
"vcmi.townHall.noCreaturesToRecruit" : "Немає істот, яких можна завербувати!",
|
||||||
|
|
||||||
|
"vcmi.townStructure.bank.borrow" : "Ви заходите в банк. Вас бачить банкір і каже: 'Ми зробили для вас спеціальну пропозицію. Ви можете взяти у нас позику в розмірі 2500 золотих на 5 днів. Але щодня ви повинні будете повертати по 500 золотих'.",
|
||||||
|
"vcmi.townStructure.bank.payBack" : "Ви заходите в банк. Банкір бачить вас і каже: 'Ви вже отримали позику. Погасіть її, перш ніж брати нову позику'.",
|
||||||
|
|
||||||
"vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:",
|
"vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:",
|
||||||
"vcmi.logicalExpressions.allOf" : "Все з перерахованого:",
|
"vcmi.logicalExpressions.allOf" : "Все з перерахованого:",
|
||||||
"vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:",
|
"vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:",
|
||||||
@ -292,6 +422,13 @@
|
|||||||
"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.help" : "Сортувати артефакти в рюкзаку за вартістю.",
|
||||||
|
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Сортувати за типом",
|
||||||
|
"vcmi.heroWindow.sortBackpackBySlot.help" : "Сортувати артефакти в рюкзаку за слотом, в який цей артефакт може бути екіпірований",
|
||||||
|
"vcmi.heroWindow.sortBackpackByClass.hover" : "Сортування за рідкістю",
|
||||||
|
"vcmi.heroWindow.sortBackpackByClass.help" : "Сортувати артефакти в рюкзаку за класом рідкісності артефакту. Скарб, Малий, Великий, Реліквія",
|
||||||
|
"vcmi.heroWindow.fusingArtifact.fusing" : "Ви володієте всіма компонентами, необхідними для злиття %s. Ви бажаєте виконати злиття? {Всі компоненти буде спожито під час злиття.}",
|
||||||
|
|
||||||
"vcmi.tavernWindow.inviteHero" : "Запросити героя",
|
"vcmi.tavernWindow.inviteHero" : "Запросити героя",
|
||||||
|
|
||||||
@ -469,6 +606,8 @@
|
|||||||
"core.seerhut.quest.reachDate.visit.4" : "Закрито до %s.",
|
"core.seerhut.quest.reachDate.visit.4" : "Закрито до %s.",
|
||||||
"core.seerhut.quest.reachDate.visit.5" : "Закрито до %s.",
|
"core.seerhut.quest.reachDate.visit.5" : "Закрито до %s.",
|
||||||
|
|
||||||
|
"mapObject.core.hillFort.object.description" : "Покращує істот. Рівні 1 - 4 коштують дешевше, ніж в асоційованому місті.",
|
||||||
|
|
||||||
"core.bonus.ADDITIONAL_ATTACK.name" : "Подвійний удар",
|
"core.bonus.ADDITIONAL_ATTACK.name" : "Подвійний удар",
|
||||||
"core.bonus.ADDITIONAL_ATTACK.description" : "Атакує двічі",
|
"core.bonus.ADDITIONAL_ATTACK.description" : "Атакує двічі",
|
||||||
"core.bonus.ADDITIONAL_RETALIATION.name" : "Додаткові відплати",
|
"core.bonus.ADDITIONAL_RETALIATION.name" : "Додаткові відплати",
|
||||||
@ -610,5 +749,19 @@
|
|||||||
"core.bonus.WIDE_BREATH.name" : "Широкий подих",
|
"core.bonus.WIDE_BREATH.name" : "Широкий подих",
|
||||||
"core.bonus.WIDE_BREATH.description" : "Атака широким подихом",
|
"core.bonus.WIDE_BREATH.description" : "Атака широким подихом",
|
||||||
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Обмежена дальність стрільби",
|
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Обмежена дальність стрільби",
|
||||||
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів"
|
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів",
|
||||||
|
"core.bonus.DISINTEGRATE.description" : "Після смерті не залишається трупа",
|
||||||
|
"core.bonus.DISINTEGRATE.name" : "Розпад",
|
||||||
|
"core.bonus.ENEMY_ATTACK_REDUCTION.description" : "При атаці ігнорується ${val}% атаки нападника",
|
||||||
|
"core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Ігнорування атаки (${val}%)",
|
||||||
|
"core.bonus.FEROCITY.description" : "Атакує ${val} більше разів, якщо вбиває когось",
|
||||||
|
"core.bonus.FEROCITY.name" : "Лютість",
|
||||||
|
"core.bonus.INVINCIBLE.description" : "На нього ніщо не може вплинути",
|
||||||
|
"core.bonus.INVINCIBLE.name" : "Невразливий",
|
||||||
|
"core.bonus.MECHANICAL.description" : "Імунітет до багатьох ефектів, можна ремонтувати",
|
||||||
|
"core.bonus.MECHANICAL.name" : "Механічний",
|
||||||
|
"core.bonus.PRISM_HEX_ATTACK_BREATH.description" : "Атака подихом у трьох напрямах",
|
||||||
|
"core.bonus.PRISM_HEX_ATTACK_BREATH.name" : "Призматична атака",
|
||||||
|
"core.bonus.REVENGE.description" : "Завдає додаткової шкоди залежно від втраченого здоров'я в бою",
|
||||||
|
"core.bonus.REVENGE.name" : "Помста"
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ android {
|
|||||||
minSdk = qtMinSdkVersion as Integer
|
minSdk = qtMinSdkVersion as Integer
|
||||||
targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project
|
targetSdk = qtTargetSdkVersion as Integer // ANDROID_TARGET_SDK_VERSION in the CMake project
|
||||||
|
|
||||||
versionCode 1600
|
versionCode 1610
|
||||||
versionName "1.6.0"
|
versionName "1.6.0"
|
||||||
|
|
||||||
setProperty("archivesBaseName", "vcmi")
|
setProperty("archivesBaseName", "vcmi")
|
||||||
|
@ -430,9 +430,10 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
|
|||||||
{
|
{
|
||||||
callAllInterfaces(cl, &IGameEventsReceiver::gameOver, pack.player, pack.victoryLossCheckResult);
|
callAllInterfaces(cl, &IGameEventsReceiver::gameOver, pack.player, pack.victoryLossCheckResult);
|
||||||
|
|
||||||
|
bool localHumanWinsGame = vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && pack.victoryLossCheckResult.victory();
|
||||||
bool lastHumanEndsGame = CSH->howManyPlayerInterfaces() == 1 && vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && !settings["session"]["spectate"].Bool();
|
bool lastHumanEndsGame = CSH->howManyPlayerInterfaces() == 1 && vstd::contains(cl.playerint, pack.player) && cl.getPlayerState(pack.player)->human && !settings["session"]["spectate"].Bool();
|
||||||
|
|
||||||
if(lastHumanEndsGame)
|
if(lastHumanEndsGame || localHumanWinsGame)
|
||||||
{
|
{
|
||||||
assert(adventureInt);
|
assert(adventureInt);
|
||||||
if(adventureInt)
|
if(adventureInt)
|
||||||
|
@ -1064,7 +1064,7 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
|
|||||||
icon = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), 0, 0, 9, 1);
|
icon = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), 0, 0, 9, 1);
|
||||||
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
|
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
|
||||||
roundRect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, 15, 18), ColorRGBA(0, 0, 0, 255), ColorRGBA(241, 216, 120, 255));
|
roundRect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, 15, 18), ColorRGBA(0, 0, 0, 255), ColorRGBA(241, 216, 120, 255));
|
||||||
round = std::make_shared<CLabel>(4, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
round = std::make_shared<CLabel>(6, 9, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
||||||
|
|
||||||
Point iconPos(pos.w - 16, pos.h - 16);
|
Point iconPos(pos.w - 16, pos.h - 16);
|
||||||
|
|
||||||
@ -1105,6 +1105,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std::
|
|||||||
const auto & font = GH.renderHandler().loadFont(FONT_SMALL);
|
const auto & font = GH.renderHandler().loadFont(FONT_SMALL);
|
||||||
int len = font->getStringWidth(tmp);
|
int len = font->getStringWidth(tmp);
|
||||||
roundRect->pos.w = len + 6;
|
roundRect->pos.w = len + 6;
|
||||||
|
round->pos = Rect(roundRect->pos.center().x, roundRect->pos.center().y, 0, 0);
|
||||||
round->setText(tmp);
|
round->setText(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,15 +32,22 @@ InputSourceKeyboard::InputSourceKeyboard()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string InputSourceKeyboard::getKeyNameWithModifiers(const std::string & keyName) const
|
std::string InputSourceKeyboard::getKeyNameWithModifiers(const std::string & keyName, bool keyUp)
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|
||||||
if (isKeyboardCtrlDown())
|
if(!keyUp)
|
||||||
|
{
|
||||||
|
wasKeyboardCtrlDown = isKeyboardCtrlDown();
|
||||||
|
wasKeyboardAltDown = isKeyboardAltDown();
|
||||||
|
wasKeyboardShiftDown = isKeyboardShiftDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasKeyboardCtrlDown)
|
||||||
result += "Ctrl+";
|
result += "Ctrl+";
|
||||||
if (isKeyboardAltDown())
|
if (wasKeyboardAltDown)
|
||||||
result += "Alt+";
|
result += "Alt+";
|
||||||
if (isKeyboardShiftDown())
|
if (wasKeyboardShiftDown)
|
||||||
result += "Shift+";
|
result += "Shift+";
|
||||||
result += keyName;
|
result += keyName;
|
||||||
|
|
||||||
@ -49,7 +56,7 @@ std::string InputSourceKeyboard::getKeyNameWithModifiers(const std::string & key
|
|||||||
|
|
||||||
void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
|
void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
|
||||||
{
|
{
|
||||||
std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym));
|
std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym), false);
|
||||||
logGlobal->trace("keyboard: key '%s' pressed", keyName);
|
logGlobal->trace("keyboard: key '%s' pressed", keyName);
|
||||||
assert(key.state == SDL_PRESSED);
|
assert(key.state == SDL_PRESSED);
|
||||||
|
|
||||||
@ -111,7 +118,7 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
|
|||||||
if(key.repeat != 0)
|
if(key.repeat != 0)
|
||||||
return; // ignore periodic event resends
|
return; // ignore periodic event resends
|
||||||
|
|
||||||
std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym));
|
std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym), true);
|
||||||
logGlobal->trace("keyboard: key '%s' released", keyName);
|
logGlobal->trace("keyboard: key '%s' released", keyName);
|
||||||
|
|
||||||
if (SDL_IsTextInputActive() == SDL_TRUE)
|
if (SDL_IsTextInputActive() == SDL_TRUE)
|
||||||
|
@ -15,7 +15,11 @@ struct SDL_KeyboardEvent;
|
|||||||
/// Class that handles keyboard input from SDL events
|
/// Class that handles keyboard input from SDL events
|
||||||
class InputSourceKeyboard
|
class InputSourceKeyboard
|
||||||
{
|
{
|
||||||
std::string getKeyNameWithModifiers(const std::string & keyName) const;
|
bool wasKeyboardCtrlDown;
|
||||||
|
bool wasKeyboardAltDown;
|
||||||
|
bool wasKeyboardShiftDown;
|
||||||
|
|
||||||
|
std::string getKeyNameWithModifiers(const std::string & keyName, bool keyUp);
|
||||||
public:
|
public:
|
||||||
InputSourceKeyboard();
|
InputSourceKeyboard();
|
||||||
|
|
||||||
|
@ -327,6 +327,11 @@ bool CVideoInstance::videoEnded()
|
|||||||
return getCurrentFrame() == nullptr;
|
return getCurrentFrame() == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CVideoInstance::CVideoInstance()
|
||||||
|
: startTimeInitialized(false), deactivationStartTimeHandling(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
CVideoInstance::~CVideoInstance()
|
CVideoInstance::~CVideoInstance()
|
||||||
{
|
{
|
||||||
sws_freeContext(sws);
|
sws_freeContext(sws);
|
||||||
@ -391,8 +396,11 @@ void CVideoInstance::tick(uint32_t msPassed)
|
|||||||
if(videoEnded())
|
if(videoEnded())
|
||||||
throw std::runtime_error("Video already ended!");
|
throw std::runtime_error("Video already ended!");
|
||||||
|
|
||||||
if(startTime == std::chrono::steady_clock::time_point())
|
if(!startTimeInitialized)
|
||||||
|
{
|
||||||
startTime = std::chrono::steady_clock::now();
|
startTime = std::chrono::steady_clock::now();
|
||||||
|
startTimeInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
auto nowTime = std::chrono::steady_clock::now();
|
auto nowTime = std::chrono::steady_clock::now();
|
||||||
double difference = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - startTime).count() / 1000.0;
|
double difference = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - startTime).count() / 1000.0;
|
||||||
@ -410,17 +418,18 @@ void CVideoInstance::tick(uint32_t msPassed)
|
|||||||
|
|
||||||
void CVideoInstance::activate()
|
void CVideoInstance::activate()
|
||||||
{
|
{
|
||||||
if(deactivationStartTime != std::chrono::steady_clock::time_point())
|
if(deactivationStartTimeHandling)
|
||||||
{
|
{
|
||||||
auto pauseDuration = std::chrono::steady_clock::now() - deactivationStartTime;
|
auto pauseDuration = std::chrono::steady_clock::now() - deactivationStartTime;
|
||||||
startTime += pauseDuration;
|
startTime += pauseDuration;
|
||||||
deactivationStartTime = std::chrono::steady_clock::time_point();
|
deactivationStartTimeHandling = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CVideoInstance::deactivate()
|
void CVideoInstance::deactivate()
|
||||||
{
|
{
|
||||||
deactivationStartTime = std::chrono::steady_clock::now();
|
deactivationStartTime = std::chrono::steady_clock::now();
|
||||||
|
deactivationStartTimeHandling = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FFMpegFormatDescription
|
struct FFMpegFormatDescription
|
||||||
|
@ -78,6 +78,8 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream
|
|||||||
Point dimensions;
|
Point dimensions;
|
||||||
|
|
||||||
/// video playback start time point
|
/// video playback start time point
|
||||||
|
bool startTimeInitialized;
|
||||||
|
bool deactivationStartTimeHandling;
|
||||||
std::chrono::steady_clock::time_point startTime;
|
std::chrono::steady_clock::time_point startTime;
|
||||||
std::chrono::steady_clock::time_point deactivationStartTime;
|
std::chrono::steady_clock::time_point deactivationStartTime;
|
||||||
|
|
||||||
@ -86,6 +88,7 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream
|
|||||||
const int MAX_FRAMESKIP = 5;
|
const int MAX_FRAMESKIP = 5;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
CVideoInstance();
|
||||||
~CVideoInstance();
|
~CVideoInstance();
|
||||||
|
|
||||||
void openVideo();
|
void openVideo();
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "../renderSDL/SDL_Extensions.h"
|
#include "../renderSDL/SDL_Extensions.h"
|
||||||
|
|
||||||
|
#include "../lib/ExceptionsCommon.h"
|
||||||
#include "../lib/filesystem/Filesystem.h"
|
#include "../lib/filesystem/Filesystem.h"
|
||||||
#include "../lib/vcmi_endian.h"
|
#include "../lib/vcmi_endian.h"
|
||||||
|
|
||||||
@ -112,6 +113,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(const ImagePath & path)
|
|||||||
|
|
||||||
SDL_Surface * ret=nullptr;
|
SDL_Surface * ret=nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
auto readFile = CResourceHandler::get()->load(path)->readAll();
|
auto readFile = CResourceHandler::get()->load(path)->readAll();
|
||||||
|
|
||||||
if (isPCX(readFile.first.get()))
|
if (isPCX(readFile.first.get()))
|
||||||
@ -146,6 +148,12 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(const ImagePath & path)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (const DataLoadingException & e)
|
||||||
|
{
|
||||||
|
logGlobal->error("%s", e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// When modifying anything here please check two use cases:
|
// When modifying anything here please check two use cases:
|
||||||
// 1) Vampire mansion in Necropolis (not 1st color is transparent)
|
// 1) Vampire mansion in Necropolis (not 1st color is transparent)
|
||||||
|
@ -111,12 +111,12 @@ public:
|
|||||||
virtual bool isTransparent(const Point & coords) const = 0;
|
virtual bool isTransparent(const Point & coords) const = 0;
|
||||||
virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0;
|
virtual void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const = 0;
|
||||||
|
|
||||||
virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const = 0;
|
[[nodiscard]] virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const = 0;
|
||||||
|
|
||||||
virtual std::shared_ptr<const ISharedImage> horizontalFlip() const = 0;
|
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> horizontalFlip() const = 0;
|
||||||
virtual std::shared_ptr<const ISharedImage> verticalFlip() const = 0;
|
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> verticalFlip() const = 0;
|
||||||
virtual std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette) const = 0;
|
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const = 0;
|
||||||
virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0;
|
[[nodiscard]] virtual std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const = 0;
|
||||||
|
|
||||||
|
|
||||||
virtual ~ISharedImage() = default;
|
virtual ~ISharedImage() = default;
|
||||||
|
@ -201,7 +201,7 @@ CBitmapFont::CBitmapFont(const std::string & filename):
|
|||||||
static const std::map<std::string, EScalingAlgorithm> filterNameToEnum = {
|
static const std::map<std::string, EScalingAlgorithm> filterNameToEnum = {
|
||||||
{ "nearest", EScalingAlgorithm::NEAREST},
|
{ "nearest", EScalingAlgorithm::NEAREST},
|
||||||
{ "bilinear", EScalingAlgorithm::BILINEAR},
|
{ "bilinear", EScalingAlgorithm::BILINEAR},
|
||||||
{ "xbrz", EScalingAlgorithm::XBRZ}
|
{ "xbrz", EScalingAlgorithm::XBRZ_ALPHA}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto filterName = settings["video"]["fontUpscalingFilter"].String();
|
auto filterName = settings["video"]["fontUpscalingFilter"].String();
|
||||||
|
@ -43,6 +43,9 @@ void ImageScaled::scaleInteger(int factor)
|
|||||||
|
|
||||||
void ImageScaled::scaleTo(const Point & size)
|
void ImageScaled::scaleTo(const Point & size)
|
||||||
{
|
{
|
||||||
|
if (source)
|
||||||
|
source = source->scaleTo(size, nullptr);
|
||||||
|
|
||||||
if (body)
|
if (body)
|
||||||
body = body->scaleTo(size * GH.screenHandler().getScalingFactor(), nullptr);
|
body = body->scaleTo(size * GH.screenHandler().getScalingFactor(), nullptr);
|
||||||
}
|
}
|
||||||
@ -137,7 +140,6 @@ void ImageScaled::prepareImages()
|
|||||||
|
|
||||||
switch(blitMode)
|
switch(blitMode)
|
||||||
{
|
{
|
||||||
case EImageBlitMode::SIMPLE:
|
|
||||||
case EImageBlitMode::WITH_SHADOW:
|
case EImageBlitMode::WITH_SHADOW:
|
||||||
case EImageBlitMode::ONLY_SHADOW:
|
case EImageBlitMode::ONLY_SHADOW:
|
||||||
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
|
case EImageBlitMode::WITH_SHADOW_AND_OVERLAY:
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#include "../render/CDefFile.h"
|
#include "../render/CDefFile.h"
|
||||||
#include "../render/Graphics.h"
|
#include "../render/Graphics.h"
|
||||||
#include "../xBRZ/xbrz.h"
|
#include "../xBRZ/xbrz.h"
|
||||||
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../render/IScreenHandler.h"
|
||||||
|
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
#include <SDL_surface.h>
|
#include <SDL_surface.h>
|
||||||
@ -276,9 +278,15 @@ void SDLImageShared::optimizeSurface()
|
|||||||
margins.x += left;
|
margins.x += left;
|
||||||
margins.y += top;
|
margins.y += top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(preScaleFactor > 1 && preScaleFactor != GH.screenHandler().getScalingFactor())
|
||||||
|
{
|
||||||
|
margins.x = margins.x * GH.screenHandler().getScalingFactor() / preScaleFactor;
|
||||||
|
margins.y = margins.y * GH.screenHandler().getScalingFactor() / preScaleFactor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette) const
|
std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const
|
||||||
{
|
{
|
||||||
if (factor <= 0)
|
if (factor <= 0)
|
||||||
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
|
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
|
||||||
@ -293,7 +301,13 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL
|
|||||||
if(preScaleFactor == factor)
|
if(preScaleFactor == factor)
|
||||||
return shared_from_this();
|
return shared_from_this();
|
||||||
else if(preScaleFactor == 1)
|
else if(preScaleFactor == 1)
|
||||||
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
|
{
|
||||||
|
// dump heuristics to differentiate tileable UI elements from map object / combat assets
|
||||||
|
if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE)
|
||||||
|
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_OPAQUE);
|
||||||
|
else
|
||||||
|
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_ALPHA);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor);
|
scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor);
|
||||||
|
|
||||||
@ -589,12 +603,12 @@ void SDLImageRGB::scaleTo(const Point & size)
|
|||||||
|
|
||||||
void SDLImageIndexed::scaleInteger(int factor)
|
void SDLImageIndexed::scaleInteger(int factor)
|
||||||
{
|
{
|
||||||
image = image->scaleInteger(factor, currentPalette);
|
image = image->scaleInteger(factor, currentPalette, blitMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLImageRGB::scaleInteger(int factor)
|
void SDLImageRGB::scaleInteger(int factor)
|
||||||
{
|
{
|
||||||
image = image->scaleInteger(factor, nullptr);
|
image = image->scaleInteger(factor, nullptr, blitMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const
|
void SDLImageRGB::exportBitmap(const boost::filesystem::path & path) const
|
||||||
|
@ -57,11 +57,11 @@ public:
|
|||||||
void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override;
|
void exportBitmap(const boost::filesystem::path & path, SDL_Palette * palette) const override;
|
||||||
Point dimensions() const override;
|
Point dimensions() const override;
|
||||||
bool isTransparent(const Point & coords) const override;
|
bool isTransparent(const Point & coords) const override;
|
||||||
std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const override;
|
[[nodiscard]] std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) const override;
|
||||||
std::shared_ptr<const ISharedImage> horizontalFlip() const override;
|
[[nodiscard]] std::shared_ptr<const ISharedImage> horizontalFlip() const override;
|
||||||
std::shared_ptr<const ISharedImage> verticalFlip() const override;
|
[[nodiscard]] std::shared_ptr<const ISharedImage> verticalFlip() const override;
|
||||||
std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette) const override;
|
[[nodiscard]] std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override;
|
||||||
std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const override;
|
[[nodiscard]] std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const override;
|
||||||
|
|
||||||
friend class SDLImageLoader;
|
friend class SDLImageLoader;
|
||||||
};
|
};
|
||||||
|
@ -683,12 +683,17 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor
|
|||||||
case EScalingAlgorithm::BILINEAR:
|
case EScalingAlgorithm::BILINEAR:
|
||||||
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||||
break;
|
break;
|
||||||
case EScalingAlgorithm::XBRZ:
|
case EScalingAlgorithm::XBRZ_ALPHA:
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate](const tbb::blocked_range<size_t> & r)
|
case EScalingAlgorithm::XBRZ_OPAQUE:
|
||||||
{
|
{
|
||||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, xbrz::ColorFormat::ARGB, {}, r.begin(), r.end());
|
auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB;
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range<size_t> & r)
|
||||||
|
{
|
||||||
|
|
||||||
|
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end());
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("invalid scaling algorithm!");
|
throw std::runtime_error("invalid scaling algorithm!");
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ enum class EScalingAlgorithm : int8_t
|
|||||||
{
|
{
|
||||||
NEAREST,
|
NEAREST,
|
||||||
BILINEAR,
|
BILINEAR,
|
||||||
XBRZ
|
XBRZ_OPAQUE, // xbrz, image edges are considered to have same color as pixel inside image
|
||||||
|
XBRZ_ALPHA // xbrz, image edges are considered to be transparent
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace CSDL_Ext
|
namespace CSDL_Ext
|
||||||
|
@ -173,7 +173,7 @@ void VideoWidgetBase::tick(uint32_t msPassed)
|
|||||||
{
|
{
|
||||||
videoInstance->tick(msPassed);
|
videoInstance->tick(msPassed);
|
||||||
|
|
||||||
if(subTitle)
|
if(!videoInstance->videoEnded() && subTitle)
|
||||||
subTitle->setText(getSubTitleLine(videoInstance->timeStamp()));
|
subTitle->setText(getSubTitleLine(videoInstance->timeStamp()));
|
||||||
|
|
||||||
if(videoInstance->videoEnded())
|
if(videoInstance->videoEnded())
|
||||||
|
@ -198,8 +198,3 @@ void SettingsMainWindow::onScreenResize()
|
|||||||
if (tab)
|
if (tab)
|
||||||
tab->updateResolutionSelector();
|
tab->updateResolutionSelector();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsMainWindow::inputModeChanged(InputMode mode)
|
|
||||||
{
|
|
||||||
tabContentArea->reset();
|
|
||||||
}
|
|
||||||
|
@ -42,6 +42,5 @@ public:
|
|||||||
|
|
||||||
void showAll(Canvas & to) override;
|
void showAll(Canvas & to) override;
|
||||||
void onScreenResize() override;
|
void onScreenResize() override;
|
||||||
void inputModeChanged(InputMode mode) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1195,6 +1195,22 @@ void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ColorFormat::ARGB_CLAMPED:
|
||||||
|
switch (factor)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
return scaleImage<Scaler2x<ColorGradientARGB>, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
|
||||||
|
case 3:
|
||||||
|
return scaleImage<Scaler3x<ColorGradientARGB>, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
|
||||||
|
case 4:
|
||||||
|
return scaleImage<Scaler4x<ColorGradientARGB>, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
|
||||||
|
case 5:
|
||||||
|
return scaleImage<Scaler5x<ColorGradientARGB>, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
|
||||||
|
case 6:
|
||||||
|
return scaleImage<Scaler6x<ColorGradientARGB>, ColorDistanceARGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case ColorFormat::ARGB:
|
case ColorFormat::ARGB:
|
||||||
switch (factor)
|
switch (factor)
|
||||||
{
|
{
|
||||||
@ -1238,6 +1254,7 @@ bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, doub
|
|||||||
case ColorFormat::RGB:
|
case ColorFormat::RGB:
|
||||||
return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
|
return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
|
||||||
case ColorFormat::ARGB:
|
case ColorFormat::ARGB:
|
||||||
|
case ColorFormat::ARGB_CLAMPED:
|
||||||
return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
|
return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
|
||||||
case ColorFormat::ARGB_UNBUFFERED:
|
case ColorFormat::ARGB_UNBUFFERED:
|
||||||
return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
|
return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance;
|
||||||
|
@ -44,6 +44,7 @@ enum class ColorFormat //from high bits -> low bits, 8 bit per channel
|
|||||||
{
|
{
|
||||||
RGB, //8 bit for each red, green, blue, upper 8 bits unused
|
RGB, //8 bit for each red, green, blue, upper 8 bits unused
|
||||||
ARGB, //including alpha channel, BGRA byte order on little-endian machines
|
ARGB, //including alpha channel, BGRA byte order on little-endian machines
|
||||||
|
ARGB_CLAMPED, // like ARGB, but edges are treated as opaque, with same color as edge
|
||||||
ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time
|
ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
debian/changelog
vendored
2
debian/changelog
vendored
@ -2,7 +2,7 @@ vcmi (1.6.0) jammy; urgency=medium
|
|||||||
|
|
||||||
* New upstream release
|
* New upstream release
|
||||||
|
|
||||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 30 Aug 2024 12:00:00 +0200
|
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 20 Dec 2024 12:00:00 +0200
|
||||||
|
|
||||||
vcmi (1.5.7) jammy; urgency=medium
|
vcmi (1.5.7) jammy; urgency=medium
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
# VCMI Project
|
# VCMI Project
|
||||||
|
|
||||||
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
||||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
|
[](https://github.com/vcmi/vcmi/releases/tag/1.6.0)
|
||||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
|
|
||||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.7)
|
|
||||||
[](https://github.com/vcmi/vcmi/releases)
|
[](https://github.com/vcmi/vcmi/releases)
|
||||||
|
|
||||||
VCMI is an open-source recreation of Heroes of Might & Magic III engine, giving it new and extended possibilities.
|
VCMI is an open-source recreation of Heroes of Might & Magic III engine, giving it new and extended possibilities.
|
||||||
|
@ -15,6 +15,7 @@ set(launcher_SRCS
|
|||||||
modManager/imageviewer_moc.cpp
|
modManager/imageviewer_moc.cpp
|
||||||
modManager/chroniclesextractor.cpp
|
modManager/chroniclesextractor.cpp
|
||||||
settingsView/csettingsview_moc.cpp
|
settingsView/csettingsview_moc.cpp
|
||||||
|
startGame/StartGameTab.cpp
|
||||||
firstLaunch/firstlaunch_moc.cpp
|
firstLaunch/firstlaunch_moc.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
helper.cpp
|
helper.cpp
|
||||||
@ -46,6 +47,7 @@ set(launcher_HEADERS
|
|||||||
modManager/imageviewer_moc.h
|
modManager/imageviewer_moc.h
|
||||||
modManager/chroniclesextractor.h
|
modManager/chroniclesextractor.h
|
||||||
settingsView/csettingsview_moc.h
|
settingsView/csettingsview_moc.h
|
||||||
|
startGame/StartGameTab.h
|
||||||
firstLaunch/firstlaunch_moc.h
|
firstLaunch/firstlaunch_moc.h
|
||||||
mainwindow_moc.h
|
mainwindow_moc.h
|
||||||
languages.h
|
languages.h
|
||||||
@ -63,6 +65,7 @@ set(launcher_FORMS
|
|||||||
settingsView/csettingsview_moc.ui
|
settingsView/csettingsview_moc.ui
|
||||||
firstLaunch/firstlaunch_moc.ui
|
firstLaunch/firstlaunch_moc.ui
|
||||||
mainwindow_moc.ui
|
mainwindow_moc.ui
|
||||||
|
startGame/StartGameTab.ui
|
||||||
updatedialog_moc.ui
|
updatedialog_moc.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
|
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="1.6.0" date="2024-08-30" type="development"/>
|
<release version="1.6.0" date="2024-12-20" type="stable"/>
|
||||||
<release version="1.5.7" date="2024-08-26" type="stable"/>
|
<release version="1.5.7" date="2024-08-26" type="stable"/>
|
||||||
<release version="1.5.6" date="2024-08-04" type="stable"/>
|
<release version="1.5.6" date="2024-08-04" type="stable"/>
|
||||||
<release version="1.5.5" date="2024-07-17" type="stable"/>
|
<release version="1.5.5" date="2024-07-17" type="stable"/>
|
||||||
|
@ -295,13 +295,21 @@ bool FirstLaunchView::heroesDataDetect()
|
|||||||
QString FirstLaunchView::getHeroesInstallDir()
|
QString FirstLaunchView::getHeroesInstallDir()
|
||||||
{
|
{
|
||||||
#ifdef VCMI_WINDOWS
|
#ifdef VCMI_WINDOWS
|
||||||
QString gogPath = QSettings("HKEY_LOCAL_MACHINE\\SOFTWARE\\GOG.com\\Games\\1207658787", QSettings::NativeFormat).value("path").toString();
|
QVector<QPair<QString, QString>> regKeys = {
|
||||||
if(!gogPath.isEmpty())
|
{ "HKEY_LOCAL_MACHINE\\SOFTWARE\\GOG.com\\Games\\1207658787", "path" }, // Gog on x86 system
|
||||||
return gogPath;
|
{ "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\GOG.com\\Games\\1207658787", "path" }, // Gog on x64 system
|
||||||
|
{ "HKEY_LOCAL_MACHINE\\SOFTWARE\\New World Computing\\Heroes of Might and Magic® III\\1.0", "AppPath" }, // H3 Complete on x86 system
|
||||||
|
{ "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\New World Computing\\Heroes of Might and Magic® III\\1.0", "AppPath" }, // H3 Complete on x64 system
|
||||||
|
{ "HKEY_LOCAL_MACHINE\\SOFTWARE\\New World Computing\\Heroes of Might and Magic III\\1.0", "AppPath" }, // some localized H3 on x86 system
|
||||||
|
{ "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\New World Computing\\Heroes of Might and Magic III\\1.0", "AppPath" }, // some localized H3 on x64 system
|
||||||
|
};
|
||||||
|
|
||||||
QString cdPath = QSettings("HKEY_LOCAL_MACHINE\\SOFTWARE\\New World Computing\\Heroes of Might and Magic® III\\1.0", QSettings::NativeFormat).value("AppPath").toString();
|
for(auto & regKey : regKeys)
|
||||||
if(!cdPath.isEmpty())
|
{
|
||||||
return cdPath;
|
QString path = QSettings(regKey.first, QSettings::NativeFormat).value(regKey.second).toString();
|
||||||
|
if(!path.isEmpty())
|
||||||
|
return path;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return QString{};
|
return QString{};
|
||||||
}
|
}
|
||||||
@ -363,6 +371,9 @@ void FirstLaunchView::extractGogData()
|
|||||||
QFile(fileExe).copy(tmpFileExe);
|
QFile(fileExe).copy(tmpFileExe);
|
||||||
QFile(fileBin).copy(tmpFileBin);
|
QFile(fileBin).copy(tmpFileBin);
|
||||||
|
|
||||||
|
logGlobal->info("Installing exe '%s' ('%s')", tmpFileExe.toStdString(), fileExe.toStdString());
|
||||||
|
logGlobal->info("Installing bin '%s' ('%s')", tmpFileBin.toStdString(), fileBin.toStdString());
|
||||||
|
|
||||||
QString errorText{};
|
QString errorText{};
|
||||||
|
|
||||||
auto isGogGalaxyExe = [](QString fileToTest) {
|
auto isGogGalaxyExe = [](QString fileToTest) {
|
||||||
@ -381,7 +392,7 @@ void FirstLaunchView::extractGogData()
|
|||||||
};
|
};
|
||||||
|
|
||||||
if(isGogGalaxyExe(tmpFileExe))
|
if(isGogGalaxyExe(tmpFileExe))
|
||||||
errorText = tr("You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer!");
|
errorText = tr("You've provided a GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer!");
|
||||||
|
|
||||||
if(errorText.isEmpty())
|
if(errorText.isEmpty())
|
||||||
errorText = Innoextract::extract(tmpFileExe, tempDir.path(), [this](float progress) {
|
errorText = Innoextract::extract(tmpFileExe, tempDir.path(), [this](float progress) {
|
||||||
@ -451,7 +462,7 @@ void FirstLaunchView::copyHeroesData(const QString & path, bool move)
|
|||||||
QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs);
|
QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs);
|
||||||
QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs);
|
QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs);
|
||||||
|
|
||||||
const auto noDataMessage = tr("Failed to detect valid Heroes III data in chosen directory.\nPlease select directory with installed Heroes III data.");
|
const auto noDataMessage = tr("Failed to detect valid Heroes III data in chosen directory.\nPlease select the directory with installed Heroes III data.");
|
||||||
if(dirData.empty())
|
if(dirData.empty())
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, tr("Heroes III data not found!"), noDataMessage);
|
QMessageBox::critical(this, tr("Heroes III data not found!"), noDataMessage);
|
||||||
@ -475,12 +486,12 @@ void FirstLaunchView::copyHeroesData(const QString & path, bool move)
|
|||||||
if (!hdFiles.empty())
|
if (!hdFiles.empty())
|
||||||
{
|
{
|
||||||
// HD Edition contains only RoE data so we can't use even unmodified files from it
|
// HD Edition contains only RoE data so we can't use even unmodified files from it
|
||||||
QMessageBox::critical(this, tr("Heroes III data not found!"), tr("Heroes III: HD Edition files are not supported by VCMI.\nPlease select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death."));
|
QMessageBox::critical(this, tr("Heroes III data not found!"), tr("Heroes III: HD Edition files are not supported by VCMI.\nPlease select the directory with Heroes III: Complete Edition or Heroes III: Shadow of Death."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoE or some other unsupported edition. Demo version?
|
// RoE or some other unsupported edition. Demo version?
|
||||||
QMessageBox::critical(this, tr("Heroes III data not found!"), tr("Unknown or unsupported Heroes III version found.\nPlease select directory with Heroes III: Complete Edition or Heroes III: Shadow of Death."));
|
QMessageBox::critical(this, tr("Heroes III data not found!"), tr("Unknown or unsupported Heroes III version found.\nPlease select the directory with Heroes III: Complete Edition or Heroes III: Shadow of Death."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,15 +552,13 @@ void FirstLaunchView::modPresetUpdate()
|
|||||||
|
|
||||||
QString FirstLaunchView::findTranslationModName()
|
QString FirstLaunchView::findTranslationModName()
|
||||||
{
|
{
|
||||||
if (!getModView())
|
auto * mainWindow = dynamic_cast<MainWindow *>(QApplication::activeWindow());
|
||||||
|
auto status = mainWindow->getTranslationStatus();
|
||||||
|
|
||||||
|
if (status == ETranslationStatus::ACTIVE || status == ETranslationStatus::NOT_AVAILABLE)
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
QString preferredlanguage = QString::fromStdString(settings["general"]["language"].String());
|
QString preferredlanguage = QString::fromStdString(settings["general"]["language"].String());
|
||||||
QString installedlanguage = QString::fromStdString(settings["session"]["language"].String());
|
|
||||||
|
|
||||||
if (preferredlanguage == installedlanguage)
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
return getModView()->getTranslationModName(preferredlanguage);
|
return getModView()->getTranslationModName(preferredlanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +55,6 @@ class FirstLaunchView : public QWidget
|
|||||||
bool checkCanInstallExtras();
|
bool checkCanInstallExtras();
|
||||||
bool checkCanInstallMod(const QString & modID);
|
bool checkCanInstallMod(const QString & modID);
|
||||||
|
|
||||||
void installMod(const QString & modID);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FirstLaunchView(QWidget * parent = nullptr);
|
explicit FirstLaunchView(QWidget * parent = nullptr);
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="installerTabs">
|
<widget class="QStackedWidget" name="installerTabs">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>2</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="pageLanguageSelect">
|
<widget class="QWidget" name="pageLanguageSelect">
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
@ -177,9 +177,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Thank you for installing VCMI!
|
<string>Thank you for installing VCMI!
|
||||||
|
|
||||||
Before you can start playing, there are a few more steps that need to be completed.
|
Before you can start playing, there are a few more steps to complete.
|
||||||
|
|
||||||
Please keep in mind that in order to use VCMI you must own the original data files for Heroes® of Might and Magic® III: Complete or The Shadow of Death.
|
Please remember that to use VCMI, you must own the original data files for Heroes® of Might and Magic® III: Complete or The Shadow of Death.
|
||||||
|
|
||||||
Heroes® of Might and Magic® III HD is currently not supported!</string>
|
Heroes® of Might and Magic® III HD is currently not supported!</string>
|
||||||
</property>
|
</property>
|
||||||
@ -307,7 +307,7 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>You can manually copy directories Maps, Data and Mp3 from the original game directory to VCMI data directory that you can see on top of this page</string>
|
<string>You can manually copy directories Maps, Data, and Mp3 from the original game directory to the VCMI data directory that you can see on top of this page</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
@ -501,8 +501,8 @@ Heroes® of Might and Magic® III HD is currently not supported!</string>
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>If you own Heroes III on gog.com you can download backup offline installer from gog.com, and VCMI will import Heroes III data using offline installer.
|
<string>If you own Heroes III on gog.com, you can download a backup offline installer from gog.com. VCMI will then import Heroes III data using the offline installer.
|
||||||
Offline installer consists of two parts, .exe and .bin. Make sure you download both of them.</string>
|
Offline installer consists of two files: ".exe" and ".bin" - you must download both.</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="textFormat">
|
<property name="textFormat">
|
||||||
<enum>Qt::PlainText</enum>
|
<enum>Qt::PlainText</enum>
|
||||||
@ -785,7 +785,7 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Install mod that provides various interface improvements, such as better interface for random maps and selectable actions in battles</string>
|
<string>Install mod that provides various interface improvements, such as a better interface for random maps and selectable actions in battles</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -809,9 +809,9 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b
|
|||||||
<string>In The Wake of Gods</string>
|
<string>In The Wake of Gods</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../resources.qrc">
|
||||||
<normaloff>:/icons/mod-disabled.png</normaloff>
|
<normaloff>:/icons/mod-disabled.png</normaloff>
|
||||||
<normalon>:icons/mod-enabled.png</normalon>:icons/mod-disabled.png</iconset>
|
<normalon>:icons/mod-enabled.png</normalon>:/icons/mod-disabled.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -876,6 +876,8 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources>
|
||||||
|
<include location="../resources.qrc"/>
|
||||||
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -83,6 +83,8 @@ QString Innoextract::getHashError(QString exeFile, QString binFile, QString exeF
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::vector<data> knownHashes = {
|
std::vector<data> knownHashes = {
|
||||||
|
// { H3_COMPLETE, "english", 973162040, 0, "7cf1ecec73e8c2f2c2619415cd16749be5641942", "" }, // setup_homm_3_complete_4.0_(10665).exe
|
||||||
|
// { H3_COMPLETE, "french", ???, 0, "7e5a737c51530a1888033d188ab0635825ee622f", "" }, // setup_homm_3_complete_french_4.0_(10665).exe
|
||||||
{ H3_COMPLETE, "english", 822520, 1005040617, "66646a353b06417fa12c6384405688c84a315cc1", "c624e2071f4e35386765ab044ad5860ac245b7f4" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(28740).exe
|
{ H3_COMPLETE, "english", 822520, 1005040617, "66646a353b06417fa12c6384405688c84a315cc1", "c624e2071f4e35386765ab044ad5860ac245b7f4" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(28740).exe
|
||||||
{ H3_COMPLETE, "french", 824960, 997305870, "072f1d4466ff16444d8c7949c6530448a9c53cfa", "9b6b451d2bd2f8b4be159e62fa6d32e87ee10455" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(french)_(28740).exe
|
{ H3_COMPLETE, "french", 824960, 997305870, "072f1d4466ff16444d8c7949c6530448a9c53cfa", "9b6b451d2bd2f8b4be159e62fa6d32e87ee10455" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(french)_(28740).exe
|
||||||
{ H3_COMPLETE, "polish", 822288, 849286313, "74ffde00156dd5a8e237668f87213387f0dd9c7c", "2523cf9943043ae100186f89e4ebf7c28be09804" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(polish)_(28740).exe
|
{ H3_COMPLETE, "polish", 822288, 849286313, "74ffde00156dd5a8e237668f87213387f0dd9c7c", "2523cf9943043ae100186f89e4ebf7c28be09804" }, // setup_heroes_of_might_and_magic_3_complete_4.0_(polish)_(28740).exe
|
||||||
|
@ -47,7 +47,6 @@ void MainWindow::computeSidePanelSizes()
|
|||||||
ui->modslistButton,
|
ui->modslistButton,
|
||||||
ui->settingsButton,
|
ui->settingsButton,
|
||||||
ui->aboutButton,
|
ui->aboutButton,
|
||||||
ui->startEditorButton,
|
|
||||||
ui->startGameButton
|
ui->startGameButton
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -78,11 +77,12 @@ MainWindow::MainWindow(QWidget * parent)
|
|||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
setAcceptDrops(true);
|
||||||
|
|
||||||
setWindowIcon(QIcon{":/icons/menu-game.png"});
|
setWindowIcon(QIcon{":/icons/menu-game.png"});
|
||||||
ui->modslistButton->setIcon(QIcon{":/icons/menu-mods.png"});
|
ui->modslistButton->setIcon(QIcon{":/icons/menu-mods.png"});
|
||||||
ui->settingsButton->setIcon(QIcon{":/icons/menu-settings.png"});
|
ui->settingsButton->setIcon(QIcon{":/icons/menu-settings.png"});
|
||||||
ui->aboutButton->setIcon(QIcon{":/icons/about-project.png"});
|
ui->aboutButton->setIcon(QIcon{":/icons/about-project.png"});
|
||||||
ui->startEditorButton->setIcon(QIcon{":/icons/menu-editor.png"});
|
|
||||||
ui->startGameButton->setIcon(QIcon{":/icons/menu-game.png"});
|
ui->startGameButton->setIcon(QIcon{":/icons/menu-game.png"});
|
||||||
|
|
||||||
#ifndef VCMI_MOBILE
|
#ifndef VCMI_MOBILE
|
||||||
@ -101,16 +101,12 @@ MainWindow::MainWindow(QWidget * parent)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef ENABLE_EDITOR
|
|
||||||
ui->startEditorButton->hide();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
computeSidePanelSizes();
|
computeSidePanelSizes();
|
||||||
|
|
||||||
bool h3DataFound = CResourceHandler::get()->existsResource(ResourcePath("DATA/GENRLTXT.TXT"));
|
bool h3DataFound = CResourceHandler::get()->existsResource(ResourcePath("DATA/GENRLTXT.TXT"));
|
||||||
|
|
||||||
if (h3DataFound && setupCompleted)
|
if (h3DataFound && setupCompleted)
|
||||||
ui->tabListWidget->setCurrentIndex(TabRows::MODS);
|
ui->tabListWidget->setCurrentIndex(TabRows::START);
|
||||||
else
|
else
|
||||||
enterSetup();
|
enterSetup();
|
||||||
|
|
||||||
@ -147,7 +143,6 @@ void MainWindow::detectPreferredLanguage()
|
|||||||
void MainWindow::enterSetup()
|
void MainWindow::enterSetup()
|
||||||
{
|
{
|
||||||
ui->startGameButton->setEnabled(false);
|
ui->startGameButton->setEnabled(false);
|
||||||
ui->startEditorButton->setEnabled(false);
|
|
||||||
ui->settingsButton->setEnabled(false);
|
ui->settingsButton->setEnabled(false);
|
||||||
ui->aboutButton->setEnabled(false);
|
ui->aboutButton->setEnabled(false);
|
||||||
ui->modslistButton->setEnabled(false);
|
ui->modslistButton->setEnabled(false);
|
||||||
@ -160,16 +155,27 @@ void MainWindow::exitSetup()
|
|||||||
writer->Bool() = true;
|
writer->Bool() = true;
|
||||||
|
|
||||||
ui->startGameButton->setEnabled(true);
|
ui->startGameButton->setEnabled(true);
|
||||||
ui->startEditorButton->setEnabled(true);
|
|
||||||
ui->settingsButton->setEnabled(true);
|
ui->settingsButton->setEnabled(true);
|
||||||
ui->aboutButton->setEnabled(true);
|
ui->aboutButton->setEnabled(true);
|
||||||
ui->modslistButton->setEnabled(true);
|
ui->modslistButton->setEnabled(true);
|
||||||
ui->tabListWidget->setCurrentIndex(TabRows::MODS);
|
ui->tabListWidget->setCurrentIndex(TabRows::MODS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::switchToStartTab()
|
||||||
|
{
|
||||||
|
ui->startGameButton->setEnabled(true);
|
||||||
|
ui->startGameButton->setChecked(true);
|
||||||
|
ui->tabListWidget->setCurrentIndex(TabRows::START);
|
||||||
|
|
||||||
|
auto* startGameTabWidget = qobject_cast<StartGameTab*>(ui->tabListWidget->widget(TabRows::START));
|
||||||
|
if(startGameTabWidget)
|
||||||
|
startGameTabWidget->refreshState();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::switchToModsTab()
|
void MainWindow::switchToModsTab()
|
||||||
{
|
{
|
||||||
ui->startGameButton->setEnabled(true);
|
ui->startGameButton->setEnabled(true);
|
||||||
|
ui->modslistButton->setChecked(true);
|
||||||
ui->tabListWidget->setCurrentIndex(TabRows::MODS);
|
ui->tabListWidget->setCurrentIndex(TabRows::MODS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,14 +202,7 @@ MainWindow::~MainWindow()
|
|||||||
|
|
||||||
void MainWindow::on_startGameButton_clicked()
|
void MainWindow::on_startGameButton_clicked()
|
||||||
{
|
{
|
||||||
hide();
|
switchToStartTab();
|
||||||
startGame({});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::on_startEditorButton_clicked()
|
|
||||||
{
|
|
||||||
hide();
|
|
||||||
startEditor({});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CModListView * MainWindow::getModView()
|
CModListView * MainWindow::getModView()
|
||||||
@ -228,6 +227,95 @@ void MainWindow::on_aboutButton_clicked()
|
|||||||
ui->tabListWidget->setCurrentIndex(TabRows::ABOUT);
|
ui->tabListWidget->setCurrentIndex(TabRows::ABOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::dragEnterEvent(QDragEnterEvent* event)
|
||||||
|
{
|
||||||
|
if(event->mimeData()->hasUrls())
|
||||||
|
for(const auto & url : event->mimeData()->urls())
|
||||||
|
for(const auto & ending : QStringList({".zip", ".h3m", ".h3c", ".vmap", ".vcmp", ".json", ".exe"}))
|
||||||
|
if(url.fileName().endsWith(ending, Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
event->acceptProposedAction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::dropEvent(QDropEvent* event)
|
||||||
|
{
|
||||||
|
const QMimeData* mimeData = event->mimeData();
|
||||||
|
|
||||||
|
if(mimeData->hasUrls())
|
||||||
|
{
|
||||||
|
const QList<QUrl> urlList = mimeData->urls();
|
||||||
|
for (const auto & url : urlList)
|
||||||
|
manualInstallFile(url.toLocalFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::manualInstallFile(QString filePath)
|
||||||
|
{
|
||||||
|
if(filePath.endsWith(".zip", Qt::CaseInsensitive) || filePath.endsWith(".exe", Qt::CaseInsensitive))
|
||||||
|
switchToModsTab();
|
||||||
|
|
||||||
|
QString fileName = QFileInfo{filePath}.fileName();
|
||||||
|
if(filePath.endsWith(".zip", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
QString filenameClean = fileName.toLower()
|
||||||
|
// mod name currently comes from zip file -> remove suffixes from github zip download
|
||||||
|
.replace(QRegularExpression("-[0-9a-f]{40}"), "")
|
||||||
|
.replace(QRegularExpression("-vcmi-.+\\.zip"), ".zip")
|
||||||
|
.replace("-main.zip", ".zip");
|
||||||
|
|
||||||
|
getModView()->downloadFile(filenameClean, QUrl::fromLocalFile(filePath), "mods");
|
||||||
|
}
|
||||||
|
else if(filePath.endsWith(".json", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
QDir configDir(QString::fromStdString(VCMIDirs::get().userConfigPath().string()));
|
||||||
|
QStringList configFile = configDir.entryList({fileName}, QDir::Filter::Files); // case insensitive check
|
||||||
|
if(!configFile.empty())
|
||||||
|
{
|
||||||
|
auto dialogResult = QMessageBox::warning(this, tr("Replace config file?"), tr("Do you want to replace %1?").arg(configFile[0]), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||||
|
if(dialogResult == QMessageBox::Yes)
|
||||||
|
{
|
||||||
|
const auto configFilePath = configDir.filePath(configFile[0]);
|
||||||
|
QFile::remove(configFilePath);
|
||||||
|
QFile::copy(filePath, configFilePath);
|
||||||
|
|
||||||
|
// reload settings
|
||||||
|
Helper::loadSettings();
|
||||||
|
for(const auto widget : qApp->allWidgets())
|
||||||
|
if(auto settingsView = qobject_cast<CSettingsView *>(widget))
|
||||||
|
settingsView->loadSettings();
|
||||||
|
|
||||||
|
getModView()->reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
getModView()->installFiles(QStringList{filePath});
|
||||||
|
}
|
||||||
|
|
||||||
|
ETranslationStatus MainWindow::getTranslationStatus()
|
||||||
|
{
|
||||||
|
QString preferredlanguage = QString::fromStdString(settings["general"]["language"].String());
|
||||||
|
QString installedlanguage = QString::fromStdString(settings["session"]["language"].String());
|
||||||
|
|
||||||
|
if (preferredlanguage == installedlanguage)
|
||||||
|
return ETranslationStatus::ACTIVE;
|
||||||
|
|
||||||
|
QString modName = getModView()->getTranslationModName(preferredlanguage);
|
||||||
|
|
||||||
|
if (modName.isEmpty())
|
||||||
|
return ETranslationStatus::NOT_AVAILABLE;
|
||||||
|
|
||||||
|
if (!getModView()->isModInstalled(modName))
|
||||||
|
return ETranslationStatus::NOT_INSTALLLED;
|
||||||
|
|
||||||
|
if (!getModView()->isModEnabled(modName))
|
||||||
|
return ETranslationStatus::DISABLED;
|
||||||
|
|
||||||
|
return ETranslationStatus::ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::updateTranslation()
|
void MainWindow::updateTranslation()
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_QT_TRANSLATIONS
|
#ifdef ENABLE_QT_TRANSLATIONS
|
||||||
|
@ -23,6 +23,14 @@ class QTableWidgetItem;
|
|||||||
class CModList;
|
class CModList;
|
||||||
class CModListView;
|
class CModListView;
|
||||||
|
|
||||||
|
enum class ETranslationStatus : int8_t
|
||||||
|
{
|
||||||
|
NOT_AVAILABLE, // translation for this language was not found in mod list. Could also happen if player is offline or disabled repository checkout
|
||||||
|
NOT_INSTALLLED, // translation mod found, but it is not installed
|
||||||
|
DISABLED, // translation mod found, and installed, but toggled off
|
||||||
|
ACTIVE // translation mod active OR game is already in specified language (e.g. English H3 for players with English language)
|
||||||
|
};
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -40,6 +48,7 @@ class MainWindow : public QMainWindow
|
|||||||
SETTINGS = 1,
|
SETTINGS = 1,
|
||||||
SETUP = 2,
|
SETUP = 2,
|
||||||
ABOUT = 3,
|
ABOUT = 3,
|
||||||
|
START = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -55,6 +64,13 @@ public:
|
|||||||
void enterSetup();
|
void enterSetup();
|
||||||
void exitSetup();
|
void exitSetup();
|
||||||
void switchToModsTab();
|
void switchToModsTab();
|
||||||
|
void switchToStartTab();
|
||||||
|
|
||||||
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
|
void dropEvent(QDropEvent *event) override;
|
||||||
|
|
||||||
|
void manualInstallFile(QString filePath);
|
||||||
|
ETranslationStatus getTranslationStatus();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent * event) override;
|
void changeEvent(QEvent * event) override;
|
||||||
@ -65,6 +81,5 @@ public slots:
|
|||||||
private slots:
|
private slots:
|
||||||
void on_modslistButton_clicked();
|
void on_modslistButton_clicked();
|
||||||
void on_settingsButton_clicked();
|
void on_settingsButton_clicked();
|
||||||
void on_startEditorButton_clicked();
|
|
||||||
void on_aboutButton_clicked();
|
void on_aboutButton_clicked();
|
||||||
};
|
};
|
||||||
|
@ -29,6 +29,57 @@
|
|||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="startGameButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>10</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Game</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="autoExclusive">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="toolButtonStyle">
|
||||||
|
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||||
|
</property>
|
||||||
|
<property name="autoRaise">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QToolButton" name="modslistButton">
|
<widget class="QToolButton" name="modslistButton">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -62,7 +113,7 @@
|
|||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="checked">
|
<property name="checked">
|
||||||
<bool>true</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="autoExclusive">
|
<property name="autoExclusive">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -146,8 +197,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
<size>
|
<size>
|
||||||
<width>32</width>
|
<width>48</width>
|
||||||
<height>32</height>
|
<height>48</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
@ -180,104 +231,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="startEditorButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>5</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Map Editor</string>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="toolButtonStyle">
|
|
||||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
|
||||||
</property>
|
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="startGameButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>10</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Start game</string>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>64</width>
|
|
||||||
<height>64</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="toolButtonStyle">
|
|
||||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
|
||||||
</property>
|
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -292,12 +245,13 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>3</number>
|
<number>4</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="CModListView" name="modlistView"/>
|
<widget class="CModListView" name="modlistView"/>
|
||||||
<widget class="CSettingsView" name="settingsView"/>
|
<widget class="CSettingsView" name="settingsView"/>
|
||||||
<widget class="FirstLaunchView" name="setupView"/>
|
<widget class="FirstLaunchView" name="setupView"/>
|
||||||
<widget class="AboutProjectView" name="aboutView"/>
|
<widget class="AboutProjectView" name="aboutView"/>
|
||||||
|
<widget class="StartGameTab" name="startGameView"/>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -329,6 +283,12 @@
|
|||||||
<header>aboutProject/aboutproject_moc.h</header>
|
<header>aboutProject/aboutproject_moc.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>StartGameTab</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>startGame/StartGameTab.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
@ -41,38 +41,26 @@ void ChroniclesExtractor::removeTempDir()
|
|||||||
tempDir.removeRecursively();
|
tempDir.removeRecursively();
|
||||||
}
|
}
|
||||||
|
|
||||||
int ChroniclesExtractor::getChronicleNo(QFile & file)
|
int ChroniclesExtractor::getChronicleNo()
|
||||||
{
|
{
|
||||||
if(!file.open(QIODevice::ReadOnly))
|
QStringList appDirCandidates = tempDir.entryList({"app"}, QDir::Filter::Dirs);
|
||||||
{
|
|
||||||
QMessageBox::critical(parent, tr("File cannot opened"), file.errorString());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray magic{"MZ"};
|
if (!appDirCandidates.empty())
|
||||||
QByteArray magicFile = file.read(magic.length());
|
|
||||||
if(!magicFile.startsWith(magic))
|
|
||||||
{
|
{
|
||||||
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an gog installer file!"));
|
QDir appDir = tempDir.filePath(appDirCandidates.front());
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray dataBegin = file.read(1'000'000);
|
for (size_t i = 1; i < chronicles.size(); ++i)
|
||||||
int chronicle = 0;
|
|
||||||
for (const auto& kv : chronicles) {
|
|
||||||
if(dataBegin.contains(kv.second))
|
|
||||||
{
|
{
|
||||||
chronicle = kv.first;
|
QString chronicleName = chronicles.at(i);
|
||||||
break;
|
QStringList chroniclesDirCandidates = appDir.entryList({chronicleName}, QDir::Filter::Dirs);
|
||||||
|
|
||||||
|
if (!chroniclesDirCandidates.empty())
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!chronicle)
|
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select a Heroes Chronicles installer file!"));
|
||||||
{
|
|
||||||
QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an chronicle installer file!"));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return chronicle;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChroniclesExtractor::extractGogInstaller(QString file)
|
bool ChroniclesExtractor::extractGogInstaller(QString file)
|
||||||
{
|
{
|
||||||
@ -129,16 +117,14 @@ void ChroniclesExtractor::createBaseMod() const
|
|||||||
|
|
||||||
for(auto & dataPath : VCMIDirs::get().dataPaths())
|
for(auto & dataPath : VCMIDirs::get().dataPaths())
|
||||||
{
|
{
|
||||||
auto file = dataPath / "config" / "heroes" / "portraitsChronicles.json";
|
auto file = pathToQString(dataPath / "config" / "heroes" / "portraitsChronicles.json");
|
||||||
auto destFolder = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "config";
|
auto destFolder = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "config";
|
||||||
if(boost::filesystem::exists(file))
|
auto destFile = pathToQString(destFolder / "portraitsChronicles.json");
|
||||||
|
if(QFile::exists(file))
|
||||||
{
|
{
|
||||||
boost::filesystem::create_directories(destFolder);
|
QDir().mkpath(pathToQString(destFolder));
|
||||||
#if BOOST_VERSION >= 107400
|
QFile::remove(destFile);
|
||||||
boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_options::overwrite_existing);
|
QFile::copy(file, destFile);
|
||||||
#else
|
|
||||||
boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_option::overwrite_if_exists);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,14 +135,13 @@ void ChroniclesExtractor::createChronicleMod(int no)
|
|||||||
dir.removeRecursively();
|
dir.removeRecursively();
|
||||||
dir.mkpath(".");
|
dir.mkpath(".");
|
||||||
|
|
||||||
QByteArray tmpChronicles = chronicles.at(no);
|
QString tmpChronicles = chronicles.at(no);
|
||||||
tmpChronicles.replace('\0', "");
|
|
||||||
|
|
||||||
QJsonObject mod
|
QJsonObject mod
|
||||||
{
|
{
|
||||||
{ "modType", "Expansion" },
|
{ "modType", "Expansion" },
|
||||||
{ "name", QString::number(no) + " - " + QString(tmpChronicles) },
|
{ "name", QString("%1 - %2").arg(no).arg(tmpChronicles) },
|
||||||
{ "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + QString(tmpChronicles) },
|
{ "description", tr("Heroes Chronicles %1 - %2").arg(no).arg(tmpChronicles) },
|
||||||
{ "author", "3DO" },
|
{ "author", "3DO" },
|
||||||
{ "version", "1.0" },
|
{ "version", "1.0" },
|
||||||
{ "contact", "vcmi.eu" },
|
{ "contact", "vcmi.eu" },
|
||||||
@ -173,8 +158,7 @@ void ChroniclesExtractor::createChronicleMod(int no)
|
|||||||
|
|
||||||
void ChroniclesExtractor::extractFiles(int no) const
|
void ChroniclesExtractor::extractFiles(int no) const
|
||||||
{
|
{
|
||||||
QByteArray tmpChronicles = chronicles.at(no);
|
QString tmpChronicles = chronicles.at(no);
|
||||||
tmpChronicles.replace('\0', "");
|
|
||||||
|
|
||||||
std::string chroniclesDir = "chronicles_" + std::to_string(no);
|
std::string chroniclesDir = "chronicles_" + std::to_string(no);
|
||||||
QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front());
|
QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front());
|
||||||
@ -230,26 +214,46 @@ void ChroniclesExtractor::extractFiles(int no) const
|
|||||||
|
|
||||||
void ChroniclesExtractor::installChronicles(QStringList exe)
|
void ChroniclesExtractor::installChronicles(QStringList exe)
|
||||||
{
|
{
|
||||||
|
logGlobal->info("Installing Chronicles");
|
||||||
|
|
||||||
extractionFile = -1;
|
extractionFile = -1;
|
||||||
fileCount = exe.size();
|
fileCount = exe.size();
|
||||||
for(QString f : exe)
|
for(QString f : exe)
|
||||||
{
|
{
|
||||||
extractionFile++;
|
extractionFile++;
|
||||||
QFile file(f);
|
|
||||||
|
|
||||||
int chronicleNo = getChronicleNo(file);
|
|
||||||
if(!chronicleNo)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
|
logGlobal->info("Creating temporary directory");
|
||||||
if(!createTempDir())
|
if(!createTempDir())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if(!extractGogInstaller(f))
|
logGlobal->info("Copying offline installer");
|
||||||
|
// FIXME: this is required at the moment for Android (and possibly iOS)
|
||||||
|
// Incoming file names are in content URI form, e.g. content://media/internal/chronicles.exe
|
||||||
|
// Qt can handle those like it does regular files
|
||||||
|
// however, innoextract fails to open such files
|
||||||
|
// so make a copy in directory to which vcmi always has full access and operate on it
|
||||||
|
QString filepath = tempDir.filePath("chr.exe");
|
||||||
|
QFile(f).copy(filepath);
|
||||||
|
QFile file(filepath);
|
||||||
|
|
||||||
|
logGlobal->info("Extracting offline installer");
|
||||||
|
if(!extractGogInstaller(filepath))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
logGlobal->info("Detecting Chronicle");
|
||||||
|
int chronicleNo = getChronicleNo();
|
||||||
|
if(!chronicleNo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
logGlobal->info("Creating base Chronicle mod");
|
||||||
createBaseMod();
|
createBaseMod();
|
||||||
|
|
||||||
|
logGlobal->info("Creating Chronicle mod");
|
||||||
createChronicleMod(chronicleNo);
|
createChronicleMod(chronicleNo);
|
||||||
|
|
||||||
|
logGlobal->info("Removing temporary directory");
|
||||||
removeTempDir();
|
removeTempDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logGlobal->info("Chronicles installed");
|
||||||
}
|
}
|
||||||
|
@ -24,21 +24,22 @@ class ChroniclesExtractor : public QObject
|
|||||||
|
|
||||||
bool createTempDir();
|
bool createTempDir();
|
||||||
void removeTempDir();
|
void removeTempDir();
|
||||||
int getChronicleNo(QFile & file);
|
int getChronicleNo();
|
||||||
bool extractGogInstaller(QString filePath);
|
bool extractGogInstaller(QString filePath);
|
||||||
void createBaseMod() const;
|
void createBaseMod() const;
|
||||||
void createChronicleMod(int no);
|
void createChronicleMod(int no);
|
||||||
void extractFiles(int no) const;
|
void extractFiles(int no) const;
|
||||||
|
|
||||||
const std::map<int, QByteArray> chronicles = {
|
const QStringList chronicles = {
|
||||||
{1, QByteArray{reinterpret_cast<const char*>(u"Warlords of the Wasteland"), 50}},
|
{}, // fake 0th "chronicle", to create 1-based list
|
||||||
{2, QByteArray{reinterpret_cast<const char*>(u"Conquest of the Underworld"), 52}},
|
"Warlords of the Wasteland",
|
||||||
{3, QByteArray{reinterpret_cast<const char*>(u"Masters of the Elements"), 46}},
|
"Conquest of the Underworld",
|
||||||
{4, QByteArray{reinterpret_cast<const char*>(u"Clash of the Dragons"), 40}},
|
"Masters of the Elements",
|
||||||
{5, QByteArray{reinterpret_cast<const char*>(u"The World Tree"), 28}},
|
"Clash of the Dragons",
|
||||||
{6, QByteArray{reinterpret_cast<const char*>(u"The Fiery Moon"), 28}},
|
"The World Tree",
|
||||||
{7, QByteArray{reinterpret_cast<const char*>(u"Revolt of the Beastmasters"), 52}},
|
"The Fiery Moon",
|
||||||
{8, QByteArray{reinterpret_cast<const char*>(u"The Sword of Frost"), 36}}
|
"Revolt of the Beastmasters",
|
||||||
|
"The Sword of Frost",
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
void installChronicles(QStringList exe);
|
void installChronicles(QStringList exe);
|
||||||
|
@ -27,12 +27,13 @@
|
|||||||
#include "../vcmiqt/jsonutils.h"
|
#include "../vcmiqt/jsonutils.h"
|
||||||
#include "../helper.h"
|
#include "../helper.h"
|
||||||
|
|
||||||
#include "../../lib/VCMIDirs.h"
|
|
||||||
#include "../../lib/CConfigHandler.h"
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/texts/Languages.h"
|
#include "../../lib/VCMIDirs.h"
|
||||||
#include "../../lib/modding/CModVersion.h"
|
|
||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
|
#include "../../lib/json/JsonUtils.h"
|
||||||
|
#include "../../lib/modding/CModVersion.h"
|
||||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||||
|
#include "../../lib/texts/Languages.h"
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ void CModListView::setupModModel()
|
|||||||
|
|
||||||
modStateModel = std::make_shared<ModStateModel>();
|
modStateModel = std::make_shared<ModStateModel>();
|
||||||
if (!cachedRepositoryData.isNull())
|
if (!cachedRepositoryData.isNull())
|
||||||
modStateModel->appendRepositories(cachedRepositoryData);
|
modStateModel->setRepositoryData(cachedRepositoryData);
|
||||||
|
|
||||||
modModel = new ModStateItemModel(modStateModel, this);
|
modModel = new ModStateItemModel(modStateModel, this);
|
||||||
manager = std::make_unique<ModStateController>(modStateModel);
|
manager = std::make_unique<ModStateController>(modStateModel);
|
||||||
@ -54,35 +55,11 @@ void CModListView::changeEvent(QEvent *event)
|
|||||||
if(event->type() == QEvent::LanguageChange)
|
if(event->type() == QEvent::LanguageChange)
|
||||||
{
|
{
|
||||||
ui->retranslateUi(this);
|
ui->retranslateUi(this);
|
||||||
modModel->reloadRepositories();
|
modModel->reloadViewModel();
|
||||||
}
|
}
|
||||||
QWidget::changeEvent(event);
|
QWidget::changeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CModListView::dragEnterEvent(QDragEnterEvent* event)
|
|
||||||
{
|
|
||||||
if(event->mimeData()->hasUrls())
|
|
||||||
for(const auto & url : event->mimeData()->urls())
|
|
||||||
for(const auto & ending : QStringList({".zip", ".h3m", ".h3c", ".vmap", ".vcmp", ".json", ".exe"}))
|
|
||||||
if(url.fileName().endsWith(ending, Qt::CaseInsensitive))
|
|
||||||
{
|
|
||||||
event->acceptProposedAction();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CModListView::dropEvent(QDropEvent* event)
|
|
||||||
{
|
|
||||||
const QMimeData* mimeData = event->mimeData();
|
|
||||||
|
|
||||||
if(mimeData->hasUrls())
|
|
||||||
{
|
|
||||||
const QList<QUrl> urlList = mimeData->urls();
|
|
||||||
for (const auto & url : urlList)
|
|
||||||
manualInstallFile(url.toLocalFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CModListView::setupFilterModel()
|
void CModListView::setupFilterModel()
|
||||||
{
|
{
|
||||||
filterModel = new CModFilterModel(modModel, this);
|
filterModel = new CModFilterModel(modModel, this);
|
||||||
@ -134,8 +111,6 @@ CModListView::CModListView(QWidget * parent)
|
|||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
setAcceptDrops(true);
|
|
||||||
|
|
||||||
ui->uninstallButton->setIcon(QIcon{":/icons/mod-delete.png"});
|
ui->uninstallButton->setIcon(QIcon{":/icons/mod-delete.png"});
|
||||||
ui->enableButton->setIcon(QIcon{":/icons/mod-enabled.png"});
|
ui->enableButton->setIcon(QIcon{":/icons/mod-enabled.png"});
|
||||||
ui->disableButton->setIcon(QIcon{":/icons/mod-disabled.png"});
|
ui->disableButton->setIcon(QIcon{":/icons/mod-disabled.png"});
|
||||||
@ -152,7 +127,7 @@ CModListView::CModListView(QWidget * parent)
|
|||||||
ui->progressWidget->setVisible(false);
|
ui->progressWidget->setVisible(false);
|
||||||
dlManager = nullptr;
|
dlManager = nullptr;
|
||||||
|
|
||||||
modModel->reloadRepositories();
|
modModel->reloadViewModel();
|
||||||
if(settings["launcher"]["autoCheckRepositories"].Bool())
|
if(settings["launcher"]["autoCheckRepositories"].Bool())
|
||||||
loadRepositories();
|
loadRepositories();
|
||||||
|
|
||||||
@ -169,8 +144,16 @@ CModListView::CModListView(QWidget * parent)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CModListView::reload()
|
||||||
|
{
|
||||||
|
modStateModel->reloadLocalState();
|
||||||
|
modModel->reloadViewModel();
|
||||||
|
}
|
||||||
|
|
||||||
void CModListView::loadRepositories()
|
void CModListView::loadRepositories()
|
||||||
{
|
{
|
||||||
|
accumulatedRepositoryData.clear();
|
||||||
|
|
||||||
QStringList repositories;
|
QStringList repositories;
|
||||||
|
|
||||||
if (settings["launcher"]["defaultRepositoryEnabled"].Bool())
|
if (settings["launcher"]["defaultRepositoryEnabled"].Bool())
|
||||||
@ -461,9 +444,10 @@ void CModListView::selectMod(const QModelIndex & index)
|
|||||||
Helper::enableScrollBySwiping(ui->modInfoBrowser);
|
Helper::enableScrollBySwiping(ui->modInfoBrowser);
|
||||||
Helper::enableScrollBySwiping(ui->changelogBrowser);
|
Helper::enableScrollBySwiping(ui->changelogBrowser);
|
||||||
|
|
||||||
QStringList notInstalledDependencies = this->getModsToInstall(modName);
|
QStringList notInstalledDependencies = getModsToInstall(modName);
|
||||||
QStringList unavailableDependencies = this->findUnavailableMods(notInstalledDependencies);
|
QStringList unavailableDependencies = findUnavailableMods(notInstalledDependencies);
|
||||||
bool translationMismatch = mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage().toStdString();
|
bool translationMismatch = mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage().toStdString();
|
||||||
|
bool modIsBeingDownloaded = enqueuedModDownloads.contains(mod.getID());
|
||||||
|
|
||||||
ui->disableButton->setVisible(modStateModel->isModInstalled(mod.getID()) && modStateModel->isModEnabled(mod.getID()));
|
ui->disableButton->setVisible(modStateModel->isModInstalled(mod.getID()) && modStateModel->isModEnabled(mod.getID()));
|
||||||
ui->enableButton->setVisible(modStateModel->isModInstalled(mod.getID()) && !modStateModel->isModEnabled(mod.getID()));
|
ui->enableButton->setVisible(modStateModel->isModInstalled(mod.getID()) && !modStateModel->isModEnabled(mod.getID()));
|
||||||
@ -474,9 +458,9 @@ void CModListView::selectMod(const QModelIndex & index)
|
|||||||
// Block buttons if action is not allowed at this time
|
// Block buttons if action is not allowed at this time
|
||||||
ui->disableButton->setEnabled(true);
|
ui->disableButton->setEnabled(true);
|
||||||
ui->enableButton->setEnabled(notInstalledDependencies.empty() && !translationMismatch);
|
ui->enableButton->setEnabled(notInstalledDependencies.empty() && !translationMismatch);
|
||||||
ui->installButton->setEnabled(unavailableDependencies.empty());
|
ui->installButton->setEnabled(unavailableDependencies.empty() && !modIsBeingDownloaded);
|
||||||
ui->uninstallButton->setEnabled(true);
|
ui->uninstallButton->setEnabled(true);
|
||||||
ui->updateButton->setEnabled(unavailableDependencies.empty());
|
ui->updateButton->setEnabled(unavailableDependencies.empty() && !modIsBeingDownloaded);
|
||||||
|
|
||||||
loadScreenshots();
|
loadScreenshots();
|
||||||
}
|
}
|
||||||
@ -564,9 +548,6 @@ QStringList CModListView::getModsToInstall(QString mod)
|
|||||||
candidates.pop_back();
|
candidates.pop_back();
|
||||||
processed.push_back(potentialToInstall);
|
processed.push_back(potentialToInstall);
|
||||||
|
|
||||||
if (modStateModel->isModExists(potentialToInstall) && modStateModel->isModInstalled(potentialToInstall))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (modStateModel->isSubmod(potentialToInstall))
|
if (modStateModel->isSubmod(potentialToInstall))
|
||||||
{
|
{
|
||||||
QString topParent = modStateModel->getTopParent(potentialToInstall);
|
QString topParent = modStateModel->getTopParent(potentialToInstall);
|
||||||
@ -580,6 +561,7 @@ QStringList CModListView::getModsToInstall(QString mod)
|
|||||||
potentialToInstall = modStateModel->getTopParent(potentialToInstall);
|
potentialToInstall = modStateModel->getTopParent(potentialToInstall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modStateModel->isModExists(potentialToInstall) && !modStateModel->isModInstalled(potentialToInstall))
|
||||||
result.push_back(potentialToInstall);
|
result.push_back(potentialToInstall);
|
||||||
|
|
||||||
if (modStateModel->isModExists(potentialToInstall))
|
if (modStateModel->isModExists(potentialToInstall))
|
||||||
@ -599,17 +581,24 @@ QStringList CModListView::getModsToInstall(QString mod)
|
|||||||
void CModListView::on_updateButton_clicked()
|
void CModListView::on_updateButton_clicked()
|
||||||
{
|
{
|
||||||
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
|
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
|
||||||
|
doUpdateMod(modName);
|
||||||
|
|
||||||
|
ui->updateButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::doUpdateMod(const QString & modName)
|
||||||
|
{
|
||||||
auto targetMod = modStateModel->getMod(modName);
|
auto targetMod = modStateModel->getMod(modName);
|
||||||
|
|
||||||
if(targetMod.isUpdateAvailable())
|
if(targetMod.isUpdateAvailable())
|
||||||
downloadFile(modName + ".zip", targetMod.getDownloadUrl(), modName, targetMod.getDownloadSizeBytes());
|
downloadMod(targetMod);
|
||||||
|
|
||||||
for(const auto & name : getModsToInstall(modName))
|
for(const auto & name : getModsToInstall(modName))
|
||||||
{
|
{
|
||||||
auto mod = modStateModel->getMod(name);
|
auto mod = modStateModel->getMod(name);
|
||||||
// update required mod, install missing (can be new dependency)
|
// update required mod, install missing (can be new dependency)
|
||||||
if(mod.isUpdateAvailable() || !mod.isInstalled())
|
if(mod.isUpdateAvailable() || !mod.isInstalled())
|
||||||
downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeBytes());
|
downloadMod(mod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,7 +611,7 @@ void CModListView::on_uninstallButton_clicked()
|
|||||||
if(modStateModel->isModEnabled(modName))
|
if(modStateModel->isModEnabled(modName))
|
||||||
manager->disableMod(modName);
|
manager->disableMod(modName);
|
||||||
manager->uninstallMod(modName);
|
manager->uninstallMod(modName);
|
||||||
modModel->reloadRepositories();
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkManagerErrors();
|
checkManagerErrors();
|
||||||
@ -632,81 +621,18 @@ void CModListView::on_installButton_clicked()
|
|||||||
{
|
{
|
||||||
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
|
QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
|
||||||
|
|
||||||
for(const auto & name : getModsToInstall(modName))
|
doInstallMod(modName);
|
||||||
{
|
|
||||||
auto mod = modStateModel->getMod(name);
|
ui->installButton->setEnabled(false);
|
||||||
if(mod.isAvailable())
|
|
||||||
downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeBytes());
|
|
||||||
else if(!modStateModel->isModEnabled(name))
|
|
||||||
enableModByName(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CModListView::on_installFromFileButton_clicked()
|
void CModListView::downloadMod(const ModState & mod)
|
||||||
{
|
{
|
||||||
// iOS can't display modal dialogs when called directly on button press
|
if (enqueuedModDownloads.contains(mod.getID()))
|
||||||
// https://bugreports.qt.io/browse/QTBUG-98651
|
return;
|
||||||
QTimer::singleShot(0, this, [this]
|
|
||||||
{
|
|
||||||
QString filter = tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json *.exe);;" +
|
|
||||||
tr("Maps") + " (*.h3m *.vmap);;" +
|
|
||||||
tr("Campaigns") + " (*.h3c *.vcmp);;" +
|
|
||||||
tr("Configs") + " (*.json);;" +
|
|
||||||
tr("Mods") + " (*.zip);;" +
|
|
||||||
tr("Gog files") + " (*.exe)";
|
|
||||||
#if defined(VCMI_MOBILE)
|
|
||||||
filter = tr("All files (*.*)"); //Workaround for sometimes incorrect mime for some extensions (e.g. for exe)
|
|
||||||
#endif
|
|
||||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns, gog files) to install..."), QDir::homePath(), filter);
|
|
||||||
|
|
||||||
for(const auto & file : files)
|
enqueuedModDownloads.push_back(mod.getID());
|
||||||
{
|
downloadFile(mod.getID() + ".zip", mod.getDownloadUrl(), mod.getName(), mod.getDownloadSizeBytes());
|
||||||
manualInstallFile(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void CModListView::manualInstallFile(QString filePath)
|
|
||||||
{
|
|
||||||
QString fileName = QFileInfo{filePath}.fileName();
|
|
||||||
if(filePath.endsWith(".zip", Qt::CaseInsensitive))
|
|
||||||
downloadFile(fileName.toLower()
|
|
||||||
// mod name currently comes from zip file -> remove suffixes from github zip download
|
|
||||||
.replace(QRegularExpression("-[0-9a-f]{40}"), "")
|
|
||||||
.replace(QRegularExpression("-vcmi-.+\\.zip"), ".zip")
|
|
||||||
.replace("-main.zip", ".zip")
|
|
||||||
, QUrl::fromLocalFile(filePath), "mods");
|
|
||||||
else if(filePath.endsWith(".json", Qt::CaseInsensitive))
|
|
||||||
{
|
|
||||||
QDir configDir(QString::fromStdString(VCMIDirs::get().userConfigPath().string()));
|
|
||||||
QStringList configFile = configDir.entryList({fileName}, QDir::Filter::Files); // case insensitive check
|
|
||||||
if(!configFile.empty())
|
|
||||||
{
|
|
||||||
auto dialogResult = QMessageBox::warning(this, tr("Replace config file?"), tr("Do you want to replace %1?").arg(configFile[0]), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
|
||||||
if(dialogResult == QMessageBox::Yes)
|
|
||||||
{
|
|
||||||
const auto configFilePath = configDir.filePath(configFile[0]);
|
|
||||||
QFile::remove(configFilePath);
|
|
||||||
QFile::copy(filePath, configFilePath);
|
|
||||||
|
|
||||||
// reload settings
|
|
||||||
Helper::loadSettings();
|
|
||||||
for(const auto widget : qApp->allWidgets())
|
|
||||||
if(auto settingsView = qobject_cast<CSettingsView *>(widget))
|
|
||||||
settingsView->loadSettings();
|
|
||||||
|
|
||||||
modStateModel->reloadLocalState();
|
|
||||||
modModel->reloadRepositories();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
downloadFile(fileName, QUrl::fromLocalFile(filePath), fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CModListView::downloadFile(QString file, QString url, QString description, qint64 sizeBytes)
|
|
||||||
{
|
|
||||||
downloadFile(file, QUrl{url}, description, sizeBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CModListView::downloadFile(QString file, QUrl url, QString description, qint64 sizeBytes)
|
void CModListView::downloadFile(QString file, QUrl url, QString description, qint64 sizeBytes)
|
||||||
@ -779,6 +705,7 @@ void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFi
|
|||||||
doInstallFiles = true;
|
doInstallFiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enqueuedModDownloads.clear();
|
||||||
dlManager->deleteLater();
|
dlManager->deleteLater();
|
||||||
dlManager = nullptr;
|
dlManager = nullptr;
|
||||||
|
|
||||||
@ -807,7 +734,7 @@ void CModListView::installFiles(QStringList files)
|
|||||||
QStringList maps;
|
QStringList maps;
|
||||||
QStringList images;
|
QStringList images;
|
||||||
QStringList exe;
|
QStringList exe;
|
||||||
JsonNode repository;
|
bool repositoryFilesEnqueued = false;
|
||||||
|
|
||||||
// TODO: some better way to separate zip's with mods and downloaded repository files
|
// TODO: some better way to separate zip's with mods and downloaded repository files
|
||||||
for(QString filename : files)
|
for(QString filename : files)
|
||||||
@ -821,7 +748,7 @@ void CModListView::installFiles(QStringList files)
|
|||||||
else if(filename.endsWith(".json", Qt::CaseInsensitive))
|
else if(filename.endsWith(".json", Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
//download and merge additional files
|
//download and merge additional files
|
||||||
const auto &repoData = JsonUtils::jsonFromFile(filename);
|
JsonNode repoData = JsonUtils::jsonFromFile(filename);
|
||||||
if(repoData["name"].isNull())
|
if(repoData["name"].isNull())
|
||||||
{
|
{
|
||||||
// This is main repository index. Download all referenced mods
|
// This is main repository index. Download all referenced mods
|
||||||
@ -830,9 +757,12 @@ void CModListView::installFiles(QStringList files)
|
|||||||
auto modNameLower = boost::algorithm::to_lower_copy(modName);
|
auto modNameLower = boost::algorithm::to_lower_copy(modName);
|
||||||
auto modJsonUrl = modJson["mod"];
|
auto modJsonUrl = modJson["mod"];
|
||||||
if(!modJsonUrl.isNull())
|
if(!modJsonUrl.isNull())
|
||||||
|
{
|
||||||
downloadFile(QString::fromStdString(modName + ".json"), QString::fromStdString(modJsonUrl.String()), tr("mods repository index"));
|
downloadFile(QString::fromStdString(modName + ".json"), QString::fromStdString(modJsonUrl.String()), tr("mods repository index"));
|
||||||
|
repositoryFilesEnqueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
repository[modNameLower] = modJson;
|
accumulatedRepositoryData[modNameLower] = modJson;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -840,35 +770,46 @@ void CModListView::installFiles(QStringList files)
|
|||||||
// This is json of a single mod. Extract name of mod and add it to repo
|
// This is json of a single mod. Extract name of mod and add it to repo
|
||||||
auto modName = QFileInfo(filename).baseName().toStdString();
|
auto modName = QFileInfo(filename).baseName().toStdString();
|
||||||
auto modNameLower = boost::algorithm::to_lower_copy(modName);
|
auto modNameLower = boost::algorithm::to_lower_copy(modName);
|
||||||
repository[modNameLower] = repoData;
|
JsonUtils::merge(accumulatedRepositoryData[modNameLower], repoData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(filename.endsWith(".png", Qt::CaseInsensitive))
|
else if(filename.endsWith(".png", Qt::CaseInsensitive))
|
||||||
images.push_back(filename);
|
images.push_back(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!repository.isNull())
|
if (!accumulatedRepositoryData.isNull() && !repositoryFilesEnqueued)
|
||||||
{
|
{
|
||||||
manager->appendRepositories(repository);
|
logGlobal->info("Installing repository: started");
|
||||||
modModel->reloadRepositories();
|
manager->setRepositoryData(accumulatedRepositoryData);
|
||||||
|
modModel->reloadViewModel();
|
||||||
|
accumulatedRepositoryData.clear();
|
||||||
|
|
||||||
static const QString repositoryCachePath = CLauncherDirs::downloadsPath() + "/repositoryCache.json";
|
static const QString repositoryCachePath = CLauncherDirs::downloadsPath() + "/repositoryCache.json";
|
||||||
JsonUtils::jsonToFile(repositoryCachePath, modStateModel->getRepositoryData());
|
JsonUtils::jsonToFile(repositoryCachePath, modStateModel->getRepositoryData());
|
||||||
|
logGlobal->info("Installing repository: ended");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mods.empty())
|
if(!mods.empty())
|
||||||
{
|
{
|
||||||
|
logGlobal->info("Installing mods: started");
|
||||||
installMods(mods);
|
installMods(mods);
|
||||||
modStateModel->reloadLocalState();
|
reload();
|
||||||
modModel->reloadRepositories();
|
logGlobal->info("Installing mods: ended");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!maps.empty())
|
if(!maps.empty())
|
||||||
|
{
|
||||||
|
logGlobal->info("Installing maps: started");
|
||||||
installMaps(maps);
|
installMaps(maps);
|
||||||
|
logGlobal->info("Installing maps: ended");
|
||||||
|
}
|
||||||
|
|
||||||
if(!exe.empty())
|
if(!exe.empty())
|
||||||
{
|
{
|
||||||
ui->progressBar->setFormat(tr("Installing chronicles"));
|
logGlobal->info("Installing chronicles: started");
|
||||||
|
ui->progressBar->setFormat(tr("Installing Heroes Chronicles"));
|
||||||
|
ui->progressWidget->setVisible(true);
|
||||||
|
ui->pushButton->setEnabled(false);
|
||||||
|
|
||||||
float prog = 0.0;
|
float prog = 0.0;
|
||||||
|
|
||||||
@ -876,6 +817,8 @@ void CModListView::installFiles(QStringList files)
|
|||||||
{
|
{
|
||||||
ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; });
|
ChroniclesExtractor ce(this, [&prog](float progress) { prog = progress; });
|
||||||
ce.installChronicles(exe);
|
ce.installChronicles(exe);
|
||||||
|
reload();
|
||||||
|
enableModByName("chronicles");
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -887,10 +830,13 @@ void CModListView::installFiles(QStringList files)
|
|||||||
|
|
||||||
if(futureExtract.get())
|
if(futureExtract.get())
|
||||||
{
|
{
|
||||||
|
hideProgressBar();
|
||||||
|
ui->pushButton->setEnabled(true);
|
||||||
|
ui->progressWidget->setVisible(false);
|
||||||
//update
|
//update
|
||||||
modStateModel->reloadLocalState();
|
reload();
|
||||||
modModel->reloadRepositories();
|
|
||||||
}
|
}
|
||||||
|
logGlobal->info("Installing chronicles: ended");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!images.empty())
|
if(!images.empty())
|
||||||
@ -914,8 +860,9 @@ void CModListView::installMods(QStringList archives)
|
|||||||
// uninstall old version of mod, if installed
|
// uninstall old version of mod, if installed
|
||||||
for(QString mod : modNames)
|
for(QString mod : modNames)
|
||||||
{
|
{
|
||||||
if(modStateModel->getMod(mod).isInstalled())
|
if(modStateModel->isModExists(mod) && modStateModel->getMod(mod).isInstalled())
|
||||||
{
|
{
|
||||||
|
logGlobal->info("Uninstalling old version of mod '%s'", mod.toStdString());
|
||||||
if (modStateModel->isModEnabled(mod))
|
if (modStateModel->isModEnabled(mod))
|
||||||
modsToEnable.push_back(mod);
|
modsToEnable.push_back(mod);
|
||||||
|
|
||||||
@ -928,20 +875,30 @@ void CModListView::installMods(QStringList archives)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload(); // FIXME: better way that won't reset selection
|
||||||
|
|
||||||
for(int i = 0; i < modNames.size(); i++)
|
for(int i = 0; i < modNames.size(); i++)
|
||||||
{
|
{
|
||||||
|
logGlobal->info("Installing mod '%s'", modNames[i].toStdString());
|
||||||
ui->progressBar->setFormat(tr("Installing mod %1").arg(modNames[i]));
|
ui->progressBar->setFormat(tr("Installing mod %1").arg(modNames[i]));
|
||||||
manager->installMod(modNames[i], archives[i]);
|
manager->installMod(modNames[i], archives[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload();
|
||||||
|
|
||||||
if (!modsToEnable.empty())
|
if (!modsToEnable.empty())
|
||||||
|
{
|
||||||
manager->enableMods(modsToEnable);
|
manager->enableMods(modsToEnable);
|
||||||
|
}
|
||||||
|
|
||||||
checkManagerErrors();
|
checkManagerErrors();
|
||||||
|
|
||||||
for(QString archive : archives)
|
for(QString archive : archives)
|
||||||
|
{
|
||||||
|
logGlobal->info("Erasing archive '%s'", archive.toStdString());
|
||||||
QFile::remove(archive);
|
QFile::remove(archive);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CModListView::installMaps(QStringList maps)
|
void CModListView::installMaps(QStringList maps)
|
||||||
{
|
{
|
||||||
@ -949,6 +906,7 @@ void CModListView::installMaps(QStringList maps)
|
|||||||
|
|
||||||
for(QString map : maps)
|
for(QString map : maps)
|
||||||
{
|
{
|
||||||
|
logGlobal->info("Importing map '%s'", map.toStdString());
|
||||||
QFile(map).rename(destDir + map.section('/', -1, -1));
|
QFile(map).rename(destDir + map.section('/', -1, -1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1036,11 +994,13 @@ void CModListView::on_screenshotsList_clicked(const QModelIndex & index)
|
|||||||
|
|
||||||
void CModListView::doInstallMod(const QString & modName)
|
void CModListView::doInstallMod(const QString & modName)
|
||||||
{
|
{
|
||||||
for(const auto & name : modStateModel->getMod(modName).getDependencies())
|
for(const auto & name : getModsToInstall(modName))
|
||||||
{
|
{
|
||||||
auto mod = modStateModel->getMod(name);
|
auto mod = modStateModel->getMod(name);
|
||||||
if(!mod.isInstalled())
|
if(mod.isAvailable())
|
||||||
downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeBytes());
|
downloadMod(mod);
|
||||||
|
else if(!modStateModel->isModEnabled(name))
|
||||||
|
enableModByName(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1060,6 +1020,39 @@ bool CModListView::isModInstalled(const QString & modName)
|
|||||||
return mod.isInstalled();
|
return mod.isInstalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList CModListView::getInstalledChronicles()
|
||||||
|
{
|
||||||
|
QStringList result;
|
||||||
|
|
||||||
|
for(const auto & modName : modStateModel->getAllMods())
|
||||||
|
{
|
||||||
|
auto mod = modStateModel->getMod(modName);
|
||||||
|
if (!mod.isInstalled())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (mod.getTopParentID() != "chronicles")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result += modName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CModListView::getUpdateableMods()
|
||||||
|
{
|
||||||
|
QStringList result;
|
||||||
|
|
||||||
|
for(const auto & modName : modStateModel->getAllMods())
|
||||||
|
{
|
||||||
|
auto mod = modStateModel->getMod(modName);
|
||||||
|
if (mod.isUpdateAvailable())
|
||||||
|
result.push_back(modName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QString CModListView::getTranslationModName(const QString & language)
|
QString CModListView::getTranslationModName(const QString & language)
|
||||||
{
|
{
|
||||||
for(const auto & modName : modStateModel->getAllMods())
|
for(const auto & modName : modStateModel->getAllMods())
|
||||||
@ -1123,3 +1116,34 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CModListView::createNewPreset(const QString & presetName)
|
||||||
|
{
|
||||||
|
modStateModel->createNewPreset(presetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::deletePreset(const QString & presetName)
|
||||||
|
{
|
||||||
|
modStateModel->deletePreset(presetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::activatePreset(const QString & presetName)
|
||||||
|
{
|
||||||
|
modStateModel->activatePreset(presetName);
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::renamePreset(const QString & oldPresetName, const QString & newPresetName)
|
||||||
|
{
|
||||||
|
modStateModel->renamePreset(oldPresetName, newPresetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CModListView::getAllPresets() const
|
||||||
|
{
|
||||||
|
return modStateModel->getAllPresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CModListView::getActivePreset() const
|
||||||
|
{
|
||||||
|
return modStateModel->getActivePreset();
|
||||||
|
}
|
||||||
|
@ -36,6 +36,9 @@ class CModListView : public QWidget
|
|||||||
ModStateItemModel * modModel;
|
ModStateItemModel * modModel;
|
||||||
CModFilterModel * filterModel;
|
CModFilterModel * filterModel;
|
||||||
CDownloadManager * dlManager;
|
CDownloadManager * dlManager;
|
||||||
|
JsonNode accumulatedRepositoryData;
|
||||||
|
|
||||||
|
QStringList enqueuedModDownloads;
|
||||||
|
|
||||||
void setupModModel();
|
void setupModModel();
|
||||||
void setupFilterModel();
|
void setupFilterModel();
|
||||||
@ -52,20 +55,13 @@ class CModListView : public QWidget
|
|||||||
// find mods unknown to mod list (not present in repo and not installed)
|
// find mods unknown to mod list (not present in repo and not installed)
|
||||||
QStringList findUnavailableMods(QStringList candidates);
|
QStringList findUnavailableMods(QStringList candidates);
|
||||||
|
|
||||||
void manualInstallFile(QString filePath);
|
|
||||||
void downloadFile(QString file, QString url, QString description, qint64 sizeBytes = 0);
|
|
||||||
void downloadFile(QString file, QUrl url, QString description, qint64 sizeBytes = 0);
|
|
||||||
|
|
||||||
void installMods(QStringList archives);
|
void installMods(QStringList archives);
|
||||||
void installMaps(QStringList maps);
|
void installMaps(QStringList maps);
|
||||||
void installFiles(QStringList mods);
|
|
||||||
|
|
||||||
QString genChangelogText(const ModState & mod);
|
QString genChangelogText(const ModState & mod);
|
||||||
QString genModInfoText(const ModState & mod);
|
QString genModInfoText(const ModState & mod);
|
||||||
|
|
||||||
void changeEvent(QEvent *event) override;
|
void changeEvent(QEvent *event) override;
|
||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
|
||||||
void dropEvent(QDropEvent *event) override;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CModListView(QWidget * parent = nullptr);
|
explicit CModListView(QWidget * parent = nullptr);
|
||||||
@ -74,6 +70,8 @@ public:
|
|||||||
void loadScreenshots();
|
void loadScreenshots();
|
||||||
void loadRepositories();
|
void loadRepositories();
|
||||||
|
|
||||||
|
void reload();
|
||||||
|
|
||||||
void disableModInfo();
|
void disableModInfo();
|
||||||
|
|
||||||
void selectMod(const QModelIndex & index);
|
void selectMod(const QModelIndex & index);
|
||||||
@ -83,18 +81,43 @@ public:
|
|||||||
/// install mod by name
|
/// install mod by name
|
||||||
void doInstallMod(const QString & modName);
|
void doInstallMod(const QString & modName);
|
||||||
|
|
||||||
|
/// update mod by name
|
||||||
|
void doUpdateMod(const QString & modName);
|
||||||
|
|
||||||
/// returns true if mod is available in repository and can be installed
|
/// returns true if mod is available in repository and can be installed
|
||||||
bool isModAvailable(const QString & modName);
|
bool isModAvailable(const QString & modName);
|
||||||
|
|
||||||
/// finds translation mod for specified languages. Returns empty string on error
|
/// finds translation mod for specified languages. Returns empty string on error
|
||||||
QString getTranslationModName(const QString & language);
|
QString getTranslationModName(const QString & language);
|
||||||
|
|
||||||
|
/// finds all already imported Heroes Chronicles mods (if any)
|
||||||
|
QStringList getInstalledChronicles();
|
||||||
|
|
||||||
|
/// finds all mods that can be updated
|
||||||
|
QStringList getUpdateableMods();
|
||||||
|
|
||||||
|
void createNewPreset(const QString & presetName);
|
||||||
|
|
||||||
|
void deletePreset(const QString & presetName);
|
||||||
|
|
||||||
|
void activatePreset(const QString & presetName);
|
||||||
|
|
||||||
|
void renamePreset(const QString & oldPresetName, const QString & newPresetName);
|
||||||
|
|
||||||
|
QStringList getAllPresets() const;
|
||||||
|
|
||||||
|
QString getActivePreset() const;
|
||||||
|
|
||||||
/// returns true if mod is currently enabled
|
/// returns true if mod is currently enabled
|
||||||
bool isModEnabled(const QString & modName);
|
bool isModEnabled(const QString & modName);
|
||||||
|
|
||||||
/// returns true if mod is currently installed
|
/// returns true if mod is currently installed
|
||||||
bool isModInstalled(const QString & modName);
|
bool isModInstalled(const QString & modName);
|
||||||
|
|
||||||
|
void downloadMod(const ModState & mod);
|
||||||
|
void downloadFile(QString file, QUrl url, QString description, qint64 sizeBytes = 0);
|
||||||
|
void installFiles(QStringList mods);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void enableModByName(QString modName);
|
void enableModByName(QString modName);
|
||||||
void disableModByName(QString modName);
|
void disableModByName(QString modName);
|
||||||
@ -109,31 +132,17 @@ private slots:
|
|||||||
void hideProgressBar();
|
void hideProgressBar();
|
||||||
|
|
||||||
void on_lineEdit_textChanged(const QString & arg1);
|
void on_lineEdit_textChanged(const QString & arg1);
|
||||||
|
|
||||||
void on_comboBox_currentIndexChanged(int index);
|
void on_comboBox_currentIndexChanged(int index);
|
||||||
|
|
||||||
void on_enableButton_clicked();
|
void on_enableButton_clicked();
|
||||||
|
|
||||||
void on_disableButton_clicked();
|
void on_disableButton_clicked();
|
||||||
|
|
||||||
void on_updateButton_clicked();
|
void on_updateButton_clicked();
|
||||||
|
|
||||||
void on_uninstallButton_clicked();
|
void on_uninstallButton_clicked();
|
||||||
|
|
||||||
void on_installButton_clicked();
|
void on_installButton_clicked();
|
||||||
|
|
||||||
void on_installFromFileButton_clicked();
|
|
||||||
|
|
||||||
void on_pushButton_clicked();
|
void on_pushButton_clicked();
|
||||||
|
|
||||||
void on_refreshButton_clicked();
|
void on_refreshButton_clicked();
|
||||||
|
|
||||||
void on_allModsView_activated(const QModelIndex & index);
|
void on_allModsView_activated(const QModelIndex & index);
|
||||||
|
|
||||||
void on_tabWidget_currentChanged(int index);
|
void on_tabWidget_currentChanged(int index);
|
||||||
|
|
||||||
void on_screenshotsList_clicked(const QModelIndex & index);
|
void on_screenshotsList_clicked(const QModelIndex & index);
|
||||||
|
|
||||||
void on_allModsView_doubleClicked(const QModelIndex &index);
|
void on_allModsView_doubleClicked(const QModelIndex &index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -42,6 +42,9 @@
|
|||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string>Filter</string>
|
<string>Filter</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="clearButtonEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -191,7 +194,9 @@
|
|||||||
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||||
p, li { white-space: pre-wrap; }
|
p, li { white-space: pre-wrap; }
|
||||||
hr { height: 1px; border-width: 0; }
|
hr { height: 1px; border-width: 0; }
|
||||||
</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;">
|
li.unchecked::marker { content: "\2610"; }
|
||||||
|
li.checked::marker { content: "\2612"; }
|
||||||
|
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html></string>
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:11pt;"><br /></p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="openExternalLinks">
|
<property name="openExternalLinks">
|
||||||
@ -317,6 +322,9 @@ hr { height: 1px; border-width: 0; }
|
|||||||
<property name="value">
|
<property name="value">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
<property name="textVisible">
|
<property name="textVisible">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -349,41 +357,6 @@ hr { height: 1px; border-width: 0; }
|
|||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>6</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="installFromFileButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>51</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>170</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Install from file</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset>
|
|
||||||
<normaloff>icons:mod-download.png</normaloff>icons:mod-download.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<spacer name="modButtonSpacer">
|
<spacer name="modButtonSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -72,9 +72,9 @@ ModStateController::ModStateController(std::shared_ptr<ModStateModel> modList)
|
|||||||
|
|
||||||
ModStateController::~ModStateController() = default;
|
ModStateController::~ModStateController() = default;
|
||||||
|
|
||||||
void ModStateController::appendRepositories(const JsonNode & repomap)
|
void ModStateController::setRepositoryData(const JsonNode & repomap)
|
||||||
{
|
{
|
||||||
modList->appendRepositories(repomap);
|
modList->setRepositoryData(repomap);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModStateController::addError(QString modname, QString message)
|
bool ModStateController::addError(QString modname, QString message)
|
||||||
@ -120,6 +120,9 @@ bool ModStateController::disableMod(QString modname)
|
|||||||
|
|
||||||
bool ModStateController::canInstallMod(QString modname)
|
bool ModStateController::canInstallMod(QString modname)
|
||||||
{
|
{
|
||||||
|
if (!modList->isModExists(modname))
|
||||||
|
return true; // for installation of unknown mods, e.g. via "Install from file" option
|
||||||
|
|
||||||
auto mod = modList->getMod(modname);
|
auto mod = modList->getMod(modname);
|
||||||
|
|
||||||
if(mod.isSubmod())
|
if(mod.isSubmod())
|
||||||
@ -155,7 +158,7 @@ bool ModStateController::canEnableMod(QString modname)
|
|||||||
|
|
||||||
//check for compatibility
|
//check for compatibility
|
||||||
if(!mod.isCompatible())
|
if(!mod.isCompatible())
|
||||||
return addError(modname, tr("Mod is not compatible, please update VCMI and checkout latest mod revisions"));
|
return addError(modname, tr("Mod is not compatible, please update VCMI and check the latest mod revisions"));
|
||||||
|
|
||||||
if (mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage().toStdString())
|
if (mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage().toStdString())
|
||||||
return addError(modname, tr("Can not enable translation mod for a different language!"));
|
return addError(modname, tr("Can not enable translation mod for a different language!"));
|
||||||
@ -189,9 +192,6 @@ bool ModStateController::doInstallMod(QString modname, QString archivePath)
|
|||||||
if(!QFile(archivePath).exists())
|
if(!QFile(archivePath).exists())
|
||||||
return addError(modname, tr("Mod archive is missing"));
|
return addError(modname, tr("Mod archive is missing"));
|
||||||
|
|
||||||
if(localMods.contains(modname))
|
|
||||||
return addError(modname, tr("Mod with such name is already installed"));
|
|
||||||
|
|
||||||
std::vector<std::string> filesToExtract;
|
std::vector<std::string> filesToExtract;
|
||||||
QString modDirName = ::detectModArchive(archivePath, modname, filesToExtract);
|
QString modDirName = ::detectModArchive(archivePath, modname, filesToExtract);
|
||||||
if(!modDirName.size())
|
if(!modDirName.size())
|
||||||
@ -235,8 +235,6 @@ bool ModStateController::doInstallMod(QString modname, QString archivePath)
|
|||||||
if(upperLevel != modDirName)
|
if(upperLevel != modDirName)
|
||||||
removeModDir(destDir + upperLevel);
|
removeModDir(destDir + upperLevel);
|
||||||
|
|
||||||
modList->reloadLocalState();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,9 +249,7 @@ bool ModStateController::doUninstallMod(QString modname)
|
|||||||
|
|
||||||
QDir modFullDir(modDir);
|
QDir modFullDir(modDir);
|
||||||
if(!removeModDir(modDir))
|
if(!removeModDir(modDir))
|
||||||
return addError(modname, tr("Mod is located in protected directory, please remove it manually:\n") + modFullDir.absolutePath());
|
return addError(modname, tr("Mod is located in a protected directory, please remove it manually:\n") + modFullDir.absolutePath());
|
||||||
|
|
||||||
modList->reloadLocalState();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ class ModStateController : public QObject, public boost::noncopyable
|
|||||||
bool doInstallMod(QString mod, QString archivePath);
|
bool doInstallMod(QString mod, QString archivePath);
|
||||||
bool doUninstallMod(QString mod);
|
bool doUninstallMod(QString mod);
|
||||||
|
|
||||||
QVariantMap localMods;
|
|
||||||
|
|
||||||
QStringList recentErrors;
|
QStringList recentErrors;
|
||||||
bool addError(QString modname, QString message);
|
bool addError(QString modname, QString message);
|
||||||
bool removeModDir(QString mod);
|
bool removeModDir(QString mod);
|
||||||
@ -37,7 +35,7 @@ public:
|
|||||||
ModStateController(std::shared_ptr<ModStateModel> modList);
|
ModStateController(std::shared_ptr<ModStateModel> modList);
|
||||||
~ModStateController();
|
~ModStateController();
|
||||||
|
|
||||||
void appendRepositories(const JsonNode & repositoriesList);
|
void setRepositoryData(const JsonNode & repositoriesList);
|
||||||
|
|
||||||
QStringList getErrors();
|
QStringList getErrors();
|
||||||
|
|
||||||
|
@ -32,34 +32,31 @@ QString ModStateItemModel::modIndexToName(const QModelIndex & index) const
|
|||||||
|
|
||||||
QString ModStateItemModel::modTypeName(QString modTypeID) const
|
QString ModStateItemModel::modTypeName(QString modTypeID) const
|
||||||
{
|
{
|
||||||
static const QMap<QString, QString> modTypes = {
|
static const QStringList modTypes = {
|
||||||
{"Translation", tr("Translation")},
|
QT_TR_NOOP("Translation"),
|
||||||
{"Town", tr("Town") },
|
QT_TR_NOOP("Town"),
|
||||||
{"Test", tr("Test") },
|
QT_TR_NOOP("Test"),
|
||||||
{"Templates", tr("Templates") },
|
QT_TR_NOOP("Templates"),
|
||||||
{"Spells", tr("Spells") },
|
QT_TR_NOOP("Spells"),
|
||||||
{"Music", tr("Music") },
|
QT_TR_NOOP("Music"),
|
||||||
{"Maps", tr("Maps") },
|
QT_TR_NOOP("Maps"),
|
||||||
{"Sounds", tr("Sounds") },
|
QT_TR_NOOP("Sounds"),
|
||||||
{"Skills", tr("Skills") },
|
QT_TR_NOOP("Skills"),
|
||||||
{"Other", tr("Other") },
|
QT_TR_NOOP("Other"),
|
||||||
{"Objects", tr("Objects") },
|
QT_TR_NOOP("Objects"),
|
||||||
{"Mechanical", tr("Mechanics") },
|
QT_TR_NOOP("Mechanics"),
|
||||||
{"Mechanics", tr("Mechanics") },
|
QT_TR_NOOP("Interface"),
|
||||||
{"Themes", tr("Interface") },
|
QT_TR_NOOP("Heroes"),
|
||||||
{"Interface", tr("Interface") },
|
QT_TR_NOOP("Graphical"),
|
||||||
{"Heroes", tr("Heroes") },
|
QT_TR_NOOP("Expansion"),
|
||||||
{"Graphic", tr("Graphical") },
|
QT_TR_NOOP("Creatures"),
|
||||||
{"Graphical", tr("Graphical") },
|
QT_TR_NOOP("Compatibility") ,
|
||||||
{"Expansion", tr("Expansion") },
|
QT_TR_NOOP("Artifacts"),
|
||||||
{"Creatures", tr("Creatures") },
|
QT_TR_NOOP("AI"),
|
||||||
{"Compatibility", tr("Compatibility") },
|
|
||||||
{"Artifacts", tr("Artifacts") },
|
|
||||||
{"AI", tr("AI") },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (modTypes.contains(modTypeID))
|
if (modTypes.contains(modTypeID))
|
||||||
return modTypes[modTypeID];
|
return tr(modTypeID.toStdString().c_str());
|
||||||
return tr("Other");
|
return tr("Other");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +195,7 @@ QVariant ModStateItemModel::headerData(int section, Qt::Orientation orientation,
|
|||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModStateItemModel::reloadRepositories()
|
void ModStateItemModel::reloadViewModel()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
@ -72,7 +72,7 @@ public:
|
|||||||
explicit ModStateItemModel(std::shared_ptr<ModStateModel> model, QObject * parent);
|
explicit ModStateItemModel(std::shared_ptr<ModStateModel> model, QObject * parent);
|
||||||
|
|
||||||
/// CModListContainer overrides
|
/// CModListContainer overrides
|
||||||
void reloadRepositories();
|
void reloadViewModel();
|
||||||
void modChanged(QString modID);
|
void modChanged(QString modID);
|
||||||
|
|
||||||
QVariant data(const QModelIndex & index, int role) const override;
|
QVariant data(const QModelIndex & index, int role) const override;
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include "modstatemodel.h"
|
#include "modstatemodel.h"
|
||||||
|
|
||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
#include "../../lib/json/JsonUtils.h"
|
|
||||||
#include "../../lib/modding/ModManager.h"
|
#include "../../lib/modding/ModManager.h"
|
||||||
|
|
||||||
ModStateModel::ModStateModel()
|
ModStateModel::ModStateModel()
|
||||||
@ -22,10 +21,9 @@ ModStateModel::ModStateModel()
|
|||||||
|
|
||||||
ModStateModel::~ModStateModel() = default;
|
ModStateModel::~ModStateModel() = default;
|
||||||
|
|
||||||
void ModStateModel::appendRepositories(const JsonNode & repositoriesList)
|
void ModStateModel::setRepositoryData(const JsonNode & repositoriesList)
|
||||||
{
|
{
|
||||||
JsonUtils::mergeCopy(*repositoryData, repositoriesList);
|
*repositoryData = repositoriesList;
|
||||||
|
|
||||||
modManager = std::make_unique<ModManager>(*repositoryData);
|
modManager = std::make_unique<ModManager>(*repositoryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,3 +126,34 @@ QString ModStateModel::getTopParent(QString modname) const
|
|||||||
else
|
else
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModStateModel::createNewPreset(const QString & presetName)
|
||||||
|
{
|
||||||
|
modManager->createNewPreset(presetName.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModStateModel::deletePreset(const QString & presetName)
|
||||||
|
{
|
||||||
|
modManager->deletePreset(presetName.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModStateModel::activatePreset(const QString & presetName)
|
||||||
|
{
|
||||||
|
modManager->activatePreset(presetName.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModStateModel::renamePreset(const QString & oldPresetName, const QString & newPresetName)
|
||||||
|
{
|
||||||
|
modManager->renamePreset(oldPresetName.toStdString(), newPresetName.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ModStateModel::getAllPresets() const
|
||||||
|
{
|
||||||
|
auto result = modManager->getAllPresets();
|
||||||
|
return stringListStdToQt(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ModStateModel::getActivePreset() const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(modManager->getActivePreset());
|
||||||
|
}
|
||||||
|
@ -27,7 +27,7 @@ public:
|
|||||||
ModStateModel();
|
ModStateModel();
|
||||||
~ModStateModel();
|
~ModStateModel();
|
||||||
|
|
||||||
void appendRepositories(const JsonNode & repositoriesList);
|
void setRepositoryData(const JsonNode & repositoriesList);
|
||||||
void reloadLocalState();
|
void reloadLocalState();
|
||||||
const JsonNode & getRepositoryData() const;
|
const JsonNode & getRepositoryData() const;
|
||||||
|
|
||||||
@ -49,4 +49,12 @@ public:
|
|||||||
|
|
||||||
bool isSubmod(QString modname);
|
bool isSubmod(QString modname);
|
||||||
QString getTopParent(QString modname) const;
|
QString getTopParent(QString modname) const;
|
||||||
|
|
||||||
|
void createNewPreset(const QString & presetName);
|
||||||
|
void deletePreset(const QString & presetName);
|
||||||
|
void activatePreset(const QString & presetName);
|
||||||
|
void renamePreset(const QString & oldPresetName, const QString & newPresetName);
|
||||||
|
|
||||||
|
QStringList getAllPresets() const;
|
||||||
|
QString getActivePreset() const;
|
||||||
};
|
};
|
||||||
|
@ -517,36 +517,26 @@ void CSettingsView::loadTranslation()
|
|||||||
if (!mainWindow)
|
if (!mainWindow)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString languageName = QString::fromStdString(settings["general"]["language"].String());
|
auto translationStatus = mainWindow->getTranslationStatus();
|
||||||
QString modName = mainWindow->getModView()->getTranslationModName(languageName);
|
bool showTranslation = translationStatus == ETranslationStatus::DISABLED || translationStatus == ETranslationStatus::NOT_INSTALLLED;
|
||||||
bool translationExists = !modName.isEmpty();
|
|
||||||
bool translationNeeded = languageName != baseLanguage;
|
|
||||||
bool showTranslation = translationNeeded && translationExists;
|
|
||||||
|
|
||||||
ui->labelTranslation->setVisible(showTranslation);
|
ui->labelTranslation->setVisible(showTranslation);
|
||||||
ui->labelTranslationStatus->setVisible(showTranslation);
|
ui->labelTranslationStatus->setVisible(showTranslation);
|
||||||
ui->pushButtonTranslation->setVisible(showTranslation);
|
ui->pushButtonTranslation->setVisible(showTranslation);
|
||||||
|
ui->pushButtonTranslation->setVisible(translationStatus != ETranslationStatus::ACTIVE);
|
||||||
|
|
||||||
if (!translationExists || !translationNeeded)
|
if (translationStatus == ETranslationStatus::ACTIVE)
|
||||||
return;
|
|
||||||
|
|
||||||
bool translationAvailable = mainWindow->getModView()->isModAvailable(modName);
|
|
||||||
bool translationEnabled = mainWindow->getModView()->isModEnabled(modName);
|
|
||||||
|
|
||||||
ui->pushButtonTranslation->setVisible(!translationEnabled);
|
|
||||||
|
|
||||||
if (translationEnabled)
|
|
||||||
{
|
{
|
||||||
ui->labelTranslationStatus->setText(tr("Active"));
|
ui->labelTranslationStatus->setText(tr("Active"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!translationEnabled && !translationAvailable)
|
if (translationStatus == ETranslationStatus::DISABLED)
|
||||||
{
|
{
|
||||||
ui->labelTranslationStatus->setText(tr("Disabled"));
|
ui->labelTranslationStatus->setText(tr("Disabled"));
|
||||||
ui->pushButtonTranslation->setText(tr("Enable"));
|
ui->pushButtonTranslation->setText(tr("Enable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (translationAvailable)
|
if (translationStatus == ETranslationStatus::NOT_INSTALLLED)
|
||||||
{
|
{
|
||||||
ui->labelTranslationStatus->setText(tr("Not Installed"));
|
ui->labelTranslationStatus->setText(tr("Not Installed"));
|
||||||
ui->pushButtonTranslation->setText(tr("Install"));
|
ui->pushButtonTranslation->setText(tr("Install"));
|
||||||
@ -614,7 +604,7 @@ void CSettingsView::on_lineEditRepositoryExtra_textEdited(const QString &arg1)
|
|||||||
void CSettingsView::on_spinBoxInterfaceScaling_valueChanged(int arg1)
|
void CSettingsView::on_spinBoxInterfaceScaling_valueChanged(int arg1)
|
||||||
{
|
{
|
||||||
Settings node = settings.write["video"]["resolution"]["scaling"];
|
Settings node = settings.write["video"]["resolution"]["scaling"];
|
||||||
node->Float() = arg1;
|
node->Float() = ui->buttonScalingAuto->isChecked() ? 0 : arg1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSettingsView::on_refreshRepositoriesButton_clicked()
|
void CSettingsView::on_refreshRepositoriesButton_clicked()
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>-797</y>
|
<y>0</y>
|
||||||
<width>729</width>
|
<width>729</width>
|
||||||
<height>1503</height>
|
<height>1503</height>
|
||||||
</rect>
|
</rect>
|
||||||
@ -1179,13 +1179,13 @@
|
|||||||
<item row="11" column="1" colspan="5">
|
<item row="11" column="1" colspan="5">
|
||||||
<widget class="QComboBox" name="comboBoxFullScreen">
|
<widget class="QComboBox" name="comboBoxFullScreen">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Select display mode for game
|
<string>Select a display mode for the game
|
||||||
|
|
||||||
Windowed - game will run inside a window that covers part of your screen
|
Windowed - the game will run inside a window that covers part of your screen.
|
||||||
|
|
||||||
Borderless Windowed Mode - game will run in a window that covers entirely of your screen, using same resolution as your screen.
|
Borderless Windowed Mode - the game will run in a full-screen window, matching your screen's resolution.
|
||||||
|
|
||||||
Fullscreen Exclusive Mode - game will cover entirety of your screen and will use selected resolution.</string>
|
Fullscreen Exclusive Mode - the game will cover the entirety of your screen and will use selected resolution.</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
|
431
launcher/startGame/StartGameTab.cpp
Normal file
431
launcher/startGame/StartGameTab.cpp
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
/*
|
||||||
|
* StartGameTab.cpp, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "StartGameTab.h"
|
||||||
|
#include "ui_StartGameTab.h"
|
||||||
|
|
||||||
|
#include "../mainwindow_moc.h"
|
||||||
|
#include "../main.h"
|
||||||
|
#include "../updatedialog_moc.h"
|
||||||
|
|
||||||
|
#include "../modManager/cmodlistview_moc.h"
|
||||||
|
|
||||||
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
|
#include "../../lib/VCMIDirs.h"
|
||||||
|
|
||||||
|
void StartGameTab::changeEvent(QEvent *event)
|
||||||
|
{
|
||||||
|
if(event->type() == QEvent::LanguageChange)
|
||||||
|
{
|
||||||
|
ui->retranslateUi(this);
|
||||||
|
refreshState();
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget::changeEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
StartGameTab::StartGameTab(QWidget * parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, ui(new Ui::StartGameTab)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
ui->buttonGameResume->setIcon(QIcon{":/icons/menu-game.png"}); //TODO: different icon?
|
||||||
|
ui->buttonGameStart->setIcon(QIcon{":/icons/menu-game.png"});
|
||||||
|
ui->buttonGameEditor->setIcon(QIcon{":/icons/menu-editor.png"});
|
||||||
|
|
||||||
|
refreshState();
|
||||||
|
|
||||||
|
ui->buttonGameResume->setVisible(false); // TODO: implement
|
||||||
|
ui->buttonPresetExport->setVisible(false); // TODO: implement
|
||||||
|
ui->buttonPresetImport->setVisible(false); // TODO: implement
|
||||||
|
|
||||||
|
#ifndef ENABLE_EDITOR
|
||||||
|
ui->buttonGameEditor->hide();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
StartGameTab::~StartGameTab()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow * StartGameTab::getMainWindow()
|
||||||
|
{
|
||||||
|
foreach(QWidget *w, qApp->allWidgets())
|
||||||
|
if(QMainWindow* mainWin = qobject_cast<QMainWindow*>(w))
|
||||||
|
return dynamic_cast<MainWindow *>(mainWin);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::refreshState()
|
||||||
|
{
|
||||||
|
refreshGameData();
|
||||||
|
refreshUpdateStatus(EGameUpdateStatus::NOT_CHECKED);//TODO - follow automatic check on startup setting
|
||||||
|
refreshTranslation(getMainWindow()->getTranslationStatus());
|
||||||
|
refreshPresets();
|
||||||
|
refreshMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::refreshPresets()
|
||||||
|
{
|
||||||
|
QSignalBlocker blocker(ui->comboBoxModPresets);
|
||||||
|
|
||||||
|
QStringList allPresets = getMainWindow()->getModView()->getAllPresets();
|
||||||
|
ui->comboBoxModPresets->clear();
|
||||||
|
ui->comboBoxModPresets->addItems(allPresets);
|
||||||
|
ui->comboBoxModPresets->setCurrentText(getMainWindow()->getModView()->getActivePreset());
|
||||||
|
ui->buttonPresetDelete->setVisible(allPresets.size() > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::refreshGameData()
|
||||||
|
{
|
||||||
|
// Some players are using pirated version of the game with some of the files missing
|
||||||
|
// leading to broken town hall menu (and possibly other dialogs)
|
||||||
|
// Provide diagnostics to indicate problem with chair-monitor adaptor layer and not with VCMI
|
||||||
|
static constexpr std::array potentiallyMissingFiles = {
|
||||||
|
"Data/TpThBkDg.bmp",
|
||||||
|
"Data/TpThBkFr.bmp",
|
||||||
|
"Data/TpThBkIn.bmp",
|
||||||
|
"Data/TpThBkNc.bmp",
|
||||||
|
"Data/TpThBkSt.bmp",
|
||||||
|
"Data/TpThBRrm.bmp",
|
||||||
|
"Data/TpThBkCs.bmp",
|
||||||
|
"Data/TpThBkRm.bmp",
|
||||||
|
"Data/TpThBkTw.bmp",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Some players for some reason don't have AB expansion campaign files
|
||||||
|
static constexpr std::array armaggedonBladeCampaigns = {
|
||||||
|
"DATA/AB",
|
||||||
|
"DATA/BLOOD",
|
||||||
|
"DATA/SLAYER",
|
||||||
|
"DATA/FESTIVAL",
|
||||||
|
"DATA/FIRE",
|
||||||
|
"DATA/FOOL",
|
||||||
|
};
|
||||||
|
|
||||||
|
bool missingSoundtrack = !CResourceHandler::get()->existsResource(AudioPath::builtin("Music/MainMenu"));
|
||||||
|
bool missingVideoFiles = !CResourceHandler::get()->existsResource(VideoPath::builtin("Video/H3Intro")) && !CResourceHandler::get()->existsResource(ResourcePath("Video/H3Intro", EResType::VIDEO_LOW_QUALITY));
|
||||||
|
bool missingGameFiles = false;
|
||||||
|
bool missingCampaings = false;
|
||||||
|
|
||||||
|
for (const auto & filename : potentiallyMissingFiles)
|
||||||
|
missingGameFiles &= !CResourceHandler::get()->existsResource(ImagePath::builtin(filename));
|
||||||
|
|
||||||
|
for (const auto & filename : armaggedonBladeCampaigns)
|
||||||
|
missingCampaings &= !CResourceHandler::get()->existsResource(ResourcePath(filename, EResType::CAMPAIGN));
|
||||||
|
|
||||||
|
ui->labelMissingCampaigns->setVisible(missingCampaings);
|
||||||
|
ui->labelMissingFiles->setVisible(missingGameFiles);
|
||||||
|
ui->labelMissingVideo->setVisible(missingVideoFiles);
|
||||||
|
ui->labelMissingSoundtrack->setVisible(missingSoundtrack);
|
||||||
|
|
||||||
|
ui->buttonMissingCampaignsHelp->setVisible(missingCampaings);
|
||||||
|
ui->buttonMissingFilesHelp->setVisible(missingGameFiles);
|
||||||
|
ui->buttonMissingVideoHelp->setVisible(missingVideoFiles);
|
||||||
|
ui->buttonMissingSoundtrackHelp->setVisible(missingSoundtrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::refreshTranslation(ETranslationStatus status)
|
||||||
|
{
|
||||||
|
ui->buttonInstallTranslation->setVisible(status == ETranslationStatus::NOT_INSTALLLED);
|
||||||
|
ui->buttonInstallTranslationHelp->setVisible(status == ETranslationStatus::NOT_INSTALLLED);
|
||||||
|
|
||||||
|
ui->buttonActivateTranslation->setVisible(status == ETranslationStatus::NOT_INSTALLLED);
|
||||||
|
ui->buttonActivateTranslationHelp->setVisible(status == ETranslationStatus::NOT_INSTALLLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::refreshMods()
|
||||||
|
{
|
||||||
|
constexpr int chroniclesCount = 8;
|
||||||
|
QStringList updateableMods = getMainWindow()->getModView()->getUpdateableMods();
|
||||||
|
QStringList chroniclesMods = getMainWindow()->getModView()->getInstalledChronicles();
|
||||||
|
|
||||||
|
ui->buttonUpdateMods->setText(tr("Update %n mods", "", updateableMods.size()));
|
||||||
|
ui->buttonUpdateMods->setVisible(!updateableMods.empty());
|
||||||
|
ui->buttonUpdateModsHelp->setVisible(!updateableMods.empty());
|
||||||
|
|
||||||
|
ui->labelChronicles->setText(tr("Heroes Chronicles:\n%n/%1 installed", "", chroniclesMods.size()).arg(chroniclesCount));
|
||||||
|
ui->labelChronicles->setVisible(chroniclesMods.size() != chroniclesCount);
|
||||||
|
ui->buttonChroniclesHelp->setVisible(chroniclesMods.size() != chroniclesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::refreshUpdateStatus(EGameUpdateStatus status)
|
||||||
|
{
|
||||||
|
QString availableVersion; // TODO
|
||||||
|
|
||||||
|
ui->labelTitleEngine->setText("VCMI " VCMI_VERSION_STRING);
|
||||||
|
ui->buttonUpdateCheck->setVisible(status == EGameUpdateStatus::NOT_CHECKED);
|
||||||
|
ui->labelUpdateNotFound->setVisible(status == EGameUpdateStatus::NO_UPDATE);
|
||||||
|
ui->labelUpdateAvailable->setVisible(status == EGameUpdateStatus::UPDATE_AVAILABLE);
|
||||||
|
ui->buttonOpenChangelog->setVisible(status == EGameUpdateStatus::UPDATE_AVAILABLE);
|
||||||
|
ui->buttonOpenDownloads->setVisible(status == EGameUpdateStatus::UPDATE_AVAILABLE);
|
||||||
|
|
||||||
|
if (status == EGameUpdateStatus::UPDATE_AVAILABLE)
|
||||||
|
ui->labelUpdateAvailable->setText(tr("Update to %1 available").arg(availableVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonGameStart_clicked()
|
||||||
|
{
|
||||||
|
getMainWindow()->hide();
|
||||||
|
startGame({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonOpenChangelog_clicked()
|
||||||
|
{
|
||||||
|
QDesktopServices::openUrl(QUrl("https://vcmi.eu/ChangeLog/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonOpenDownloads_clicked()
|
||||||
|
{
|
||||||
|
QDesktopServices::openUrl(QUrl("https://vcmi.eu/download/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonUpdateCheck_clicked()
|
||||||
|
{
|
||||||
|
UpdateDialog::showUpdateDialog(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonGameEditor_clicked()
|
||||||
|
{
|
||||||
|
getMainWindow()->hide();
|
||||||
|
startEditor({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonImportFiles_clicked()
|
||||||
|
{
|
||||||
|
const auto & importFunctor = [this]
|
||||||
|
{
|
||||||
|
#ifndef VCMI_MOBILE
|
||||||
|
QString filter =
|
||||||
|
tr("All supported files") + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json *.exe);;" +
|
||||||
|
tr("Maps") + " (*.h3m *.vmap);;" +
|
||||||
|
tr("Campaigns") + " (*.h3c *.vcmp);;" +
|
||||||
|
tr("Configs") + " (*.json);;" +
|
||||||
|
tr("Mods") + " (*.zip);;" +
|
||||||
|
tr("Gog files") + " (*.exe)";
|
||||||
|
#else
|
||||||
|
//Workaround for sometimes incorrect mime for some extensions (e.g. for exe)
|
||||||
|
QString filter = tr("All files (*.*)");
|
||||||
|
#endif
|
||||||
|
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files (configs, mods, maps, campaigns, gog files) to install..."), QDir::homePath(), filter);
|
||||||
|
|
||||||
|
for(const auto & file : files)
|
||||||
|
{
|
||||||
|
logGlobal->info("Importing file %s", file.toStdString());
|
||||||
|
getMainWindow()->manualInstallFile(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// iOS can't display modal dialogs when called directly on button press
|
||||||
|
// https://bugreports.qt.io/browse/QTBUG-98651
|
||||||
|
QTimer::singleShot(0, this, importFunctor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonInstallTranslation_clicked()
|
||||||
|
{
|
||||||
|
if (getMainWindow()->getTranslationStatus() == ETranslationStatus::NOT_INSTALLLED)
|
||||||
|
{
|
||||||
|
QString preferredlanguage = QString::fromStdString(settings["general"]["language"].String());
|
||||||
|
QString modName = getMainWindow()->getModView()->getTranslationModName(preferredlanguage);
|
||||||
|
getMainWindow()->getModView()->doInstallMod(modName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonActivateTranslation_clicked()
|
||||||
|
{
|
||||||
|
QString preferredlanguage = QString::fromStdString(settings["general"]["language"].String());
|
||||||
|
QString modName = getMainWindow()->getModView()->getTranslationModName(preferredlanguage);
|
||||||
|
getMainWindow()->getModView()->enableModByName(modName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonUpdateMods_clicked()
|
||||||
|
{
|
||||||
|
QStringList updateableMods = getMainWindow()->getModView()->getUpdateableMods();
|
||||||
|
|
||||||
|
getMainWindow()->switchToModsTab();
|
||||||
|
|
||||||
|
for (const auto & modName : updateableMods)
|
||||||
|
getMainWindow()->getModView()->doUpdateMod(modName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonHelpImportFiles_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"This option allows you to import additional data files into your VCMI installation. "
|
||||||
|
"At the moment, following options are supported:\n\n"
|
||||||
|
" - Heroes III Maps (.h3m or .vmap).\n"
|
||||||
|
" - Heroes III Campaigns (.h3c or .vcmp).\n"
|
||||||
|
" - Heroes III Chronicles using offline backup installer from GOG.com (.exe).\n"
|
||||||
|
" - VCMI mods in zip format (.zip)\n"
|
||||||
|
" - VCMI configuration files (.json)\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
QMessageBox::information(this, ui->buttonImportFiles->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonInstallTranslationHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"Your Heroes III version uses different language. "
|
||||||
|
"VCMI provides translations of the game into various languages that you can use. "
|
||||||
|
"Use this option to automatically install such translation to your language."
|
||||||
|
);
|
||||||
|
QMessageBox::information(this, ui->buttonInstallTranslation->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonActivateTranslationHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"Translation of Heroes III into your language is installed, but has been turned off. "
|
||||||
|
"Use this option to enable it."
|
||||||
|
);
|
||||||
|
|
||||||
|
QMessageBox::information(this, ui->buttonActivateTranslation->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonUpdateModsHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"A new version of some of the mods that you have installed is now available in mod repository. "
|
||||||
|
"Use this option to automatically update all your mods to latest version.\n\n"
|
||||||
|
"WARNING: In some cases, updated versions of mods may not be compatible with your existing saves. "
|
||||||
|
"You many want to postpone mod update until you finish any of your ongoing games."
|
||||||
|
);
|
||||||
|
|
||||||
|
QMessageBox::information(this, ui->buttonUpdateMods->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonChroniclesHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"If you own Heroes Chronicles on gog.com, you can use offline backup installers provided by gog "
|
||||||
|
"to import Heroes Chronicles data into VCMI as custom campaigns.\n"
|
||||||
|
"To import Heroes Chronicles, download offline backup installer of each chronicle that you wish to install, "
|
||||||
|
"select 'Import files' option and select downloaded file. "
|
||||||
|
"This will generate and install mod for VCMI that contains imported chronicles"
|
||||||
|
);
|
||||||
|
|
||||||
|
QMessageBox::information(this, ui->labelChronicles->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonMissingSoundtrackHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"VCMI has detected that Heroes III music files are missing from your installation. "
|
||||||
|
"VCMI will run, but in-game music will not be available.\n\n"
|
||||||
|
"To resolve this problem, please copy missing mp3 files from Heroes III to VCMI data files directory manually "
|
||||||
|
"or reinstall VCMI and re-import Heroes III data files"
|
||||||
|
);
|
||||||
|
QMessageBox::information(this, ui->labelMissingSoundtrack->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonMissingVideoHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"VCMI has detected that Heroes III video files are missing from your installation. "
|
||||||
|
"VCMI will run, but in-game cutscenes will not be available.\n\n"
|
||||||
|
"To resolve this problem, please copy VIDEO.VID file from Heroes III to VCMI data files directory manually "
|
||||||
|
"or reinstall VCMI and re-import Heroes III data files"
|
||||||
|
);
|
||||||
|
QMessageBox::information(this, ui->labelMissingVideo->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonMissingFilesHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"VCMI has detected that some of Heroes III data files are missing from your installation. "
|
||||||
|
"You may attempt to run VCMI, but game may not work as expected or crash.\n\n"
|
||||||
|
"To resolve this problem, please reinstall game and reimport data files using supported version of Heroes III. "
|
||||||
|
"VCMI requires Heroes III: Shadow of Death or Complete Edition to run, which you can get (for example) from gog.com"
|
||||||
|
);
|
||||||
|
QMessageBox::information(this, ui->labelMissingFiles->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonMissingCampaignsHelp_clicked()
|
||||||
|
{
|
||||||
|
QString message = tr(
|
||||||
|
"VCMI has detected that some of Heroes III: Armageddon's Blade data files are missing from your installation. "
|
||||||
|
"VCMI will work, but Armageddon's Blade campaigns will not be available.\n\n"
|
||||||
|
"To resolve this problem, please copy missing data files from Heroes III to VCMI data files directory manually "
|
||||||
|
"or reinstall VCMI and re-import Heroes III data files"
|
||||||
|
);
|
||||||
|
QMessageBox::information(this, ui->labelMissingCampaigns->text(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonPresetExport_clicked()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonPresetImport_clicked()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonPresetNew_clicked()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QString presetName = QInputDialog::getText(
|
||||||
|
this,
|
||||||
|
ui->buttonPresetNew->text(),
|
||||||
|
tr("Enter preset name:"),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
QString(),
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (ok && !presetName.isEmpty())
|
||||||
|
{
|
||||||
|
getMainWindow()->getModView()->createNewPreset(presetName);
|
||||||
|
getMainWindow()->getModView()->activatePreset(presetName);
|
||||||
|
refreshPresets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonPresetDelete_clicked()
|
||||||
|
{
|
||||||
|
QString activePresetBefore = getMainWindow()->getModView()->getActivePreset();
|
||||||
|
QStringList allPresets = getMainWindow()->getModView()->getAllPresets();
|
||||||
|
|
||||||
|
allPresets.removeAll(activePresetBefore);
|
||||||
|
if (!allPresets.empty())
|
||||||
|
{
|
||||||
|
getMainWindow()->getModView()->activatePreset(allPresets.front());
|
||||||
|
getMainWindow()->getModView()->deletePreset(activePresetBefore);
|
||||||
|
refreshPresets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_comboBoxModPresets_currentTextChanged(const QString &presetName)
|
||||||
|
{
|
||||||
|
getMainWindow()->getModView()->activatePreset(presetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartGameTab::on_buttonPresetRename_clicked()
|
||||||
|
{
|
||||||
|
QString currentName = getMainWindow()->getModView()->getActivePreset();
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
QString newName = QInputDialog::getText(
|
||||||
|
this,
|
||||||
|
ui->buttonPresetNew->text(),
|
||||||
|
tr("Rename preset '%1' to:").arg(currentName),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
currentName,
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (ok && !newName.isEmpty())
|
||||||
|
{
|
||||||
|
getMainWindow()->getModView()->renamePreset(currentName, newName);
|
||||||
|
refreshPresets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
83
launcher/startGame/StartGameTab.h
Normal file
83
launcher/startGame/StartGameTab.h
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* StartGameTab.cpp, part of VCMI engine
|
||||||
|
*
|
||||||
|
* Authors: listed in file AUTHORS in main folder
|
||||||
|
*
|
||||||
|
* License: GNU General Public License v2.0 or later
|
||||||
|
* Full text of license available in license.txt file, in main folder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class StartGameTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EGameUpdateStatus : int8_t
|
||||||
|
{
|
||||||
|
NOT_CHECKED,
|
||||||
|
NO_UPDATE,
|
||||||
|
UPDATE_AVAILABLE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ETranslationStatus : int8_t;
|
||||||
|
|
||||||
|
class MainWindow;
|
||||||
|
|
||||||
|
class StartGameTab : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
MainWindow * getMainWindow();
|
||||||
|
|
||||||
|
void refreshUpdateStatus(EGameUpdateStatus status);
|
||||||
|
void refreshTranslation(ETranslationStatus status);
|
||||||
|
void refreshMods();
|
||||||
|
void refreshPresets();
|
||||||
|
void refreshGameData();
|
||||||
|
|
||||||
|
void changeEvent(QEvent *event) override;
|
||||||
|
public:
|
||||||
|
explicit StartGameTab(QWidget * parent = nullptr);
|
||||||
|
~StartGameTab();
|
||||||
|
|
||||||
|
void refreshState();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_buttonGameStart_clicked();
|
||||||
|
void on_buttonOpenChangelog_clicked();
|
||||||
|
void on_buttonOpenDownloads_clicked();
|
||||||
|
void on_buttonUpdateCheck_clicked();
|
||||||
|
void on_buttonGameEditor_clicked();
|
||||||
|
void on_buttonImportFiles_clicked();
|
||||||
|
void on_buttonInstallTranslation_clicked();
|
||||||
|
void on_buttonActivateTranslation_clicked();
|
||||||
|
void on_buttonUpdateMods_clicked();
|
||||||
|
void on_buttonHelpImportFiles_clicked();
|
||||||
|
void on_buttonInstallTranslationHelp_clicked();
|
||||||
|
void on_buttonActivateTranslationHelp_clicked();
|
||||||
|
void on_buttonUpdateModsHelp_clicked();
|
||||||
|
void on_buttonChroniclesHelp_clicked();
|
||||||
|
void on_buttonMissingSoundtrackHelp_clicked();
|
||||||
|
void on_buttonMissingVideoHelp_clicked();
|
||||||
|
void on_buttonMissingFilesHelp_clicked();
|
||||||
|
void on_buttonMissingCampaignsHelp_clicked();
|
||||||
|
|
||||||
|
void on_buttonPresetExport_clicked();
|
||||||
|
|
||||||
|
void on_buttonPresetImport_clicked();
|
||||||
|
|
||||||
|
void on_buttonPresetNew_clicked();
|
||||||
|
|
||||||
|
void on_buttonPresetDelete_clicked();
|
||||||
|
|
||||||
|
void on_comboBoxModPresets_currentTextChanged(const QString &arg1);
|
||||||
|
|
||||||
|
void on_buttonPresetRename_clicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::StartGameTab * ui;
|
||||||
|
};
|
857
launcher/startGame/StartGameTab.ui
Normal file
857
launcher/startGame/StartGameTab.ui
Normal file
@ -0,0 +1,857 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>StartGameTab</class>
|
||||||
|
<widget class="QWidget" name="StartGameTab">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>757</width>
|
||||||
|
<height>372</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3" columnstretch="1,1,1">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="labelTitleDataFiles">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Game Data Files</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="labelTitleModPreset">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Mod Preset</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QLabel" name="labelTitleEngine">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QScrollArea" name="scrollArea_2">
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Sunken</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>246</width>
|
||||||
|
<height>350</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="labelMissingFiles">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Unsupported or corrupted game data detected!</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonHelpImportFiles">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonMissingFilesHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="labelChronicles">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonUpdateModsHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonInstallTranslationHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonInstallTranslation">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Install Translation</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="labelMissingCampaigns">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Armaggedon's Blade campaigns are missing!</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="labelMissingVideo">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>No video files detected!</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonImportFiles">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Import files</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonUpdateMods">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonMissingSoundtrackHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonMissingCampaignsHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="labelMissingSoundtrack">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>No soundtrack detected!</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonMissingVideoHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonActivateTranslation">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Activate Translation</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonChroniclesHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QPushButton" name="buttonActivateTranslationHelp">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>247</width>
|
||||||
|
<height>350</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout" rowstretch="1,0,0,0,0,0,0,0,0,0">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonPresetExport">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Export to Clipboard</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonPresetNew">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Create New Preset</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QComboBox" name="comboBoxModPresets">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonPresetDelete">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete Current Preset</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonPresetImport">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Import from Clipboard</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QPushButton" name="buttonPresetRename">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Rename Current Preset</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QScrollArea" name="scrollArea_3">
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents_3">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>272</width>
|
||||||
|
<height>350</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelUpdateNotFound">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>You are using the latest version</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelUpdateAvailable">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="buttonUpdateCheck">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Check For Updates</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="buttonOpenDownloads">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Go to Downloads Page</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="buttonOpenChangelog">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Go to Changelog Page</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="buttonGameResume">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>80</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Resume</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolButtonStyle">
|
||||||
|
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="buttonGameEditor">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>80</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Editor</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolButtonStyle">
|
||||||
|
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="buttonGameStart">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>80</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Play</string>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolButtonStyle">
|
||||||
|
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -98,7 +98,7 @@ void UpdateDialog::loadFromJson(const JsonNode & node)
|
|||||||
node["changeLog"].getType() != JsonNode::JsonType::DATA_STRING ||
|
node["changeLog"].getType() != JsonNode::JsonType::DATA_STRING ||
|
||||||
node["downloadLinks"].getType() != JsonNode::JsonType::DATA_STRUCT) //we need at least one link - other are optional
|
node["downloadLinks"].getType() != JsonNode::JsonType::DATA_STRUCT) //we need at least one link - other are optional
|
||||||
{
|
{
|
||||||
ui->plainTextEdit->setPlainText(tr("Cannot read JSON from url or incorrect JSON data"));
|
ui->plainTextEdit->setPlainText(tr("Cannot read JSON from URL or incorrect JSON data"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1400,8 +1400,10 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
|
|||||||
case EventCondition::TRANSPORT:
|
case EventCondition::TRANSPORT:
|
||||||
{
|
{
|
||||||
const auto * t = getTown(condition.objectID);
|
const auto * t = getTown(condition.objectID);
|
||||||
return (t->visitingHero && t->visitingHero->getOwner() == player && t->visitingHero->hasArt(condition.objectType.as<ArtifactID>())) ||
|
bool garrisonedWon = t->garrisonHero && t->garrisonHero->getOwner() == player && t->garrisonHero->hasArt(condition.objectType.as<ArtifactID>());
|
||||||
(t->garrisonHero && t->garrisonHero->getOwner() == player && t->garrisonHero->hasArt(condition.objectType.as<ArtifactID>()));
|
bool visitingWon = t->visitingHero && t->visitingHero->getOwner() == player && t->visitingHero->hasArt(condition.objectType.as<ArtifactID>());
|
||||||
|
|
||||||
|
return garrisonedWon || visitingWon;
|
||||||
}
|
}
|
||||||
case EventCondition::DAYS_PASSED:
|
case EventCondition::DAYS_PASSED:
|
||||||
{
|
{
|
||||||
@ -1436,6 +1438,9 @@ PlayerColor CGameState::checkForStandardWin() const
|
|||||||
TeamID winnerTeam = TeamID::NO_TEAM;
|
TeamID winnerTeam = TeamID::NO_TEAM;
|
||||||
for(const auto & elem : players)
|
for(const auto & elem : players)
|
||||||
{
|
{
|
||||||
|
if(elem.second.status == EPlayerStatus::WINNER)
|
||||||
|
return elem.second.color;
|
||||||
|
|
||||||
if(elem.second.status == EPlayerStatus::INGAME && elem.first.isValidPlayer())
|
if(elem.second.status == EPlayerStatus::INGAME && elem.first.isValidPlayer())
|
||||||
{
|
{
|
||||||
if(supposedWinner == PlayerColor::NEUTRAL)
|
if(supposedWinner == PlayerColor::NEUTRAL)
|
||||||
|
@ -1923,7 +1923,8 @@ int CGHeroInstance::getBasePrimarySkillValue(PrimarySkill which) const
|
|||||||
{
|
{
|
||||||
std::string cachingStr = "type_PRIMARY_SKILL_base_" + std::to_string(static_cast<int>(which));
|
std::string cachingStr = "type_PRIMARY_SKILL_base_" + std::to_string(static_cast<int>(which));
|
||||||
auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
|
auto selector = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(which)).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
|
||||||
return valOfBonuses(selector, cachingStr);
|
auto minSkillValue = VLC->engineSettings()->getVector(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS)[which.getNum()];
|
||||||
|
return std::max(valOfBonuses(selector, cachingStr), minSkillValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@ -120,8 +120,7 @@ const JsonNode & ModDescription::getLocalizedValue(const std::string & keyName)
|
|||||||
|
|
||||||
const JsonNode & ModDescription::getValue(const std::string & keyName) const
|
const JsonNode & ModDescription::getValue(const std::string & keyName) const
|
||||||
{
|
{
|
||||||
const JsonNode & localValue = getLocalValue(keyName);
|
if (!isInstalled() || isUpdateAvailable())
|
||||||
if (localValue.isNull())
|
|
||||||
return getRepositoryValue(keyName);
|
return getRepositoryValue(keyName);
|
||||||
else
|
else
|
||||||
return getLocalValue(keyName);
|
return getLocalValue(keyName);
|
||||||
|
@ -163,7 +163,7 @@ ModsPresetState::ModsPresetState()
|
|||||||
CResourceHandler::get("local")->createResource(settingsPath.getOriginalName() + ".json");
|
CResourceHandler::get("local")->createResource(settingsPath.getOriginalName() + ".json");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(modConfig["presets"].isNull())
|
if(modConfig["presets"].isNull() || modConfig["presets"].Struct().empty())
|
||||||
{
|
{
|
||||||
modConfig["activePreset"] = JsonNode("default");
|
modConfig["activePreset"] = JsonNode("default");
|
||||||
if(modConfig["activeMods"].isNull())
|
if(modConfig["activeMods"].isNull())
|
||||||
@ -171,6 +171,10 @@ ModsPresetState::ModsPresetState()
|
|||||||
else
|
else
|
||||||
importInitialPreset(); // 1.5 format import
|
importInitialPreset(); // 1.5 format import
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto allPresets = getAllPresets();
|
||||||
|
if (!vstd::contains(allPresets, modConfig["activePreset"].String()))
|
||||||
|
modConfig["activePreset"] = JsonNode(allPresets.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModsPresetState::createInitialPreset()
|
void ModsPresetState::createInitialPreset()
|
||||||
@ -326,6 +330,61 @@ void ModsPresetState::saveConfigurationState() const
|
|||||||
file << modConfig.toCompactString();
|
file << modConfig.toCompactString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModsPresetState::createNewPreset(const std::string & presetName)
|
||||||
|
{
|
||||||
|
if (modConfig["presets"][presetName].isNull())
|
||||||
|
modConfig["presets"][presetName]["mods"].Vector().emplace_back("vcmi");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModsPresetState::deletePreset(const std::string & presetName)
|
||||||
|
{
|
||||||
|
if (modConfig["presets"].Struct().size() < 2)
|
||||||
|
throw std::runtime_error("Unable to delete last preset!");
|
||||||
|
|
||||||
|
modConfig["presets"].Struct().erase(presetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModsPresetState::activatePreset(const std::string & presetName)
|
||||||
|
{
|
||||||
|
if (modConfig["presets"].Struct().count(presetName) == 0)
|
||||||
|
throw std::runtime_error("Unable to activate non-exinsting preset!");
|
||||||
|
|
||||||
|
modConfig["activePreset"].String() = presetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModsPresetState::renamePreset(const std::string & oldPresetName, const std::string & newPresetName)
|
||||||
|
{
|
||||||
|
if (oldPresetName == newPresetName)
|
||||||
|
throw std::runtime_error("Unable to rename preset to the same name!");
|
||||||
|
|
||||||
|
if (modConfig["presets"].Struct().count(oldPresetName) == 0)
|
||||||
|
throw std::runtime_error("Unable to rename non-existing last preset!");
|
||||||
|
|
||||||
|
if (modConfig["presets"].Struct().count(newPresetName) != 0)
|
||||||
|
throw std::runtime_error("Unable to rename preset - preset with such name already exists!");
|
||||||
|
|
||||||
|
modConfig["presets"][newPresetName] = modConfig["presets"][oldPresetName];
|
||||||
|
modConfig["presets"].Struct().erase(oldPresetName);
|
||||||
|
|
||||||
|
if (modConfig["activePreset"].String() == oldPresetName)
|
||||||
|
modConfig["activePreset"].String() = newPresetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ModsPresetState::getAllPresets() const
|
||||||
|
{
|
||||||
|
std::vector<std::string> presets;
|
||||||
|
|
||||||
|
for (const auto & preset : modConfig["presets"].Struct())
|
||||||
|
presets.push_back(preset.first);
|
||||||
|
|
||||||
|
return presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ModsPresetState::getActivePreset() const
|
||||||
|
{
|
||||||
|
return modConfig["activePreset"].String();
|
||||||
|
}
|
||||||
|
|
||||||
ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad, const JsonNode & repositoryList)
|
ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad, const JsonNode & repositoryList)
|
||||||
{
|
{
|
||||||
JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json"));
|
JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json"));
|
||||||
@ -595,7 +654,7 @@ void ModManager::updatePreset(const ModDependenciesResolver & testResolver)
|
|||||||
for (const auto & modID : newBrokenMods)
|
for (const auto & modID : newBrokenMods)
|
||||||
{
|
{
|
||||||
const auto & mod = getModDescription(modID);
|
const auto & mod = getModDescription(modID);
|
||||||
if (vstd::contains(newActiveMods, mod.getTopParentID()))
|
if (mod.getTopParentID().empty() || vstd::contains(newActiveMods, mod.getTopParentID()))
|
||||||
modsPreset->setModActive(modID, false);
|
modsPreset->setModActive(modID, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,4 +762,38 @@ void ModDependenciesResolver::tryAddMods(TModList modsToResolve, const ModsStora
|
|||||||
brokenMods.insert(brokenMods.end(), modsToResolve.begin(), modsToResolve.end());
|
brokenMods.insert(brokenMods.end(), modsToResolve.begin(), modsToResolve.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModManager::createNewPreset(const std::string & presetName)
|
||||||
|
{
|
||||||
|
modsPreset->createNewPreset(presetName);
|
||||||
|
modsPreset->saveConfigurationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModManager::deletePreset(const std::string & presetName)
|
||||||
|
{
|
||||||
|
modsPreset->deletePreset(presetName);
|
||||||
|
modsPreset->saveConfigurationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModManager::activatePreset(const std::string & presetName)
|
||||||
|
{
|
||||||
|
modsPreset->activatePreset(presetName);
|
||||||
|
modsPreset->saveConfigurationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModManager::renamePreset(const std::string & oldPresetName, const std::string & newPresetName)
|
||||||
|
{
|
||||||
|
modsPreset->renamePreset(oldPresetName, newPresetName);
|
||||||
|
modsPreset->saveConfigurationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ModManager::getAllPresets() const
|
||||||
|
{
|
||||||
|
return modsPreset->getAllPresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ModManager::getActivePreset() const
|
||||||
|
{
|
||||||
|
return modsPreset->getActivePreset();
|
||||||
|
}
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@ -50,6 +50,14 @@ class ModsPresetState : boost::noncopyable
|
|||||||
public:
|
public:
|
||||||
ModsPresetState();
|
ModsPresetState();
|
||||||
|
|
||||||
|
void createNewPreset(const std::string & presetName);
|
||||||
|
void deletePreset(const std::string & presetName);
|
||||||
|
void activatePreset(const std::string & presetName);
|
||||||
|
void renamePreset(const std::string & oldPresetName, const std::string & newPresetName);
|
||||||
|
|
||||||
|
std::vector<std::string> getAllPresets() const;
|
||||||
|
std::string getActivePreset() const;
|
||||||
|
|
||||||
void setModActive(const TModID & modName, bool isActive);
|
void setModActive(const TModID & modName, bool isActive);
|
||||||
|
|
||||||
void addRootMod(const TModID & modName);
|
void addRootMod(const TModID & modName);
|
||||||
@ -139,6 +147,14 @@ public:
|
|||||||
|
|
||||||
void tryEnableMods(const TModList & modList);
|
void tryEnableMods(const TModList & modList);
|
||||||
void tryDisableMod(const TModID & modName);
|
void tryDisableMod(const TModID & modName);
|
||||||
|
|
||||||
|
void createNewPreset(const std::string & presetName);
|
||||||
|
void deletePreset(const std::string & presetName);
|
||||||
|
void activatePreset(const std::string & presetName);
|
||||||
|
void renamePreset(const std::string & oldPresetName, const std::string & newPresetName);
|
||||||
|
|
||||||
|
std::vector<std::string> getAllPresets() const;
|
||||||
|
std::string getActivePreset() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@ -199,7 +199,11 @@ void NetworkConnection::close()
|
|||||||
{
|
{
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
socket->close(ec);
|
socket->close(ec);
|
||||||
|
#if BOOST_VERSION >= 108700
|
||||||
|
timer->cancel();
|
||||||
|
#else
|
||||||
timer->cancel(ec);
|
timer->cancel(ec);
|
||||||
|
#endif
|
||||||
|
|
||||||
//NOTE: ignoring error code, intended
|
//NOTE: ignoring error code, intended
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,11 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#if BOOST_VERSION >= 108700
|
||||||
|
using NetworkContext = boost::asio::io_context;
|
||||||
|
#else
|
||||||
using NetworkContext = boost::asio::io_service;
|
using NetworkContext = boost::asio::io_service;
|
||||||
|
#endif
|
||||||
using NetworkSocket = boost::asio::ip::tcp::socket;
|
using NetworkSocket = boost::asio::ip::tcp::socket;
|
||||||
using NetworkAcceptor = boost::asio::ip::tcp::acceptor;
|
using NetworkAcceptor = boost::asio::ip::tcp::acceptor;
|
||||||
using NetworkBuffer = boost::asio::streambuf;
|
using NetworkBuffer = boost::asio::streambuf;
|
||||||
|
@ -1193,6 +1193,17 @@ void RemoveObject::applyGs(CGameState *gs)
|
|||||||
if (initiator.isValidPlayer())
|
if (initiator.isValidPlayer())
|
||||||
gs->getPlayerState(initiator)->destroyedObjects.insert(objectID);
|
gs->getPlayerState(initiator)->destroyedObjects.insert(objectID);
|
||||||
|
|
||||||
|
if(obj->getOwner().isValidPlayer())
|
||||||
|
{
|
||||||
|
gs->getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten
|
||||||
|
|
||||||
|
FlaggableMapObject* flaggableObject = dynamic_cast<FlaggableMapObject*>(obj);
|
||||||
|
if(flaggableObject)
|
||||||
|
{
|
||||||
|
flaggableObject->markAsDeleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(obj->ID == Obj::HERO) //remove beaten hero
|
if(obj->ID == Obj::HERO) //remove beaten hero
|
||||||
{
|
{
|
||||||
auto * beatenHero = dynamic_cast<CGHeroInstance *>(obj);
|
auto * beatenHero = dynamic_cast<CGHeroInstance *>(obj);
|
||||||
@ -1251,18 +1262,6 @@ void RemoveObject::applyGs(CGameState *gs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obj->getOwner().isValidPlayer())
|
|
||||||
{
|
|
||||||
gs->getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten
|
|
||||||
|
|
||||||
FlaggableMapObject* flaggableObject = dynamic_cast<FlaggableMapObject*>(obj);
|
|
||||||
if(flaggableObject)
|
|
||||||
{
|
|
||||||
flaggableObject->markAsDeleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
gs->map->instanceNames.erase(obj->instanceName);
|
gs->map->instanceNames.erase(obj->instanceName);
|
||||||
gs->map->objects[objectID.getNum()].dellNull();
|
gs->map->objects[objectID.getNum()].dellNull();
|
||||||
gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
|
gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
|
||||||
|
BIN
mapeditor/icons/document-open-recent.png
Normal file
BIN
mapeditor/icons/document-open-recent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
@ -67,10 +67,10 @@ void HeroSkillsWidget::on_checkBox_toggled(bool checked)
|
|||||||
|
|
||||||
void HeroSkillsWidget::obtainData()
|
void HeroSkillsWidget::obtainData()
|
||||||
{
|
{
|
||||||
ui->attack->setValue(hero.getPrimSkillLevel(PrimarySkill::ATTACK));
|
ui->attack->setValue(hero.getBasePrimarySkillValue(PrimarySkill::ATTACK));
|
||||||
ui->defence->setValue(hero.getPrimSkillLevel(PrimarySkill::DEFENSE));
|
ui->defence->setValue(hero.getBasePrimarySkillValue(PrimarySkill::DEFENSE));
|
||||||
ui->power->setValue(hero.getPrimSkillLevel(PrimarySkill::SPELL_POWER));
|
ui->power->setValue(hero.getBasePrimarySkillValue(PrimarySkill::SPELL_POWER));
|
||||||
ui->knowledge->setValue(hero.getPrimSkillLevel(PrimarySkill::KNOWLEDGE));
|
ui->knowledge->setValue(hero.getBasePrimarySkillValue(PrimarySkill::KNOWLEDGE));
|
||||||
|
|
||||||
if(!hero.secSkills.empty() && hero.secSkills.front().first.getNum() == -1)
|
if(!hero.secSkills.empty() && hero.secSkills.front().first.getNum() == -1)
|
||||||
return;
|
return;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QListWidget>
|
||||||
|
|
||||||
#include "../lib/VCMIDirs.h"
|
#include "../lib/VCMIDirs.h"
|
||||||
#include "../lib/VCMI_Lib.h"
|
#include "../lib/VCMI_Lib.h"
|
||||||
@ -222,6 +224,8 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||||||
ui->toolFill->setIcon(QIcon{":/icons/tool-fill.png"});
|
ui->toolFill->setIcon(QIcon{":/icons/tool-fill.png"});
|
||||||
ui->toolSelect->setIcon(QIcon{":/icons/tool-select.png"});
|
ui->toolSelect->setIcon(QIcon{":/icons/tool-select.png"});
|
||||||
ui->actionOpen->setIcon(QIcon{":/icons/document-open.png"});
|
ui->actionOpen->setIcon(QIcon{":/icons/document-open.png"});
|
||||||
|
ui->actionOpenRecent->setIcon(QIcon{":/icons/document-open-recent.png"});
|
||||||
|
ui->menuOpenRecent->setIcon(QIcon{":/icons/document-open-recent.png"});
|
||||||
ui->actionSave->setIcon(QIcon{":/icons/document-save.png"});
|
ui->actionSave->setIcon(QIcon{":/icons/document-save.png"});
|
||||||
ui->actionNew->setIcon(QIcon{":/icons/document-new.png"});
|
ui->actionNew->setIcon(QIcon{":/icons/document-new.png"});
|
||||||
ui->actionLevel->setIcon(QIcon{":/icons/toggle-underground.png"});
|
ui->actionLevel->setIcon(QIcon{":/icons/toggle-underground.png"});
|
||||||
@ -265,6 +269,8 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||||||
scenePreview = new QGraphicsScene(this);
|
scenePreview = new QGraphicsScene(this);
|
||||||
ui->objectPreview->setScene(scenePreview);
|
ui->objectPreview->setScene(scenePreview);
|
||||||
|
|
||||||
|
connect(ui->actionOpenRecentMore, &QAction::triggered, this, &MainWindow::on_actionOpenRecent_triggered);
|
||||||
|
|
||||||
//loading objects
|
//loading objects
|
||||||
loadObjectsTree();
|
loadObjectsTree();
|
||||||
|
|
||||||
@ -412,9 +418,21 @@ bool MainWindow::openMap(const QString & filenameSelect)
|
|||||||
|
|
||||||
filename = filenameSelect;
|
filename = filenameSelect;
|
||||||
initializeMap(controller.map()->version != EMapFormat::VCMI);
|
initializeMap(controller.map()->version != EMapFormat::VCMI);
|
||||||
|
|
||||||
|
updateRecentMenu(filenameSelect);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateRecentMenu(const QString & filenameSelect) {
|
||||||
|
QSettings s(Ui::teamName, Ui::appName);
|
||||||
|
QStringList recentFiles = s.value(recentlyOpenedFilesSetting).toStringList();
|
||||||
|
recentFiles.removeAll(filenameSelect);
|
||||||
|
recentFiles.prepend(filenameSelect);
|
||||||
|
constexpr int maxRecentFiles = 10;
|
||||||
|
s.setValue(recentlyOpenedFilesSetting, QStringList(recentFiles.mid(0, maxRecentFiles)));
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionOpen_triggered()
|
void MainWindow::on_actionOpen_triggered()
|
||||||
{
|
{
|
||||||
if(!getAnswerAboutUnsavedChanges())
|
if(!getAnswerAboutUnsavedChanges())
|
||||||
@ -429,6 +447,91 @@ void MainWindow::on_actionOpen_triggered()
|
|||||||
openMap(filenameSelect);
|
openMap(filenameSelect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_actionOpenRecent_triggered()
|
||||||
|
{
|
||||||
|
QSettings s(Ui::teamName, Ui::appName);
|
||||||
|
QStringList recentFiles = s.value(recentlyOpenedFilesSetting).toStringList();
|
||||||
|
|
||||||
|
class RecentFileDialog : public QDialog
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
RecentFileDialog(const QStringList& recentFiles, QWidget *parent)
|
||||||
|
: QDialog(parent), layout(new QVBoxLayout(this)), listWidget(new QListWidget(this))
|
||||||
|
{
|
||||||
|
|
||||||
|
setWindowTitle(tr("Recently Opened Files"));
|
||||||
|
setMinimumWidth(600);
|
||||||
|
|
||||||
|
connect(listWidget, &QListWidget::itemActivated, this, [this](QListWidgetItem *item)
|
||||||
|
{
|
||||||
|
accept();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const QString &file : recentFiles)
|
||||||
|
{
|
||||||
|
QListWidgetItem *item = new QListWidgetItem(file);
|
||||||
|
listWidget->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select most recent items by default.
|
||||||
|
// This enables a "CTRL+R => Enter"-workflow instead of "CTRL+R => 'mouse click on first item'"
|
||||||
|
if(listWidget->count() > 0)
|
||||||
|
{
|
||||||
|
listWidget->item(0)->setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
layout->setSizeConstraint(QLayout::SetMaximumSize);
|
||||||
|
layout->addWidget(listWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getSelectedFilePath() const
|
||||||
|
{
|
||||||
|
return listWidget->currentItem()->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVBoxLayout * layout;
|
||||||
|
QListWidget * listWidget;
|
||||||
|
};
|
||||||
|
|
||||||
|
RecentFileDialog d(recentFiles, this);
|
||||||
|
if(d.exec() == QDialog::Accepted && getAnswerAboutUnsavedChanges())
|
||||||
|
{
|
||||||
|
openMap(d.getSelectedFilePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_menuOpenRecent_aboutToShow()
|
||||||
|
{
|
||||||
|
// Clear all actions except "More...", lest the list will grow with each
|
||||||
|
// showing of the list
|
||||||
|
for (QAction* action : ui->menuOpenRecent->actions()) {
|
||||||
|
if (action != ui->actionOpenRecentMore) {
|
||||||
|
ui->menuOpenRecent->removeAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSettings s(Ui::teamName, Ui::appName);
|
||||||
|
QStringList recentFiles = s.value(recentlyOpenedFilesSetting).toStringList();
|
||||||
|
|
||||||
|
// Dynamically populate menuOpenRecent with one action per file.
|
||||||
|
for (const QString & file : recentFiles) {
|
||||||
|
QAction *action = new QAction(file, this);
|
||||||
|
ui->menuOpenRecent->insertAction(ui->actionOpenRecentMore, action);
|
||||||
|
connect(action, &QAction::triggered, this, [this, file]() {
|
||||||
|
if(!getAnswerAboutUnsavedChanges())
|
||||||
|
return;
|
||||||
|
openMap(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally add a separator between recent entries and "More..."
|
||||||
|
if(recentFiles.size() > 0) {
|
||||||
|
ui->menuOpenRecent->insertSeparator(ui->actionOpenRecentMore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::saveMap()
|
void MainWindow::saveMap()
|
||||||
{
|
{
|
||||||
if(!controller.map())
|
if(!controller.map())
|
||||||
@ -534,7 +637,7 @@ void MainWindow::roadOrRiverButtonClicked(ui8 type, bool isRoad)
|
|||||||
controller.commitRoadOrRiverChange(mapLevel, type, isRoad);
|
controller.commitRoadOrRiverChange(mapLevel, type, isRoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addGroupIntoCatalog(const std::string & groupName, bool staticOnly)
|
void MainWindow::addGroupIntoCatalog(const QString & groupName, bool staticOnly)
|
||||||
{
|
{
|
||||||
auto knownObjects = VLC->objtypeh->knownObjects();
|
auto knownObjects = VLC->objtypeh->knownObjects();
|
||||||
for(auto ID : knownObjects)
|
for(auto ID : knownObjects)
|
||||||
@ -546,13 +649,13 @@ void MainWindow::addGroupIntoCatalog(const std::string & groupName, bool staticO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addGroupIntoCatalog(const std::string & groupName, bool useCustomName, bool staticOnly, int ID)
|
void MainWindow::addGroupIntoCatalog(const QString & groupName, bool useCustomName, bool staticOnly, int ID)
|
||||||
{
|
{
|
||||||
QStandardItem * itemGroup = nullptr;
|
QStandardItem * itemGroup = nullptr;
|
||||||
auto itms = objectsModel.findItems(QString::fromStdString(groupName));
|
auto itms = objectsModel.findItems(groupName);
|
||||||
if(itms.empty())
|
if(itms.empty())
|
||||||
{
|
{
|
||||||
itemGroup = new QStandardItem(QString::fromStdString(groupName));
|
itemGroup = new QStandardItem(groupName);
|
||||||
objectsModel.appendRow(itemGroup);
|
objectsModel.appendRow(itemGroup);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -684,138 +787,158 @@ void MainWindow::loadObjectsTree()
|
|||||||
ui->treeView->setSelectionMode(QAbstractItemView::SingleSelection);
|
ui->treeView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(treeViewSelected(const QModelIndex &, const QModelIndex &)));
|
connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(treeViewSelected(const QModelIndex &, const QModelIndex &)));
|
||||||
|
|
||||||
|
//groups
|
||||||
|
enum GroupCat { TOWNS, OBJECTS, HEROES, ARTIFACTS, RESOURCES, BANKS, DWELLINGS, GROUNDS, TELEPORTS, MINES, TRIGGERS, MONSTERS, QUESTS, WOG_OBJECTS, OBSTACLES, OTHER };
|
||||||
|
QMap<GroupCat, QString> groups = {
|
||||||
|
{ TOWNS, tr("Towns") },
|
||||||
|
{ OBJECTS, tr("Objects") },
|
||||||
|
{ HEROES, tr("Heroes") },
|
||||||
|
{ ARTIFACTS, tr("Artifacts") },
|
||||||
|
{ RESOURCES, tr("Resources") },
|
||||||
|
{ BANKS, tr("Banks") },
|
||||||
|
{ DWELLINGS, tr("Dwellings") },
|
||||||
|
{ GROUNDS, tr("Grounds") },
|
||||||
|
{ TELEPORTS, tr("Teleports") },
|
||||||
|
{ MINES, tr("Mines") },
|
||||||
|
{ TRIGGERS, tr("Triggers") },
|
||||||
|
{ MONSTERS, tr("Monsters") },
|
||||||
|
{ QUESTS, tr("Quests") },
|
||||||
|
{ WOG_OBJECTS, tr("Wog Objects") },
|
||||||
|
{ OBSTACLES, tr("Obstacles") },
|
||||||
|
{ OTHER, tr("Other") },
|
||||||
|
};
|
||||||
|
|
||||||
//adding objects
|
//adding objects
|
||||||
addGroupIntoCatalog("TOWNS", false, false, Obj::TOWN);
|
addGroupIntoCatalog(groups[TOWNS], false, false, Obj::TOWN);
|
||||||
addGroupIntoCatalog("TOWNS", false, false, Obj::RANDOM_TOWN);
|
addGroupIntoCatalog(groups[TOWNS], false, false, Obj::RANDOM_TOWN);
|
||||||
addGroupIntoCatalog("TOWNS", true, false, Obj::SHIPYARD);
|
addGroupIntoCatalog(groups[TOWNS], true, false, Obj::SHIPYARD);
|
||||||
addGroupIntoCatalog("TOWNS", true, false, Obj::GARRISON);
|
addGroupIntoCatalog(groups[TOWNS], true, false, Obj::GARRISON);
|
||||||
addGroupIntoCatalog("TOWNS", true, false, Obj::GARRISON2);
|
addGroupIntoCatalog(groups[TOWNS], true, false, Obj::GARRISON2);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::ARENA);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::ARENA);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::BUOY);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::BUOY);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::CARTOGRAPHER);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::CARTOGRAPHER);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SWAN_POND);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SWAN_POND);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::COVER_OF_DARKNESS);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::COVER_OF_DARKNESS);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::CORPSE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::CORPSE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::FAERIE_RING);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::FAERIE_RING);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::FOUNTAIN_OF_FORTUNE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::FOUNTAIN_OF_FORTUNE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::FOUNTAIN_OF_YOUTH);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::FOUNTAIN_OF_YOUTH);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::GARDEN_OF_REVELATION);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::GARDEN_OF_REVELATION);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::HILL_FORT);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::HILL_FORT);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::IDOL_OF_FORTUNE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::IDOL_OF_FORTUNE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::LIBRARY_OF_ENLIGHTENMENT);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::LIBRARY_OF_ENLIGHTENMENT);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::LIGHTHOUSE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::LIGHTHOUSE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SCHOOL_OF_MAGIC);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SCHOOL_OF_MAGIC);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::MAGIC_SPRING);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::MAGIC_SPRING);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::MAGIC_WELL);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::MAGIC_WELL);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::MERCENARY_CAMP);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::MERCENARY_CAMP);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::MERMAID);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::MERMAID);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::MYSTICAL_GARDEN);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::MYSTICAL_GARDEN);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::OASIS);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::OASIS);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::LEAN_TO);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::LEAN_TO);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::OBELISK);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::OBELISK);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::REDWOOD_OBSERVATORY);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::REDWOOD_OBSERVATORY);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::PILLAR_OF_FIRE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::PILLAR_OF_FIRE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::STAR_AXIS);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::STAR_AXIS);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::RALLY_FLAG);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::RALLY_FLAG);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::WATERING_HOLE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::WATERING_HOLE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SCHOLAR);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SCHOLAR);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SHRINE_OF_MAGIC_INCANTATION);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SHRINE_OF_MAGIC_INCANTATION);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SHRINE_OF_MAGIC_GESTURE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SHRINE_OF_MAGIC_GESTURE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SHRINE_OF_MAGIC_THOUGHT);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SHRINE_OF_MAGIC_THOUGHT);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SIRENS);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SIRENS);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::STABLES);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::STABLES);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::TAVERN);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::TAVERN);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::TEMPLE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::TEMPLE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::DEN_OF_THIEVES);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::DEN_OF_THIEVES);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::LEARNING_STONE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::LEARNING_STONE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::TREE_OF_KNOWLEDGE);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::TREE_OF_KNOWLEDGE);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::WAGON);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::WAGON);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SCHOOL_OF_WAR);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SCHOOL_OF_WAR);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::WAR_MACHINE_FACTORY);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::WAR_MACHINE_FACTORY);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::WARRIORS_TOMB);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::WARRIORS_TOMB);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::WITCH_HUT);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::WITCH_HUT);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::SANCTUARY);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::SANCTUARY);
|
||||||
addGroupIntoCatalog("OBJECTS", true, false, Obj::MARLETTO_TOWER);
|
addGroupIntoCatalog(groups[OBJECTS], true, false, Obj::MARLETTO_TOWER);
|
||||||
addGroupIntoCatalog("HEROES", true, false, Obj::PRISON);
|
addGroupIntoCatalog(groups[HEROES], true, false, Obj::PRISON);
|
||||||
addGroupIntoCatalog("HEROES", false, false, Obj::HERO);
|
addGroupIntoCatalog(groups[HEROES], false, false, Obj::HERO);
|
||||||
addGroupIntoCatalog("HEROES", false, false, Obj::RANDOM_HERO);
|
addGroupIntoCatalog(groups[HEROES], false, false, Obj::RANDOM_HERO);
|
||||||
addGroupIntoCatalog("HEROES", false, false, Obj::HERO_PLACEHOLDER);
|
addGroupIntoCatalog(groups[HEROES], false, false, Obj::HERO_PLACEHOLDER);
|
||||||
addGroupIntoCatalog("HEROES", false, false, Obj::BOAT);
|
addGroupIntoCatalog(groups[HEROES], false, false, Obj::BOAT);
|
||||||
addGroupIntoCatalog("ARTIFACTS", true, false, Obj::ARTIFACT);
|
addGroupIntoCatalog(groups[ARTIFACTS], true, false, Obj::ARTIFACT);
|
||||||
addGroupIntoCatalog("ARTIFACTS", false, false, Obj::RANDOM_ART);
|
addGroupIntoCatalog(groups[ARTIFACTS], false, false, Obj::RANDOM_ART);
|
||||||
addGroupIntoCatalog("ARTIFACTS", false, false, Obj::RANDOM_TREASURE_ART);
|
addGroupIntoCatalog(groups[ARTIFACTS], false, false, Obj::RANDOM_TREASURE_ART);
|
||||||
addGroupIntoCatalog("ARTIFACTS", false, false, Obj::RANDOM_MINOR_ART);
|
addGroupIntoCatalog(groups[ARTIFACTS], false, false, Obj::RANDOM_MINOR_ART);
|
||||||
addGroupIntoCatalog("ARTIFACTS", false, false, Obj::RANDOM_MAJOR_ART);
|
addGroupIntoCatalog(groups[ARTIFACTS], false, false, Obj::RANDOM_MAJOR_ART);
|
||||||
addGroupIntoCatalog("ARTIFACTS", false, false, Obj::RANDOM_RELIC_ART);
|
addGroupIntoCatalog(groups[ARTIFACTS], false, false, Obj::RANDOM_RELIC_ART);
|
||||||
addGroupIntoCatalog("ARTIFACTS", true, false, Obj::SPELL_SCROLL);
|
addGroupIntoCatalog(groups[ARTIFACTS], true, false, Obj::SPELL_SCROLL);
|
||||||
addGroupIntoCatalog("ARTIFACTS", true, false, Obj::PANDORAS_BOX);
|
addGroupIntoCatalog(groups[ARTIFACTS], true, false, Obj::PANDORAS_BOX);
|
||||||
addGroupIntoCatalog("RESOURCES", true, false, Obj::RANDOM_RESOURCE);
|
addGroupIntoCatalog(groups[RESOURCES], true, false, Obj::RANDOM_RESOURCE);
|
||||||
addGroupIntoCatalog("RESOURCES", false, false, Obj::RESOURCE);
|
addGroupIntoCatalog(groups[RESOURCES], false, false, Obj::RESOURCE);
|
||||||
addGroupIntoCatalog("RESOURCES", true, false, Obj::SEA_CHEST);
|
addGroupIntoCatalog(groups[RESOURCES], true, false, Obj::SEA_CHEST);
|
||||||
addGroupIntoCatalog("RESOURCES", true, false, Obj::TREASURE_CHEST);
|
addGroupIntoCatalog(groups[RESOURCES], true, false, Obj::TREASURE_CHEST);
|
||||||
addGroupIntoCatalog("RESOURCES", true, false, Obj::CAMPFIRE);
|
addGroupIntoCatalog(groups[RESOURCES], true, false, Obj::CAMPFIRE);
|
||||||
addGroupIntoCatalog("RESOURCES", true, false, Obj::SHIPWRECK_SURVIVOR);
|
addGroupIntoCatalog(groups[RESOURCES], true, false, Obj::SHIPWRECK_SURVIVOR);
|
||||||
addGroupIntoCatalog("RESOURCES", true, false, Obj::FLOTSAM);
|
addGroupIntoCatalog(groups[RESOURCES], true, false, Obj::FLOTSAM);
|
||||||
addGroupIntoCatalog("BANKS", true, false, Obj::CREATURE_BANK);
|
addGroupIntoCatalog(groups[BANKS], true, false, Obj::CREATURE_BANK);
|
||||||
addGroupIntoCatalog("BANKS", true, false, Obj::DRAGON_UTOPIA);
|
addGroupIntoCatalog(groups[BANKS], true, false, Obj::DRAGON_UTOPIA);
|
||||||
addGroupIntoCatalog("BANKS", true, false, Obj::CRYPT);
|
addGroupIntoCatalog(groups[BANKS], true, false, Obj::CRYPT);
|
||||||
addGroupIntoCatalog("BANKS", true, false, Obj::DERELICT_SHIP);
|
addGroupIntoCatalog(groups[BANKS], true, false, Obj::DERELICT_SHIP);
|
||||||
addGroupIntoCatalog("BANKS", true, false, Obj::PYRAMID);
|
addGroupIntoCatalog(groups[BANKS], true, false, Obj::PYRAMID);
|
||||||
addGroupIntoCatalog("BANKS", true, false, Obj::SHIPWRECK);
|
addGroupIntoCatalog(groups[BANKS], true, false, Obj::SHIPWRECK);
|
||||||
addGroupIntoCatalog("DWELLINGS", true, false, Obj::CREATURE_GENERATOR1);
|
addGroupIntoCatalog(groups[DWELLINGS], true, false, Obj::CREATURE_GENERATOR1);
|
||||||
addGroupIntoCatalog("DWELLINGS", true, false, Obj::CREATURE_GENERATOR2);
|
addGroupIntoCatalog(groups[DWELLINGS], true, false, Obj::CREATURE_GENERATOR2);
|
||||||
addGroupIntoCatalog("DWELLINGS", true, false, Obj::CREATURE_GENERATOR3);
|
addGroupIntoCatalog(groups[DWELLINGS], true, false, Obj::CREATURE_GENERATOR3);
|
||||||
addGroupIntoCatalog("DWELLINGS", true, false, Obj::CREATURE_GENERATOR4);
|
addGroupIntoCatalog(groups[DWELLINGS], true, false, Obj::CREATURE_GENERATOR4);
|
||||||
addGroupIntoCatalog("DWELLINGS", true, false, Obj::REFUGEE_CAMP);
|
addGroupIntoCatalog(groups[DWELLINGS], true, false, Obj::REFUGEE_CAMP);
|
||||||
addGroupIntoCatalog("DWELLINGS", false, false, Obj::RANDOM_DWELLING);
|
addGroupIntoCatalog(groups[DWELLINGS], false, false, Obj::RANDOM_DWELLING);
|
||||||
addGroupIntoCatalog("DWELLINGS", false, false, Obj::RANDOM_DWELLING_LVL);
|
addGroupIntoCatalog(groups[DWELLINGS], false, false, Obj::RANDOM_DWELLING_LVL);
|
||||||
addGroupIntoCatalog("DWELLINGS", false, false, Obj::RANDOM_DWELLING_FACTION);
|
addGroupIntoCatalog(groups[DWELLINGS], false, false, Obj::RANDOM_DWELLING_FACTION);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::CURSED_GROUND1);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::CURSED_GROUND1);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::MAGIC_PLAINS1);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::MAGIC_PLAINS1);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::CLOVER_FIELD);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::CLOVER_FIELD);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::CURSED_GROUND2);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::CURSED_GROUND2);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::EVIL_FOG);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::EVIL_FOG);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::FAVORABLE_WINDS);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::FAVORABLE_WINDS);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::FIERY_FIELDS);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::FIERY_FIELDS);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::HOLY_GROUNDS);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::HOLY_GROUNDS);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::LUCID_POOLS);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::LUCID_POOLS);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::MAGIC_CLOUDS);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::MAGIC_CLOUDS);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::MAGIC_PLAINS2);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::MAGIC_PLAINS2);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::ROCKLANDS);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::ROCKLANDS);
|
||||||
addGroupIntoCatalog("GROUNDS", true, false, Obj::HOLE);
|
addGroupIntoCatalog(groups[GROUNDS], true, false, Obj::HOLE);
|
||||||
addGroupIntoCatalog("TELEPORTS", true, false, Obj::MONOLITH_ONE_WAY_ENTRANCE);
|
addGroupIntoCatalog(groups[TELEPORTS], true, false, Obj::MONOLITH_ONE_WAY_ENTRANCE);
|
||||||
addGroupIntoCatalog("TELEPORTS", true, false, Obj::MONOLITH_ONE_WAY_EXIT);
|
addGroupIntoCatalog(groups[TELEPORTS], true, false, Obj::MONOLITH_ONE_WAY_EXIT);
|
||||||
addGroupIntoCatalog("TELEPORTS", true, false, Obj::MONOLITH_TWO_WAY);
|
addGroupIntoCatalog(groups[TELEPORTS], true, false, Obj::MONOLITH_TWO_WAY);
|
||||||
addGroupIntoCatalog("TELEPORTS", true, false, Obj::SUBTERRANEAN_GATE);
|
addGroupIntoCatalog(groups[TELEPORTS], true, false, Obj::SUBTERRANEAN_GATE);
|
||||||
addGroupIntoCatalog("TELEPORTS", true, false, Obj::WHIRLPOOL);
|
addGroupIntoCatalog(groups[TELEPORTS], true, false, Obj::WHIRLPOOL);
|
||||||
addGroupIntoCatalog("MINES", true, false, Obj::MINE);
|
addGroupIntoCatalog(groups[MINES], true, false, Obj::MINE);
|
||||||
addGroupIntoCatalog("MINES", false, false, Obj::ABANDONED_MINE);
|
addGroupIntoCatalog(groups[MINES], false, false, Obj::ABANDONED_MINE);
|
||||||
addGroupIntoCatalog("MINES", true, false, Obj::WINDMILL);
|
addGroupIntoCatalog(groups[MINES], true, false, Obj::WINDMILL);
|
||||||
addGroupIntoCatalog("MINES", true, false, Obj::WATER_WHEEL);
|
addGroupIntoCatalog(groups[MINES], true, false, Obj::WATER_WHEEL);
|
||||||
addGroupIntoCatalog("TRIGGERS", true, false, Obj::EVENT);
|
addGroupIntoCatalog(groups[TRIGGERS], true, false, Obj::EVENT);
|
||||||
addGroupIntoCatalog("TRIGGERS", true, false, Obj::GRAIL);
|
addGroupIntoCatalog(groups[TRIGGERS], true, false, Obj::GRAIL);
|
||||||
addGroupIntoCatalog("TRIGGERS", true, false, Obj::SIGN);
|
addGroupIntoCatalog(groups[TRIGGERS], true, false, Obj::SIGN);
|
||||||
addGroupIntoCatalog("TRIGGERS", true, false, Obj::OCEAN_BOTTLE);
|
addGroupIntoCatalog(groups[TRIGGERS], true, false, Obj::OCEAN_BOTTLE);
|
||||||
addGroupIntoCatalog("MONSTERS", false, false, Obj::MONSTER);
|
addGroupIntoCatalog(groups[MONSTERS], false, false, Obj::MONSTER);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L1);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L1);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L2);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L2);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L3);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L3);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L4);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L4);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L5);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L5);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L6);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L6);
|
||||||
addGroupIntoCatalog("MONSTERS", true, false, Obj::RANDOM_MONSTER_L7);
|
addGroupIntoCatalog(groups[MONSTERS], true, false, Obj::RANDOM_MONSTER_L7);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::SEER_HUT);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::SEER_HUT);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::BORDER_GATE);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::BORDER_GATE);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::QUEST_GUARD);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::QUEST_GUARD);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::HUT_OF_MAGI);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::HUT_OF_MAGI);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::EYE_OF_MAGI);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::EYE_OF_MAGI);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::BORDERGUARD);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::BORDERGUARD);
|
||||||
addGroupIntoCatalog("QUESTS", true, false, Obj::KEYMASTER);
|
addGroupIntoCatalog(groups[QUESTS], true, false, Obj::KEYMASTER);
|
||||||
addGroupIntoCatalog("wog object", true, false, Obj::WOG_OBJECT);
|
addGroupIntoCatalog(groups[WOG_OBJECTS], true, false, Obj::WOG_OBJECT);
|
||||||
addGroupIntoCatalog("OBSTACLES", true);
|
addGroupIntoCatalog(groups[OBSTACLES], true);
|
||||||
addGroupIntoCatalog("OTHER", false);
|
addGroupIntoCatalog(groups[OTHER], false);
|
||||||
}
|
}
|
||||||
catch(const std::exception &)
|
catch(const std::exception &)
|
||||||
{
|
{
|
||||||
|
@ -28,6 +28,7 @@ class MainWindow : public QMainWindow
|
|||||||
const QString mainWindowSizeSetting = "MainWindow/Size";
|
const QString mainWindowSizeSetting = "MainWindow/Size";
|
||||||
const QString mainWindowPositionSetting = "MainWindow/Position";
|
const QString mainWindowPositionSetting = "MainWindow/Position";
|
||||||
const QString lastDirectorySetting = "MainWindow/Directory";
|
const QString lastDirectorySetting = "MainWindow/Directory";
|
||||||
|
const QString recentlyOpenedFilesSetting = "MainWindow/RecentlyOpenedFiles";
|
||||||
|
|
||||||
#ifdef ENABLE_QT_TRANSLATIONS
|
#ifdef ENABLE_QT_TRANSLATIONS
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
@ -59,6 +60,10 @@ public:
|
|||||||
private slots:
|
private slots:
|
||||||
void on_actionOpen_triggered();
|
void on_actionOpen_triggered();
|
||||||
|
|
||||||
|
void on_actionOpenRecent_triggered();
|
||||||
|
|
||||||
|
void on_menuOpenRecent_aboutToShow();
|
||||||
|
|
||||||
void on_actionSave_as_triggered();
|
void on_actionSave_as_triggered();
|
||||||
|
|
||||||
void on_actionNew_triggered();
|
void on_actionNew_triggered();
|
||||||
@ -153,8 +158,8 @@ public slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void preparePreview(const QModelIndex & index);
|
void preparePreview(const QModelIndex & index);
|
||||||
void addGroupIntoCatalog(const std::string & groupName, bool staticOnly);
|
void addGroupIntoCatalog(const QString & groupName, bool staticOnly);
|
||||||
void addGroupIntoCatalog(const std::string & groupName, bool useCustomName, bool staticOnly, int ID);
|
void addGroupIntoCatalog(const QString & groupName, bool useCustomName, bool staticOnly, int ID);
|
||||||
|
|
||||||
QAction * getActionPlayer(const PlayerColor &);
|
QAction * getActionPlayer(const PlayerColor &);
|
||||||
|
|
||||||
@ -170,6 +175,8 @@ private:
|
|||||||
|
|
||||||
void parseCommandLine(ExtractionOptions & extractionOptions);
|
void parseCommandLine(ExtractionOptions & extractionOptions);
|
||||||
|
|
||||||
|
void updateRecentMenu(const QString & filenameSelect);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow * ui;
|
Ui::MainWindow * ui;
|
||||||
ObjectBrowserProxyModel * objectBrowser = nullptr;
|
ObjectBrowserProxyModel * objectBrowser = nullptr;
|
||||||
|
@ -58,8 +58,15 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>File</string>
|
<string>File</string>
|
||||||
</property>
|
</property>
|
||||||
|
<widget class="QMenu" name="menuOpenRecent">
|
||||||
|
<property name="title">
|
||||||
|
<string>Open Recent</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionOpenRecentMore"/>
|
||||||
|
</widget>
|
||||||
<addaction name="actionNew"/>
|
<addaction name="actionNew"/>
|
||||||
<addaction name="actionOpen"/>
|
<addaction name="actionOpen"/>
|
||||||
|
<addaction name="menuOpenRecent"/>
|
||||||
<addaction name="actionSave"/>
|
<addaction name="actionSave"/>
|
||||||
<addaction name="actionSave_as"/>
|
<addaction name="actionSave_as"/>
|
||||||
<addaction name="actionExport"/>
|
<addaction name="actionExport"/>
|
||||||
@ -133,6 +140,7 @@
|
|||||||
</attribute>
|
</attribute>
|
||||||
<addaction name="actionNew"/>
|
<addaction name="actionNew"/>
|
||||||
<addaction name="actionOpen"/>
|
<addaction name="actionOpen"/>
|
||||||
|
<addaction name="actionOpenRecent"/>
|
||||||
<addaction name="actionSave"/>
|
<addaction name="actionSave"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionUndo"/>
|
<addaction name="actionUndo"/>
|
||||||
@ -1019,6 +1027,19 @@
|
|||||||
<string notr="true">Ctrl+O</string>
|
<string notr="true">Ctrl+O</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionOpenRecent">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open Recent</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionOpenRecentMore">
|
||||||
|
<property name="text">
|
||||||
|
<string>More...</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string notr="true">Ctrl+R</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionSave">
|
<action name="actionSave">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save</string>
|
<string>Save</string>
|
||||||
|
@ -43,7 +43,7 @@ void LoseConditions::initialize(MapController & c)
|
|||||||
|
|
||||||
for(auto & s : conditionStringsLose)
|
for(auto & s : conditionStringsLose)
|
||||||
{
|
{
|
||||||
ui->loseComboBox->addItem(QString::fromStdString(s));
|
ui->loseComboBox->addItem(tr(s.c_str()));
|
||||||
}
|
}
|
||||||
ui->standardLoseCheck->setChecked(false);
|
ui->standardLoseCheck->setChecked(false);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ void VictoryConditions::initialize(MapController & c)
|
|||||||
|
|
||||||
for(auto & s : conditionStringsWin)
|
for(auto & s : conditionStringsWin)
|
||||||
{
|
{
|
||||||
ui->victoryComboBox->addItem(QString::fromStdString(s));
|
ui->victoryComboBox->addItem(tr(s.c_str()));
|
||||||
}
|
}
|
||||||
ui->standardVictoryCheck->setChecked(false);
|
ui->standardVictoryCheck->setChecked(false);
|
||||||
ui->onlyForHumansCheck->setChecked(false);
|
ui->onlyForHumansCheck->setChecked(false);
|
||||||
|
@ -24,7 +24,7 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent)
|
|||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
//set colors and teams
|
//set colors and teams
|
||||||
ui->teamId->addItem("No team", QVariant(TeamID::NO_TEAM));
|
ui->teamId->addItem(tr("No team"), QVariant(TeamID::NO_TEAM));
|
||||||
for(int i = 0, index = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
|
for(int i = 0, index = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
|
||||||
{
|
{
|
||||||
if(i == playerId || !controller.map()->players[i].canAnyonePlay())
|
if(i == playerId || !controller.map()->players[i].canAnyonePlay())
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user