1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-21 17:17:06 +02:00

Merge branch 'vcmi/beta' into 'vcmi/develop'

This commit is contained in:
Ivan Savenko 2024-06-21 12:58:36 +00:00
commit 3bea383b59
69 changed files with 735 additions and 328 deletions

View File

@ -66,7 +66,7 @@ jobs:
pack: 1
pack_type: RelWithDebInfo
extension: exe
preset: windows-msvc-release-ccache
preset: windows-msvc-release
- platform: mingw
os: ubuntu-22.04
test: 0
@ -207,8 +207,15 @@ jobs:
- name: Configure
run: |
if [[ ${{matrix.preset}} == linux-gcc-test ]]; then GCC14=1; fi
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }} ${GCC14:+-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14}
if [[ ${{matrix.preset}} == linux-gcc-test ]]
then
cmake -DENABLE_CCACHE:BOOL=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 --preset ${{ matrix.preset }}
elif [[ ${{matrix.platform}} != msvc ]]
then
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
else
cmake --preset ${{ matrix.preset }}
fi
- name: Build
run: |
@ -283,7 +290,7 @@ jobs:
with:
name: Android JNI ${{matrix.platform}}
path: |
${{ github.workspace }}/android/vcmi-app/src/main/jniLibs
${{github.workspace}}/out/build/${{matrix.preset}}/android-build/libs
- name: Upload build
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }}
@ -306,7 +313,7 @@ jobs:
matrix:
include:
- platform: android-32
os: ubuntu-22.04
os: macos-14
preset: android-conan-ninja-release
conan_profile: android-32
conan_options: --conf tools.android:ndk_path=$ANDROID_NDK_ROOT
@ -346,6 +353,12 @@ jobs:
env:
GENERATE_ONLY_BUILT_CONFIG: 1
- uses: actions/setup-java@v4
if: ${{ startsWith(matrix.platform, 'android') }}
with:
distribution: 'temurin'
java-version: '11'
- name: Build Number
run: |
source '${{github.workspace}}/CI/get_package_name.sh'
@ -365,6 +378,9 @@ jobs:
- name: Build Preset
run: |
cmake --build --preset ${{matrix.preset}}
env:
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Download libs x64
uses: actions/download-artifact@v4

View File

@ -259,27 +259,46 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
return BattleAction::makeDefend(stack);
}
std::sort(hexes.begin(), hexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
{
return reachability.distances[h1] < reachability.distances[h2];
});
std::vector<BattleHex> targetHexes = hexes;
for(auto hex : hexes)
for(int i = 0; i < 5; i++)
{
if(vstd::contains(avHexes, hex))
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
{
return reachability.distances[h1] < reachability.distances[h2];
});
for(auto hex : targetHexes)
{
return BattleAction::makeMove(stack, hex);
if(vstd::contains(avHexes, hex))
{
return BattleAction::makeMove(stack, hex);
}
if(stack->coversPos(hex))
{
logAi->warn("Warning: already standing on neighbouring tile!");
//We shouldn't even be here...
return BattleAction::makeDefend(stack);
}
}
if(stack->coversPos(hex))
if(reachability.distances[targetHexes.front()] <= GameConstants::BFIELD_SIZE)
{
logAi->warn("Warning: already standing on neighbouring tile!");
//We shouldn't even be here...
return BattleAction::makeDefend(stack);
break;
}
std::vector<BattleHex> copy = targetHexes;
for(auto hex : copy)
{
vstd::concatenate(targetHexes, hex.allNeighbouringTiles());
}
vstd::removeDuplicates(targetHexes);
}
BattleHex bestNeighbor = hexes.front();
BattleHex bestNeighbor = targetHexes.front();
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
{
@ -602,10 +621,10 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
ps.value = scoreEvaluator.evaluateExchange(*cachedAttack, 0, *targets, innerCache, state);
}
for(auto unit : allUnits)
for(const auto & unit : allUnits)
{
auto newHealth = unit->getAvailableHealth();
auto oldHealth = healthOfStack[unit->unitId()];
auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units
if(oldHealth != newHealth)
{
@ -732,6 +751,3 @@ void BattleEvaluator::print(const std::string & text) const
{
logAi->trace("%s Battle AI[%p]: %s", playerID.toString(), this, text);
}

View File

@ -390,7 +390,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
const AttackPossibility & ap,
uint8_t turn,
PotentialTargets & targets,
std::shared_ptr<HypotheticBattle> hb)
std::shared_ptr<HypotheticBattle> hb) const
{
ReachabilityData result;
@ -402,7 +402,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
for(auto hex : hexes)
{
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap[hex] : getOneTurnReachableUnits(turn, hex));
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
}
vstd::removeDuplicates(allReachableUnits);
@ -481,7 +481,7 @@ float BattleExchangeEvaluator::evaluateExchange(
uint8_t turn,
PotentialTargets & targets,
DamageCache & damageCache,
std::shared_ptr<HypotheticBattle> hb)
std::shared_ptr<HypotheticBattle> hb) const
{
BattleScore score = calculateExchange(ap, turn, targets, damageCache, hb);
@ -502,7 +502,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
uint8_t turn,
PotentialTargets & targets,
DamageCache & damageCache,
std::shared_ptr<HypotheticBattle> hb)
std::shared_ptr<HypotheticBattle> hb) const
{
#if BATTLE_TRACE_LEVEL>=1
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
@ -613,7 +613,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
}
else
{
auto reachable = exchangeBattle->battleGetUnitsIf([&](const battle::Unit * u) -> bool
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
{
if(u->unitSide() == attacker->unitSide())
return false;
@ -621,7 +621,10 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
return false;
return vstd::contains_if(reachabilityMap[u->getPosition()], [&](const battle::Unit * other) -> bool
if (!u->getPosition().isValid())
return false; // e.g. tower shooters
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
{
return attacker->unitId() == other->unitId();
});
@ -732,7 +735,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr<HypotheticBa
}
}
std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex)
std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const
{
std::vector<const battle::Unit *> result;
@ -756,13 +759,10 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
auto unitSpeed = unit->getMovementRange(turn);
auto radius = unitSpeed * (turn + 1);
ReachabilityInfo unitReachability = vstd::getOrCompute(
reachabilityCache,
unit->unitId(),
[&](ReachabilityInfo & data)
{
data = turnBattle.getReachability(unit);
});
auto reachabilityIter = reachabilityCache.find(unit->unitId());
assert(reachabilityIter != reachabilityCache.end()); // missing updateReachabilityMap call?
ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
bool reachable = unitReachability.distances[hex] <= radius;

View File

@ -139,7 +139,7 @@ private:
uint8_t turn,
PotentialTargets & targets,
DamageCache & damageCache,
std::shared_ptr<HypotheticBattle> hb);
std::shared_ptr<HypotheticBattle> hb) const;
bool canBeHitThisTurn(const AttackPossibility & ap);
@ -162,16 +162,16 @@ public:
uint8_t turn,
PotentialTargets & targets,
DamageCache & damageCache,
std::shared_ptr<HypotheticBattle> hb);
std::shared_ptr<HypotheticBattle> hb) const;
std::vector<const battle::Unit *> getOneTurnReachableUnits(uint8_t turn, BattleHex hex);
std::vector<const battle::Unit *> getOneTurnReachableUnits(uint8_t turn, BattleHex hex) const;
void updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb);
ReachabilityData getExchangeUnits(
const AttackPossibility & ap,
uint8_t turn,
PotentialTargets & targets,
std::shared_ptr<HypotheticBattle> hb);
std::shared_ptr<HypotheticBattle> hb) const;
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);

View File

@ -1300,7 +1300,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
{
destinationTeleport = exitId;
if(exitPos.valid())
destinationTeleportPos = h->convertFromVisitablePos(exitPos);
destinationTeleportPos = exitPos;
cb->moveHero(*h, h->pos, false);
destinationTeleport = ObjectInstanceID();
destinationTeleportPos = int3(-1);
@ -1310,17 +1310,32 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
auto doChannelProbing = [&]() -> void
{
auto currentPos = h->visitablePos();
auto currentExit = getObj(currentPos, true)->id;
auto currentTeleport = getObj(currentPos, true);
status.setChannelProbing(true);
for(auto exit : teleportChannelProbingList)
doTeleportMovement(exit, int3(-1));
teleportChannelProbingList.clear();
status.setChannelProbing(false);
if(currentTeleport)
{
auto currentExit = currentTeleport->id;
doTeleportMovement(currentExit, currentPos);
status.setChannelProbing(true);
for(auto exit : teleportChannelProbingList)
doTeleportMovement(exit, int3(-1));
teleportChannelProbingList.clear();
status.setChannelProbing(false);
doTeleportMovement(currentExit, currentPos);
}
else
{
logAi->debug("Unexpected channel probbing at " + currentPos.toString());
teleportChannelProbingList.clear();
status.setChannelProbing(false);
}
};
teleportChannelProbingList.clear();
status.setChannelProbing(false);
for(; i > 0; i--)
{
int3 currentCoord = path.nodes[i].coord;

View File

@ -17,10 +17,10 @@ namespace NKAI
struct ClusterObjectInfo
{
float priority;
float movementCost;
uint64_t danger;
uint8_t turn;
float priority = 0.f;
float movementCost = 0.f;
uint64_t danger = 0;
uint8_t turn = 0;
};
struct ObjectInstanceIDHash

View File

@ -1203,7 +1203,10 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
std::vector<const ChainActor *> actorsVector(actorsOfInitial.begin(), actorsOfInitial.end());
tbb::concurrent_vector<CGPathNode *> output;
if(actorsVector.size() * initialNodes.size() > 1000)
// TODO: re-enable after fixing thread races. See issue for details:
// https://github.com/vcmi/vcmi/pull/4130
#if 0
if (actorsVector.size() * initialNodes.size() > 1000)
{
tbb::parallel_for(tbb::blocked_range<size_t>(0, actorsVector.size()), [&](const tbb::blocked_range<size_t> & r)
{
@ -1216,6 +1219,7 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
std::copy(output.begin(), output.end(), std::back_inserter(initialNodes));
}
else
#endif
{
for(auto actor : actorsVector)
{

View File

@ -1874,7 +1874,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
{
destinationTeleport = exitId;
if(exitPos.valid())
destinationTeleportPos = h->convertFromVisitablePos(exitPos);
destinationTeleportPos = exitPos;
cb->moveHero(*h, h->pos, false);
destinationTeleport = ObjectInstanceID();
destinationTeleportPos = int3(-1);

View File

@ -1,3 +1,79 @@
# 1.5.2 -> 1.5.3
### Stability
* Fixed possible crash when hero class has no valid commander.
* Fixed crash when pressing spacebar or enter during combat when hero has no tactics skill.
* Fixed crash when receiving a commander level-up after winning a battle in a garrison owned by an enemy player.
* Fixed possible crash when exiting a multiplayer game.
* Game will now display an error message and exit after loading instead of crashing silently if a creature's combat animation is missing.
* Game should now generate crash dump on uncaught c++ exception throw
* Fixed crash when player finishes game with negative score
* Fixed crash when opening tavern window in some localisations
* Fixed crash on loading previously generated random map when mods that add object with same name are used
* Game will now display an error message instead of silent crash if game data directory is not accessible
### Mechanics
* Transport Artefact victory condition will no longer trigger if another player has completed it.
* Fixed wandering monster combat not triggering when landing in its zone of control when flying from above the monster using the Fly spell.
* Fixed potentially infinite movement loop when the hero has Admiral's Hat whirlpool immunity and the hero tries to enter and exit the same whirlpool.
* If game picks gold for a random resource pile that has predetermined by map amount, its amount will be correctly multiplied by 100
* Fixed hero not being able to learn spells from a mod in some cases, even if they are available from the town's mage guild.
* The game will now actually take resources from seers' huts with the Gather Resources mission instead of awarding them.
* Heroes with double spell points will no longer trigger the Mana Vortex.
* If turn timer runs out during pve battle game will end player turn after a battle instead of forcing retreat
### Interface
* Fixed reversed button functions in Exchange Window
* Fixed allied towns being missing from the list when using the advanced or expert Town Portal spell.
* Fixed corrupted UI that could appear for a frame under certain conditions
* The '*' symbol and non-printable characters can no longer be used in savegames due to Windows file system restrictions.
* Pressing Ctrl while hovering over the adventure map will now display tile coordinates in the status bar.
* Selection of another hero while hero is selected now requires Shift press instead of Ctrl
* Fixed hero troops in the info box view flashing briefly during hero movement.
* Reduced excessive memory usage on adventure map by several hundreds of megabytes (most noticeable on systems with large screen resolution)
* Haptic feedback is now enabled by default on Android and on iOS
* It is now possible to scroll through artifacts backpack using mouse wheel or swipe
### Launcher
* Android now uses the same Qt-based launcher as other systems
* Fixed attempt to install a submod when installing new mod that depends on a submod of another mod
* Fixed wrong order of activating mods in chain when installing multiple mods at once
* Mod list no longer shows mod version column. Version is now only shown in the mod description.
* Launcher will now skip the Heroes 3 data import step if data has been found automatically
* Fixed inport of existing data files on iOS. This option now requires iOS 13 or later
* Fixed import using offline installer on iOS.
* Buttons to open data directories in the Help tab are now hidden on mobile systems if they can't be opened with file browser
* Added the configuration files directory to the Help tab as it is located separately on Linux systems
* Removed H3 data language selection during setup in favor of auto-detection
* Replaced checkboxes with toggle buttons for easier of access on touchscreens.
* Added interface for configuring several previously existing but inaccessible options in Launcher:
* Selection of input tolerance precision for all input types
* Relative cursor mode for mobile systems (was only available on Android)
* Haptic feedback toggle for mobile systems (was only available on Android)
* Sound and music volume (was only available in game)
* Selection of long touch interval (was only available in game)
* Selection of upscaling filter used by SDL
* Controller input sensitivity and acceleration.
### AI
* Fixed crash when Nullkiller AI tries to explore after losing the hero in combat.
* Fixed rare crash when Nullkiller AI tries to use portals
* Fixed potential crash when Nullkiller AI has access to Town Portal spell
* Fixed potential crash when Battle AI selects a spell to cast from a hero with summon spells.
* Several fixes to Nullkiller AI exploration logic
* Fixed bug leading to Battle AI doing nothing if targeted unit is unreachable
### Random Maps Generator
* Fixed crash when player selects a random number of players and selects a different colour to play, resulting in a non-continuous list of players.
* Fixed rare crash when generating maps with water
### Map Editor
* Fixed crash on closing map editor
### Modding
* Added new building type 'thievesGuild' which implements HotA building in Cove.
* Creature terrain limiter now actually accepts terrain as parameter
# 1.5.1 -> 1.5.2
### Stability

View File

@ -348,6 +348,15 @@ namespace vstd
return std::find(c.begin(),c.end(),i);
}
// returns existing value from map, or default value if key does not exists
template <typename Map>
const typename Map::mapped_type & find_or(const Map& m, const typename Map::key_type& key, const typename Map::mapped_type& defaultValue) {
auto it = m.find(key);
if (it == m.end())
return defaultValue;
return it->second;
}
//returns first key that maps to given value if present, returns success via found if provided
template <typename Key, typename T>
Key findKey(const std::map<Key, T> & map, const T & value, bool * found = nullptr)
@ -684,20 +693,6 @@ namespace vstd
return false;
}
template<class M, class Key, class F>
typename M::mapped_type & getOrCompute(M & m, const Key & k, F f)
{
typedef typename M::mapped_type V;
std::pair<typename M::iterator, bool> r = m.insert(typename M::value_type(k, V()));
V & v = r.first->second;
if(r.second)
f(v);
return v;
}
//c++20 feature
template<typename Arithmetic, typename Floating>
Arithmetic lerp(const Arithmetic & a, const Arithmetic & b, const Floating & f)

View File

@ -252,6 +252,13 @@
"vcmi.battleWindow.damageEstimation.damage.1" : "%d Schaden",
"vcmi.battleWindow.damageEstimation.kills" : "%d werden verenden",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d werden verenden",
"vcmi.battleWindow.damageRetaliation.will" : "Wird Vergeltung üben ",
"vcmi.battleWindow.damageRetaliation.may" : "Kann Vergeltung üben ",
"vcmi.battleWindow.damageRetaliation.never" : "Wird keine Vergeltung üben.",
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
"vcmi.battleWindow.killed" : "Getötet",
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s wurden durch gezielte Schüsse getötet!",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s wurde mit einem gezielten Schuss getötet!",

View File

@ -125,6 +125,13 @@
"vcmi.lobby.mod.state.version" : "Розбіжність версій",
"vcmi.lobby.mod.state.excessive" : "Має бути вимкнена",
"vcmi.lobby.mod.state.missing" : "Не встановлена",
"vcmi.lobby.pvp.coin.hover" : "Монетка.",
"vcmi.lobby.pvp.coin.help" : "Підкинути монетку",
"vcmi.lobby.pvp.randomTown.hover" : "Випадкове місто",
"vcmi.lobby.pvp.randomTown.help" : "Написати в чаті випадкове місто",
"vcmi.lobby.pvp.randomTownVs.hover" : "Випадкові міста",
"vcmi.lobby.pvp.randomTownVs.help" : "Написати в чаті два випадкових міста",
"vcmi.lobby.pvp.versus" : "проти",
"vcmi.client.errors.invalidMap" : "{Пошкоджена карта або кампанія}\n\nНе вдалося запустити гру! Вибрана карта або кампанія може бути невірною або пошкодженою. Причина:\n%s",
"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
@ -253,7 +260,6 @@
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
"vcmi.battleWindow.killed" : "Загинуло",
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s було вбито влучними пострілами!",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s було вбито влучним пострілом!",
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s було вбито влучними пострілами!",
@ -381,6 +387,14 @@
"vcmi.optionsTab.simturns.months.1" : " %d місяць",
"vcmi.optionsTab.simturns.months.2" : " %d місяці",
"vcmi.optionsTab.extraOptions.hover" : "Розширені опції",
"vcmi.optionsTab.extraOptions.help" : "Додаткові налаштування для гри",
"vcmi.optionsTab.cheatAllowed.hover" : "Дозволити чит-коди",
"vcmi.optionsTab.unlimitedReplay.hover" : "Необмежена кількість перегравань бою",
"vcmi.optionsTab.cheatAllowed.help" : "{Дозволити чіт-коди}\nДозволяє вводити чит-коди під час гри.",
"vcmi.optionsTab.unlimitedReplay.help" : "{Необмежена кількість перегравань бою}\nКількість перегравань боїв не обмежена.",
// Custom victory conditions for H3 campaigns and HotA maps
"vcmi.map.victoryCondition.daysPassed.toOthers" : "Ворогу вдалося вижити до сьогоднішнього дня. Він переміг!",
"vcmi.map.victoryCondition.daysPassed.toSelf" : "Вітаємо! Вам вдалося залишитися в живих. Перемога за вами!",
@ -389,6 +403,84 @@
"vcmi.map.victoryCondition.collectArtifacts.message" : "Здобути три артефакти",
"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Вітаємо! Усі ваші вороги переможені, і ви маєте Альянс Ангелів! Перемога ваша!",
"vcmi.map.victoryCondition.angelicAlliance.message" : "Перемогти всіх ворогів і створити Альянс Ангелів",
"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "На жаль, ви втратили частину Альянсу Ангелів. Все втрачено.",
// few strings from WoG used by vcmi
// "vcmi.stackExperience.description" : "» S t a c k E x p e r i e n c e D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",
"vcmi.stackExperience.rank.0" : "Базовий",
"vcmi.stackExperience.rank.1" : "Початківець",
"vcmi.stackExperience.rank.2" : "Підготовлений",
"vcmi.stackExperience.rank.3" : "Кваліфікований",
"vcmi.stackExperience.rank.4" : "Перевірений",
"vcmi.stackExperience.rank.5" : "Ветеран",
"vcmi.stackExperience.rank.6" : "Адепт",
"vcmi.stackExperience.rank.7" : "Експерт",
"vcmi.stackExperience.rank.8" : "Еліта",
"vcmi.stackExperience.rank.9" : "Майстер",
"vcmi.stackExperience.rank.10" : "Ас",
// Strings for HotA Seer Hut / Quest Guards
"core.seerhut.quest.heroClass.complete.0" : "А, ти %s. Ось тобі подарунок. Приймаєш?",
"core.seerhut.quest.heroClass.complete.1" : "А, ти %s. Ось тобі подарунок. Приймаєш?",
"core.seerhut.quest.heroClass.complete.2" : "А, ти %s. Ось тобі подарунок. Приймаєш?",
"core.seerhut.quest.heroClass.complete.3" : "Вартові помічають, що ви - %s, і пропонують вас пропустити. Чи погоджуєтесь ви?",
"core.seerhut.quest.heroClass.complete.4" : "Вартові помічають, що ви - %s, і пропонують вас пропустити. Чи погоджуєтесь ви?",
"core.seerhut.quest.heroClass.complete.5" : "Вартові помічають, що ви - %s, і пропонують вас пропустити. Чи погоджуєтесь ви?",
"core.seerhut.quest.heroClass.description.0" : "Відправити %s до %s",
"core.seerhut.quest.heroClass.description.1" : "Відправити %s до %s",
"core.seerhut.quest.heroClass.description.2" : "Відправити %s до %s",
"core.seerhut.quest.heroClass.description.3" : "Відправити %s до щоб відкрити ворота",
"core.seerhut.quest.heroClass.description.4" : "Відправити %s до щоб відкрити ворота",
"core.seerhut.quest.heroClass.description.5" : "Відправити %s до щоб відкрити ворота",
"core.seerhut.quest.heroClass.hover.0" : "(шукає героя класу %s)",
"core.seerhut.quest.heroClass.hover.1" : "(шукає героя класу %s)",
"core.seerhut.quest.heroClass.hover.2" : "(шукає героя класу %s)",
"core.seerhut.quest.heroClass.hover.3" : "(шукає героя класу %s)",
"core.seerhut.quest.heroClass.hover.4" : "(шукає героя класу %s)",
"core.seerhut.quest.heroClass.hover.5" : "(шукає героя класу %s)",
"core.seerhut.quest.heroClass.receive.0" : "У мене є подарунок для %s.",
"core.seerhut.quest.heroClass.receive.1" : "У мене є подарунок для %s.",
"core.seerhut.quest.heroClass.receive.2" : "У мене є подарунок для %s.",
"core.seerhut.quest.heroClass.receive.3" : "Вартові кажуть, що пропускають лише %s.",
"core.seerhut.quest.heroClass.receive.4" : "Вартові кажуть, що пропускають лише %s.",
"core.seerhut.quest.heroClass.receive.5" : "Вартові кажуть, що пропускають лише %s.",
"core.seerhut.quest.heroClass.visit.0" : "Ти не %s. У мене для тебе нічого немає. Йди геть!",
"core.seerhut.quest.heroClass.visit.1" : "Ти не %s. У мене для тебе нічого немає. Йди геть!",
"core.seerhut.quest.heroClass.visit.2" : "Ти не %s. У мене для тебе нічого немає. Йди геть!",
"core.seerhut.quest.heroClass.visit.3" : "Вартові пропускають лише %s.",
"core.seerhut.quest.heroClass.visit.4" : "Вартові пропускають лише %s.",
"core.seerhut.quest.heroClass.visit.5" : "Вартові пропускають лише %s.",
"core.seerhut.quest.reachDate.complete.0" : "Тепер я вільний. Ось що у мене є для тебе. Ти приймаєш?",
"core.seerhut.quest.reachDate.complete.1" : "Тепер я вільний. Ось що у мене є для тебе. Ти приймаєш?",
"core.seerhut.quest.reachDate.complete.2" : "Тепер я вільний. Ось що у мене є для тебе. Ти приймаєш?",
"core.seerhut.quest.reachDate.complete.3" : "Тепер ви можете пройти. Бажаєте пройти?",
"core.seerhut.quest.reachDate.complete.4" : "Тепер ви можете пройти. Бажаєте пройти?",
"core.seerhut.quest.reachDate.complete.5" : "Тепер ви можете пройти. Бажаєте пройти?",
"core.seerhut.quest.reachDate.description.0" : "Зачекайте до %s для %s",
"core.seerhut.quest.reachDate.description.1" : "Зачекайте до %s для %s",
"core.seerhut.quest.reachDate.description.2" : "Зачекайте до %s для %s",
"core.seerhut.quest.reachDate.description.3" : "Зачекайте до %s, щоб відкрити ворота",
"core.seerhut.quest.reachDate.description.4" : "Зачекайте до %s, щоб відкрити ворота",
"core.seerhut.quest.reachDate.description.5" : "Зачекайте до %s, щоб відкрити ворота",
"core.seerhut.quest.reachDate.hover.0" : "(Повертайтеся не раніше, ніж через %s)",
"core.seerhut.quest.reachDate.hover.1" : "(Повертайтеся не раніше, ніж через %s)",
"core.seerhut.quest.reachDate.hover.2" : "(Повертайтеся не раніше, ніж через %s)",
"core.seerhut.quest.reachDate.hover.3" : "(Повертайтеся не раніше, ніж через %s)",
"core.seerhut.quest.reachDate.hover.4" : "(Повертайтеся не раніше, ніж через %s)",
"core.seerhut.quest.reachDate.hover.5" : "(Повертайтеся не раніше, ніж через %s)",
"core.seerhut.quest.reachDate.receive.0" : "Я зайнят. Повертайтеся не раніше, ніж через %s",
"core.seerhut.quest.reachDate.receive.1" : "Я зайнят. Повертайтеся не раніше, ніж через %s",
"core.seerhut.quest.reachDate.receive.2" : "Я зайнят. Повертайтеся не раніше, ніж через %s",
"core.seerhut.quest.reachDate.receive.3" : "Закрито до %s.",
"core.seerhut.quest.reachDate.receive.4" : "Закрито до %s.",
"core.seerhut.quest.reachDate.receive.5" : "Закрито до %s.",
"core.seerhut.quest.reachDate.visit.0" : "Я зайнятий. Приходь не раніше, ніж %s",
"core.seerhut.quest.reachDate.visit.1" : "Я зайнятий. Приходь не раніше, ніж %s",
"core.seerhut.quest.reachDate.visit.2" : "Я зайнятий. Приходь не раніше, ніж %s",
"core.seerhut.quest.reachDate.visit.3" : "Закрито до %s.",
"core.seerhut.quest.reachDate.visit.4" : "Закрито до %s.",
"core.seerhut.quest.reachDate.visit.5" : "Закрито до %s.",
"core.bonus.ADDITIONAL_ATTACK.name" : "Подвійний удар",
"core.bonus.ADDITIONAL_ATTACK.description" : "Атакує двічі",

View File

@ -2,16 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.vcmi.vcmi">
<!-- %%INSERT_PERMISSIONS -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS_DISABLED -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<supports-screens
android:largeScreens="true"

View File

@ -210,7 +210,14 @@ int main(int argc, char * argv[])
logGlobal->info("The log file will be saved to %s", logPath);
// Init filesystem and settings
preinitDLL(::console, false);
try
{
preinitDLL(::console, false);
}
catch (const DataLoadingException & e)
{
handleFatalError(e.what(), true);
}
Settings session = settings.write["session"];
auto setSettingBool = [&](std::string key, std::string arg) {

View File

@ -245,8 +245,15 @@ void CPlayerInterface::performAutosave()
int txtlen = TextOperations::getUnicodeCharactersCount(name);
TextOperations::trimRightUnicode(name, std::max(0, txtlen - 15));
std::string forbiddenChars("\\/:*?\"<>| ");
std::replace_if(name.begin(), name.end(), [&](char c) { return std::string::npos != forbiddenChars.find(c); }, '_' );
auto const & isSymbolIllegal = [&](char c) {
static const std::string forbiddenChars("\\/:*?\"<>| ");
bool charForbidden = forbiddenChars.find(c) != std::string::npos;
bool charNonprintable = static_cast<unsigned char>(c) < static_cast<unsigned char>(' ');
return charForbidden || charNonprintable;
};
std::replace_if(name.begin(), name.end(), isSymbolIllegal, '_' );
prefix = name + "_" + cb->getStartInfo()->startTimeIso8601 + "/";
}
@ -1087,18 +1094,20 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
{
EVENT_HANDLER_CALLED_BY_CLIENT;
std::vector<ObjectInstanceID> tmpObjects;
if(objects.size() && dynamic_cast<const CGTownInstance *>(cb->getObj(objects[0])))
{
// sorting towns (like in client)
std::vector <const CGTownInstance*> Towns = LOCPLINT->localState->getOwnedTowns();
for(auto town : Towns)
for(auto item : objects)
if(town == cb->getObj(item))
tmpObjects.push_back(item);
}
else // other object list than town
tmpObjects = objects;
std::vector<ObjectInstanceID> objectGuiOrdered = objects;
std::map<ObjectInstanceID, int> townOrder;
auto ownedTowns = localState->getOwnedTowns();
for (int i = 0; i < ownedTowns.size(); ++i)
townOrder[ownedTowns[i]->id] = i;
auto townComparator = [&townOrder](const ObjectInstanceID & left, const ObjectInstanceID & right){
uint32_t leftIndex= townOrder.count(left) ? townOrder.at(left) : std::numeric_limits<uint32_t>::max();
uint32_t rightIndex = townOrder.count(right) ? townOrder.at(right) : std::numeric_limits<uint32_t>::max();
return leftIndex < rightIndex;
};
std::stable_sort(objectGuiOrdered.begin(), objectGuiOrdered.end(), townComparator);
auto selectCallback = [=](int selection)
{
@ -1114,9 +1123,9 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
const std::string localDescription = description.toString();
std::vector<int> tempList;
tempList.reserve(tmpObjects.size());
tempList.reserve(objectGuiOrdered.size());
for(auto item : tmpObjects)
for(auto item : objectGuiOrdered)
tempList.push_back(item.getNum());
CComponent localIconC(icon);
@ -1125,7 +1134,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
localIconC.removeChild(localIcon.get(), false);
std::vector<std::shared_ptr<IImage>> images;
for(auto & obj : tmpObjects)
for(auto & obj : objectGuiOrdered)
{
if(!settings["general"]["enableUiEnhancements"].Bool())
break;
@ -1140,8 +1149,8 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
auto wnd = std::make_shared<CObjectListWindow>(tempList, localIcon, localTitle, localDescription, selectCallback, 0, images);
wnd->onExit = cancelCallback;
wnd->onPopup = [this, tmpObjects](int index) { CRClickPopup::createAndPush(cb->getObj(tmpObjects[index]), GH.getCursorPosition()); };
wnd->onClicked = [this, tmpObjects](int index) { adventureInt->centerOnObject(cb->getObj(tmpObjects[index])); GH.windows().totalRedraw(); };
wnd->onPopup = [this, objectGuiOrdered](int index) { CRClickPopup::createAndPush(cb->getObj(objectGuiOrdered[index]), GH.getCursorPosition()); };
wnd->onClicked = [this, objectGuiOrdered](int index) { adventureInt->centerOnObject(cb->getObj(objectGuiOrdered[index])); GH.windows().totalRedraw(); };
GH.windows().pushWindow(wnd);
}

View File

@ -607,9 +607,18 @@ void CClient::removeGUI() const
#ifdef VCMI_ANDROID
extern "C" JNIEXPORT jboolean JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jclass cls)
{
boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
logGlobal->info("Received emergency save game request");
if(!LOCPLINT || !LOCPLINT->cb)
{
logGlobal->info("... but no active player interface found!");
return false;
}
if (!CSH || !CSH->logicConnection)
{
logGlobal->info("... but no active connection found!");
return false;
}

View File

@ -91,9 +91,7 @@ void HeroMovementController::showTeleportDialog(const CGHeroInstance * hero, Tel
for(size_t i = 0; i < exits.size(); ++i)
{
const auto * teleporter = LOCPLINT->cb->getObj(exits[i].first);
if(teleporter && teleporter->visitableAt(nextNode.coord))
if(exits[i].second == nextNode.coord)
{
// Remove this node from path - it will be covered by teleportation
//LOCPLINT->localState->removeLastNode(hero);

View File

@ -564,7 +564,7 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
}
else
{
if(GH.isKeyboardCmdDown()) //normal click behaviour (as no hero selected)
if(GH.isKeyboardShiftDown()) //normal click behaviour (as no hero selected)
{
if(canSelect)
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
@ -643,19 +643,19 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
std::string text = LOCPLINT->localState->getCurrentHero() ? objAtTile->getHoverText(LOCPLINT->localState->getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
boost::replace_all(text,"\n"," ");
if (GH.isKeyboardShiftDown())
if (GH.isKeyboardCmdDown())
text.append(" (" + std::to_string(targetPosition.x) + ", " + std::to_string(targetPosition.y) + ")");
GH.statusbar()->write(text);
}
else if(isTargetPositionVisible)
{
std::string tileTooltipText = CGI->mh->getTerrainDescr(targetPosition, false);
if (GH.isKeyboardShiftDown())
if (GH.isKeyboardCmdDown())
tileTooltipText.append(" (" + std::to_string(targetPosition.x) + ", " + std::to_string(targetPosition.y) + ")");
GH.statusbar()->write(tileTooltipText);
}
if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN || GH.isKeyboardCtrlDown())
if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN || GH.isKeyboardShiftDown())
{
if(objAtTile)
{

View File

@ -118,9 +118,9 @@ void CGuiHandler::renderFrame()
if (settings["video"]["showfps"].Bool())
drawFPSCounter();
}
SDL_UpdateTexture(screenTexture, nullptr, screen->pixels, screen->pitch);
SDL_UpdateTexture(screenTexture, nullptr, screen->pixels, screen->pitch);
}
SDL_RenderClear(mainRenderer);
SDL_RenderCopy(mainRenderer, screenTexture, nullptr, nullptr);

View File

@ -63,17 +63,43 @@ auto HighScoreCalculation::calculate()
return summary;
}
struct HighScoreCreature
{
CreatureID creature;
int min;
int max;
};
static std::vector<HighScoreCreature> getHighscoreCreaturesList()
{
JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json"));
std::vector<HighScoreCreature> ret;
for(auto & json : configCreatures["creatures"].Vector())
{
HighScoreCreature entry;
entry.creature = CreatureID::decode(json["creature"].String());
entry.max = json["max"].isNull() ? std::numeric_limits<int>::max() : json["max"].Integer();
entry.min = json["min"].isNull() ? std::numeric_limits<int>::min() : json["min"].Integer();
ret.push_back(entry);
}
return ret;
}
CreatureID HighScoreCalculation::getCreatureForPoints(int points, bool campaign)
{
static const JsonNode configCreatures(JsonPath::builtin("CONFIG/highscoreCreatures.json"));
auto creatures = configCreatures["creatures"].Vector();
static const std::vector<HighScoreCreature> creatures = getHighscoreCreaturesList();
int divide = campaign ? 5 : 1;
for(auto & creature : creatures)
if(points / divide <= creature["max"].Integer() && points / divide >= creature["min"].Integer())
return CreatureID::decode(creature["creature"].String());
if(points / divide <= creature.max && points / divide >= creature.min)
return creature.creature;
return -1;
throw std::runtime_error("Unable to find creature for score " + std::to_string(points));
}
CHighScoreScreen::CHighScoreScreen(HighScorePage highscorepage, int highlighted)

View File

@ -60,7 +60,7 @@
#include "../../lib/CRandomGenerator.h"
std::shared_ptr<CMainMenu> CMM;
ISelectionScreenInfo * SEL;
ISelectionScreenInfo * SEL = nullptr;
static void do_quit()
{

View File

@ -235,7 +235,10 @@ void SDLImage::setFlagColor(PlayerColor player)
bool SDLImage::isTransparent(const Point & coords) const
{
return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
if (surf)
return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
else
return true;
}
Point SDLImage::dimensions() const

View File

@ -83,6 +83,9 @@ void CArtifactsOfHeroBase::init(
leftBackpackRoll->block(true);
rightBackpackRoll->block(true);
backpackScroller = std::make_shared<BackpackScroller>(this, Rect(380, 30, 278, 382));
backpackScroller->setScrollingEnabled(false);
setRedrawParent(true);
}
@ -208,6 +211,8 @@ void CArtifactsOfHeroBase::updateBackpackSlots()
leftBackpackRoll->block(!scrollingPossible);
if(rightBackpackRoll)
rightBackpackRoll->block(!scrollingPossible);
if (backpackScroller)
backpackScroller->setScrollingEnabled(scrollingPossible);
}
void CArtifactsOfHeroBase::updateSlot(const ArtifactPosition & slot)
@ -277,3 +282,17 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
artPlace->setArtifact(nullptr);
}
}
BackpackScroller::BackpackScroller(CArtifactsOfHeroBase * owner, const Rect & dimensions)
: Scrollable(0, Point(), Orientation::HORIZONTAL)
, owner(owner)
{
pos = dimensions + pos.topLeft();
setPanningStep(46);
}
void BackpackScroller::scrollBy(int distance)
{
if (distance != 0)
owner->scrollBackpack(distance < 0);
}

View File

@ -10,10 +10,12 @@
#pragma once
#include "CArtPlace.h"
#include "Scrollable.h"
#include "../gui/Shortcut.h"
class CButton;
class BackpackScroller;
class CArtifactsOfHeroBase : virtual public CIntObject, public CKeyShortcut
{
@ -54,6 +56,7 @@ public:
std::vector<ArtPlacePtr> backpack;
std::shared_ptr<CButton> leftBackpackRoll;
std::shared_ptr<CButton> rightBackpackRoll;
std::shared_ptr<BackpackScroller> backpackScroller;
const std::vector<Point> slotPos =
{
@ -71,3 +74,13 @@ protected:
// Assigns an artifacts to an artifact place depending on it's new slot ID
virtual void setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot);
};
class BackpackScroller : public Scrollable
{
CArtifactsOfHeroBase * owner;
void scrollBy(int distance) override;
public:
BackpackScroller(CArtifactsOfHeroBase * owner, const Rect & dimensions);
};

View File

@ -637,6 +637,9 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A
assert(CGI->townh->size() > faction);
if (cre->animDefName.empty())
throw std::runtime_error("Creature " + cre->getJsonKey() + " has no valid combat animation!");
if(Big)
bg = std::make_shared<CPicture>((*CGI->townh)[faction]->creatureBg130);
else

View File

@ -1548,6 +1548,9 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
const CBuilding * building = nullptr;
for(auto & buildingID : boxList[row][col])//we are looking for the first not built structure
{
if (town->town->buildings.count(buildingID) == 0)
throw std::runtime_error("Town " + Town->town->faction->getJsonKey() + " has no building with ID " + std::to_string(buildingID.getNum()));
const CBuilding * current = town->town->buildings.at(buildingID);
if(vstd::contains(town->builtBuildings, buildingID))
{

View File

@ -491,14 +491,21 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
}
else if(LOCPLINT->cb->howManyHeroes(true) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP))
{
MetaString message;
message.appendTextID("core.tvrninfo.1");
message.replaceNumber(LOCPLINT->cb->howManyHeroes(true));
//Cannot recruit. You already have %d Heroes.
recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true)));
recruit->addHoverText(EButtonState::NORMAL, message.toString());
recruit->block(true);
}
else if(LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
{
//Cannot recruit. You already have %d Heroes.
recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false)));
MetaString message;
message.appendTextID("core.tvrninfo.1");
message.replaceNumber(LOCPLINT->cb->howManyHeroes(false));
recruit->addHoverText(EButtonState::NORMAL, message.toString());
recruit->block(true);
}
else if(dynamic_cast<const CGTownInstance *>(TavernObj) && dynamic_cast<const CGTownInstance *>(TavernObj)->visitingHero)

View File

@ -1,6 +1,6 @@
{
"creatures": [
{ "min" : 1, "max" : 4, "creature": "imp" },
{ "max" : 4, "creature": "imp" },
{ "min" : 5, "max" : 8, "creature": "gremlin" },
{ "min" : 9, "max" : 12, "creature": "gnoll" },
{ "min" : 13, "max" : 16, "creature": "troglodyte" },
@ -117,6 +117,6 @@
{ "min" : 389, "max" : 391, "creature": "titan" },
{ "min" : 392, "max" : 394, "creature": "goldDragon" },
{ "min" : 395, "max" : 397, "creature": "blackDragon" },
{ "min" : 398, "max" : 500, "creature": "archangel" }
{ "min" : 398, "creature": "archangel" }
]
}

View File

@ -2,6 +2,7 @@
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.1)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.2)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.3/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.3)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project

View File

@ -349,10 +349,10 @@ Negates all natural immunities for affected stacks. (Orb of Vulnerability)
### OPENING_BATTLE_SPELL
In battle, army affected by this bonus will cast spell at the very start of the battle
In battle, army affected by this bonus will cast spell at the very start of the battle. Spell is always cast at expert level.
- subtype: spell identifer
- val: spell mastery level
- val: duration of the spell, in rounds
### FREE_SHIP_BOARDING

View File

@ -26,6 +26,8 @@ set(launcher_SRCS
if(APPLE_IOS)
list(APPEND launcher_SRCS
ios/launchGame.m
ios/revealdirectoryinfiles.h
ios/revealdirectoryinfiles.mm
ios/selectdirectory.h
ios/selectdirectory.mm
)
@ -110,12 +112,9 @@ endif()
assign_source_group(${launcher_SRCS} ${launcher_HEADERS} ${launcher_RESOURCES} ${launcher_TS} ${launcher_ICON})
# TODO: enabling AUTORCC breaks msvc build on CI
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
if(NOT (MSVC AND "$ENV{GITHUB_ACTIONS}" STREQUAL true))
set(CMAKE_AUTORCC ON)
endif()
set(CMAKE_AUTORCC ON)
if(POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)

View File

@ -16,6 +16,24 @@
#include "../../lib/GameConstants.h"
#include "../../lib/VCMIDirs.h"
#ifdef VCMI_IOS
#include "ios/revealdirectoryinfiles.h"
#endif
namespace
{
void revealDirectoryInFileBrowser(QLineEdit * dirLineEdit)
{
const auto dirUrl = QUrl::fromLocalFile(QFileInfo{dirLineEdit->text()}.absoluteFilePath());
#ifdef VCMI_IOS
iOS_utils::revealDirectoryInFiles(dirUrl);
#else
QDesktopServices::openUrl(dirUrl);
#endif
}
}
void AboutProjectView::hideAndStretchWidget(QGridLayout * layout, QWidget * toHide, QWidget * toStretch)
{
toHide->hide();
@ -47,10 +65,12 @@ AboutProjectView::AboutProjectView(QWidget * parent)
// On mobile platforms these directories are generally not accessible from phone itself, only via USB connection from PC
// Remove "Open" buttons and stretch line with text into now-empty space
hideAndStretchWidget(ui->gridLayout, ui->openGameDataDir, ui->lineEditGameDir);
#ifdef VCMI_ANDROID
hideAndStretchWidget(ui->gridLayout, ui->openUserDataDir, ui->lineEditUserDataDir);
hideAndStretchWidget(ui->gridLayout, ui->openTempDir, ui->lineEditTempDir);
hideAndStretchWidget(ui->gridLayout, ui->openConfigDir, ui->lineEditConfigDir);
#endif
#endif
}
void AboutProjectView::changeEvent(QEvent *event)
@ -68,22 +88,22 @@ void AboutProjectView::on_updatesButton_clicked()
void AboutProjectView::on_openGameDataDir_clicked()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(ui->lineEditGameDir->text()).absoluteFilePath()));
revealDirectoryInFileBrowser(ui->lineEditGameDir);
}
void AboutProjectView::on_openUserDataDir_clicked()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(ui->lineEditUserDataDir->text()).absoluteFilePath()));
revealDirectoryInFileBrowser(ui->lineEditUserDataDir);
}
void AboutProjectView::on_openTempDir_clicked()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(ui->lineEditTempDir->text()).absoluteFilePath()));
revealDirectoryInFileBrowser(ui->lineEditTempDir);
}
void AboutProjectView::on_openConfigDir_clicked()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(ui->lineEditConfigDir->text()).absoluteFilePath()));
revealDirectoryInFileBrowser(ui->lineEditConfigDir);
}
void AboutProjectView::on_pushButtonDiscord_clicked()
@ -106,7 +126,6 @@ void AboutProjectView::on_pushButtonHomepage_clicked()
QDesktopServices::openUrl(QUrl("https://vcmi.eu/"));
}
void AboutProjectView::on_pushButtonBugreport_clicked()
{
QDesktopServices::openUrl(QUrl("https://github.com/vcmi/vcmi/issues"));

View File

@ -25,14 +25,11 @@ class AboutProjectView : public QWidget
/// Hides a widget and expands second widgets to take place of first widget in layout
void hideAndStretchWidget(QGridLayout * layout, QWidget * toHide, QWidget * toStretch);
public:
explicit AboutProjectView(QWidget * parent = nullptr);
public slots:
private slots:
void on_updatesButton_clicked();
void on_openGameDataDir_clicked();
@ -55,5 +52,4 @@ private slots:
private:
Ui::AboutProjectView * ui;
};

View File

@ -0,0 +1,17 @@
/*
* revealdirectoryinfiles.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include <QUrl>
namespace iOS_utils
{
void revealDirectoryInFiles(QUrl dirUrl);
}

View File

@ -0,0 +1,22 @@
/*
* revealdirectoryinfiles.mm, 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 "revealdirectoryinfiles.h"
#import <UIKit/UIKit.h>
namespace iOS_utils
{
void revealDirectoryInFiles(QUrl dirUrl)
{
auto urlComponents = [NSURLComponents componentsWithURL:dirUrl.toNSURL() resolvingAgainstBaseURL:NO];
urlComponents.scheme = @"shareddocuments";
[UIApplication.sharedApplication openURL:urlComponents.URL options:@{} completionHandler:nil];
}
}

View File

@ -87,8 +87,6 @@ void CSettingsView::updateCheckbuttonText(QToolButton * button)
void CSettingsView::loadSettings()
{
setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool());
#ifdef VCMI_MOBILE
ui->comboBoxFullScreen->hide();
ui->labelFullScreen->hide();
@ -111,7 +109,6 @@ void CSettingsView::loadSettings()
ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float());
ui->spinBoxFramerateLimit->setValue(settings["video"]["targetfps"].Float());
ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool());
setCheckbuttonState(ui->buttonVSync, settings["video"]["vsync"].Bool());
ui->sliderReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100));
ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String()));
@ -123,43 +120,27 @@ void CSettingsView::loadSettings()
ui->spinBoxNetworkPort->setValue(settings["server"]["localPort"].Integer());
setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool());
ui->lineEditRepositoryDefault->setText(QString::fromStdString(settings["launcher"]["defaultRepositoryURL"].String()));
ui->lineEditRepositoryExtra->setText(QString::fromStdString(settings["launcher"]["extraRepositoryURL"].String()));
ui->lineEditRepositoryDefault->setEnabled(settings["launcher"]["defaultRepositoryEnabled"].Bool());
ui->lineEditRepositoryExtra->setEnabled(settings["launcher"]["extraRepositoryEnabled"].Bool());
setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool());
setCheckbuttonState(ui->buttonRepositoryExtra, settings["launcher"]["extraRepositoryEnabled"].Bool());
setCheckbuttonState(ui->buttonIgnoreSslErrors, settings["launcher"]["ignoreSslErrors"].Bool());
setCheckbuttonState(ui->buttonAutoSave, settings["general"]["saveFrequency"].Integer() > 0);
ui->spinBoxAutoSaveLimit->setValue(settings["general"]["autosaveCountLimit"].Integer());
setCheckbuttonState(ui->buttonAutoSavePrefix, settings["general"]["useSavePrefix"].Bool());
ui->lineEditAutoSavePrefix->setText(QString::fromStdString(settings["general"]["savePrefix"].String()));
ui->lineEditAutoSavePrefix->setEnabled(settings["general"]["useSavePrefix"].Bool());
Languages::fillLanguages(ui->comboBoxLanguage, false);
fillValidRenderers();
std::string cursorType = settings["video"]["cursor"].String();
int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType);
setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex);
std::string upscalingFilter = settings["video"]["scalingMode"].String();
int upscalingFilterIndex = vstd::find_pos(upscalingFilterTypes, upscalingFilter);
ui->comboBoxUpscalingFilter->setCurrentIndex(upscalingFilterIndex);
ui->sliderMusicVolume->setValue(settings["general"]["music"].Integer());
ui->sliderSoundVolume->setValue(settings["general"]["sound"].Integer());
setCheckbuttonState(ui->buttonRelativeCursorMode, settings["general"]["userRelativePointer"].Bool());
ui->sliderRelativeCursorSpeed->setValue(settings["general"]["relativePointerSpeedMultiplier"].Integer());
setCheckbuttonState(ui->buttonHapticFeedback, settings["launcher"]["hapticFeedback"].Bool());
ui->sliderLongTouchDuration->setValue(settings["general"]["longTouchTimeMilliseconds"].Integer());
ui->slideToleranceDistanceMouse->setValue(settings["input"]["mouseToleranceDistance"].Integer());
ui->sliderToleranceDistanceTouch->setValue(settings["input"]["touchToleranceDistance"].Integer());
@ -168,6 +149,30 @@ void CSettingsView::loadSettings()
ui->sliderControllerSticksAcceleration->setValue(settings["input"]["controllerAxisScale"].Float() * 100);
ui->lineEditGameLobbyHost->setText(QString::fromStdString(settings["lobby"]["hostname"].String()));
ui->spinBoxNetworkPortLobby->setValue(settings["lobby"]["port"].Integer());
loadToggleButtonSettings();
}
void CSettingsView::loadToggleButtonSettings()
{
setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool());
setCheckbuttonState(ui->buttonVSync, settings["video"]["vsync"].Bool());
setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool());
setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool());
setCheckbuttonState(ui->buttonRepositoryExtra, settings["launcher"]["extraRepositoryEnabled"].Bool());
setCheckbuttonState(ui->buttonIgnoreSslErrors, settings["launcher"]["ignoreSslErrors"].Bool());
setCheckbuttonState(ui->buttonAutoSave, settings["general"]["saveFrequency"].Integer() > 0);
setCheckbuttonState(ui->buttonAutoSavePrefix, settings["general"]["useSavePrefix"].Bool());
setCheckbuttonState(ui->buttonRelativeCursorMode, settings["general"]["userRelativePointer"].Bool());
setCheckbuttonState(ui->buttonHapticFeedback, settings["general"]["hapticFeedback"].Bool());
std::string cursorType = settings["video"]["cursor"].String();
int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType);
setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex);
}
void CSettingsView::fillValidResolutions()
@ -431,6 +436,7 @@ void CSettingsView::changeEvent(QEvent *event)
ui->retranslateUi(this);
Languages::fillLanguages(ui->comboBoxLanguage, false);
loadTranslation();
loadToggleButtonSettings();
}
QWidget::changeEvent(event);
}

View File

@ -23,6 +23,7 @@ public:
~CSettingsView();
void loadSettings();
void loadToggleButtonSettings();
void loadTranslation();
void setDisplayList();
void changeEvent(QEvent *event) override;

View File

@ -79,7 +79,7 @@
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="234"/>
<source>Configuration files directory</source>
<translation type="unfinished"></translation>
<translation>Verzeichnis der Konfiguarions-Dateien</translation>
</message>
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="297"/>
@ -294,7 +294,7 @@
<message>
<location filename="../modManager/cmodlistview_moc.ui" line="105"/>
<source>Reload repositories</source>
<translation type="unfinished"></translation>
<translation>Verzeichnis aktualisieren</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.ui" line="340"/>
@ -669,62 +669,62 @@ Installation erfolgreich heruntergeladen?</translation>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="59"/>
<source>Online Lobby port</source>
<translation type="unfinished"></translation>
<translation>Online-Lobby-Port</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="66"/>
<source>Autocombat AI in battles</source>
<translation type="unfinished"></translation>
<translation>Autokampf-KI in Kämpfen</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="73"/>
<source>Sticks Sensitivity</source>
<translation type="unfinished"></translation>
<translation>Sticks Empfindlichkeit</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="87"/>
<source>Haptic Feedback</source>
<translation type="unfinished"></translation>
<translation>Haptisches Feedback</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="94"/>
<source>Software Cursor</source>
<translation type="unfinished"></translation>
<translation>Software-Cursor</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="108"/>
<source>Online Lobby address</source>
<translation type="unfinished"></translation>
<translation>Adresse der Online-Lobby</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="115"/>
<source>Upscaling Filter</source>
<translation type="unfinished"></translation>
<translation>Hochskalierungsfilter</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="122"/>
<source>Use Relative Pointer Mode</source>
<translation type="unfinished"></translation>
<translation>Relativen Zeigermodus verwenden</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="195"/>
<source>Nearest</source>
<translation type="unfinished"></translation>
<translation>Nearest</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="200"/>
<source>Linear</source>
<translation type="unfinished"></translation>
<translation>Linear</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="205"/>
<source>Best (Linear)</source>
<translation type="unfinished"></translation>
<translation>Bester (Linear)</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="231"/>
<source>Input - Touchscreen</source>
<translation type="unfinished"></translation>
<translation>Eingabe - Touchscreen</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="440"/>
@ -734,62 +734,62 @@ Installation erfolgreich heruntergeladen?</translation>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="474"/>
<source>Network</source>
<translation type="unfinished"></translation>
<translation>Netzwerk</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="540"/>
<source>Audio</source>
<translation type="unfinished"></translation>
<translation>Audio</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="578"/>
<source>Relative Pointer Speed</source>
<translation type="unfinished"></translation>
<translation>Relative Zeigergeschwindigkeit</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="585"/>
<source>Music Volume</source>
<translation type="unfinished"></translation>
<translation>Musik Lautstärke</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="606"/>
<source>Ignore SSL errors</source>
<translation type="unfinished"></translation>
<translation>SSL-Fehler ignorieren</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="618"/>
<source>Input - Mouse</source>
<translation type="unfinished"></translation>
<translation>Eingabe - Maus</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="628"/>
<source>Long Touch Duration</source>
<translation type="unfinished"></translation>
<translation>Dauer der Berührung für &quot;lange Berührung&quot;</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="635"/>
<source>%</source>
<translation type="unfinished"></translation>
<translation>%</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="658"/>
<source>Controller Click Tolerance</source>
<translation type="unfinished"></translation>
<translation>Toleranz bei Controller Klick</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="665"/>
<source>Touch Tap Tolerance</source>
<translation type="unfinished"></translation>
<translation>Toleranz bei Berührungen</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="704"/>
<source>Input - Controller</source>
<translation type="unfinished"></translation>
<translation>Eingabe - Controller</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="771"/>
<source>Sound Volume</source>
<translation type="unfinished"></translation>
<translation>Sound-Lautstärke</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="801"/>
@ -828,12 +828,12 @@ Installation erfolgreich heruntergeladen?</translation>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="839"/>
<source>Mouse Click Tolerance</source>
<translation type="unfinished"></translation>
<translation>Toleranz bei Mausklick</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="853"/>
<source>Sticks Acceleration</source>
<translation type="unfinished"></translation>
<translation>Sticks Beschleunigung</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="995"/>
@ -1064,12 +1064,12 @@ Heroes III: HD Edition wird derzeit nicht unterstützt!</translation>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="304"/>
<source>Use offline installer from gog.com</source>
<translation type="unfinished"></translation>
<translation>Offline-Installer von gog.com verwenden</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="317"/>
<source>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</source>
<translation type="unfinished"></translation>
<translation>Es können die Verzeichnisse Maps, Data und Mp3 manuell aus dem ursprünglichen Spielverzeichnis in das VCMI-Datenverzeichnis kopiert werden, das oben auf dieser Seite gesehen werden kann</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="336"/>
@ -1106,23 +1106,24 @@ Heroes III: HD Edition wird derzeit nicht unterstützt!</translation>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="395"/>
<source>Installing... %p%</source>
<translation type="unfinished"></translation>
<translation>Installation... %p%</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="424"/>
<source>If you already have Heroes III files on your device, you can select this directory and VCMI will copy the existing data automatically.</source>
<translation type="unfinished"></translation>
<translation>Wenn bereits Heroes III-Dateien auf Ihrem Gerät sind, kann dieses Verzeichnis auswählt werden und VCMI wird die vorhandenen Daten automatisch kopieren.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="466"/>
<source>Copy existing files</source>
<translation type="unfinished"></translation>
<translation>Vorhandene Dateien kopieren</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="511"/>
<source>If you own Heroes III on gog.com you can download backup offline installer from gog.com, and VCMI will import Heroes III data using offline installer.
Offline installer consists of two parts, .exe and .bin. Make sure you download both of them.</source>
<translation type="unfinished"></translation>
<translation>Wenn Sie Heroes III auf gog.com besitzen, können Sie den Backup-Offline-Installer von gog.com herunterladen, und VCMI wird die Daten von Heroes III mit dem Offline-Installer importieren.
Der Offline-Installer besteht aus zwei Teilen, .exe und .bin. Stellen Sie sicher, dass Sie beide Teile herunterladen.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="696"/>
@ -1173,7 +1174,7 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="354"/>
<source>Manual Installation</source>
<translation type="unfinished"></translation>
<translation>Manuelle Installation</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="367"/>
@ -1487,13 +1488,14 @@ Bitte wählen Sie ein Verzeichnis mit Heroes III: Complete Edition oder Heroes I
<message>
<location filename="../main.cpp" line="121"/>
<source>Error starting executable</source>
<translation type="unfinished"></translation>
<translation>Fehler beim Starten der ausführbaren Datei</translation>
</message>
<message>
<location filename="../main.cpp" line="122"/>
<source>Failed to start %1
Reason: %2</source>
<translation type="unfinished"></translation>
<translation>Start von %1 fehlgeschlagen
Grund: %2</translation>
</message>
</context>
<context>

View File

@ -64,7 +64,7 @@
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="114"/>
<source>Data Directories</source>
<translation>Теки даних гри</translation>
<translation>Теки гри</translation>
</message>
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="175"/>
@ -79,7 +79,7 @@
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="234"/>
<source>Configuration files directory</source>
<translation type="unfinished"></translation>
<translation>Тека файлів конфігурації</translation>
</message>
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="297"/>
@ -294,7 +294,7 @@
<message>
<location filename="../modManager/cmodlistview_moc.ui" line="105"/>
<source>Reload repositories</source>
<translation type="unfinished"></translation>
<translation>Обновити репозиторії</translation>
</message>
<message>
<location filename="../modManager/cmodlistview_moc.ui" line="340"/>
@ -630,7 +630,7 @@ Install successfully downloaded?</source>
<message>
<location filename="../settingsView/csettingsview_moc.cpp" line="85"/>
<source>Off</source>
<translation>Вимкнено</translation>
<translation>Ні</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="452"/>
@ -669,62 +669,62 @@ Install successfully downloaded?</source>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="59"/>
<source>Online Lobby port</source>
<translation type="unfinished"></translation>
<translation>Порт онлайн лобі</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="66"/>
<source>Autocombat AI in battles</source>
<translation type="unfinished"></translation>
<translation>ШІ автобою</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="73"/>
<source>Sticks Sensitivity</source>
<translation type="unfinished"></translation>
<translation>Чутливість стиків</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="87"/>
<source>Haptic Feedback</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="94"/>
<source>Software Cursor</source>
<translation type="unfinished"></translation>
<translation>Програмний курсор</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="108"/>
<source>Online Lobby address</source>
<translation type="unfinished"></translation>
<translation>Адреса онлайн-лобі</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="115"/>
<source>Upscaling Filter</source>
<translation type="unfinished"></translation>
<translation>Фільтр масштабування</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="122"/>
<source>Use Relative Pointer Mode</source>
<translation type="unfinished"></translation>
<translation>Режим відносного вказівника</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="195"/>
<source>Nearest</source>
<translation type="unfinished"></translation>
<translation>Найближчий</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="200"/>
<source>Linear</source>
<translation type="unfinished"></translation>
<translation>Лінійний</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="205"/>
<source>Best (Linear)</source>
<translation type="unfinished"></translation>
<translation>Найкращий (лінійний)</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="231"/>
<source>Input - Touchscreen</source>
<translation type="unfinished"></translation>
<translation>Введення - Сенсорний екран</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="440"/>
@ -734,62 +734,62 @@ Install successfully downloaded?</source>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="474"/>
<source>Network</source>
<translation type="unfinished"></translation>
<translation>Мережа</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="540"/>
<source>Audio</source>
<translation type="unfinished"></translation>
<translation>Аудіо</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="578"/>
<source>Relative Pointer Speed</source>
<translation type="unfinished"></translation>
<translation>Швидкість відносного вказівника</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="585"/>
<source>Music Volume</source>
<translation type="unfinished"></translation>
<translation>Гучність музики</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="606"/>
<source>Ignore SSL errors</source>
<translation type="unfinished"></translation>
<translation>Ігнорувати помилки SSL</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="618"/>
<source>Input - Mouse</source>
<translation type="unfinished"></translation>
<translation>Введення - Миша</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="628"/>
<source>Long Touch Duration</source>
<translation type="unfinished"></translation>
<translation>Тривалість довгого дотику</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="635"/>
<source>%</source>
<translation type="unfinished"></translation>
<translation>%</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="658"/>
<source>Controller Click Tolerance</source>
<translation type="unfinished"></translation>
<translation>Допуск на натискання контролера</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="665"/>
<source>Touch Tap Tolerance</source>
<translation type="unfinished"></translation>
<translation>Допуск на натискання дотиком</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="704"/>
<source>Input - Controller</source>
<translation type="unfinished"></translation>
<translation>Введення - Контролер</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="771"/>
<source>Sound Volume</source>
<translation type="unfinished"></translation>
<translation>Гучність звуку</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="801"/>
@ -828,12 +828,12 @@ Install successfully downloaded?</source>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="839"/>
<source>Mouse Click Tolerance</source>
<translation type="unfinished"></translation>
<translation>Допуск кліків миші</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="853"/>
<source>Sticks Acceleration</source>
<translation type="unfinished"></translation>
<translation>Прискорення стиків</translation>
</message>
<message>
<location filename="../settingsView/csettingsview_moc.ui" line="995"/>
@ -858,7 +858,7 @@ Install successfully downloaded?</source>
<message>
<location filename="../settingsView/csettingsview_moc.cpp" line="83"/>
<source>On</source>
<translation>Увімкнено</translation>
<translation>Так</translation>
</message>
<message>
<source>Cursor</source>
@ -1064,12 +1064,12 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="304"/>
<source>Use offline installer from gog.com</source>
<translation type="unfinished"></translation>
<translation>Використати офлайн-інсталятор з gog.com</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="317"/>
<source>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</source>
<translation type="unfinished"></translation>
<translation>Ви можете вручну скопіювати теки Maps, Data та Mp3 з теки оригінальної гри до теки даних VCMI, яку ви можете побачити вгорі цієї сторінки</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="336"/>
@ -1106,23 +1106,24 @@ Heroes® of Might and Magic® III HD наразі не підтримуєтьс
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="395"/>
<source>Installing... %p%</source>
<translation type="unfinished"></translation>
<translation>Встановлюємо... %p%</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="424"/>
<source>If you already have Heroes III files on your device, you can select this directory and VCMI will copy the existing data automatically.</source>
<translation type="unfinished"></translation>
<translation>Якщо на вашому пристрої вже є файли Heroes III, ви можете вибрати цю теку і VCMI автоматично скопіює наявні дані.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="466"/>
<source>Copy existing files</source>
<translation type="unfinished"></translation>
<translation>Скопіювати існуючі файли</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="511"/>
<source>If you own Heroes III on gog.com you can download backup offline installer from gog.com, and VCMI will import Heroes III data using offline installer.
Offline installer consists of two parts, .exe and .bin. Make sure you download both of them.</source>
<translation type="unfinished"></translation>
<translation>Якщо у вас є Heroes III на gog.com, ви можете завантажити резервну копію офлайн-інсталятора з gog.com, і VCMI імпортує дані Heroes III за допомогою офлайн-інсталятора.
Офлайн-інсталятор складається з двох частин, .exe та .bin. Переконайтеся, що ви завантажили обидві частини.</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="696"/>
@ -1173,7 +1174,7 @@ Offline installer consists of two parts, .exe and .bin. Make sure you download b
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="354"/>
<source>Manual Installation</source>
<translation type="unfinished"></translation>
<translation>Ручне встановлення</translation>
</message>
<message>
<location filename="../firstLaunch/firstlaunch_moc.ui" line="367"/>
@ -1487,13 +1488,14 @@ Please select directory with Heroes III: Complete Edition or Heroes III: Shadow
<message>
<location filename="../main.cpp" line="121"/>
<source>Error starting executable</source>
<translation type="unfinished"></translation>
<translation>Помилка запуску виконуваного файлу</translation>
</message>
<message>
<location filename="../main.cpp" line="122"/>
<source>Failed to start %1
Reason: %2</source>
<translation type="unfinished"></translation>
<translation>Не вдалося запустити %1
Причина: %2</translation>
</message>
</context>
<context>

View File

@ -648,7 +648,9 @@ std::shared_ptr<CCreature> CCreatureHandler::loadFromJson(const std::string & sc
if (!cre->special &&
!CResourceHandler::get()->existsResource(cre->animDefName) &&
!CResourceHandler::get()->existsResource(cre->animDefName.addPrefix("SPRITES/")))
!CResourceHandler::get()->existsResource(cre->animDefName.toType<EResType::JSON>()) &&
!CResourceHandler::get()->existsResource(cre->animDefName.addPrefix("SPRITES/")) &&
!CResourceHandler::get()->existsResource(cre->animDefName.addPrefix("SPRITES/").toType<EResType::JSON>()))
throw ModLoadingException(scope, "creature " + cre->getJsonKey() + " has no combat animation but is not marked as special!" );
JsonNode advMapFile = node["graphics"]["map"];

View File

@ -158,17 +158,17 @@ void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact *>
void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)
{
for (ui32 i = 0; i < gs->map->allowedSpells.size(); i++) //spellh size appears to be greater (?)
for (auto const & spellID : gs->map->allowedSpells)
{
const spells::Spell * spell = SpellID(i).toSpell();
const auto * spell = spellID.toEntity(VLC);
if (!isAllowed(spell->getId()))
if (!isAllowed(spellID))
continue;
if (level.has_value() && spell->getLevel() != level)
continue;
out.push_back(spell->getId());
out.push_back(spellID);
}
}

View File

@ -65,7 +65,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Se
BonusSubtypeID subtype;
BonusSource source = BonusSource::OTHER; //source type" uses BonusSource values - what gave that bonus
BonusSource targetSourceType;//Bonuses of what origin this amplifies, uses BonusSource values. Needed for PERCENT_TO_TARGET_TYPE.
BonusSource targetSourceType = BonusSource::OTHER;//Bonuses of what origin this amplifies, uses BonusSource values. Needed for PERCENT_TO_TARGET_TYPE.
si32 val = 0;
BonusSourceID sid; //source id: id of object/artifact/spell
BonusValueType valType = BonusValueType::ADDITIVE_VALUE;

View File

@ -51,8 +51,15 @@ namespace Selector
return seffectRange;
}
DLL_LINKAGE CWillLastTurns turns;
DLL_LINKAGE CWillLastDays days;
DLL_LINKAGE CWillLastTurns turns(int turns)
{
return CWillLastTurns(turns);
}
DLL_LINKAGE CWillLastDays days(int days)
{
return CWillLastDays(days);
}
CSelector DLL_LINKAGE typeSubtype(BonusType Type, BonusSubtypeID Subtype)
{

View File

@ -81,8 +81,11 @@ public:
class DLL_LINKAGE CWillLastTurns
{
public:
int turnsRequested;
public:
CWillLastTurns(int turnsRequested):
turnsRequested(turnsRequested)
{}
bool operator()(const Bonus *bonus) const
{
@ -90,18 +93,17 @@ public:
|| !Bonus::NTurns(bonus) //so do every not expriing after N-turns effect
|| bonus->turnsRemain > turnsRequested;
}
CWillLastTurns& operator()(const int &setVal)
{
turnsRequested = setVal;
return *this;
}
};
class DLL_LINKAGE CWillLastDays
{
public:
int daysRequested;
public:
CWillLastDays(int daysRequested):
daysRequested(daysRequested)
{}
bool operator()(const Bonus *bonus) const
{
if(daysRequested <= 0 || Bonus::Permanent(bonus) || Bonus::OneBattle(bonus))
@ -112,14 +114,8 @@ public:
{
return bonus->turnsRemain > daysRequested;
}
return false; // TODO: ONE_WEEK need support for turnsRemain, but for now we'll exclude all unhandled durations
}
CWillLastDays& operator()(const int &setVal)
{
daysRequested = setVal;
return *this;
}
};
@ -131,8 +127,8 @@ namespace Selector
extern DLL_LINKAGE const CSelectFieldEqual<BonusSource> & sourceType();
extern DLL_LINKAGE const CSelectFieldEqual<BonusSource> & targetSourceType();
extern DLL_LINKAGE const CSelectFieldEqual<BonusLimitEffect> & effectRange();
extern DLL_LINKAGE CWillLastTurns turns;
extern DLL_LINKAGE CWillLastDays days;
CWillLastTurns DLL_LINKAGE turns(int turns);
CWillLastDays DLL_LINKAGE days(int days);
CSelector DLL_LINKAGE typeSubtype(BonusType Type, BonusSubtypeID Subtype);
CSelector DLL_LINKAGE typeSubtypeInfo(BonusType type, BonusSubtypeID subtype, const CAddInfo & info);

View File

@ -118,7 +118,7 @@ class DLL_LINKAGE HasAnotherBonusLimiter : public ILimiter //applies only to nod
public:
BonusType type;
BonusSubtypeID subtype;
BonusSource source;
BonusSource source = BonusSource::OTHER;
BonusSourceID sid;
bool isSubtypeRelevant; //check for subtype only if this is true
bool isSourceRelevant; //check for bonus source only if this is true

View File

@ -10,6 +10,8 @@
#include "StdInc.h"
#include "CFileInputStream.h"
#include "../ExceptionsCommon.h"
VCMI_LIB_NAMESPACE_BEGIN
CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 start, si64 size)
@ -18,7 +20,7 @@ CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 st
fileStream{file.c_str(), std::ios::in | std::ios::binary}
{
if (fileStream.fail())
throw std::runtime_error("File " + file.string() + " isn't available.");
throw DataLoadingException("Failed to open file '" + file.string() + "'. Reason: " + strerror(errno) );
if (dataSize == 0)
{

View File

@ -12,14 +12,22 @@
#include "CFileInputStream.h"
#include "../ExceptionsCommon.h"
VCMI_LIB_NAMESPACE_BEGIN
CFilesystemLoader::CFilesystemLoader(std::string _mountPoint, boost::filesystem::path baseDirectory, size_t depth, bool initial):
baseDirectory(std::move(baseDirectory)),
mountPoint(std::move(_mountPoint)),
fileList(listFiles(mountPoint, depth, initial)),
recursiveDepth(depth)
{
try {
fileList = listFiles(mountPoint, depth, initial);
}
catch (const boost::filesystem::filesystem_error & e) {
throw DataLoadingException("Failed to load content of '" + baseDirectory.string() + "'. Reason: " + e.what());
}
logGlobal->trace("File system loaded, %d files found", fileList.size());
}

View File

@ -129,7 +129,7 @@ void AObjectTypeHandler::preInitObject(CGObjectInstance * obj) const
obj->ID = Obj(type);
obj->subID = subtype;
obj->typeName = typeName;
obj->subTypeName = subTypeName;
obj->subTypeName = getJsonKey();
obj->blockVisit = blockVisit;
obj->removable = removable;
}

View File

@ -181,13 +181,14 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
if(visitors.empty())
{
if(h->mana < h->manaLimit() * 2)
{
cb->setManaPoints (heroID, 2 * h->manaLimit());
//TODO: investigate line below
//cb->setObjProperty (town->id, ObjProperty::VISITED, true);
iw.text.appendRawString(getVisitingBonusGreeting());
cb->showInfoDialog(&iw);
//extra visit penalty if hero alredy had double mana points (or even more?!)
town->addHeroToStructureVisitors(h, indexOnTV);
//TODO: investigate line below
//cb->setObjProperty (town->id, ObjProperty::VISITED, true);
iw.text.appendRawString(getVisitingBonusGreeting());
cb->showInfoDialog(&iw);
town->addHeroToStructureVisitors(h, indexOnTV);
}
}
break;
}

View File

@ -141,6 +141,8 @@ bool CQuest::checkQuest(const CGHeroInstance * h) const
void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
{
// FIXME: this should be part of 'reward', and not hacking into limiter state that should only limit access to such reward
for(auto & elem : mission.artifacts)
{
if(h->hasArt(elem))
@ -164,9 +166,9 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
}
}
}
cb->takeCreatures(h->id, mission.creatures);
cb->giveResources(h->getOwner(), mission.resources);
cb->giveResources(h->getOwner(), -mission.resources);
}
void CQuest::addTextReplacements(IGameCallback * cb, MetaString & text, std::vector<Component> & components) const

View File

@ -263,6 +263,9 @@ void CGResource::pickRandomObject(CRandomGenerator & rand)
ID = Obj::RESOURCE;
subID = rand.nextInt(EGameResID::WOOD, EGameResID::GOLD);
setType(ID, subID);
if (subID == EGameResID::GOLD && amount != CGResource::RANDOM_AMOUNT)
amount *= CGResource::GOLD_AMOUNT_MULTIPLIER;
}
}
@ -275,7 +278,7 @@ void CGResource::initObj(CRandomGenerator & rand)
switch(resourceID().toEnum())
{
case EGameResID::GOLD:
amount = rand.nextInt(5, 10) * 100;
amount = rand.nextInt(5, 10) * CGResource::GOLD_AMOUNT_MULTIPLIER;
break;
case EGameResID::WOOD: case EGameResID::ORE:
amount = rand.nextInt(6, 10);
@ -490,7 +493,7 @@ void CGMonolith::onHeroVisit( const CGHeroInstance * h ) const
auto exits = cb->getTeleportChannelExits(channel);
for(const auto & exit : exits)
{
td.exits.push_back(std::make_pair(exit, h->convertFromVisitablePos(cb->getObj(exit)->visitablePos())));
td.exits.push_back(std::make_pair(exit, cb->getObj(exit)->visitablePos()));
}
}
@ -522,9 +525,9 @@ void CGMonolith::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer,
else if(vstd::isValidIndex(exits, answer))
dPos = exits[answer].second;
else
dPos = hero->convertFromVisitablePos(cb->getObj(randomExit)->visitablePos());
dPos = cb->getObj(randomExit)->visitablePos();
cb->moveHero(hero->id, dPos, EMovementMode::MONOLITH);
cb->moveHero(hero->id, hero->convertFromVisitablePos(dPos), EMovementMode::MONOLITH);
}
void CGMonolith::initObj(CRandomGenerator & rand)
@ -566,7 +569,7 @@ void CGSubterraneanGate::onHeroVisit( const CGHeroInstance * h ) const
else
{
auto exit = getRandomExit(h);
td.exits.push_back(std::make_pair(exit, h->convertFromVisitablePos(cb->getObj(exit)->visitablePos())));
td.exits.push_back(std::make_pair(exit, cb->getObj(exit)->visitablePos()));
}
cb->showTeleportDialog(&td);
@ -676,7 +679,7 @@ void CGWhirlpool::onHeroVisit( const CGHeroInstance * h ) const
{
auto blockedPosList = cb->getObj(exit)->getBlockedPos();
for(const auto & bPos : blockedPosList)
td.exits.push_back(std::make_pair(exit, h->convertFromVisitablePos(bPos)));
td.exits.push_back(std::make_pair(exit, bPos));
}
}
@ -700,10 +703,10 @@ void CGWhirlpool::teleportDialogAnswered(const CGHeroInstance *hero, ui32 answer
const auto * obj = cb->getObj(exit);
std::set<int3> tiles = obj->getBlockedPos();
dPos = hero->convertFromVisitablePos(*RandomGeneratorUtil::nextItem(tiles, CRandomGenerator::getDefault()));
dPos = *RandomGeneratorUtil::nextItem(tiles, CRandomGenerator::getDefault());
}
cb->moveHero(hero->id, dPos, EMovementMode::MONOLITH);
cb->moveHero(hero->id, hero->convertFromVisitablePos(dPos), EMovementMode::MONOLITH);
}
bool CGWhirlpool::isProtected(const CGHeroInstance * h)

View File

@ -122,8 +122,9 @@ class DLL_LINKAGE CGResource : public CArmedInstance
public:
using CArmedInstance::CArmedInstance;
static constexpr ui32 RANDOM_AMOUNT = 0;
ui32 amount = RANDOM_AMOUNT; //0 if random
static constexpr uint32_t RANDOM_AMOUNT = 0;
static constexpr uint32_t GOLD_AMOUNT_MULTIPLIER = 100;
uint32_t amount = RANDOM_AMOUNT; //0 if random
MetaString message;

View File

@ -208,13 +208,13 @@ class DLL_LINKAGE CMapHeader: public Serializeable
void setupEvents();
public:
static const int MAP_SIZE_SMALL = 36;
static const int MAP_SIZE_MIDDLE = 72;
static const int MAP_SIZE_LARGE = 108;
static const int MAP_SIZE_XLARGE = 144;
static const int MAP_SIZE_HUGE = 180;
static const int MAP_SIZE_XHUGE = 216;
static const int MAP_SIZE_GIANT = 252;
static constexpr int MAP_SIZE_SMALL = 36;
static constexpr int MAP_SIZE_MIDDLE = 72;
static constexpr int MAP_SIZE_LARGE = 108;
static constexpr int MAP_SIZE_XLARGE = 144;
static constexpr int MAP_SIZE_HUGE = 180;
static constexpr int MAP_SIZE_XHUGE = 216;
static constexpr int MAP_SIZE_GIANT = 252;
CMapHeader();
virtual ~CMapHeader();

View File

@ -1315,7 +1315,7 @@ CGObjectInstance * CMapLoaderH3M::readResource(const int3 & mapPosition, std::sh
if(GameResID(objectTemplate->subid) == GameResID(EGameResID::GOLD))
{
// Gold is multiplied by 100.
object->amount *= 100;
object->amount *= CGResource::GOLD_AMOUNT_MULTIPLIER;
}
reader->skipZero(4);
return object;

View File

@ -227,10 +227,7 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co
if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName)))
{
JsonParsingSettings settings;
settings.mode = JsonParsingSettings::JsonFormatMode::JSON; // TODO: remove once Android launcher with its strict parser is gone
CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName), settings));
CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName)));
if (!parent.empty()) // this is submod, add parent to dependencies
mod.dependencies.insert(parent);

View File

@ -284,9 +284,12 @@ void CMapGenOptions::resetPlayersMap()
while (players.size() > realPlayersCnt)
{
while (eraseLastPlayer(EPlayerType::AI));
while (eraseLastPlayer(EPlayerType::COMP_ONLY));
while (eraseLastPlayer(EPlayerType::HUMAN));
if (eraseLastPlayer(EPlayerType::AI))
continue;
if (eraseLastPlayer(EPlayerType::COMP_ONLY))
continue;
if (eraseLastPlayer(EPlayerType::HUMAN))
continue;
}
//First colors from the list are assigned to human players, then to CPU players
@ -503,8 +506,9 @@ void CMapGenOptions::finalize(CRandomGenerator & rand)
if (getHumanOrCpuPlayerCount() == RANDOM_SIZE)
{
auto possiblePlayers = mapTemplate->getPlayers().getNumbers();
int requiredPlayers = countHumanPlayers() + countCompOnlyPlayers();
//ignore all non-randomized players, make sure these players will not be missing after roll
possiblePlayers.erase(possiblePlayers.begin(), possiblePlayers.lower_bound(countHumanPlayers() + countCompOnlyPlayers()));
possiblePlayers.erase(possiblePlayers.begin(), possiblePlayers.lower_bound(requiredPlayers));
vstd::erase_if(possiblePlayers, [maxPlayers](int i)
{
@ -598,7 +602,9 @@ void CMapGenOptions::updatePlayers()
{
auto it = itrev;
--it;
if (players.size() == getHumanOrCpuPlayerCount()) break;
if (players.size() == getHumanOrCpuPlayerCount())
break;
if(it->second.getPlayerType() != EPlayerType::HUMAN)
{
players.erase(it);

View File

@ -455,9 +455,9 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
auto player = PlayerColor(*owner - 1);
auto playerSettings = map.getMapGenOptions().getPlayersSettings();
FactionID faction = FactionID::RANDOM;
if (vstd::contains(playerSettings, player))
if (playerSettings.size() > player)
{
faction = playerSettings[player].getStartingTown();
faction = std::next(playerSettings.begin(), player)->second.getStartingTown();
}
else
{

View File

@ -369,12 +369,7 @@ ui32 RmgMap::getTotalZoneCount() const
bool RmgMap::isAllowedSpell(const SpellID & sid) const
{
assert(sid.getNum() >= 0);
if (sid.getNum() < mapInstance->allowedSpells.size())
{
return mapInstance->allowedSpells.count(sid);
}
else
return false;
return mapInstance->allowedSpells.count(sid);
}
void RmgMap::dump(bool zoneId) const

View File

@ -54,12 +54,14 @@ void TownPlacer::placeTowns(ObjectManager & manager)
//set zone types to player faction, generate main town
logGlobal->info("Preparing playing zone");
int player_id = *zone.getOwner() - 1;
auto& playerInfo = map.getPlayer(player_id);
PlayerColor player(player_id);
if(playerInfo.canAnyonePlay())
const auto & playerSettings = map.getMapGenOptions().getPlayersSettings();
PlayerColor player;
if (playerSettings.size() > player_id)
{
player = PlayerColor(player_id);
zone.setTownType(map.getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown());
const auto & currentPlayerSettings = std::next(playerSettings.begin(), player_id);
player = currentPlayerSettings->first;
zone.setTownType(currentPlayerSettings->second.getStartingTown());
if(zone.getTownType() == FactionID::RANDOM)
zone.setTownType(getRandomTownType(true));
@ -87,11 +89,12 @@ void TownPlacer::placeTowns(ObjectManager & manager)
//register MAIN town of zone only
map.registerZone(town->getFaction());
if(playerInfo.canAnyonePlay()) //configure info for owning player
if(player.isValidPlayer()) //configure info for owning player
{
logGlobal->trace("Fill player info %d", player_id);
// Update player info
auto & playerInfo = map.getPlayer(player.getNum());
playerInfo.allowedFactions.clear();
playerInfo.allowedFactions.insert(zone.getTownType());
playerInfo.hasMainTown = true;

View File

@ -219,6 +219,7 @@ bool WaterProxy::waterKeepConnection(const rmg::ZoneConnection & connection, boo
const auto & zoneA = connection.getZoneA();
const auto & zoneB = connection.getZoneB();
RecursiveLock lock(externalAccessMutex);
for(auto & lake : lakes)
{
if(lake.neighbourZones.count(zoneA) && lake.neighbourZones.count(zoneB))

View File

@ -77,6 +77,7 @@ void CConnection::sendPack(const CPack * pack)
if (!connectionPtr)
throw std::runtime_error("Attempt to send packet on a closed connection!");
packWriter->buffer.clear();
*serializer & pack;
logNetwork->trace("Sending a pack of type %s", typeid(*pack).name());

View File

@ -8,6 +8,7 @@
*
*/
#include "StdInc.h"
#include "mapcontroller.h"
#include "../lib/ArtifactUtils.h"
@ -57,6 +58,7 @@ void MapController::connectScenes()
MapController::~MapController()
{
main = nullptr;
}
const std::unique_ptr<CMap> & MapController::getMapUniquePtr() const
@ -228,6 +230,8 @@ void MapController::setMap(std::unique_ptr<CMap> cmap)
_map->getEditManager()->getUndoManager().setUndoCallback([this](bool allowUndo, bool allowRedo)
{
if(!main)
return;
main->enableUndo(allowUndo);
main->enableRedo(allowRedo);
}

View File

@ -11,7 +11,6 @@
#pragma once
//code is copied from vcmiclient/mapHandler.h with minimal changes
#include "StdInc.h"
#include "../lib/int3.h"
#include "Animation.h"

View File

@ -10,7 +10,6 @@
#pragma once
#include "StdInc.h"
#include <QDialog>
#include "playerparams.h"

View File

@ -129,37 +129,37 @@
<message>
<location filename="../inspector/herospellwidget.ui" line="29"/>
<source>Spells</source>
<translation type="unfinished">Zaubersprüche</translation>
<translation>Zaubersprüche</translation>
</message>
<message>
<location filename="../inspector/herospellwidget.ui" line="47"/>
<source>Customize spells</source>
<translation type="unfinished"></translation>
<translation>Zaubersprüche anpassen</translation>
</message>
<message>
<location filename="../inspector/herospellwidget.ui" line="76"/>
<source>Level 1</source>
<translation type="unfinished"></translation>
<translation>Level 1</translation>
</message>
<message>
<location filename="../inspector/herospellwidget.ui" line="114"/>
<source>Level 2</source>
<translation type="unfinished"></translation>
<translation>Level 2</translation>
</message>
<message>
<location filename="../inspector/herospellwidget.ui" line="152"/>
<source>Level 3</source>
<translation type="unfinished"></translation>
<translation>Level 3</translation>
</message>
<message>
<location filename="../inspector/herospellwidget.ui" line="190"/>
<source>Level 4</source>
<translation type="unfinished"></translation>
<translation>Level 4</translation>
</message>
<message>
<location filename="../inspector/herospellwidget.ui" line="228"/>
<source>Level 5</source>
<translation type="unfinished"></translation>
<translation>Level 5</translation>
</message>
</context>
<context>
@ -1738,32 +1738,32 @@
<message>
<location filename="../windownewmap.ui" line="164"/>
<source>S (36x36)</source>
<translation type="unfinished"></translation>
<translation>S (36x36)</translation>
</message>
<message>
<location filename="../windownewmap.ui" line="169"/>
<source>M (72x72)</source>
<translation type="unfinished"></translation>
<translation>M (72x72)</translation>
</message>
<message>
<location filename="../windownewmap.ui" line="174"/>
<source>L (108x108)</source>
<translation type="unfinished"></translation>
<translation>L (108x108)</translation>
</message>
<message>
<location filename="../windownewmap.ui" line="184"/>
<source>H (180x180)</source>
<translation type="unfinished"></translation>
<translation>H (180x180)</translation>
</message>
<message>
<location filename="../windownewmap.ui" line="189"/>
<source>XH (216x216)</source>
<translation type="unfinished"></translation>
<translation>XH (216x216)</translation>
</message>
<message>
<location filename="../windownewmap.ui" line="194"/>
<source>G (252x252)</source>
<translation type="unfinished"></translation>
<translation>G (252x252)</translation>
</message>
<message>
<location filename="../windownewmap.ui" line="248"/>

View File

@ -108,7 +108,7 @@ bool WindowNewMap::loadUserSettings()
ui->widthTxt->setText(QString::number(mapGenOptions.getWidth()));
ui->heightTxt->setText(QString::number(mapGenOptions.getHeight()));
for(auto & sz : mapSizes)
for(const auto & sz : mapSizes)
{
if(sz.second.first == mapGenOptions.getWidth() &&
sz.second.second == mapGenOptions.getHeight())

View File

@ -11,6 +11,8 @@
#pragma once
#include <QDialog>
#include "../lib/mapping/CMapHeader.h"
#include "../lib/rmg/CMapGenOptions.h"
namespace Ui
@ -62,7 +64,7 @@ class WindowNewMap : public QDialog
{7, 6},
{8, 7}
};
const std::map<int, std::pair<int, int>> mapSizes
{
{0, {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_SMALL}},

View File

@ -662,6 +662,10 @@ void CVCMIServer::updateAndPropagateLobbyState()
{
si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);
}
else
{
si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::AI);
}
}
}

View File

@ -313,10 +313,8 @@ void TurnTimerHandler::onBattleLoop(const BattleID & battleID, int waitTime)
}
else
{
BattleAction retreat;
retreat.side = side;
retreat.actionType = EActionType::RETREAT; //harsh punishment
gameHandler.battles->makePlayerBattleAction(battleID, player, retreat);
// battle vs neutrals - no-op, let battle run till the end
// once battle is over player turn will be over due to running out of timer on adventure map
}
}
}