1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-15 00:05:02 +02:00

Merge remote-tracking branch 'origin/beta' into random_prison_distributor

This commit is contained in:
Tomasz Zieliński
2023-12-24 06:46:18 +01:00
168 changed files with 5737 additions and 1340 deletions

View File

@ -116,7 +116,7 @@ jobs:
pack_type: RelWithDebInfo
extension: exe
preset: windows-msvc-release-ccache
- platform: mingw-ubuntu
- platform: mingw
os: ubuntu-22.04
test: 0
pack: 1
@ -126,6 +126,16 @@ jobs:
cmake_args: -G Ninja
preset: windows-mingw-conan-linux
conan_profile: mingw64-linux.jinja
- platform: mingw-32
os: ubuntu-22.04
test: 0
pack: 1
pack_type: Release
extension: exe
cpack_args: -D CPACK_NSIS_EXECUTABLE=`which makensis`
cmake_args: -G Ninja
preset: windows-mingw-conan-linux
conan_profile: mingw32-linux.jinja
- platform: android-32
os: ubuntu-22.04
extension: apk

View File

@ -427,33 +427,36 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
state->nextTurn(unit->unitId());
PotentialTargets pt(unit, damageCache, state);
PotentialTargets potentialTargets(unit, damageCache, state);
if(!pt.possibleAttacks.empty())
if(!potentialTargets.possibleAttacks.empty())
{
AttackPossibility ap = pt.bestAction();
AttackPossibility attackPossibility = potentialTargets.bestAction();
auto swb = state->getForUpdate(unit->unitId());
*swb = *ap.attackerState;
auto stackWithBonuses = state->getForUpdate(unit->unitId());
*stackWithBonuses = *attackPossibility.attackerState;
if(ap.defenderDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilAttack);
if(ap.attackerDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
for(auto affected : ap.affectedUnits)
if(attackPossibility.defenderDamageReduce > 0)
{
swb = state->getForUpdate(affected->unitId());
*swb = *affected;
stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
stackWithBonuses->removeUnitBonus(Bonus::UntilOwnAttack);
}
if(attackPossibility.attackerDamageReduce > 0)
stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
if(ap.defenderDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
swb->removeUnitBonus(Bonus::UntilAttack);
for(auto affected : attackPossibility.affectedUnits)
{
stackWithBonuses = state->getForUpdate(affected->unitId());
*stackWithBonuses = *affected;
if(attackPossibility.defenderDamageReduce > 0)
stackWithBonuses->removeUnitBonus(Bonus::UntilBeingAttacked);
if(attackPossibility.attackerDamageReduce > 0 && attackPossibility.attack.defender->unitId() == affected->unitId())
stackWithBonuses->removeUnitBonus(Bonus::UntilAttack);
}
}
auto bav = pt.bestActionValue();
auto bav = potentialTargets.bestActionValue();
//best action is from effective owner`s point if view, we need to convert to our point if view
if(state->battleGetOwner(unit) != playerID)

View File

@ -17,4 +17,5 @@ RC={{ target_host }}-windres
{% macro generate_conf(target_host) -%}
tools.build:compiler_executables = {"c": "{{ target_host }}-gcc", "cpp": "{{ target_host }}-g++"}
tools.build:sysroot = /usr/{{ target_host }}
tools.build:defines = ["WINVER=0x0601", "_WIN32_WINNT=0x0601"]
{%- endmacro -%}

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
sudo apt-get update
sudo apt-get install ninja-build mingw-w64 nsis
sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix
# Workaround for getting new MinGW headers on Ubuntu 22.04.
# Remove it once MinGW headers version in repository will be 10.0 at least
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-common_10.0.0-3_all.deb \
&& sudo dpkg -i mingw-w64-common_10.0.0-3_all.deb;
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-i686-dev_10.0.0-3_all.deb \
&& sudo dpkg -i mingw-w64-i686-dev_10.0.0-3_all.deb;
mkdir ~/.conan ; cd ~/.conan
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.1/vcmi-deps-windows-conan-w32.tgz" \
| tar -xzf -

View File

@ -12,5 +12,5 @@ curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-
&& sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-3_all.deb;
mkdir ~/.conan ; cd ~/.conan
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.0/vcmi-deps-windows-conan-w64.tgz" \
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.1/vcmi-deps-windows-conan-w64.tgz" \
| tar -xzf -

View File

@ -255,7 +255,10 @@ endif()
if(MINGW OR MSVC)
# Windows Vista or newer for FuzzyLite 6 to compile
add_definitions(-D_WIN32_WINNT=0x0600)
# Except for conan which already has this definition in its preset
if(NOT USING_CONAN)
add_definitions(-D_WIN32_WINNT=0x0600)
endif()
#delete lib prefix for dlls (libvcmi -> vcmi)
set(CMAKE_SHARED_LIBRARY_PREFIX "")

View File

@ -1,3 +1,28 @@
# 1.4.0 -> 1.4.1
### General
* Fixed position for interaction with starting heroes
* Fixed smooth map scrolling when running at high framerate
* Fixed calculation of Fire Shield damage when caster has artifacts that increase its damage
* Fixed untranslated message when visiting signs with random text
* Fixed slider scrolling to maximum value when clicking on "scroll right" button
* Fixed events and seer huts not activating in some cases
* Fixed bug leading to Artifact Merchant selling Grails in loaded saved games
* Fixed placement of objects in random maps near the top border of the map
* Creatures under Slayer spell will no longer deal additional damage to creatures not affected by Slayer
* Description of a mod in Launcher will no longer be converted to lower-case
* Game will no longer fail to generate random map when AI-only players option is set to non-zero value
* Added option to mute audio when VCMI window is not active
* Added option to disable smooth map scrolling
* Reverted ban on U-turns in pathfinder
### Stability
* Fixed crash on using mods made for VCMI 1.3
* Fixed crash on generating random map with large number of monoliths
* Fixed crash on losing mission-critical hero in battle
* Fixed crash on generating growth detalization in some localizations
* Fixed crash on loading of some user-made maps
# 1.3.2 -> 1.4.0
### General

View File

@ -36,14 +36,27 @@
"vcmi.heroOverview.spells" : "魔法",
"vcmi.radialWheel.mergeSameUnit" : "合并相同生物",
"vcmi.radialWheel.showUnitInformation" : "显示生物信息",
"vcmi.radialWheel.fillSingleUnit" : "单个生物填充空格",
"vcmi.radialWheel.splitSingleUnit" : "分割单个生物",
"vcmi.radialWheel.splitUnitEqually" : "平均分配生物",
"vcmi.radialWheel.moveUnit" : "将生物移动到部队",
"vcmi.radialWheel.splitUnit" : "分割生物到其他空位",
"vcmi.radialWheel.heroGetArmy" : "移动生物",
"vcmi.radialWheel.heroSwapArmy" : "交换生物",
"vcmi.radialWheel.heroExchange" : "开启英雄交换",
"vcmi.radialWheel.heroGetArtifacts" : "移动宝物",
"vcmi.radialWheel.heroSwapArtifacts" : "交换宝物",
"vcmi.radialWheel.heroDismiss" : "解雇英雄",
"vcmi.radialWheel.moveTop" : "移到顶端",
"vcmi.radialWheel.moveUp" : "上移",
"vcmi.radialWheel.moveDown" : "下移",
"vcmi.radialWheel.moveBottom" : "移到底端",
"vcmi.mainMenu.serverConnecting" : "连接中...",
"vcmi.mainMenu.serverAddressEnter" : "使用地址:",
"vcmi.mainMenu.serverConnectionFailed" : "连接失败",
"vcmi.mainMenu.serverClosing" : "关闭中...",
"vcmi.mainMenu.hostTCP" : "创建TCP/IP游戏",
"vcmi.mainMenu.joinTCP" : "加入TCP/IP游戏",
@ -51,11 +64,18 @@
"vcmi.lobby.filename" : "文件名",
"vcmi.lobby.creationDate" : "创建时间",
"vcmi.lobby.scenarioName" : "场景名称",
"vcmi.lobby.mapPreview" : "地图预览",
"vcmi.lobby.noPreview" : "无地上部分",
"vcmi.lobby.noUnderground" : "无地下部分",
"vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。",
"vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}",
"vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}",
"vcmi.server.confirmReconnect" : "您想要重连上一个会话么?",
"vcmi.server.errors.modNoDependency" : "读取mod包 {'%s'}失败!\n 需要的mod {'%s'} 没有安装或无效!\n",
"vcmi.server.errors.modConflict" : "读取的mod包 {'%s'}无法运行!\n 与另一个mod {'%s'}冲突!\n",
"vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!",
"vcmi.settingsMainWindow.generalTab.hover" : "常规",
"vcmi.settingsMainWindow.generalTab.help" : "切换到“常规”选项卡 - 设置游戏客户端呈现",
@ -92,6 +112,10 @@
"vcmi.systemOptions.hapticFeedbackButton.help" : "{触觉反馈}\n\n切换触摸输入的触觉反馈。",
"vcmi.systemOptions.enableUiEnhancementsButton.hover" : "界面增强",
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{界面增强}\n\n显示所有界面增强内容,如大背包和魔法书等。",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "增大魔法书界面",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{增大魔法书界面}\n\n可以在魔法书单页中显示更多的魔法,从而获得更好的视觉效果。",
"vcmi.systemOptions.audioMuteFocus.hover" : "切换窗口时静音",
"vcmi.systemOptions.audioMuteFocus.help" : "{切换窗口时静音}\n\n快速切换窗口时将静音,在工作时,切换游戏窗口不会有声音。",
"vcmi.adventureOptions.infoBarPick.hover" : "在信息面板显示消息",
"vcmi.adventureOptions.infoBarPick.help" : "{在信息面板显示消息}\n\n来自访问地图物件的信息将显示在信息面板,而不是弹出窗口。",
@ -107,6 +131,8 @@
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{信息面板生物管理}\n\n允许在信息面板中重新排列生物,而不是在默认组件之间循环。",
"vcmi.adventureOptions.leftButtonDrag.hover" : "左键拖动地图",
"vcmi.adventureOptions.leftButtonDrag.help" : "{左键拖动地图}\n\n启用后,按住左键移动鼠标将拖动冒险地图视图。",
"vcmi.adventureOptions.smoothDragging.hover" : "平滑地图拖动",
"vcmi.adventureOptions.smoothDragging.help" : "{平滑地图拖动}\n\n启用后,地图拖动会产生柔和的羽化效果。",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@ -140,6 +166,9 @@
"vcmi.battleOptions.skipBattleIntroMusic.hover": "跳过战斗开始音乐",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
"vcmi.adventureMap.revisitObject.hover" : "重新访问",
"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
"vcmi.battleWindow.pressKeyToSkipIntro" : "按下任意键立即开始战斗",
"vcmi.battleWindow.damageEstimation.melee" : "近战攻击 %CREATURE (%DAMAGE).",
"vcmi.battleWindow.damageEstimation.meleeKills" : "近战攻击 %CREATURE (%DAMAGE, %KILLS).",
@ -154,6 +183,15 @@
"vcmi.battleResultsWindow.applyResultsLabel" : "接受战斗结果",
"vcmi.tutorialWindow.title" : "触摸屏介绍",
"vcmi.tutorialWindow.decription.RightClick" : "触摸并按住要右键单击的元素。 触摸可用区域以关闭。",
"vcmi.tutorialWindow.decription.MapPanning" : "用一根手指触摸并拖动来移动地图。",
"vcmi.tutorialWindow.decription.MapZooming" : "用两根手指捏合可更改地图缩放比例。",
"vcmi.tutorialWindow.decription.RadialWheel" : "滑动可打开径向轮以执行各种操作,例如生物/英雄管理和城镇排序。",
"vcmi.tutorialWindow.decription.BattleDirection" : "要从特定方向攻击,请向要进行攻击的方向滑动。",
"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "如果手指距离足够远,可以取消攻击方向手势。",
"vcmi.tutorialWindow.decription.AbortSpell" : "触摸并按住可取消魔法。",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "显示可招募生物",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{显示可招募生物}\n\n在城镇摘要(城镇屏幕的左下角)中显示可招募的生物数量,而不是增长。",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "显示生物增长",
@ -202,14 +240,68 @@
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "同盟关系",
"vcmi.randomMapTab.widgets.roadTypesLabel" : "道路类型",
"vcmi.optionsTab.chessFieldBase.hover" : "额外计时器",
"vcmi.optionsTab.chessFieldTurn.hover" : "转动计时器",
"vcmi.optionsTab.turnOptions.hover" : "回合选项",
"vcmi.optionsTab.turnOptions.help" : "选择回合计时器并同步回合选项",
"vcmi.optionsTab.selectPreset" : "预设",
"vcmi.optionsTab.chessFieldBase.hover" : "基本计时器",
"vcmi.optionsTab.chessFieldTurn.hover" : "回合计时器",
"vcmi.optionsTab.chessFieldBattle.hover" : "战斗计时器",
"vcmi.optionsTab.chessFieldUnit.hover" : "堆栈计时器",
"vcmi.optionsTab.chessFieldBase.help" : "当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。",
"vcmi.optionsTab.chessFieldTurn.help" : "当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。",
"vcmi.optionsTab.chessFieldBattle.help" : "战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。",
"vcmi.optionsTab.chessFieldUnit.help" : "当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。",
"vcmi.optionsTab.chessFieldUnit.hover" : "单位计时器",
"vcmi.optionsTab.chessFieldBase.help" : "当 {回合计时器} 达到 0 时使用。在游戏开始时设置一次。达到 0 时,结束当前回合。任何正在进行的战斗都会以失败告终。",
"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "在战斗外或{战斗计时器}耗尽时使用。 每回合重置。 剩余部分在回合结束时添加至 {基本计时器}。",
"vcmi.optionsTab.chessFieldTurnDiscard.help" : "战斗外或{战斗计时器}耗尽时使用。 每回合重置。 任何未花费的时间都会丢失。",
"vcmi.optionsTab.chessFieldBattle.help" : "在与 AI 的战斗中使用,或者在 {单位计时器} 耗尽时用于 pvp 战斗。在每次战斗开始时重置。",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "在 PVP 战斗中选择单位动作时使用。 在单位回合结束时将剩余物添加到{战斗计时器}。",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "在 PVP 战斗中选择单位动作时使用。 在每个单位回合开始时重置。 任何未花费的时间都会丢失。",
"vcmi.optionsTab.accumulate" : "累积",
"vcmi.optionsTab.simturnsTitle" : "同时进行回合",
"vcmi.optionsTab.simturnsMin.hover" : "最少回合",
"vcmi.optionsTab.simturnsMax.hover" : "最多回合",
"vcmi.optionsTab.simturnsAI.hover" : "(测试中) AI回合同时行动",
"vcmi.optionsTab.simturnsMin.help" : "同时游戏进行的最少指定天数。在此期间玩家之间的联系将被阻止",
"vcmi.optionsTab.simturnsMax.help" : "同时游戏指定的最多天数或直到与其他玩家联系",
"vcmi.optionsTab.simturnsAI.help" : "{AI回合同时行动}\n实验选项。启用同时回合后,允许 AI 玩家与人类玩家同时行动。",
"vcmi.optionsTab.turnTime.select" : "回合计时器预设",
"vcmi.optionsTab.turnTime.unlimited" : "无限时",
"vcmi.optionsTab.turnTime.classic.1" : "经典计时器: 1 分钟",
"vcmi.optionsTab.turnTime.classic.2" : "经典计时器: 2 分钟",
"vcmi.optionsTab.turnTime.classic.5" : "经典计时器: 5 分钟",
"vcmi.optionsTab.turnTime.classic.10" : "经典计时器: 10 分钟",
"vcmi.optionsTab.turnTime.classic.20" : "经典计时器: 20 分钟",
"vcmi.optionsTab.turnTime.classic.30" : "经典计时器: 30 分钟",
"vcmi.optionsTab.turnTime.chess.20" : "国际象棋计时器: 20:00 + 10:00 + 02:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.16" : "国际象棋计时器: 16:00 + 08:00 + 01:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.8" : "国际象棋计时器: 08:00 + 04:00 + 01:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.4" : "国际象棋计时器: 04:00 + 02:00 + 00:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.2" : "国际象棋计时器: 02:00 + 01:00 + 00:15 + 00:00",
"vcmi.optionsTab.turnTime.chess.1" : "国际象棋计时器: 01:00 + 01:00 + 00:00 + 00:00",
"vcmi.optionsTab.simturns.select" : "同时进行回合预设",
"vcmi.optionsTab.simturns.none" : "不同时进行回合",
"vcmi.optionsTab.simturns.tillContactMax" : "同时进行: 可进行联系",
"vcmi.optionsTab.simturns.tillContact1" : "同时进行: 1 周, 联系时中断",
"vcmi.optionsTab.simturns.tillContact2" : "同时进行: 2 周, 联系时中断",
"vcmi.optionsTab.simturns.tillContact4" : "同时进行: 1 月, 联系时中断",
"vcmi.optionsTab.simturns.blocked1" : "同时进行: 1 周, 屏蔽联系",
"vcmi.optionsTab.simturns.blocked2" : "同时进行: 2 周, 屏蔽联系",
"vcmi.optionsTab.simturns.blocked4" : "同时进行: 1 月, 屏蔽联系",
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
// Using this information, VCMI will automatically select correct plural form for every possible amount
"vcmi.optionsTab.simturns.days.0" : " %d 天",
"vcmi.optionsTab.simturns.days.1" : " %d 天",
"vcmi.optionsTab.simturns.days.2" : " %d 天",
"vcmi.optionsTab.simturns.weeks.0" : " %d 周",
"vcmi.optionsTab.simturns.weeks.1" : " %d 周",
"vcmi.optionsTab.simturns.weeks.2" : " %d 周",
"vcmi.optionsTab.simturns.months.0" : " %d 月",
"vcmi.optionsTab.simturns.months.1" : " %d 月",
"vcmi.optionsTab.simturns.months.2" : " %d 月",
// Custom victory conditions for H3 campaigns and HotA maps
"vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!",
@ -275,7 +367,7 @@
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "忽略防御 (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "当攻击时,目标生物${val}%的防御力将被无视。",
"core.bonus.FIRE_IMMUNITY.name": "火系免疫",
"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法",
"core.bonus.FIRE_IMMUNITY.description": "免疫所有火系魔法",
"core.bonus.FIRE_SHIELD.name": "烈火神盾 (${val}%)",
"core.bonus.FIRE_SHIELD.description": "反弹部分受到的近战伤害",
"core.bonus.FIRST_STRIKE.name": "抢先反击",

View File

@ -30,15 +30,33 @@
"vcmi.capitalColors.6" : "Tyrkysový",
"vcmi.capitalColors.7" : "Růžový",
"vcmi.heroOverview.startingArmy" : "Počáteční jednotky",
"vcmi.heroOverview.warMachine" : "Bojové stroje",
"vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti",
"vcmi.heroOverview.spells" : "Kouzla",
"vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky",
"vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou",
"vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku",
"vcmi.radialWheel.splitUnitEqually" : "Rovnoměrně rozdělit jednotky",
"vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně",
"vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu",
"vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice",
"vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny",
"vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou",
"vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů",
"vcmi.radialWheel.heroGetArtifacts" : "Získat artefakty od jiního hrdiny",
"vcmi.radialWheel.heroSwapArtifacts" : "Vyměnit artefakty s jiným hrdinou",
"vcmi.radialWheel.heroDismiss" : "Propustit hrdinu",
"vcmi.radialWheel.moveTop" : "Move to top",
"vcmi.radialWheel.moveUp" : "Move up",
"vcmi.radialWheel.moveDown" : "Move down",
"vcmi.radialWheel.moveBottom" : "Move to bottom",
"vcmi.mainMenu.serverConnecting" : "Připojování...",
"vcmi.mainMenu.serverAddressEnter" : "Zadejte adresu:",
"vcmi.mainMenu.serverConnectionFailed" : "Připojování selhalo",
"vcmi.mainMenu.serverClosing" : "Zavírání...",
"vcmi.mainMenu.hostTCP" : "Pořádat hru TCP/IP",
"vcmi.mainMenu.joinTCP" : "Připojit se do hry TCP/IP",
@ -46,10 +64,18 @@
"vcmi.lobby.filepath" : "Název souboru",
"vcmi.lobby.creationDate" : "Datum vytvoření",
"vcmi.lobby.scenarioName" : "Název scénáře",
"vcmi.lobby.mapPreview" : "Náhled mapy",
"vcmi.lobby.noPreview" : "bez náhledu",
"vcmi.lobby.noUnderground" : "bez podzemí",
"vcmi.server.errors.existingProcess" : "Již běží jiný server VCMI. Prosím, ukončete ho před startem nové hry.",
"vcmi.server.errors.modsToEnable" : "{Následující modifikace jsou nutné pro načtení hry}",
"vcmi.server.errors.modsToDisable" : "{Následující modifikace musí být zakázány}",
"vcmi.server.confirmReconnect" : "Chcete se připojit k poslední relaci?",
"vcmi.server.errors.modNoDependency" : "Nelze načíst modifikaci {'%s'}!\n Závisí na modifikaci {'%s'}, která není aktivní!\n",
"vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n",
"vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!",
"vcmi.settingsMainWindow.generalTab.hover" : "Obecné",
"vcmi.settingsMainWindow.generalTab.help" : "Přepne na kartu obecných nastavení, která obsahuje nastavení související s obecným chováním klienta hry",
@ -84,6 +110,10 @@
"vcmi.systemOptions.framerateButton.help" : "{Zobrazit FPS}\n\nPřepne viditelnost počitadla snímků za sekundu v rohu obrazovky hry",
"vcmi.systemOptions.hapticFeedbackButton.hover" : "Vibrace",
"vcmi.systemOptions.hapticFeedbackButton.help" : "{Vibrace}\n\nPřepnout stav vibrací při dotykovém ovládání",
"vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Vylepšení rozhraní",
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Vylepšení rozhraní}\n\nZapne různá vylepšení rozhraní, jako je tlačítko batohu atd. Zakažte pro zážitek klasické hry.",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Velká kniha kouzel",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Velká kniha kouzel}\n\nPovolí větší knihu kouzel, do které se jich více vleze na jednu stranu. Animace změny stránek s tímto nastavením nefunguje.",
"vcmi.adventureOptions.infoBarPick.hover" : "Zobrazit zprávy v panelu informací",
"vcmi.adventureOptions.infoBarPick.help" : "{Zobrazit zprávy v panelu informací}\n\nKdyž bude možné, herní zprávy z návštěv míst na mapě budou zobrazeny v panelu informací místo ve zvláštním okně.",
@ -97,8 +127,8 @@
"vcmi.adventureOptions.borderScroll.help" : "{Posouvání okraji}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Info Panel Creature Management", //TODO
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Info Panel Creature Management}\n\nAllows rearranging creatures in info panel instead of cycling between default components",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Left Click Drag Map",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Left Click Drag Map}\n\nWhen enabled, moving mouse with left button pressed will drag adventure map view",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Posouvání mapy levým kliknutím",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Posouvání mapy levým kliknutím}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@ -129,6 +159,8 @@
"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.",
"vcmi.adventureMap.revisitObject.hover" : "Znovu navštívit místo",
"vcmi.adventureMap.revisitObject.help" : "{Znovu navštívit místo}\n\nPokud se hrdina nachází na nějakém místě mapy, může jej znovu navštívit.",
"vcmi.battleWindow.pressKeyToSkipIntro" : "Stiskněte jakoukoliv klávesu pro okamžité zahájení bitvy",
"vcmi.battleWindow.damageEstimation.melee" : "Zaútočit na %CREATURE (%DAMAGE).",
@ -144,6 +176,15 @@
"vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy",
"vcmi.tutorialWindow.title" : "Úvod ovládání dotykem",
"vcmi.tutorialWindow.decription.RightClick" : "Klepněte a držte prvek, na který byste chtěli použít pravé tlačítko myši. Klepněte na volnou oblast pro zavření.",
"vcmi.tutorialWindow.decription.MapPanning" : "Klepněte a držte jedním prstem pro posouvání mapy.",
"vcmi.tutorialWindow.decription.MapZooming" : "Přibližte dva prsty k sobě pro přiblížení mapy.",
"vcmi.tutorialWindow.decription.RadialWheel" : "Přejetí otevře kruhovou nabídku pro různé akce, třeba správa hrdiny/bojovnínků a příkazy měst.",
"vcmi.tutorialWindow.decription.BattleDirection" : "Pro útok ze speifického úhlu, přejeďte směrem, ze kterého má být útok vykonán.",
"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesto útoku pod úhlem může být zrušeno, pokud, pokud je prst dostatečně daleko.",
"vcmi.tutorialWindow.decription.AbortSpell" : "Klepněte a držte pro zrušení kouzla.",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Zobrazit dostupné jednotky",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Zobrazit dostupné jednotky}\n\nZobrazit počet jednotek dostupných ke koupení místo jejich týdenního přírůstku v přehledu města. (levý spodní okraj obrazovky města).",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Zobrazit týdenní přírůstek jednotek",
@ -152,18 +193,18 @@
"vcmi.otherOptions.compactTownCreatureInfo.help": "{Kompaktní informace o jednotkách}\n\nZobrazit menší informace o jednotkách města v jeho přehledu (levý spodní okraj obrazovky města).",
"vcmi.townHall.missingBase" : "Základní budova %s musí být postavena jako první",
"vcmi.townHall.noCreaturesToRecruit" : "Žádné jednotky k vycvičení!", //TODO
"vcmi.townHall.greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
"vcmi.townHall.greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
"vcmi.townHall.noCreaturesToRecruit" : "Žádné jednotky k vycvičení!",
"vcmi.townHall.greetingManaVortex" : "Při pobytu u místa %s se vaše tělo naplnilo novou energií. Máte dvojnásobné množství maximální magické energie.",
"vcmi.townHall.greetingKnowledge" : "Studujete glyfy na the %s a porozumíte fungování různých magií (+1 Znalosti).",
"vcmi.townHall.greetingSpellPower" : "%s vás učí nové cesty zaměření vaší magické síly (+1 Síla kouzel).",
"vcmi.townHall.greetingExperience" : "Návštěva %s vás naučila spoustu nových dovedností (+1000 zkušeností).",
"vcmi.townHall.greetingAttack" : "Čas strávený poblíž místa zvaného %s allows you to learn more effective combat skills (+1 Attack Skill).",
"vcmi.townHall.greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
"vcmi.townHall.hasNotProduced" : "%s zatím nic nevyrobil.",
"vcmi.townHall.hasProduced" : "%s vyrobil %d %s tento týden.",
"vcmi.townHall.greetingAttack" : "Čas strávený poblíž místa zvaného %s vám dovolil se naučit efektivnější bojové dovednosti (+1 Útočná síla).",
"vcmi.townHall.greetingDefence" : "Trávíte čas na místě zvaném %s, zkušení bojovníci vás u toho naučili nové metody obrany (+1 Obranná síla).",
"vcmi.townHall.hasNotProduced" : "%s - zatím nic nevyrobeno.",
"vcmi.townHall.hasProduced" : "%s - vyrobeno %d %s tento týden.",
"vcmi.townHall.greetingCustomBonus" : "%s vám dává +%d %s%s",
"vcmi.townHall.greetingCustomUntil" : " do další bitvy.",
"vcmi.townHall.greetingInTownMagicWell" : "%s obnovil na maximum vaši magickou energii.",
"vcmi.townHall.greetingInTownMagicWell" : "%s - obnoveno na maximum vaši magickou energii.",
"vcmi.logicalExpressions.anyOf" : "Něco z následujících:",
"vcmi.logicalExpressions.allOf" : "Všechny následující:",
@ -174,17 +215,17 @@
"vcmi.heroWindow.openBackpack.hover" : "Open artifact backpack window",
"vcmi.heroWindow.openBackpack.help" : "Opens window that allows easier artifact backpack management",
"vcmi.commanderWindow.artifactMessage" : "Do you want to return this artifact to the hero?",
"vcmi.commanderWindow.artifactMessage" : "Chcete navrátit tento artefakt hrdinovi?",
"vcmi.creatureWindow.showBonuses.hover" : "Switch to bonuses view",
"vcmi.creatureWindow.showBonuses.hover" : "Přepnout na zobrazení bonusů",
"vcmi.creatureWindow.showBonuses.help" : "Display all active bonuses of the commander",
"vcmi.creatureWindow.showSkills.hover" : "Switch to skills view",
"vcmi.creatureWindow.showSkills.hover" : "Přepnout na zobrazení schoostí",
"vcmi.creatureWindow.showSkills.help" : "Display all learned skills of the commander",
"vcmi.creatureWindow.returnArtifact.hover" : "Vrátit artefakt",
"vcmi.creatureWindow.returnArtifact.help" : "Klikněte na toto tlačítko pro navrácení artefaktů do hrdinova batohu",
"vcmi.questLog.hideComplete.hover" : "Hide complete quests",
"vcmi.questLog.hideComplete.help" : "Hide all completed quests",
"vcmi.questLog.hideComplete.hover" : "Skrýt dokončené úkoly",
"vcmi.questLog.hideComplete.help" : "Skrýt všechny dokončené úkoly",
"vcmi.randomMapTab.widgets.randomTemplate" : "(Random)",
"vcmi.randomMapTab.widgets.templateLabel" : "Šablona",
@ -192,9 +233,67 @@
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments",
"vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest",
"vcmi.optionsTab.widgets.labelTimer" : "Časovač",
"vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Klasický časovač",
"vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Šachový časovač",
"vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu",
"vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo tahů a nastavení souběžných tahů",
"vcmi.optionsTab.selectPreset" : "Preset",
"vcmi.optionsTab.chessFieldBase.hover" : "Base timer",
"vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer",
"vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer",
"vcmi.optionsTab.chessFieldUnit.hover" : "Unit timer",
"vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.",
"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.",
"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Any unspent time is lost",
"vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Used when selecting unit action in pvp combat. Leftover added to {Battle Timer} at end of unit turn.",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn. Any unspent time is lost",
"vcmi.optionsTab.accumulate" : "Accumulate",
"vcmi.optionsTab.simturnsTitle" : "Souběžné tahy",
"vcmi.optionsTab.simturnsMin.hover" : "At least for",
"vcmi.optionsTab.simturnsMax.hover" : "At most for",
"vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns",
"vcmi.optionsTab.simturnsMin.help" : "Play simultaneously for specified number of days. Contacts between players during this period are blocked",
"vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player",
"vcmi.optionsTab.simturnsAI.help" : "{Simultaneous AI Turns}\nExperimental option. Allows AI players to act at the same time as human player when simultaneous turns are enabled.",
"vcmi.optionsTab.turnTime.select" : "Select turn timer preset",
"vcmi.optionsTab.turnTime.unlimited" : "Unlimited turn time",
"vcmi.optionsTab.turnTime.classic.1" : "Classic timer: 1 minute",
"vcmi.optionsTab.turnTime.classic.2" : "Classic timer: 2 minutes",
"vcmi.optionsTab.turnTime.classic.5" : "Classic timer: 5 minutes",
"vcmi.optionsTab.turnTime.classic.10" : "Classic timer: 10 minutes",
"vcmi.optionsTab.turnTime.classic.20" : "Classic timer: 20 minutes",
"vcmi.optionsTab.turnTime.classic.30" : "Classic timer: 30 minutes",
"vcmi.optionsTab.turnTime.chess.20" : "Chess: 20:00 + 10:00 + 02:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.16" : "Chess: 16:00 + 08:00 + 01:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.8" : "Chess: 08:00 + 04:00 + 01:00 + 00:00",
"vcmi.optionsTab.turnTime.chess.4" : "Chess: 04:00 + 02:00 + 00:30 + 00:00",
"vcmi.optionsTab.turnTime.chess.2" : "Chess: 02:00 + 01:00 + 00:15 + 00:00",
"vcmi.optionsTab.turnTime.chess.1" : "Chess: 01:00 + 01:00 + 00:00 + 00:00",
"vcmi.optionsTab.simturns.select" : "Select simultaneous turns preset",
"vcmi.optionsTab.simturns.none" : "No simultaneous turns",
"vcmi.optionsTab.simturns.tillContactMax" : "Simturns: Until contact",
"vcmi.optionsTab.simturns.tillContact1" : "Simturns: 1 week, break on contact",
"vcmi.optionsTab.simturns.tillContact2" : "Simturns: 2 weeks, break on contact",
"vcmi.optionsTab.simturns.tillContact4" : "Simturns: 1 month, break on contact",
"vcmi.optionsTab.simturns.blocked1" : "Simturns: 1 week, contacts blocked",
"vcmi.optionsTab.simturns.blocked2" : "Simturns: 2 weeks, contacts blocked",
"vcmi.optionsTab.simturns.blocked4" : "Simturns: 1 month, contacts blocked",
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
// Using this information, VCMI will automatically select correct plural form for every possible amount
"vcmi.optionsTab.simturns.days.0" : " %d dní",
"vcmi.optionsTab.simturns.days.1" : " %d den",
"vcmi.optionsTab.simturns.days.2" : " %d dny",
"vcmi.optionsTab.simturns.weeks.0" : " %d týdnů",
"vcmi.optionsTab.simturns.weeks.1" : " %d týden",
"vcmi.optionsTab.simturns.weeks.2" : " %d týdny",
"vcmi.optionsTab.simturns.months.0" : " %d měsíců",
"vcmi.optionsTab.simturns.months.1" : " %d měsíc",
"vcmi.optionsTab.simturns.months.2" : " %d měsíce",
// Custom victory conditions for H3 campaigns and HotA maps
"vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!",
@ -203,162 +302,162 @@
"vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulace! Porazili jste všechny nepřátele zamořující tuto zemi a můžete si nárokovat vítězství!",
"vcmi.map.victoryCondition.collectArtifacts.message" : "Získejte tři artefakty",
"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulace! Všichni vaši nepřítelé byli poraženi a máte Andělskou alianci! Vítězství je vaše!",
"vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a vyrobte Andělskou alianci",
"vcmi.map.victoryCondition.angelicAlliance.message" : "Porazte všechny nepřátele a utužte Andělskou alianci",
// 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" : "Basic",
"vcmi.stackExperience.rank.1" : "Novice",
"vcmi.stackExperience.rank.2" : "Trained",
"vcmi.stackExperience.rank.3" : "Skilled",
"vcmi.stackExperience.rank.4" : "Proven",
"vcmi.stackExperience.rank.5" : "Veteran",
"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", //TODO
"vcmi.stackExperience.rank.0" : "Začátečník",
"vcmi.stackExperience.rank.1" : "Učeň",
"vcmi.stackExperience.rank.2" : "Trénovaný",
"vcmi.stackExperience.rank.3" : "Zručný",
"vcmi.stackExperience.rank.4" : "Prověřený",
"vcmi.stackExperience.rank.5" : "Veterán",
"vcmi.stackExperience.rank.6" : "Adept",
"vcmi.stackExperience.rank.7" : "Expert",
"vcmi.stackExperience.rank.8" : "Elite",
"vcmi.stackExperience.rank.8" : "Elit",
"vcmi.stackExperience.rank.9" : "Master",
"vcmi.stackExperience.rank.10" : "Ace",
"vcmi.stackExperience.rank.10" : "Eso",
"core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý úder",
"core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát",
"core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations",
"core.bonus.ADDITIONAL_RETALIATION.description": "May retaliate ${val} extra times",
"core.bonus.ADDITIONAL_RETALIATION.name": "Další odveta",
"core.bonus.ADDITIONAL_RETALIATION.description": "Může zaútočit zpět navíc ${val}x",
"core.bonus.AIR_IMMUNITY.name": "Vzdušná odolnost",
"core.bonus.AIR_IMMUNITY.description": "Immune to all spells from the school of Air magic",
"core.bonus.AIR_IMMUNITY.description": "Imunní všem kouzlům školy vzdušné magie",
"core.bonus.ATTACKS_ALL_ADJACENT.name": "Útok okolo",
"core.bonus.ATTACKS_ALL_ADJACENT.description": "Útočí na všechny sousední jednotky",
"core.bonus.BLOCKS_RETALIATION.name": "No retaliation",
"core.bonus.BLOCKS_RETALIATION.description": "Enemy cannot retaliate",
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "No ranged retaliation",
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Enemy cannot retaliate by using a ranged attack",
"core.bonus.BLOCKS_RETALIATION.name": "Žádná odplata",
"core.bonus.BLOCKS_RETALIATION.description": "Nepřítel nemůže zaútočit zpět",
"core.bonus.BLOCKS_RANGED_RETALIATION.name": "Žádná odplata na dálku",
"core.bonus.BLOCKS_RANGED_RETALIATION.description": "Nepřítel nemůže zaútočit zpět útokem na dálku",
"core.bonus.CATAPULT.name": "Katapult",
"core.bonus.CATAPULT.description": "Attacks siege walls",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Reduce Casting Cost (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Reduces the spellcasting cost for the hero by ${val}",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Magic Damper (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Increases spellcasting cost of enemy spells by ${val}",
"core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge",
"core.bonus.CATAPULT.description": "Útočí na ochranné hradby",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name": "Snížit cenu kouzel (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description": "Snižuje cenu energie hrdiny o ${val}",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name": "Tlumič magie (${val})",
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Zvyšuje cenu energie kouzlení nepřítele o ${val}",
"core.bonus.CHARGE_IMMUNITY.name": "Immune to Charge", // TODO
"core.bonus.CHARGE_IMMUNITY.description": "Immune to Cavalier's and Champion's Charge",
"core.bonus.DARKNESS.name": "Darkness cover",
"core.bonus.DARKNESS.description": "Creates a shroud of darkness with a ${val} radius",
"core.bonus.DEATH_STARE.name": "Death Stare (${val}%)",
"core.bonus.DEATH_STARE.description": "Has a ${val}% chance to kill a single creature",
"core.bonus.DEFENSIVE_STANCE.name": "Defense Bonus",
"core.bonus.DEFENSIVE_STANCE.description": "+${val} Defense when defending",
"core.bonus.DESTRUCTION.name": "Destruction",
"core.bonus.DESTRUCTION.description": "Has ${val}% chance to kill extra units after attack",
"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Death Blow",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "Has a ${val}% chance of dealing double base damage when attacking",
"core.bonus.DARKNESS.name": "Závoj temnoty",
"core.bonus.DARKNESS.description": "Vytvoří clonu temnoty v oblasti ${val} polí",
"core.bonus.DEATH_STARE.name": "Smrtící pohled (${val}%)",
"core.bonus.DEATH_STARE.description": " ${val}% šanci zabít jednu creature",
"core.bonus.DEFENSIVE_STANCE.name": "Obranný bonus",
"core.bonus.DEFENSIVE_STANCE.description": "+${val} obranné síly při obraně",
"core.bonus.DESTRUCTION.name": "Zničení",
"core.bonus.DESTRUCTION.description": " ${val}% šanci zabít další jednotky po útoku",
"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Smrtící rána",
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": " ${val}% šanci na udělení dvojnásobného základního poškození při útoku",
"core.bonus.DRAGON_NATURE.name": "Drak",
"core.bonus.DRAGON_NATURE.description": "Creature has a Dragon Nature",
"core.bonus.DRAGON_NATURE.description": "Jednotka má povahu draka",
"core.bonus.EARTH_IMMUNITY.name": "Zemní odolnost",
"core.bonus.EARTH_IMMUNITY.description": "Immune to all spells from the school of Earth magic",
"core.bonus.ENCHANTER.name": "Enchanter",
"core.bonus.ENCHANTER.description": "Can cast mass ${subtype.spell} every turn",
"core.bonus.ENCHANTED.name": "Enchanted",
"core.bonus.ENCHANTED.description": "Affected by permanent ${subtype.spell}",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Ignore Defense (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "When attacking, ${val}% of the defender's defense is ignored",
"core.bonus.EARTH_IMMUNITY.description": "Imunní všem kouzlům školy zemské magie",
"core.bonus.ENCHANTER.name": "Zaklínač",
"core.bonus.ENCHANTER.description": "Může masově seslat ${subtype.spell} každý tah",
"core.bonus.ENCHANTED.name": "Očarovaný",
"core.bonus.ENCHANTED.description": "Trvale ovlivněm kouzlem ${subtype.spell}",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Nevšímá si ${val} % bodů obrany",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Pří útoku nebude brát v potaz ${val}% bodů obrany obránce",
"core.bonus.FIRE_IMMUNITY.name": "Ohnivá odolnost",
"core.bonus.FIRE_IMMUNITY.description": "Immune to all spells from the school of Fire magic",
"core.bonus.FIRE_IMMUNITY.description": "Imunní všem kouzlům školy ohnivé magie",
"core.bonus.FIRE_SHIELD.name": "Ohnivý štít (${val}%)",
"core.bonus.FIRE_SHIELD.description": "Reflects part of melee damage",
"core.bonus.FIRST_STRIKE.name": "First Strike",
"core.bonus.FIRST_STRIKE.description": "This creature retaliates before being attacked",
"core.bonus.FEAR.name": "Fear",
"core.bonus.FEAR.description": "Causes Fear on an enemy stack",
"core.bonus.FIRE_SHIELD.description": "Odrazí část zranení útoku zblízka",
"core.bonus.FIRST_STRIKE.name": "První úder",
"core.bonus.FIRST_STRIKE.description": "Tato jednotka útočí zpět ještě než je na ni zaútočeno",
"core.bonus.FEAR.name": "Strach",
"core.bonus.FEAR.description": "Způsobí strach nepřátelskému oddílu",
"core.bonus.FEARLESS.name": "Nebojácnost",
"core.bonus.FEARLESS.description": "Odolnost proti strachu",
"core.bonus.FLYING.name": "Letec",
"core.bonus.FLYING.description": "Při pohybu létá (přes překážky)",
"core.bonus.FREE_SHOOTING.name": "Blízké výstřely",
"core.bonus.FREE_SHOOTING.description": "Může použít výstřely při útoku zblízka",
"core.bonus.GARGOYLE.name": "Gargoyle",
"core.bonus.GARGOYLE.description": "Cannot be raised or healed",
"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Reduce Damage (${val}%)",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Reduces physical damage from ranged or melee attacks",
"core.bonus.GARGOYLE.name": "Chrlič",
"core.bonus.GARGOYLE.description": "Cannot be raised or healed", // TODO
"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Snižuje poškození (${val}%)",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Snižuje poškození od útoků z dálky a blízka",
"core.bonus.HATE.name": "Nesnáší ${subtype.creature}",
"core.bonus.HATE.description": "Does ${val}% more damage to ${subtype.creature}",
"core.bonus.HATE.description": "Dává o ${val} % větší zranění jednotce ${subtype.creature}",
"core.bonus.HEALER.name": "Léčitel",
"core.bonus.HEALER.description": "Léčí spojenecké jednotky",
"core.bonus.HP_REGENERATION.name": "Regenerace",
"core.bonus.HP_REGENERATION.description": "Každé kolo léčí ${val} životů",
"core.bonus.JOUSTING.name": "Champion charge",
"core.bonus.JOUSTING.description": "+${val}% damage for each hex travelled",
"core.bonus.JOUSTING.name": "Nabití šampiona",
"core.bonus.JOUSTING.description": "+${val}% poškození za každé projité pole",
"core.bonus.KING.name": "Král",
"core.bonus.KING.description": "Vulnerable to SLAYER level ${val} or higher",
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Spell immunity 1-${val}",
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Immune to spells of levels 1-${val}",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Limited shooting range",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Unable to target units farther than ${val} hexes",
"core.bonus.KING.description": "Zranitelný zabijákovi úrovně ${val} a vyšší",
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odolnost kouzel 1-${val}",
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odolnost vůči kouzlům úrovní 1-${val}",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Omezený dosah střelby",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nevystřelí na jednotky dále než ${val} polí",
"core.bonus.LIFE_DRAIN.name": "Vysátí životů (${val}%)",
"core.bonus.LIFE_DRAIN.description": "Vysaje ${val}% uděleného poškození",
"core.bonus.MANA_CHANNELING.name": "Magic Channel ${val}%",
"core.bonus.MANA_CHANNELING.description": "Gives your hero ${val}% of the mana spent by the enemy",
"core.bonus.MANA_CHANNELING.name": "Magic Channel ${val}%", // TODO
"core.bonus.MANA_CHANNELING.description": "Dá vašemu hrdinovi ${val} % many využité nepřítelem",
"core.bonus.MANA_DRAIN.name": "Vysátí many",
"core.bonus.MANA_DRAIN.description": "Každé kolo vysaje ${val} many",
"core.bonus.MAGIC_MIRROR.name": "Kouzelné zrcadlo (${val}%)",
"core.bonus.MAGIC_MIRROR.description": "Has a ${val}% chance to redirect an offensive spell to an enemy unit",
"core.bonus.MAGIC_RESISTANCE.name": "Magic Resistance (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description": "Has a ${val}% chance to resist an enemy spell",
"core.bonus.MIND_IMMUNITY.name": "Mind Spell Immunity",
"core.bonus.MIND_IMMUNITY.description": "Immune to Mind-type spells",
"core.bonus.NO_DISTANCE_PENALTY.name": "No distance penalty",
"core.bonus.NO_DISTANCE_PENALTY.description": "Does full damage at any distance",
"core.bonus.NO_MELEE_PENALTY.name": "No melee penalty",
"core.bonus.NO_MELEE_PENALTY.description": "Creature has no Melee Penalty",
"core.bonus.NO_MORALE.name": "Neutral Morale",
"core.bonus.NO_MORALE.description": "Creature is immune to morale effects",
"core.bonus.NO_WALL_PENALTY.name": "No wall penalty",
"core.bonus.NO_WALL_PENALTY.description": "Full damage during siege",
"core.bonus.NON_LIVING.name": "Non living",
"core.bonus.NON_LIVING.description": "Immunity to many effects",
"core.bonus.RANDOM_SPELLCASTER.name": "Random spellcaster",
"core.bonus.RANDOM_SPELLCASTER.description": "Can cast random spell",
"core.bonus.RANGED_RETALIATION.name": "Ranged retaliation",
"core.bonus.RANGED_RETALIATION.description": "Can perform ranged counterattack",
"core.bonus.RECEPTIVE.name": "Receptive",
"core.bonus.RECEPTIVE.description": "No Immunity to Friendly Spells",
"core.bonus.REBIRTH.name": "Rebirth (${val}%)",
"core.bonus.REBIRTH.description": "${val}% of stack will rise after death",
"core.bonus.RETURN_AFTER_STRIKE.name": "Attack and Return",
"core.bonus.RETURN_AFTER_STRIKE.description": "Returns after melee attack",
"core.bonus.SHOOTER.name": "Ranged",
"core.bonus.SHOOTER.description": "Creature can shoot",
"core.bonus.SHOOTS_ALL_ADJACENT.name": "Shoot all around",
"core.bonus.SHOOTS_ALL_ADJACENT.description": "This creature's ranged attacks strike all targets in a small area",
"core.bonus.SOUL_STEAL.name": "Soul Steal",
"core.bonus.SOUL_STEAL.description": "Gains ${val} new creatures for each enemy killed",
"core.bonus.SPELLCASTER.name": "Spellcaster",
"core.bonus.SPELLCASTER.description": "Can cast ${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.name": "Cast After Attack",
"core.bonus.SPELL_AFTER_ATTACK.description": "Has a ${val}% chance to cast ${subtype.spell} after it attacks",
"core.bonus.SPELL_BEFORE_ATTACK.name": "Cast Before Attack",
"core.bonus.SPELL_BEFORE_ATTACK.description": "Has a ${val}% chance to cast ${subtype.spell} before it attacks",
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Spell Resistance",
"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Damage from spells reduced by ${val}%.",
"core.bonus.MAGIC_MIRROR.description": " ${val}% šanci odrazit útočné kouzlo na nepřátelskou jednotku",
"core.bonus.MAGIC_RESISTANCE.name": "Magická odolnost (${val}%)",
"core.bonus.MAGIC_RESISTANCE.description": " ${val}% šanci ustát nepřátelské kouzlo",
"core.bonus.MIND_IMMUNITY.name": "Imunita kouzel mysli",
"core.bonus.MIND_IMMUNITY.description": "Imunní vůči kouzlům cílícím na mysl",
"core.bonus.NO_DISTANCE_PENALTY.name": "Bez penalizace vzdálenosti",
"core.bonus.NO_DISTANCE_PENALTY.description": "Plné poškození na jakoukoliv vzdálenost",
"core.bonus.NO_MELEE_PENALTY.name": "Bez penalizace útoku zblízka",
"core.bonus.NO_MELEE_PENALTY.description": "Jednotka není penalizována za útok zblízka",
"core.bonus.NO_MORALE.name": "Neutrální morálka",
"core.bonus.NO_MORALE.description": "Jednotka je imunní vůči efektu morálky",
"core.bonus.NO_WALL_PENALTY.name": "Bez penalizace hradbami",
"core.bonus.NO_WALL_PENALTY.description": "Plné poškození při obléhání",
"core.bonus.NON_LIVING.name": "Neživoucí",
"core.bonus.NON_LIVING.description": "Imunní vůči mnohým efektům",
"core.bonus.RANDOM_SPELLCASTER.name": "Náhodný kouzelník",
"core.bonus.RANDOM_SPELLCASTER.description": "Může seslat náhodné kouzlo",
"core.bonus.RANGED_RETALIATION.name": "Vzdálená msta",
"core.bonus.RANGED_RETALIATION.description": "Může provést protiútok na dálku",
"core.bonus.RECEPTIVE.name": "Přijímavý",
"core.bonus.RECEPTIVE.description": "Není imunní vůči přátelským kouzlům",
"core.bonus.REBIRTH.name": "Znovuzrození (${val}%)",
"core.bonus.REBIRTH.description": "${val}% oddílu se po smrti znovu narodí",
"core.bonus.RETURN_AFTER_STRIKE.name": "Útok a návrat",
"core.bonus.RETURN_AFTER_STRIKE.description": "Navrátí se po útoku na blízko",
"core.bonus.SHOOTER.name": "Střelec",
"core.bonus.SHOOTER.description": "Jednotka může střílet",
"core.bonus.SHOOTS_ALL_ADJACENT.name": "Střílí okolo",
"core.bonus.SHOOTS_ALL_ADJACENT.description": "Vzdálené útoky této jednotky zasáhnou všechny cíle v malé oblasti",
"core.bonus.SOUL_STEAL.name": "Zloděj duší",
"core.bonus.SOUL_STEAL.description": "Získá ${val} nových jednotek za každého zabitého nepřítele",
"core.bonus.SPELLCASTER.name": "Kouzelník",
"core.bonus.SPELLCASTER.description": "Může seslat ${subtype.spell}",
"core.bonus.SPELL_AFTER_ATTACK.name": "Kouzlení po útoku",
"core.bonus.SPELL_AFTER_ATTACK.description": " ${val}% šanci seslat ${subtype.spell} po zaútočení",
"core.bonus.SPELL_BEFORE_ATTACK.name": "Kouzlení před útokem",
"core.bonus.SPELL_BEFORE_ATTACK.description": " ${val}% šanci seslat ${subtype.spell} před zaútočením",
"core.bonus.SPELL_DAMAGE_REDUCTION.name": "Magická odolnost",
"core.bonus.SPELL_DAMAGE_REDUCTION.description": "Zranění od kouzel sníženo o ${val}%.",
"core.bonus.SPELL_IMMUNITY.name": "Odolnost vůči kouzlům",
"core.bonus.SPELL_IMMUNITY.description": "Odolnost proti ${subtype.spell}",
"core.bonus.SPELL_LIKE_ATTACK.name": "Spell-like attack",
"core.bonus.SPELL_LIKE_ATTACK.description": "Attacks with ${subtype.spell}",
"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura of Resistance",
"core.bonus.SPELL_RESISTANCE_AURA.description": "Nearby stacks get ${val}% magic resistance",
"core.bonus.SUMMON_GUARDIANS.name": "Summon guardians",
"core.bonus.SUMMON_GUARDIANS.description": "At the start of battle summons ${subtype.creature} (${val}%)",
"core.bonus.SYNERGY_TARGET.name": "Synergizable",
"core.bonus.SPELL_LIKE_ATTACK.name": "Útok kouzlem",
"core.bonus.SPELL_LIKE_ATTACK.description": "Útočí kouzlem ${subtype.spell}",
"core.bonus.SPELL_RESISTANCE_AURA.name": "Aura odolnosti",
"core.bonus.SPELL_RESISTANCE_AURA.description": "Oddíly poblíž získají ${val}% magickou odolnost",
"core.bonus.SUMMON_GUARDIANS.name": "Povolat strážce",
"core.bonus.SUMMON_GUARDIANS.description": "Na začátku bitvy povolá ${subtype.creature} (${val}%)",
"core.bonus.SYNERGY_TARGET.name": "Synergizable", // TODO
"core.bonus.SYNERGY_TARGET.description": "This creature is vulnerable to synergy effect",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Breath",
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Breath Attack (2-hex range)",
"core.bonus.THREE_HEADED_ATTACK.name": "Three-headed attack",
"core.bonus.THREE_HEADED_ATTACK.description": "Attacks three adjacent units",
"core.bonus.TRANSMUTATION.name": "Transmutation",
"core.bonus.TRANSMUTATION.description": "${val}% chance to transform attacked unit to a different type",
"core.bonus.TWO_HEX_ATTACK_BREATH.name": "Dech",
"core.bonus.TWO_HEX_ATTACK_BREATH.description": "Dechový útok (dosah do dvou polí)",
"core.bonus.THREE_HEADED_ATTACK.name": "Tříhlavý útok",
"core.bonus.THREE_HEADED_ATTACK.description": "Útočí na tři sousední jednotky",
"core.bonus.TRANSMUTATION.name": "Transmutace",
"core.bonus.TRANSMUTATION.description": "${val}% šance na přeměnu útočené jednotky na jiný druh",
"core.bonus.UNDEAD.name": "Nemrtvý",
"core.bonus.UNDEAD.description": "Jednotka je nemrtvá",
"core.bonus.UNLIMITED_RETALIATIONS.name": "Unlimited retaliations",
"core.bonus.UNLIMITED_RETALIATIONS.description": "Can retaliate against an unlimited number of attacks",
"core.bonus.WATER_IMMUNITY.name": "Water immunity",
"core.bonus.WATER_IMMUNITY.description": "Immune to all spells from the school of Water magic",
"core.bonus.WIDE_BREATH.name": "Wide breath",
"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)"
"core.bonus.UNLIMITED_RETALIATIONS.name": "Neomezené odvety",
"core.bonus.UNLIMITED_RETALIATIONS.description": "Může se mstít za neomezený počet útoků",
"core.bonus.WATER_IMMUNITY.name": "Vodní odolnost",
"core.bonus.WATER_IMMUNITY.description": "Imunní všem kouzlům školy vodní magie",
"core.bonus.WIDE_BREATH.name": "Široký dech",
"core.bonus.WIDE_BREATH.description": "Útočí širokým dechem (více polí)"
}

View File

@ -54,6 +54,8 @@
"vcmi.radialWheel.moveDown" : "Move down",
"vcmi.radialWheel.moveBottom" : "Move to bottom",
"vcmi.spellBook.search" : "search...",
"vcmi.mainMenu.serverConnecting" : "Connecting...",
"vcmi.mainMenu.serverAddressEnter" : "Enter address:",
"vcmi.mainMenu.serverConnectionFailed" : "Failed to connect",
@ -69,6 +71,7 @@
"vcmi.lobby.noPreview" : "no preview",
"vcmi.lobby.noUnderground" : "no underground",
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",
"vcmi.server.errors.modsToEnable" : "{Following mods are required}",
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",

View File

@ -54,6 +54,8 @@
"vcmi.radialWheel.moveDown" : "Nach unten bewegen",
"vcmi.radialWheel.moveBottom" : "Ganz nach unten bewegen",
"vcmi.spellBook.search" : "suchen...",
"vcmi.mainMenu.serverConnecting" : "Verbinde...",
"vcmi.mainMenu.serverAddressEnter" : "Addresse eingeben:",
"vcmi.mainMenu.serverConnectionFailed" : "Verbindung fehlgeschlagen",

View File

@ -69,6 +69,7 @@
"vcmi.lobby.noPreview" : "огляд недоступний",
"vcmi.lobby.noUnderground" : "немає підземелля",
"vcmi.client.errors.missingCampaigns" : "{Не вистачає файлів даних}\n\nФайли даних кампаній не знайдено! Можливо, ви використовуєте неповні або пошкоджені файли даних Heroes 3. Будь ласка, перевстановіть дані гри.",
"vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його",
"vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}",
"vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}",
@ -114,6 +115,8 @@
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Розширення інтерфейсу}\n\nУвімкніть різні розширення інтерфейсу для покращення якості життя. Наприклад, більша книга заклинань, рюкзак тощо. Вимкнути, щоб отримати більш класичний досвід.",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Велика книга заклять",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Велика книга заклять}\n\nВмикає більшу книгу заклять, яка вміщує більше заклять на сторінці. Якщо цей параметр увімкнено, анімація зміни сторінок книги заклять не буде відображатися.",
"vcmi.systemOptions.audioMuteFocus.hover" : "Тиша при втраті фокусу",
"vcmi.systemOptions.audioMuteFocus.help" : "{Тиша при втраті фокусу}\n\nВимкнути звук коли вікно не у фокусі. Виняток становлять ігрові сповіщення та звук нового ходу.",
"vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна",
"vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу",
@ -129,6 +132,8 @@
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Керування істотами у вікні статусу}\n\nДозволяє впорядковувати істот у вікні статусу замість циклічного перемикання між типовими компонентами",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Переміщення мапи лівою кнопкою",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Переміщення мапи лівою кнопкою}\n\nЯкщо увімкнено, переміщення миші з натиснутою лівою кнопкою буде перетягувати мапу пригод",
"vcmi.adventureOptions.smoothDragging.hover" : "Плавне перетягування мапи",
"vcmi.adventureOptions.smoothDragging.help" : "{Плавне перетягування мапи}\n\nЯкщо увімкнено, перетягування мапи має сучасний ефект завершення.",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",

View File

@ -10,8 +10,8 @@ android {
applicationId "is.xyz.vcmi"
minSdk 19
targetSdk 33
versionCode 1410
versionName "1.4.1"
versionCode 1420
versionName "1.4.2"
setProperty("archivesBaseName", "vcmi")
}

View File

@ -156,14 +156,6 @@ public class NativeMethods
}
}
@SuppressWarnings(Const.JNI_METHOD_SUPPRESS)
public static String getFormattedDateTime()
{
String currentDate = new SimpleDateFormat((new SimpleDateFormat()).toLocalizedPattern(), Locale.getDefault()).format(new Date());
return currentDate;
}
private static void internalProgressDisplay(final boolean show)
{
final Context ctx = SDL.getContext();

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="url_project_page" translatable="false">https://vcmi.eu</string>
<string name="url_project_repo" translatable="false">https://github.com/vcmi/vcmi</string>
<string name="url_launcher_repo" translatable="false">https://github.com/vcmi/vcmi-android</string>
<string name="url_launcher_privacy" translatable="false">https://github.com/vcmi/vcmi/blob/master/docs/players/Privacy_Policy.md</string>
<string name="app_name">VCMI</string>
<string name="server_name">Server VCMI</string>
<string name="launcher_title">Spouštěč VCMI</string>
<string name="launcher_btn_scale_title">Škálování herního rozlišení</string>
<string name="launcher_btn_scale_subtitle_unknown">Současné: neznámé</string>
<string name="launcher_btn_scale_subtitle">Současné: %1$d%%</string>
<string name="launcher_btn_start_title">Spustit VCMI</string>
<string name="launcher_btn_start_subtitle">Současná verze VCMI: %1$s</string>
<string name="launcher_btn_mods_title">Modifikace</string>
<string name="launcher_btn_mods_subtitle">Nainstalovat nové frakce, přeměty a bonusy</string>
<string name="launcher_btn_language_title">Jazyk</string>
<string name="launcher_btn_language_subtitle_unknown">Současný: neznámý</string>
<string name="launcher_btn_language_subtitle">Současný: %1$s</string>
<string name="launcher_btn_pointermode_title">Změnit režim ukazatele</string>
<string name="launcher_btn_pointermode_subtitle">Současný: %1$s</string>
<string name="launcher_btn_pointermulti_title">Násobitel rychlosti relativního ukazatele</string>
<string name="launcher_btn_pointermulti_subtitle">Současný: %1$s</string>
<string name="launcher_btn_sound_title">Hlasitost zvuků</string>
<string name="launcher_btn_music_title">Hlasitost hudby</string>
<string name="launcher_btn_adventure_ai">AI světa</string>
<string name="launcher_btn_adventure_ai_title">Změnit AI světa</string>
<string name="launcher_btn_import_title">Importovat data VCMI</string>
<string name="launcher_btn_import_description">Zkopírovat soubury VCMI do vestavěného úložiště. Můžete importovat starou složku dat vcmi z vydání 0.99 nebo soubory HOMM3</string>
<string name="launcher_btn_export_title">Exportovat data VCMI</string>
<string name="launcher_btn_export_description">Udělat kopii dat VCMI před odinstalací nebo pro synchronizaci s desktopovou verzí. Též můžete přímo přistoupit k interním datům.</string>
<string name="launcher_progress_copy">Kopírování %1$s</string>
<string name="launcher_version">Současná verze spouštěče: %1$s</string>
<string name="launcher_error_vcmi_data_root_failed">Nelze vytvořit datovou složku VCMI v %1$s.</string>
<string name="launcher_error_h3_data_missing">Nelze najít datovou složku v \'%1$s\'. Vložte do ní své datové soubory HoMM3 nebo použijte tlačítko níže. Možná budete muset také restartovat aplikaci.</string>
<string name="launcher_error_vcmi_data_internal_missing">Nelze najít nebo rozbalit data vcmi ze zdrojů aplikace. Zkuste přeinstalovat aplikaci.</string>
<string name="launcher_error_vcmi_data_internal_update">Nelze aktualizovat data vcmi ze zdrojů aplikace. Zkuste přeinstalovat aplikaci.</string>
<string name="launcher_error_permissions">Tato aplikace potřebuje oprávnění k zápisu pro použití obsahu na externím úložišti</string>
<string name="launcher_error_permission_broken">Nelze správně vyřešit oprávnění</string>
<string name="mods_item_author_template">od %1$s</string>
<string name="misc_try_again">Zkusit znovu</string>
<string name="launcher_section_init">Inicializae hry</string>
<string name="launcher_section_settings">Nastavení</string>
<string name="menu_mods_download_repo">Stáhnout data repozitáře</string>
<string name="misc_pointermode_normal">Normální</string>
<string name="misc_pointermode_relative">Relativní</string>
<string name="menu_launcher_about">O spouštěči</string>
<string name="mods_title">Nalezené modifikace</string>
<string name="mods_failed_mod_loading">Nelze načíst modifikaci ve složce \'%1$s\'</string>
<string name="mods_removal_title">Odebírání %1$s</string>
<string name="mods_removal_confirmation">Jste si jisti odebráním %1$s?</string>
<string name="about_title">O aplikaci</string>
<string name="about_version_app">Verze aplikace: %1$s</string>
<string name="about_version_launcher">Verze spouštěče: %1$s</string>
<string name="about_section_project">Projekt</string>
<string name="about_section_legal">Právní záležitosti</string>
<string name="about_links_main">Hlavní stránka: %1$s</string>
<string name="about_links_repo">Repozitář projektu: %1$s</string>
<string name="about_links_repo_launcher">Repozitář spouštěče: %1$s</string>
<string name="about_btn_authors">Autoři</string>
<string name="about_btn_privacy">Zásady ochrany osobních údajů: %1$s</string>
<string name="about_error_opening_url">Nebylo možné otevřít webovou stránku (nenalezena patřičná aplikace)</string>
<string name="dialog_authors_vcmi">Autoři VCMI</string>
<string name="dialog_authors_launcher">Autoři spouštěče</string>
<string name="launcher_error_config_saving_failed">Nelze uložit konfigurační soubor VCMI; důvod: %1$s</string>
</resources>

View File

@ -55,12 +55,14 @@
namespace po = boost::program_options;
namespace po_style = boost::program_options::command_line_style;
static std::atomic<bool> quitRequestedDuringOpeningPlayback = false;
static po::variables_map vm;
#ifndef VCMI_IOS
void processCommand(const std::string &message);
#endif
void playIntro();
[[noreturn]] static void quitApplication();
static void mainLoop();
static CBasicLogConfigurator *logConfig;
@ -257,10 +259,10 @@ int main(int argc, char * argv[])
};
testFile("DATA/HELP.TXT", "VCMI requires Heroes III: Shadow of Death or Heroes III: Complete data files to run!");
testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
testFile("DATA/PLAYERS.PAL", "Heroes III data files are missing or corruped! Please reinstall them.");
testFile("SPRITES/DEFAULT.DEF", "Heroes III data files are missing or corruped! Please reinstall them.");
testFile("DATA/TENTCOLR.TXT", "Heroes III: Restoration of Erathia (including HD Edition) data files are not supported!");
testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
testFile("DATA/PLAYERS.PAL", "Heroes III data files (Data/H3Bitmap.lod) are incomplete or corruped! Please reinstall them.");
testFile("SPRITES/DEFAULT.DEF", "Heroes III data files (Data/H3Sprite.lod) are incomplete or corruped! Please reinstall them.");
srand ( (unsigned int)time(nullptr) );
@ -313,7 +315,6 @@ int main(int argc, char * argv[])
GH.screenHandler().clearScreen();
}
#ifndef VCMI_NO_THREADED_LOAD
#ifdef VCMI_ANDROID // android loads the data quite slowly so we display native progressbar to prevent having only black screen for few seconds
{
@ -327,6 +328,9 @@ int main(int argc, char * argv[])
#endif // ANDROID
#endif // THREADED
if (quitRequestedDuringOpeningPlayback)
quitApplication();
if(!settings["session"]["headless"].Bool())
{
pomtime.getDiff();
@ -414,7 +418,7 @@ int main(int argc, char * argv[])
else
{
while(true)
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
}
return 0;
@ -451,7 +455,7 @@ static void mainLoop()
}
}
static void quitApplication()
[[noreturn]] static void quitApplication()
{
if(!settings["session"]["headless"].Bool())
{
@ -487,7 +491,8 @@ static void quitApplication()
vstd::clear_pointer(CSH);
vstd::clear_pointer(VLC);
vstd::clear_pointer(console);// should be removed after everything else since used by logging
// sometimes leads to a hang. TODO: investigate
//vstd::clear_pointer(console);// should be removed after everything else since used by logging
if(!settings["session"]["headless"].Bool())
GH.screenHandler().close();
@ -501,14 +506,29 @@ static void quitApplication()
std::cout << "Ending...\n";
// this method is always called from event/network threads, which keep interface mutex locked
// unlock it here to avoid assertion failure on GH destruction in exit()
GH.interfaceMutex.unlock();
exit(0);
// Perform quick exit without executing static destructors and let OS cleanup anything that we did not
// We generally don't care about them and this leads to numerous issues, e.g.
// destruction of locked mutexes (fails an assertion), even in third-party libraries (as well as native libs on Android)
// Android - std::quick_exit is available only starting from API level 21
// Mingw, macOS and iOS - std::quick_exit is unavailable (at least in current version of CI)
#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE)
::exit(0);
#else
std::quick_exit(0);
#endif
}
void handleQuit(bool ask)
{
// FIXME: avoids crash if player attempts to close game while opening is still playing
// use cursor handler as indicator that loading is not done yet
// proper solution would be to abort init thread (or wait for it to finish)
if (!CCS->curh)
{
quitRequestedDuringOpeningPlayback = true;
return;
}
if(ask)
{
CCS->curh->set(Cursor::Map::POINTER);

View File

@ -637,7 +637,7 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
SDL_Rect rect = CSDL_Ext::toSDL(pos);
SDL_RenderClear(mainRenderer);
SDL_RenderFillRect(mainRenderer, &rect);
SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
SDL_RenderPresent(mainRenderer);

View File

@ -15,6 +15,7 @@
#include "lobby/OptionsTab.h"
#include "lobby/RandomMapTab.h"
#include "lobby/TurnOptionsTab.h"
#include "lobby/SelectionTab.h"
#include "lobby/CBonusSelection.h"
@ -47,7 +48,7 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientCon
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{
if(pack.clientId != pack.c->connectionID)
if(pack.clientId != handler.c->connectionID)
{
result = false;
return;
@ -95,6 +96,9 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
case LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS:
lobby->toggleTab(lobby->tabRand);
break;
case LobbyGuiAction::OPEN_TURN_OPTIONS:
lobby->toggleTab(lobby->tabTurnOptions);
break;
}
}

View File

@ -239,6 +239,8 @@ void PlayerLocalState::swapWanderingHero(int pos1, int pos2)
{
assert(wanderingHeroes[pos1] && wanderingHeroes[pos2]);
std::swap(wanderingHeroes[pos1], wanderingHeroes[pos2]);
adventureInt->onHeroOrderChanged();
}
const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()

View File

@ -42,7 +42,7 @@ public:
{
//on which page we left spellbook
int spellbookLastPageBattle = 0;
int spellbokLastPageAdvmap = 0;
int spellbookLastPageAdvmap = 0;
int spellbookLastTabBattle = 4;
int spellbookLastTabAdvmap = 4;
@ -50,7 +50,7 @@ public:
void serialize(Handler & h, const int version)
{
h & spellbookLastPageBattle;
h & spellbokLastPageAdvmap;
h & spellbookLastPageAdvmap;
h & spellbookLastTabBattle;
h & spellbookLastTabAdvmap;
}

View File

@ -65,8 +65,8 @@ AdventureMapInterface::AdventureMapInterface():
shortcuts->setState(EAdventureState::MAKING_TURN);
widget->getMapView()->onViewMapActivated();
if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.isEnabled() || LOCPLINT->cb->getStartInfo()->turnTimerInfo.isBattleEnabled())
watches = std::make_shared<TurnTimerWidget>();
if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.turnTimer != 0)
watches = std::make_shared<TurnTimerWidget>(Point(24, 24));
addUsedEvents(KEYBOARD | TIME);
}
@ -331,6 +331,11 @@ void AdventureMapInterface::onTownOrderChanged()
widget->getTownList()->updateWidget();
}
void AdventureMapInterface::onHeroOrderChanged()
{
widget->getHeroList()->updateWidget();
}
void AdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
{
if (positions)

View File

@ -149,6 +149,9 @@ public:
/// Called when town order changes
void onTownOrderChanged();
/// Called when hero order changes
void onHeroOrderChanged();
/// Called when map audio should be paused, e.g. on combat or town screen access
void onAudioPaused();

View File

@ -151,6 +151,9 @@ void CInGameConsole::keyPressed (EShortcut key)
break;
case EShortcut::GAME_ACTIVATE_CONSOLE:
if(GH.isKeyboardAltDown())
return; //QoL for alt-tab operating system shortcut
if(!enteredText.empty())
endEnteringText(false);
else

View File

@ -218,8 +218,7 @@ CHeroList::CEmptyHeroItem::CEmptyHeroItem()
CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
: CListItem(parent),
hero(Hero),
parentList(parent)
hero(Hero)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
@ -285,19 +284,17 @@ void CHeroList::CHeroItem::gesture(bool on, const Point & initialPosition, const
const CGHeroInstance * heroLower = (heroPos > heroes.size() - 2) ? nullptr : heroes[heroPos + 1];
std::vector<RadialMenuConfig> menuElements = {
{ RadialMenuConfig::ITEM_ALT_NN, heroUpper != nullptr, "altUpTop", "vcmi.radialWheel.moveTop", [this, heroPos]()
{ RadialMenuConfig::ITEM_ALT_NN, heroUpper != nullptr, "altUpTop", "vcmi.radialWheel.moveTop", [heroPos]()
{
for (int i = heroPos; i > 0; i--)
LOCPLINT->localState->swapWanderingHero(i, i - 1);
parentList->updateWidget();
} },
{ RadialMenuConfig::ITEM_ALT_NW, heroUpper != nullptr, "altUp", "vcmi.radialWheel.moveUp", [this, heroPos](){LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1); parentList->updateWidget(); } },
{ RadialMenuConfig::ITEM_ALT_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [this, heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); parentList->updateWidget(); } },
{ RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, heroPos, heroes]()
{ RadialMenuConfig::ITEM_ALT_NW, heroUpper != nullptr, "altUp", "vcmi.radialWheel.moveUp", [heroPos](){LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1); } },
{ RadialMenuConfig::ITEM_ALT_SW, heroLower != nullptr, "altDown", "vcmi.radialWheel.moveDown", [heroPos](){ LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1); } },
{ RadialMenuConfig::ITEM_ALT_SS, heroLower != nullptr, "altDownBottom", "vcmi.radialWheel.moveBottom", [heroPos, heroes]()
{
for (int i = heroPos; i < heroes.size() - 1; i++)
LOCPLINT->localState->swapWanderingHero(i, i + 1);
parentList->updateWidget();
} },
};
@ -365,8 +362,7 @@ std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
}
CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
CListItem(parent),
parentList(parent)
CListItem(parent)
{
const std::vector<const CGTownInstance *> towns = LOCPLINT->localState->getOwnedTowns();
townIndex = std::distance(towns.begin(), std::find(towns.begin(), towns.end(), Town));
@ -430,15 +426,13 @@ void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const
{
for (int i = townIndex; i > 0; i--)
LOCPLINT->localState->swapOwnedTowns(i, i - 1);
parentList->updateWidget();
} },
{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [this, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); parentList->updateWidget(); } },
{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [this, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); parentList->updateWidget(); } },
{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [this, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); } },
{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [this, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); } },
{ RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [this, towns]()
{
for (int i = townIndex; i < towns.size() - 1; i++)
LOCPLINT->localState->swapOwnedTowns(i, i + 1);
parentList->updateWidget();
} },
};

View File

@ -117,7 +117,6 @@ class CHeroList : public CList
std::shared_ptr<CAnimImage> movement;
std::shared_ptr<CAnimImage> mana;
std::shared_ptr<CAnimImage> portrait;
CHeroList *parentList;
public:
const CGHeroInstance * const hero;
@ -152,7 +151,6 @@ class CTownList : public CList
class CTownItem : public CListItem
{
std::shared_ptr<CAnimImage> picture;
CTownList *parentList;
public:
int townIndex;

View File

@ -15,54 +15,86 @@
#include "../CPlayerInterface.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleStacksController.h"
#include "../render/EFont.h"
#include "../render/Graphics.h"
#include "../gui/CGuiHandler.h"
#include "../gui/TextAlignment.h"
#include "../render/Graphics.h"
#include "../widgets/Images.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/TextControls.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
#include "../../lib/CPlayerState.h"
#include "../../lib/filesystem/ResourcePath.h"
#include "../../lib/CStack.h"
#include "../../lib/StartInfo.h"
TurnTimerWidget::DrawRect::DrawRect(const Rect & r, const ColorRGBA & c):
CIntObject(), rect(r), color(c)
TurnTimerWidget::TurnTimerWidget(const Point & position)
: TurnTimerWidget(position, PlayerColor::NEUTRAL)
{}
TurnTimerWidget::TurnTimerWidget(const Point & position, PlayerColor player)
: CIntObject(TIME)
, lastSoundCheckSeconds(0)
, isBattleMode(player.isValidPlayer())
{
}
void TurnTimerWidget::DrawRect::showAll(Canvas & to)
{
to.drawColor(rect, color);
CIntObject::showAll(to);
}
TurnTimerWidget::TurnTimerWidget():
InterfaceObjectConfigurable(TIME),
turnTime(0), lastTurnTime(0), cachedTurnTime(0), lastPlayer(PlayerColor::CANNOT_DETERMINE)
{
REGISTER_BUILDER("drawRect", &TurnTimerWidget::buildDrawRect);
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos += position;
pos.w = 0;
pos.h = 0;
recActions &= ~DEACTIVATE;
const auto & timers = LOCPLINT->cb->getStartInfo()->turnTimerInfo;
const JsonNode config(JsonPath::builtin("config/widgets/turnTimer.json"));
backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DiBoxBck"), pos); // 1 px smaller on all sides
build(config);
if (isBattleMode)
backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BRIGHT_YELLOW);
else
backgroundBorder = std::make_shared<TransparentFilledRectangle>(pos, ColorRGBA(0, 0, 0, 128), Colors::BLACK);
std::transform(variables["notificationTime"].Vector().begin(),
variables["notificationTime"].Vector().end(),
std::inserter(notifications, notifications.begin()),
[](const JsonNode & node){ return node.Integer(); });
}
if (isBattleMode)
{
pos.w = 76;
std::shared_ptr<TurnTimerWidget::DrawRect> TurnTimerWidget::buildDrawRect(const JsonNode & config) const
{
logGlobal->debug("Building widget TurnTimerWidget::DrawRect");
auto rect = readRect(config["rect"]);
auto color = readColor(config["color"]);
return std::make_shared<TurnTimerWidget::DrawRect>(rect, color);
pos.h += 20;
playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
if (timers.battleTimer != 0)
{
pos.h += 20;
playerLabelsBattle[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
}
if (!timers.accumulatingUnitTimer && timers.unitTimer != 0)
{
pos.h += 20;
playerLabelsUnit[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
}
updateTextLabel(player, LOCPLINT->cb->getPlayerTurnTime(player));
}
else
{
if (!timers.accumulatingTurnTimer && timers.baseTimer != 0)
pos.w = 120;
else
pos.w = 60;
for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player)
{
if (LOCPLINT->cb->getStartInfo()->playerInfos.count(player) == 0)
continue;
if (!LOCPLINT->cb->getStartInfo()->playerInfos.at(player).isControlledByHuman())
continue;
pos.h += 20;
playerLabelsMain[player] = std::make_shared<CLabel>(pos.w / 2, pos.h - 10, FONT_BIG, ETextAlignment::CENTER, graphics->playerColors[player], "");
updateTextLabel(player, LOCPLINT->cb->getPlayerTurnTime(player));
}
}
backgroundTexture->pos = Rect::createAround(pos, -1);
backgroundBorder->pos = pos;
}
void TurnTimerWidget::show(Canvas & to)
@ -70,98 +102,95 @@ void TurnTimerWidget::show(Canvas & to)
showAll(to);
}
void TurnTimerWidget::setTime(PlayerColor player, int time)
void TurnTimerWidget::updateNotifications(PlayerColor player, int timeMs)
{
int newTime = time / 1000;
if(player == LOCPLINT->playerID
&& newTime != turnTime
&& notifications.count(newTime))
if(player != LOCPLINT->playerID)
return;
int newTimeSeconds = timeMs / 1000;
if (newTimeSeconds != lastSoundCheckSeconds && notificationThresholds.count(newTimeSeconds))
CCS->soundh->playSound(AudioPath::builtin("WE5"));
lastSoundCheckSeconds = newTimeSeconds;
}
static std::string msToString(int timeMs)
{
int timeSeconds = timeMs / 1000;
std::ostringstream oss;
oss << timeSeconds / 60 << ":" << std::setw(2) << std::setfill('0') << timeSeconds % 60;
return oss.str();
}
void TurnTimerWidget::updateTextLabel(PlayerColor player, const TurnTimerInfo & timer)
{
const auto & timerSettings = LOCPLINT->cb->getStartInfo()->turnTimerInfo;
auto mainLabel = playerLabelsMain[player];
if (isBattleMode)
{
CCS->soundh->playSound(AudioPath::fromJson(variables["notificationSound"]));
}
mainLabel->setText(msToString(timer.baseTimer + timer.turnTimer));
turnTime = newTime;
if(auto w = widget<CLabel>("timer"))
{
std::ostringstream oss;
oss << turnTime / 60 << ":" << std::setw(2) << std::setfill('0') << turnTime % 60;
w->setText(oss.str());
if(graphics && LOCPLINT && LOCPLINT->cb
&& variables["textColorFromPlayerColor"].Bool()
&& player.isValidPlayer())
if (timerSettings.battleTimer != 0)
{
w->setColor(graphics->playerColors[player]);
auto battleLabel = playerLabelsBattle[player];
if (timer.battleTimer != 0)
{
if (timerSettings.accumulatingUnitTimer)
battleLabel->setText("+" + msToString(timer.battleTimer + timer.unitTimer));
else
battleLabel->setText("+" + msToString(timer.battleTimer));
}
else
battleLabel->setText("");
}
if (!timerSettings.accumulatingUnitTimer && timerSettings.unitTimer != 0)
{
auto unitLabel = playerLabelsUnit[player];
if (timer.unitTimer != 0)
unitLabel->setText("+" + msToString(timer.unitTimer));
else
unitLabel->setText("");
}
}
else
{
if (!timerSettings.accumulatingTurnTimer && timerSettings.baseTimer != 0)
mainLabel->setText(msToString(timer.baseTimer) + "+" + msToString(timer.turnTimer));
else
mainLabel->setText(msToString(timer.baseTimer + timer.turnTimer));
}
}
void TurnTimerWidget::updateTimer(PlayerColor player, uint32_t msPassed)
{
const auto & time = LOCPLINT->cb->getPlayerTurnTime(player);
if(time.isActive)
cachedTurnTime -= msPassed;
if(cachedTurnTime < 0)
cachedTurnTime = 0; //do not go below zero
if(lastPlayer != player)
{
lastPlayer = player;
lastTurnTime = 0;
}
auto timeCheckAndUpdate = [&](int time)
{
if(time / 1000 != lastTurnTime / 1000)
{
//do not update timer on this tick
lastTurnTime = time;
cachedTurnTime = time;
}
else
setTime(player, cachedTurnTime);
};
auto * playerInfo = LOCPLINT->cb->getPlayer(player);
if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman()))
{
if(time.isBattle)
timeCheckAndUpdate(time.baseTimer + time.turnTimer + time.battleTimer + time.unitTimer);
else
timeCheckAndUpdate(time.baseTimer + time.turnTimer);
}
else
timeCheckAndUpdate(0);
const auto & gamestateTimer = LOCPLINT->cb->getPlayerTurnTime(player);
updateNotifications(player, gamestateTimer.valueMs());
updateTextLabel(player, gamestateTimer);
}
void TurnTimerWidget::tick(uint32_t msPassed)
{
if(!LOCPLINT || !LOCPLINT->cb)
return;
if(LOCPLINT->battleInt)
for(const auto & player : playerLabelsMain)
{
if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack())
updateTimer(stack->getOwner(), msPassed);
else
updateTimer(PlayerColor::NEUTRAL, msPassed);
}
else
{
if(LOCPLINT->makingTurn)
updateTimer(LOCPLINT->playerID, msPassed);
else
if (LOCPLINT->battleInt)
{
for(PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p)
{
if(LOCPLINT->cb->isPlayerMakingTurn(p))
{
updateTimer(p, msPassed);
break;
}
}
const auto & battle = LOCPLINT->battleInt->getBattle();
bool isDefender = battle->sideToPlayer(BattleSide::DEFENDER) == player.first;
bool isAttacker = battle->sideToPlayer(BattleSide::ATTACKER) == player.first;
bool isMakingUnitTurn = battle->battleActiveUnit() && battle->battleActiveUnit()->unitOwner() == player.first;
bool isEngagedInBattle = isDefender || isAttacker;
// Due to way our network message queue works during battle animation
// client actually does not receives updates from server as to which timer is active when game has battle animations playing
// so during battle skip updating timer unless game is waiting for player to select action
if (isEngagedInBattle && !isMakingUnitTurn)
continue;
}
updateTimer(player.first, msPassed);
}
}

View File

@ -11,50 +11,43 @@
#pragma once
#include "../gui/CIntObject.h"
#include "../gui/InterfaceObjectConfigurable.h"
#include "../render/Canvas.h"
#include "../render/Colors.h"
#include "../../lib/TurnTimerInfo.h"
class CAnimImage;
class CLabel;
class CFilledTexture;
class TransparentFilledRectangle;
VCMI_LIB_NAMESPACE_BEGIN
class PlayerColor;
VCMI_LIB_NAMESPACE_END
class TurnTimerWidget : public InterfaceObjectConfigurable
class TurnTimerWidget : public CIntObject
{
private:
int lastSoundCheckSeconds;
bool isBattleMode;
class DrawRect : public CIntObject
{
const Rect rect;
const ColorRGBA color;
const std::set<int> notificationThresholds = {1, 2, 3, 4, 5, 10, 20, 30};
public:
DrawRect(const Rect &, const ColorRGBA &);
void showAll(Canvas & to) override;
};
int turnTime;
int lastTurnTime;
int cachedTurnTime;
PlayerColor lastPlayer;
std::set<int> notifications;
std::shared_ptr<DrawRect> buildDrawRect(const JsonNode & config) const;
std::map<PlayerColor, std::shared_ptr<CLabel>> playerLabelsMain;
std::map<PlayerColor, std::shared_ptr<CLabel>> playerLabelsBattle;
std::map<PlayerColor, std::shared_ptr<CLabel>> playerLabelsUnit;
std::shared_ptr<CFilledTexture> backgroundTexture;
std::shared_ptr<TransparentFilledRectangle> backgroundBorder;
void updateTimer(PlayerColor player, uint32_t msPassed);
public:
void show(Canvas & to) override;
void tick(uint32_t msPassed) override;
void setTime(PlayerColor player, int time);
void updateNotifications(PlayerColor player, int timeMs);
void updateTextLabel(PlayerColor player, const TurnTimerInfo & timer);
TurnTimerWidget();
public:
/// Activates adventure map mode in which widget will display timer for all players
TurnTimerWidget(const Point & position);
/// Activates battle mode in which timer displays only timer of specific player
TurnTimerWidget(const Point & position, PlayerColor player);
};

View File

@ -107,7 +107,8 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
{
auto onIntroPlayed = [this]()
{
if(LOCPLINT->battleInt)
// Make sure that battle have not ended while intro was playing AND that a different one has not started
if(LOCPLINT->battleInt.get() == this)
onIntroSoundPlayed();
};

View File

@ -691,6 +691,8 @@ void BattleStacksController::endAction(const BattleAction & action)
void BattleStacksController::startAction(const BattleAction & action)
{
// if timer run out and we did not act in time - deactivate current stack
setActiveStack(nullptr);
removeExpiredColorFilters();
}

View File

@ -31,6 +31,7 @@
#include "../render/Canvas.h"
#include "../render/IRenderHandler.h"
#include "../adventureMap/CInGameConsole.h"
#include "../adventureMap/TurnTimerWidget.h"
#include "../../CCallback.h"
#include "../../lib/CGeneralTextHandler.h"
@ -39,6 +40,7 @@
#include "../../lib/CStack.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/filesystem/ResourcePath.h"
#include "../../lib/StartInfo.h"
#include "../windows/settings/SettingsMainWindow.h"
BattleWindow::BattleWindow(BattleInterface & owner):
@ -83,6 +85,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
createQueue();
createStickyHeroInfoWindows();
createTimerInfoWindows();
if ( owner.tacticsMode )
tacticPhaseStarted();
@ -128,8 +131,8 @@ void BattleWindow::createStickyHeroInfoWindows()
InfoAboutHero info;
info.initFromHero(owner.defendingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
Point position = (GH.screenDimensions().x >= 1000)
? Point(pos.x + pos.w + 15, pos.y)
: Point(pos.x + pos.w -79, pos.y + 135);
? Point(pos.x + pos.w + 15, pos.y + 60)
: Point(pos.x + pos.w -79, pos.y + 195);
defenderHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, &position);
}
if(owner.attackingHeroInstance)
@ -137,8 +140,8 @@ void BattleWindow::createStickyHeroInfoWindows()
InfoAboutHero info;
info.initFromHero(owner.attackingHeroInstance, InfoAboutHero::EInfoLevel::INBATTLE);
Point position = (GH.screenDimensions().x >= 1000)
? Point(pos.x - 93, pos.y)
: Point(pos.x + 1, pos.y + 135);
? Point(pos.x - 93, pos.y + 60)
: Point(pos.x + 1, pos.y + 195);
attackerHeroWindow = std::make_shared<HeroInfoBasicPanel>(info, &position);
}
@ -154,6 +157,33 @@ void BattleWindow::createStickyHeroInfoWindows()
}
}
void BattleWindow::createTimerInfoWindows()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || LOCPLINT->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
{
PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER);
PlayerColor defender = owner.getBattle()->sideToPlayer(BattleSide::DEFENDER);
if (attacker.isValidPlayer())
{
if (GH.screenDimensions().x >= 1000)
attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92, 1), attacker);
else
attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker);
}
if (defender.isValidPlayer())
{
if (GH.screenDimensions().x >= 1000)
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w + 16, 1), defender);
else
defenderTimerWidget = std::make_shared<TurnTimerWidget>(Point(pos.w - 78, 135), defender);
}
}
}
BattleWindow::~BattleWindow()
{
CPlayerInterface::battleInt = nullptr;
@ -557,6 +587,15 @@ void BattleWindow::bSpellf()
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
% heroName % CGI->artifacts()->getByIndex(artID)->getNameTranslated()));
}
else if(blockingBonus->source == BonusSource::OBJECT_TYPE)
{
if(blockingBonus->sid.as<MapObjectID>() == Obj::GARRISON || blockingBonus->sid.as<MapObjectID>() == Obj::GARRISON2)
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[684]);
}
}
else
{
logGlobal->warn("Unexpected problem with readiness to cast spell");
}
}

View File

@ -24,6 +24,7 @@ class BattleInterface;
class BattleConsole;
class BattleRenderer;
class StackQueue;
class TurnTimerWidget;
class HeroInfoBasicPanel;
/// GUI object that handles functionality of panel at the bottom of combat screen
@ -36,6 +37,9 @@ class BattleWindow : public InterfaceObjectConfigurable
std::shared_ptr<HeroInfoBasicPanel> attackerHeroWindow;
std::shared_ptr<HeroInfoBasicPanel> defenderHeroWindow;
std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
/// button press handling functions
void bOptionsf();
void bSurrenderf();
@ -65,6 +69,7 @@ class BattleWindow : public InterfaceObjectConfigurable
void toggleStickyHeroWindowsVisibility();
void createStickyHeroInfoWindows();
void createTimerInfoWindows();
std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;

View File

@ -273,17 +273,15 @@ void CBonusSelection::createBonusesIcons()
}
case CampaignBonusType::HERO:
desc.appendLocalString(EMetaText::GENERAL_TXT, 718);
desc.replaceTextID(TextIdentifier("core", "genrltxt", "capColors", bonDescs[i].info1).get());
if(bonDescs[i].info2 == 0xFFFF)
{
desc.replaceLocalString(EMetaText::GENERAL_TXT, 101);
desc.appendLocalString(EMetaText::GENERAL_TXT, 720); // Start with random hero
picNumber = -1;
picName = "CBONN1A3.BMP";
}
else
{
desc.appendLocalString(EMetaText::GENERAL_TXT, 715); // Start with %s
desc.replaceTextID(CGI->heroh->objects[bonDescs[i].info2]->getNameTextID());
}
break;

View File

@ -120,6 +120,8 @@ void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
CSH->sendGuiAction(LobbyGuiAction::OPEN_SCENARIO_LIST);
else if(tab == tabRand)
CSH->sendGuiAction(LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS);
else if(tab == tabTurnOptions)
CSH->sendGuiAction(LobbyGuiAction::OPEN_TURN_OPTIONS);
CSelectionBase::toggleTab(tab);
}

View File

@ -135,7 +135,7 @@ InfoCard::InfoCard()
Rect descriptionRect(26, 149, 320, 115);
mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
chat = std::make_shared<CChatBox>(Rect(26, 132, 340, 132));
chat = std::make_shared<CChatBox>(Rect(18, 126, 335, 143));
if(SEL->screenType == ESelectionScreen::campaignList)
{
@ -332,9 +332,12 @@ CChatBox::CChatBox(const Rect & rect)
setRedrawParent(true);
const int height = static_cast<int>(graphics->fonts[FONT_SMALL]->getLineHeight());
inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height), EFonts::FONT_SMALL, 0);
Rect textInputArea(1, rect.h - height, rect.w - 1, height);
Rect chatHistoryArea(3, 1, rect.w - 3, rect.h - height - 1);
inputBackground = std::make_shared<TransparentFilledRectangle>(textInputArea, ColorRGBA(0,0,0,192));
inputBox = std::make_shared<CTextInput>(textInputArea, EFonts::FONT_SMALL, 0);
inputBox->removeUsedEvents(KEYBOARD);
chatHistory = std::make_shared<CTextBox>("", Rect(0, 0, rect.w, rect.h - height), 1);
chatHistory = std::make_shared<CTextBox>("", chatHistoryArea, 1);
chatHistory->label->color = Colors::GREEN;
}

View File

@ -33,6 +33,7 @@ class CChatBox;
class CLabel;
class CFlagBox;
class CLabelGroup;
class TransparentFilledRectangle;
class ISelectionScreenInfo
{
@ -122,6 +123,7 @@ class CChatBox : public CIntObject
public:
std::shared_ptr<CTextBox> chatHistory;
std::shared_ptr<CTextInput> inputBox;
std::shared_ptr<TransparentFilledRectangle> inputBackground;
CChatBox(const Rect & rect);

View File

@ -503,7 +503,7 @@ void OptionsTab::SelectionWindow::recreate()
int count = 0;
for(auto & elem : allowedHeroes)
{
CHero * type = VLC->heroh->objects[elem];
const CHero * type = elem.toHeroType();
if(type->heroClass->faction == selectedFaction)
{
count++;

View File

@ -358,7 +358,11 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
}
for(auto r : VLC->roadTypeHandler->objects)
{
if(auto w = widget<CToggleButton>(r->getJsonKey()))
// Workaround for vcmi-extras bug
std::string jsonKey = r->getJsonKey();
std::string identifier = jsonKey.substr(jsonKey.find(':')+1);
if(auto w = widget<CToggleButton>(identifier))
{
w->setSelected(opts->isRoadEnabled(r->getId()));
}

View File

@ -29,8 +29,7 @@
#include "../../lib/CCreatureHandler.h"
#include "../../lib/constants/EntityIdentifiers.h"
#include "../../lib/TextOperations.h"
#include "vstd/DateUtils.h"
#include "../../lib/Languages.h"
auto HighScoreCalculation::calculate()
{
@ -264,7 +263,7 @@ int CHighScoreInputScreen::addEntry(std::string text) {
newNode["scenarioName"].String() = calc.calculate().cheater ? CGI->generaltexth->translate("core.genrltxt.260") : calc.parameters[0].scenarioName;
newNode["days"].Integer() = calc.calculate().sumDays;
newNode["points"].Integer() = calc.calculate().cheater ? 0 : calc.calculate().total;
newNode["datetime"].String() = vstd::getFormattedDateTime(std::time(nullptr));
newNode["datetime"].String() = TextOperations::getFormattedDateTimeLocal(std::time(nullptr));
newNode["posFlag"].Bool() = true;
baseNode.push_back(newNode);

View File

@ -382,12 +382,29 @@ void CMainMenu::openCampaignLobby(std::shared_ptr<CampaignState> campaign)
void CMainMenu::openCampaignScreen(std::string name)
{
if(vstd::contains(CMainMenuConfig::get().getCampaigns().Struct(), name))
auto const & config = CMainMenuConfig::get().getCampaigns();
if(!vstd::contains(config.Struct(), name))
{
GH.windows().createAndPushWindow<CCampaignScreen>(CMainMenuConfig::get().getCampaigns(), name);
logGlobal->error("Unknown campaign set: %s", name);
return;
}
logGlobal->error("Unknown campaign set: %s", name);
bool campaignsFound = true;
for (auto const & entry : config[name]["items"].Vector())
{
ResourcePath resourceID(entry["file"].String(), EResType::CAMPAIGN);
if (!CResourceHandler::get()->existsResource(resourceID))
campaignsFound = false;
}
if (!campaignsFound)
{
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.client.errors.missingCampaigns"), std::vector<std::shared_ptr<CComponent>>(), PlayerColor(1));
return;
}
GH.windows().createAndPushWindow<CCampaignScreen>(config, name);
}
void CMainMenu::startTutorial()

View File

@ -139,7 +139,7 @@ void CCommanderArtPlace::clickPressed(const Point & cursorPosition)
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {});
}
void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
void CCommanderArtPlace::showPopupWindow(const Point& cursorPosition)
{
if(ourArt && text.size())
CArtPlace::showPopupWindow(cursorPosition);
@ -180,16 +180,25 @@ bool CArtPlace::isSelected() const
return selection->visible;
}
void CHeroArtPlace::clickPressed(const Point & cursorPosition)
void CArtPlace::clickPressed(const Point & cursorPosition)
{
if(leftClickCallback)
leftClickCallback(*this);
if(clickPressedCallback)
clickPressedCallback(*this, cursorPosition);
}
void CHeroArtPlace::showPopupWindow(const Point & cursorPosition)
void CArtPlace::showPopupWindow(const Point & cursorPosition)
{
if(showPopupCallback)
showPopupCallback(*this);
showPopupCallback(*this, cursorPosition);
}
void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
{
if(!on)
return;
if(gestureCallback)
gestureCallback(*this, initialPosition);
}
void CArtPlace::showAll(Canvas & to)
@ -216,6 +225,21 @@ void CArtPlace::setArtifact(const CArtifactInstance * art)
}
}
void CArtPlace::setClickPressedCallback(ClickFunctor callback)
{
clickPressedCallback = callback;
}
void CArtPlace::setShowPopupCallback(ClickFunctor callback)
{
showPopupCallback = callback;
}
void CArtPlace::setGestureCallback(ClickFunctor callback)
{
gestureCallback = callback;
}
void CHeroArtPlace::addCombinedArtInfo(std::map<const CArtifact*, int> & arts)
{
for(const auto & combinedArt : arts)

View File

@ -32,14 +32,24 @@ public:
class CArtPlace : public LRClickableAreaWTextComp
{
public:
using ClickFunctor = std::function<void(CArtPlace&, const Point&)>;
ArtifactPosition slot;
CArtPlace(Point position, const CArtifactInstance * art = nullptr);
const CArtifactInstance* getArt();
const CArtifactInstance * getArt();
void lockSlot(bool on);
bool isLocked() const;
void selectSlot(bool on);
bool isSelected() const;
void showAll(Canvas & to) override;
void setArtifact(const CArtifactInstance * art);
void setClickPressedCallback(ClickFunctor callback);
void setShowPopupCallback(ClickFunctor callback);
void setGestureCallback(ClickFunctor callback);
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
protected:
std::shared_ptr<CAnimImage> image;
@ -47,6 +57,9 @@ protected:
int imageIndex;
std::shared_ptr<CAnimImage> selection;
bool locked;
ClickFunctor clickPressedCallback;
ClickFunctor showPopupCallback;
ClickFunctor gestureCallback;
void setInternals(const CArtifactInstance * artInst);
};
@ -68,15 +81,7 @@ public:
class CHeroArtPlace: public CArtPlace
{
public:
using ClickFunctor = std::function<void(CHeroArtPlace&)>;
ArtifactPosition slot;
ClickFunctor leftClickCallback;
ClickFunctor showPopupCallback;
CHeroArtPlace(Point position, const CArtifactInstance * art = nullptr);
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
void addCombinedArtInfo(std::map<const CArtifact*, int> & arts);
};

View File

@ -23,8 +23,8 @@ CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
: visibleArtSet(ArtBearer::ArtBearer::HERO)
{
init(
std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1),
std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1),
std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
position,
std::bind(&CArtifactsOfHeroAltar::scrollBackpack, this, _1));
pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
@ -69,7 +69,7 @@ void CArtifactsOfHeroAltar::scrollBackpack(int offset)
redraw();
}
void CArtifactsOfHeroAltar::pickUpArtifact(CHeroArtPlace & artPlace)
void CArtifactsOfHeroAltar::pickUpArtifact(CArtPlace & artPlace)
{
if(const auto art = artPlace.getArt())
{

View File

@ -26,7 +26,7 @@ public:
void updateWornSlots() override;
void updateBackpackSlots() override;
void scrollBackpack(int offset) override;
void pickUpArtifact(CHeroArtPlace & artPlace);
void pickUpArtifact(CArtPlace & artPlace);
void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
void pickedArtMoveToAltar(const ArtifactPosition & slot);
void deleteFromVisible(const CArtifactInstance * artInst);

View File

@ -17,60 +17,28 @@
#include "ObjectLists.h"
#include "../CPlayerInterface.h"
#include "../../lib/ArtifactUtils.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/networkPacks/ArtifactLocation.h"
#include "../../CCallback.h"
CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax)
: slotsColumnsMax(slotsColumnsMax)
, slotsRowsMax(slotsRowsMax)
{
OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
pos += position;
setRedrawParent(true);
}
CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack()
: CArtifactsOfHeroBackpack(8, 8)
{
const auto backpackCap = VLC->settings()->getInteger(EGameSettings::HEROES_BACKPACK_CAP);
auto visibleCapacityMax = HERO_BACKPACK_WINDOW_SLOT_ROWS * HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
auto visibleCapacityMax = slotsRowsMax * slotsColumnsMax;
if(backpackCap >= 0)
visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax;
backpack.resize(visibleCapacityMax);
size_t artPlaceIdx = 0;
for(auto & artPlace : backpack)
{
const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % HERO_BACKPACK_WINDOW_SLOT_COLUMNS),
slotSizeWithMargin * (artPlaceIdx / HERO_BACKPACK_WINDOW_SLOT_COLUMNS));
backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
artPlace = std::make_shared<CHeroArtPlace>(pos);
artPlace->setArtifact(nullptr);
artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
artPlace->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
artPlaceIdx++;
}
if(backpackCap < 0 || visibleCapacityMax < backpackCap)
{
auto onCreate = [](size_t index) -> std::shared_ptr<CIntObject>
{
return std::make_shared<CIntObject>();
};
CListBoxWithCallback::MovedPosCallback posMoved = [this](size_t pos) -> void
{
scrollBackpack(static_cast<int>(pos) * HERO_BACKPACK_WINDOW_SLOT_COLUMNS - backpackPos);
};
backpackListBox = std::make_shared<CListBoxWithCallback>(
posMoved, onCreate, Point(0, 0), Point(0, 0), HERO_BACKPACK_WINDOW_SLOT_ROWS, 0, 0, 1,
Rect(HERO_BACKPACK_WINDOW_SLOT_COLUMNS * slotSizeWithMargin + sliderPosOffsetX, 0, HERO_BACKPACK_WINDOW_SLOT_ROWS * slotSizeWithMargin - 2, 0));
}
pos.w = visibleCapacityMax > HERO_BACKPACK_WINDOW_SLOT_COLUMNS ? HERO_BACKPACK_WINDOW_SLOT_COLUMNS : visibleCapacityMax;
pos.w *= slotSizeWithMargin;
if(backpackListBox)
pos.w += sliderPosOffsetX + 16; // 16 is slider width. TODO: get it from CListBox directly;
pos.h = (visibleCapacityMax / HERO_BACKPACK_WINDOW_SLOT_COLUMNS);
if(visibleCapacityMax % HERO_BACKPACK_WINDOW_SLOT_COLUMNS != 0)
pos.h += 1;
pos.h *= slotSizeWithMargin;
initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap);
}
void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc)
@ -78,7 +46,7 @@ void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, co
LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
}
void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
void CArtifactsOfHeroBackpack::pickUpArtifact(CArtPlace & artPlace)
{
LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
@ -107,5 +75,145 @@ void CArtifactsOfHeroBackpack::updateBackpackSlots()
size_t CArtifactsOfHeroBackpack::getActiveSlotRowsNum()
{
return (curHero->artifactsInBackpack.size() + HERO_BACKPACK_WINDOW_SLOT_COLUMNS - 1) / HERO_BACKPACK_WINDOW_SLOT_COLUMNS;
return (curHero->artifactsInBackpack.size() + slotsColumnsMax - 1) / slotsColumnsMax;
}
size_t CArtifactsOfHeroBackpack::getSlotsNum()
{
return backpack.size();
}
void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider)
{
OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
backpack.resize(slots);
size_t artPlaceIdx = 0;
for(auto & artPlace : backpack)
{
const auto pos = Point(slotSizeWithMargin * (artPlaceIdx % slotsColumnsMax),
slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax));
backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
artPlace = std::make_shared<CHeroArtPlace>(pos);
artPlace->setArtifact(nullptr);
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
artPlaceIdx++;
}
if(slider)
{
auto onCreate = [](size_t index) -> std::shared_ptr<CIntObject>
{
return std::make_shared<CIntObject>();
};
CListBoxWithCallback::MovedPosCallback posMoved = [this](size_t pos) -> void
{
scrollBackpack(static_cast<int>(pos) * slotsColumnsMax - backpackPos);
};
backpackListBox = std::make_shared<CListBoxWithCallback>(
posMoved, onCreate, Point(0, 0), Point(0, 0), slotsRowsMax, 0, 0, 1,
Rect(slotsColumnsMax * slotSizeWithMargin + sliderPosOffsetX, 0, slotsRowsMax * slotSizeWithMargin - 2, 0));
}
pos.w = slots > slotsColumnsMax ? slotsColumnsMax : slots;
pos.w *= slotSizeWithMargin;
if(slider)
pos.w += sliderPosOffsetX + 16; // 16 is slider width. TODO: get it from CListBox directly;
pos.h = calcRows(slots) * slotSizeWithMargin;
}
size_t CArtifactsOfHeroBackpack::calcRows(size_t slots)
{
size_t rows = 0;
if(slotsColumnsMax != 0)
{
rows = slots / slotsColumnsMax;
if(slots % slotsColumnsMax != 0)
rows += 1;
}
return rows;
}
CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot)
: CArtifactsOfHeroBackpack(0, 0)
{
assert(filterBySlot != ArtifactPosition::FIRST_AVAILABLE);
if(!ArtifactUtils::isSlotEquipment(filterBySlot))
return;
this->filterBySlot = filterBySlot;
}
void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
{
if(curHero == hero)
return;
curHero = hero;
if(curHero)
{
ArtifactID artInSlotId = ArtifactID::NONE;
SpellID scrollInSlotSpellId = SpellID::NONE;
if(auto artInSlot = curHero->getArt(filterBySlot))
{
artInSlotId = artInSlot->getTypeId();
scrollInSlotSpellId = artInSlot->getScrollSpellID();
}
std::map<const ArtifactID, const CArtifactInstance*> filteredArts;
for(auto & slotInfo : curHero->artifactsInBackpack)
if(slotInfo.artifact->getTypeId() != artInSlotId && !slotInfo.artifact->isScroll() &&
slotInfo.artifact->artType->canBePutAt(curHero, filterBySlot, true))
{
filteredArts.insert(std::pair(slotInfo.artifact->getTypeId(), slotInfo.artifact));
}
std::map<const SpellID, const CArtifactInstance*> filteredScrolls;
if(filterBySlot == ArtifactPosition::MISC1 || filterBySlot == ArtifactPosition::MISC2 || filterBySlot == ArtifactPosition::MISC3 ||
filterBySlot == ArtifactPosition::MISC4 || filterBySlot == ArtifactPosition::MISC5)
{
for(auto & slotInfo : curHero->artifactsInBackpack)
{
if(slotInfo.artifact->isScroll() && slotInfo.artifact->getScrollSpellID() != scrollInSlotSpellId)
filteredScrolls.insert(std::pair(slotInfo.artifact->getScrollSpellID(), slotInfo.artifact));
}
}
backpack.clear();
auto requiredSlots = filteredArts.size() + filteredScrolls.size();
slotsColumnsMax = ceilf(sqrtf(requiredSlots));
slotsRowsMax = calcRows(requiredSlots);
initAOHbackpack(requiredSlots, false);
auto artPlace = backpack.begin();
for(auto & art : filteredArts)
setSlotData(*artPlace++, curHero->getSlotByInstance(art.second), *curHero);
for(auto & art : filteredScrolls)
setSlotData(*artPlace++, curHero->getSlotByInstance(art.second), *curHero);
}
}
ArtifactPosition CArtifactsOfHeroQuickBackpack::getFilterSlot()
{
return filterBySlot;
}
void CArtifactsOfHeroQuickBackpack::selectSlotAt(const Point & position)
{
for(auto & artPlace : backpack)
artPlace->selectSlot(artPlace->pos.isInside(position));
}
void CArtifactsOfHeroQuickBackpack::swapSelected()
{
ArtifactLocation backpackLoc(curHero->id, ArtifactPosition::PRE_FIRST);
for(auto & artPlace : backpack)
if(artPlace->isSelected())
{
backpackLoc.slot = artPlace->slot;
break;
}
if(backpackLoc.slot != ArtifactPosition::PRE_FIRST && filterBySlot != ArtifactPosition::PRE_FIRST && curHero)
swapArtifacts(backpackLoc, ArtifactLocation(curHero->id, filterBySlot));
}

View File

@ -22,18 +22,36 @@ class CListBoxWithCallback;
class CArtifactsOfHeroBackpack : public CArtifactsOfHeroBase
{
public:
CArtifactsOfHeroBackpack(const Point & position);
CArtifactsOfHeroBackpack(size_t slotsColumnsMax, size_t slotsRowsMax);
CArtifactsOfHeroBackpack();
void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
void pickUpArtifact(CHeroArtPlace & artPlace);
void pickUpArtifact(CArtPlace & artPlace);
void scrollBackpack(int offset) override;
void updateBackpackSlots() override;
size_t getActiveSlotRowsNum();
size_t getSlotsNum();
private:
protected:
std::shared_ptr<CListBoxWithCallback> backpackListBox;
std::vector<std::shared_ptr<CPicture>> backpackSlotsBackgrounds;
const size_t HERO_BACKPACK_WINDOW_SLOT_COLUMNS = 8;
const size_t HERO_BACKPACK_WINDOW_SLOT_ROWS = 8;
size_t slotsColumnsMax;
size_t slotsRowsMax;
const int slotSizeWithMargin = 46;
const int sliderPosOffsetX = 10;
const int sliderPosOffsetX = 5;
void initAOHbackpack(size_t slots, bool slider);
size_t calcRows(size_t slots);
};
class CArtifactsOfHeroQuickBackpack : public CArtifactsOfHeroBackpack
{
public:
CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot);
void setHero(const CGHeroInstance * hero);
ArtifactPosition getFilterSlot();
void selectSlotAt(const Point & position);
void swapSelected();
private:
ArtifactPosition filterBySlot;
};

View File

@ -56,8 +56,8 @@ void CArtifactsOfHeroBase::setPutBackPickedArtifactCallback(PutBackPickedArtCall
}
void CArtifactsOfHeroBase::init(
CHeroArtPlace::ClickFunctor lClickCallback,
CHeroArtPlace::ClickFunctor showPopupCallback,
CArtPlace::ClickFunctor lClickCallback,
CArtPlace::ClickFunctor showPopupCallback,
const Point & position,
BpackScrollFunctor scrollCallback)
{
@ -78,14 +78,14 @@ void CArtifactsOfHeroBase::init(
{
artPlace.second->slot = artPlace.first;
artPlace.second->setArtifact(nullptr);
artPlace.second->leftClickCallback = lClickCallback;
artPlace.second->showPopupCallback = showPopupCallback;
artPlace.second->setClickPressedCallback(lClickCallback);
artPlace.second->setShowPopupCallback(showPopupCallback);
}
for(auto artPlace : backpack)
{
artPlace->setArtifact(nullptr);
artPlace->leftClickCallback = lClickCallback;
artPlace->showPopupCallback = showPopupCallback;
artPlace->setClickPressedCallback(lClickCallback);
artPlace->setShowPopupCallback(showPopupCallback);
}
leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(-1);}, EShortcut::MOVE_LEFT);
rightBackpackRoll = std::make_shared<CButton>(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [scrollCallback]() {scrollCallback(+1);}, EShortcut::MOVE_RIGHT);
@ -95,16 +95,22 @@ void CArtifactsOfHeroBase::init(
setRedrawParent(true);
}
void CArtifactsOfHeroBase::leftClickArtPlace(CHeroArtPlace & artPlace)
void CArtifactsOfHeroBase::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
{
if(leftClickCallback)
leftClickCallback(*this, artPlace);
if(clickPressedCallback)
clickPressedCallback(*this, artPlace, cursorPosition);
}
void CArtifactsOfHeroBase::rightClickArtPlace(CHeroArtPlace & artPlace)
void CArtifactsOfHeroBase::showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
{
if(showPopupCallback)
showPopupCallback(*this, artPlace);
showPopupCallback(*this, artPlace, cursorPosition);
}
void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
{
if(gestureCallback)
gestureCallback(*this, artPlace, cursorPosition);
}
void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
@ -241,6 +247,15 @@ const CArtifactInstance * CArtifactsOfHeroBase::getPickedArtifact()
return curHero->getArt(ArtifactPosition::TRANSITION_POS);
}
void CArtifactsOfHeroBase::addGestureCallback(CArtPlace::ClickFunctor callback)
{
for(auto & artPlace : artWorn)
{
artPlace.second->setGestureCallback(callback);
artPlace.second->addUsedEvents(GESTURE);
}
}
void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosition & slot, const CArtifactSet & artSet)
{
// Spurious call from artifactMoved in attempt to update hidden backpack slot

View File

@ -21,17 +21,19 @@ protected:
public:
using ArtPlaceMap = std::map<ArtifactPosition, ArtPlacePtr>;
using ClickFunctor = std::function<void(CArtifactsOfHeroBase&, CHeroArtPlace&)>;
using ClickFunctor = std::function<void(CArtifactsOfHeroBase&, CArtPlace&, const Point&)>;
using PutBackPickedArtCallback = std::function<void()>;
ClickFunctor leftClickCallback;
ClickFunctor clickPressedCallback;
ClickFunctor showPopupCallback;
ClickFunctor gestureCallback;
CArtifactsOfHeroBase();
virtual void putBackPickedArtifact();
virtual void setPutBackPickedArtifactCallback(PutBackPickedArtCallback callback);
virtual void leftClickArtPlace(CHeroArtPlace & artPlace);
virtual void rightClickArtPlace(CHeroArtPlace & artPlace);
virtual void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
virtual void showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
virtual void setHero(const CGHeroInstance * hero);
virtual const CGHeroInstance * getHero() const;
virtual void scrollBackpack(int offset);
@ -42,6 +44,7 @@ public:
virtual void updateBackpackSlots();
virtual void updateSlot(const ArtifactPosition & slot);
virtual const CArtifactInstance * getPickedArtifact();
void addGestureCallback(CArtPlace::ClickFunctor callback);
protected:
const CGHeroInstance * curHero;

View File

@ -30,14 +30,15 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto
{
artPlace.second->slot = artPlace.first;
artPlace.second->setArtifact(nullptr);
artPlace.second->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
artPlace.second->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
}
addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2));
for(auto artPlace : backpack)
{
artPlace->setArtifact(nullptr);
artPlace->leftClickCallback = std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1);
artPlace->showPopupCallback = std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1);
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
}
leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1));
rightBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, +1));
@ -55,7 +56,7 @@ void CArtifactsOfHeroKingdom::swapArtifacts(const ArtifactLocation & srcLoc, con
LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
}
void CArtifactsOfHeroKingdom::pickUpArtifact(CHeroArtPlace & artPlace)
void CArtifactsOfHeroKingdom::pickUpArtifact(CArtPlace & artPlace)
{
LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));

View File

@ -24,5 +24,5 @@ public:
std::shared_ptr<CButton> leftScroll, std::shared_ptr<CButton> rightScroll);
~CArtifactsOfHeroKingdom();
void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
void pickUpArtifact(CHeroArtPlace & artPlace);
void pickUpArtifact(CArtPlace & artPlace);
};

View File

@ -19,10 +19,11 @@
CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
{
init(
std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1),
std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1),
std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
position,
std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
addGestureCallback(std::bind(&CArtifactsOfHeroBase::gestureArtPlace, this, _1, _2));
}
CArtifactsOfHeroMain::~CArtifactsOfHeroMain()
@ -35,7 +36,7 @@ void CArtifactsOfHeroMain::swapArtifacts(const ArtifactLocation & srcLoc, const
LOCPLINT->cb->swapArtifacts(srcLoc, dstLoc);
}
void CArtifactsOfHeroMain::pickUpArtifact(CHeroArtPlace & artPlace)
void CArtifactsOfHeroMain::pickUpArtifact(CArtPlace & artPlace)
{
LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));

View File

@ -23,5 +23,5 @@ public:
CArtifactsOfHeroMain(const Point & position);
~CArtifactsOfHeroMain();
void swapArtifacts(const ArtifactLocation & srcLoc, const ArtifactLocation & dstLoc);
void pickUpArtifact(CHeroArtPlace & artPlace);
void pickUpArtifact(CArtPlace & artPlace);
};

View File

@ -15,8 +15,8 @@
CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position)
{
init(
std::bind(&CArtifactsOfHeroBase::leftClickArtPlace, this, _1),
std::bind(&CArtifactsOfHeroBase::rightClickArtPlace, this, _1),
std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2),
std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2),
position,
std::bind(&CArtifactsOfHeroMarket::scrollBackpack, this, _1));
};

View File

@ -14,7 +14,7 @@
class CArtifactsOfHeroMarket : public CArtifactsOfHeroBase
{
public:
std::function<void(CHeroArtPlace*)> selectArtCallback;
std::function<void(CArtPlace*)> selectArtCallback;
CArtifactsOfHeroMarket(const Point & position);
void scrollBackpack(int offset) override;

View File

@ -23,6 +23,7 @@
#include "../windows/CHeroWindow.h"
#include "../windows/CSpellWindow.h"
#include "../windows/GUIClasses.h"
#include "../windows/CHeroBackpackWindow.h"
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"
@ -39,18 +40,19 @@ void CWindowWithArtifacts::addSet(CArtifactsOfHeroPtr artSet)
void CWindowWithArtifacts::addSetAndCallbacks(CArtifactsOfHeroPtr artSet)
{
CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackHandler = []() -> void
CArtifactsOfHeroBase::PutBackPickedArtCallback artPutBackFunctor = []() -> void
{
CCS->curh->dragAndDropCursor(nullptr);
};
addSet(artSet);
std::visit([this, artPutBackHandler](auto artSetWeak)
std::visit([this, artPutBackFunctor](auto artSetWeak)
{
auto artSet = artSetWeak.lock();
artSet->leftClickCallback = std::bind(&CWindowWithArtifacts::leftClickArtPlaceHero, this, _1, _2);
artSet->showPopupCallback = std::bind(&CWindowWithArtifacts::rightClickArtPlaceHero, this, _1, _2);
artSet->setPutBackPickedArtifactCallback(artPutBackHandler);
artSet->clickPressedCallback = std::bind(&CWindowWithArtifacts::clickPressedArtPlaceHero, this, _1, _2, _3);
artSet->showPopupCallback = std::bind(&CWindowWithArtifacts::showPopupArtPlaceHero, this, _1, _2, _3);
artSet->gestureCallback = std::bind(&CWindowWithArtifacts::gestureArtPlaceHero, this, _1, _2, _3);
artSet->setPutBackPickedArtifactCallback(artPutBackFunctor);
}, artSet);
}
@ -77,7 +79,7 @@ const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact()
return nullptr;
}
void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
void CWindowWithArtifacts::clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
{
const auto artSetWeak = findAOHbyRef(artsInst);
assert(artSetWeak.has_value());
@ -85,7 +87,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
if(artPlace.isLocked())
return;
const auto checkSpecialArts = [](const CGHeroInstance * hero, CHeroArtPlace & artPlace) -> bool
const auto checkSpecialArts = [](const CGHeroInstance * hero, CArtPlace & artPlace) -> bool
{
if(artPlace.getArt()->getTypeId() == ArtifactID::SPELLBOOK)
{
@ -206,10 +208,18 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
}
}
}
else if constexpr(std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
{
const auto hero = artSetPtr->getHero();
artSetPtr->swapArtifacts(ArtifactLocation(hero->id, artPlace.slot),
ArtifactLocation(hero->id, artSetPtr->getFilterSlot()));
if(closeCallback)
closeCallback();
}
}, artSetWeak.value());
}
void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace)
void CWindowWithArtifacts::showPopupArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
{
const auto artSetWeak = findAOHbyRef(artsInst);
assert(artSetWeak.has_value());
@ -218,7 +228,7 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
return;
std::visit(
[&artPlace](auto artSetWeak) -> void
[&artPlace, &cursorPosition](auto artSetWeak) -> void
{
const auto artSetPtr = artSetWeak.lock();
@ -239,16 +249,40 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns
return;
}
if(artPlace.text.size())
artPlace.LRClickableAreaWTextComp::showPopupWindow(GH.getCursorPosition());
artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
}
}
// Altar window, Market window right click handler
else if constexpr(
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroAltar>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>>)
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMarket>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroQuickBackpack>>)
{
if(artPlace.getArt() && artPlace.text.size())
artPlace.LRClickableAreaWTextComp::showPopupWindow(GH.getCursorPosition());
artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
}
}, artSetWeak.value());
}
void CWindowWithArtifacts::gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition)
{
const auto artSetWeak = findAOHbyRef(artsInst);
assert(artSetWeak.has_value());
if(artPlace.isLocked())
return;
std::visit(
[&artPlace, cursorPosition](auto artSetWeak) -> void
{
const auto artSetPtr = artSetWeak.lock();
if constexpr(
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroMain>> ||
std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
{
GH.windows().createAndPushWindow<CHeroQuickBackpackWindow>(artSetPtr->getHero(), artPlace.slot);
auto backpackWindow = GH.windows().topWindow<CHeroQuickBackpackWindow>();
backpackWindow->moveTo(cursorPosition - Point(1, 1));
backpackWindow->fitToScreen(15);
}
}, artSetWeak.value());
}

View File

@ -24,7 +24,8 @@ public:
std::weak_ptr<CArtifactsOfHeroAltar>,
std::weak_ptr<CArtifactsOfHeroKingdom>,
std::weak_ptr<CArtifactsOfHeroMain>,
std::weak_ptr<CArtifactsOfHeroBackpack>>;
std::weak_ptr<CArtifactsOfHeroBackpack>,
std::weak_ptr<CArtifactsOfHeroQuickBackpack>>;
using CloseCallback = std::function<void()>;
void addSet(CArtifactsOfHeroPtr artSet);
@ -32,8 +33,9 @@ public:
void addCloseCallback(CloseCallback callback);
const CGHeroInstance * getHeroPickedArtifact();
const CArtifactInstance * getPickedArtifact();
void leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
void rightClickArtPlaceHero(CArtifactsOfHeroBase & artsInst, CHeroArtPlace & artPlace);
void clickPressedArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
void showPopupArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
void gestureArtPlaceHero(CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition);
void artifactRemoved(const ArtifactLocation & artLoc) override;
void artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) override;

View File

@ -121,9 +121,10 @@ void LRClickableAreaWTextComp::showPopupWindow(const Point & cursorPosition)
}
CHeroArea::CHeroArea(int x, int y, const CGHeroInstance * hero)
: CIntObject(LCLICK | HOVER),
: CIntObject(LCLICK | SHOW_POPUP | HOVER),
hero(hero),
clickFunctor(nullptr)
clickFunctor(nullptr),
clickRFunctor(nullptr)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@ -147,12 +148,23 @@ void CHeroArea::addClickCallback(ClickFunctor callback)
clickFunctor = callback;
}
void CHeroArea::addRClickCallback(ClickFunctor callback)
{
clickRFunctor = callback;
}
void CHeroArea::clickPressed(const Point & cursorPosition)
{
if(clickFunctor)
clickFunctor();
}
void CHeroArea::showPopupWindow(const Point & cursorPosition)
{
if(clickRFunctor)
clickRFunctor();
}
void CHeroArea::hover(bool on)
{
if (on && hero)

View File

@ -190,12 +190,15 @@ public:
CHeroArea(int x, int y, const CGHeroInstance * hero);
void addClickCallback(ClickFunctor callback);
void addRClickCallback(ClickFunctor callback);
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
void hover(bool on) override;
private:
const CGHeroInstance * hero;
std::shared_ptr<CAnimImage> portrait;
ClickFunctor clickFunctor;
ClickFunctor clickRFunctor;
ClickFunctor showPopupHandler;
};

View File

@ -375,7 +375,7 @@ void CTextBox::setText(const std::string & text)
else if(slider)
{
// decrease width again if slider still used
label->pos.w = pos.w - 32;
label->pos.w = pos.w - 16;
assert(label->pos.w > 0);
label->setText(text);
slider->setAmount(label->textSize.y);
@ -383,12 +383,12 @@ void CTextBox::setText(const std::string & text)
else if(label->textSize.y > label->pos.h)
{
// create slider and update widget
label->pos.w = pos.w - 32;
label->pos.w = pos.w - 16;
assert(label->pos.w > 0);
label->setText(text);
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255 - DISPOSE);
slider = std::make_shared<CSlider>(Point(pos.w - 32, 0), pos.h, std::bind(&CTextBox::sliderMoved, this, _1),
slider = std::make_shared<CSlider>(Point(pos.w - 16, 0), pos.h, std::bind(&CTextBox::sliderMoved, this, _1),
label->pos.h, label->textSize.y, 0, Orientation::VERTICAL, CSlider::EStyle(sliderStyle));
slider->setScrollStep((int)graphics->fonts[label->font]->getLineHeight());
slider->setPanningStep(1);

View File

@ -22,25 +22,19 @@
CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero)
: CWindowObject((EOptions)0)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 410, 425));
arts = std::make_shared<CArtifactsOfHeroBackpack>(Point(windowMargin, windowMargin));
arts->setHero(hero);
stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 0, 0));
arts = std::make_shared<CArtifactsOfHeroBackpack>();
arts->moveBy(Point(windowMargin, windowMargin));
addSetAndCallbacks(arts);
arts->setHero(hero);
addCloseCallback(std::bind(&CHeroBackpackWindow::close, this));
quitButton = std::make_shared<CButton>(Point(), AnimationPath::builtin("IOKAY32.def"), CButton::tooltip(""), [this]() { close(); }, EShortcut::GLOBAL_RETURN);
stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
pos.w = stretchedBackground->pos.w;
pos.h = stretchedBackground->pos.h;
pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
pos.h = stretchedBackground->pos.h = arts->pos.h + quitButton->pos.h + 3 * windowMargin;
quitButton->moveTo(Point(pos.x + pos.w / 2 - quitButton->pos.w / 2, pos.y + arts->pos.h + 2 * windowMargin));
center();
quitButton->moveBy(Point(GH.screenDimensions().x / 2 - quitButton->pos.w / 2 - quitButton->pos.x, arts->pos.h + 2 * windowMargin));
}
void CHeroBackpackWindow::showAll(Canvas & to)
@ -48,3 +42,46 @@ void CHeroBackpackWindow::showAll(Canvas & to)
CIntObject::showAll(to);
CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
}
CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot)
: CWindowObject((EOptions)0)
{
OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
stretchedBackground = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, 0, 0));
arts = std::make_shared<CArtifactsOfHeroQuickBackpack>(targetSlot);
arts->moveBy(Point(windowMargin, windowMargin));
addSetAndCallbacks(static_cast<std::weak_ptr<CArtifactsOfHeroQuickBackpack>>(arts));
arts->setHero(hero);
addCloseCallback(std::bind(&CHeroQuickBackpackWindow::close, this));
addUsedEvents(GESTURE);
pos.w = stretchedBackground->pos.w = arts->pos.w + 2 * windowMargin;
pos.h = stretchedBackground->pos.h = arts->pos.h + windowMargin;
}
void CHeroQuickBackpackWindow::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
{
if(on)
return;
arts->swapSelected();
close();
}
void CHeroQuickBackpackWindow::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
{
arts->selectSlotAt(currentPosition);
redraw();
}
void CHeroQuickBackpackWindow::showAll(Canvas & to)
{
if(arts->getSlotsNum() == 0)
{
// Dirty solution for closing that window
close();
return;
}
CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15);
CIntObject::showAll(to);
}

View File

@ -19,11 +19,26 @@ class CHeroBackpackWindow : public CWindowObject, public CWindowWithArtifacts
public:
CHeroBackpackWindow(const CGHeroInstance * hero);
private:
protected:
std::shared_ptr<CArtifactsOfHeroBackpack> arts;
std::shared_ptr<CButton> quitButton;
std::shared_ptr<CFilledTexture> stretchedBackground;
const int windowMargin = 10;
const int windowMargin = 5;
void showAll(Canvas & to) override;
};
class CHeroQuickBackpackWindow : public CWindowObject, public CWindowWithArtifacts
{
public:
CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot);
void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
private:
std::shared_ptr<CArtifactsOfHeroQuickBackpack> arts;
std::shared_ptr<CFilledTexture> stretchedBackground;
const int windowMargin = 5;
void showAll(Canvas & to) override;
};

View File

@ -111,6 +111,5 @@ public:
void createBackpackWindow();
//friends
friend void CHeroArtPlace::clickPressed(const Point & cursorPosition);
friend class CPlayerInterface;
};

View File

@ -13,8 +13,6 @@
#include "../lobby/SelectionTab.h"
#include <vstd/DateUtils.h>
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../widgets/CComponent.h"
@ -29,6 +27,7 @@
#include "../render/Graphics.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/TextOperations.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/campaign/CampaignState.h"
#include "../../lib/mapping/CMap.h"
@ -42,6 +41,7 @@
#include "../../lib/serializer/CLoadFile.h"
#include "../../lib/StartInfo.h"
#include "../../lib/rmg/CMapGenOptions.h"
#include "../../lib/Languages.h"
CMapOverview::CMapOverview(std::string mapName, std::string fileName, std::string date, ResourcePath resource, ESelectionScreen tabType)
: CWindowObject(BORDERED | RCLICK_POPUP), resource(resource), mapName(mapName), fileName(fileName), date(date), tabType(tabType)
@ -199,7 +199,7 @@ CMapOverviewWidget::CMapOverviewWidget(CMapOverview& parent):
if(p.date.empty())
{
std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(ResourcePath(p.resource.getName(), p.tabType == ESelectionScreen::campaignList ? EResType::CAMPAIGN : EResType::MAP)));
w->setText(vstd::getFormattedDateTime(time));
w->setText(TextOperations::getFormattedDateTimeLocal(time));
}
else
w->setText(p.date);

View File

@ -124,70 +124,22 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
offL = offR = offT = offB = offRM = 0;
spellsPerPage = 12;
}
pos = background->center(Point(pos.w/2 + pos.x, pos.h/2 + pos.y));
//initializing castable spells
mySpells.reserve(CGI->spellh->objects.size());
for(const CSpell * spell : CGI->spellh->objects)
if(settings["general"]["enableUiEnhancements"].Bool())
{
if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell))
mySpells.push_back(spell);
}
std::sort(mySpells.begin(), mySpells.end(), spellsorter);
Rect r(90, isBigSpellbook ? 480 : 420, isBigSpellbook ? 160 : 110, 16);
const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75);
const ColorRGBA borderColor = ColorRGBA(128, 100, 75);
const ColorRGBA grayedColor = ColorRGBA(158, 130, 105);
searchBoxRectangle = std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor);
searchBoxDescription = std::make_shared<CLabel>(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search"));
//initializing sizes of spellbook's parts
for(auto & elem : sitesPerTabAdv)
elem = 0;
for(auto & elem : sitesPerTabBattle)
elem = 0;
for(const auto spell : mySpells)
{
int * sitesPerOurTab = spell->isCombat() ? sitesPerTabBattle : sitesPerTabAdv;
++sitesPerOurTab[4];
spell->forEachSchool([&sitesPerOurTab](const SpellSchool & school, bool & stop)
{
++sitesPerOurTab[school];
});
}
if(sitesPerTabAdv[4] % spellsPerPage == 0)
sitesPerTabAdv[4]/=spellsPerPage;
else
sitesPerTabAdv[4] = sitesPerTabAdv[4]/spellsPerPage + 1;
for(int v=0; v<4; ++v)
{
if(sitesPerTabAdv[v] <= spellsPerPage - 2)
sitesPerTabAdv[v] = 1;
else
{
if((sitesPerTabAdv[v] - spellsPerPage - 2) % spellsPerPage == 0)
sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 1;
else
sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 2;
}
}
if(sitesPerTabBattle[4] % spellsPerPage == 0)
sitesPerTabBattle[4]/=spellsPerPage;
else
sitesPerTabBattle[4] = sitesPerTabBattle[4]/spellsPerPage + 1;
for(int v=0; v<4; ++v)
{
if(sitesPerTabBattle[v] <= spellsPerPage - 2)
sitesPerTabBattle[v] = 1;
else
{
if((sitesPerTabBattle[v] - spellsPerPage - 2) % spellsPerPage == 0)
sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 1;
else
sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 2;
}
searchBox = std::make_shared<CTextInput>(r, FONT_SMALL, std::bind(&CSpellWindow::searchInput, this));
}
processSpells();
//numbers of spell pages computed
@ -253,7 +205,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
selectedTab = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap;
schoolTab->setFrame(selectedTab, 0);
int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbokLastPageAdvmap;
int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap;
// spellbook last page battle index is not reset after battle, so this needs to stay here
vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1));
setCurrentPage(cp);
@ -292,14 +244,114 @@ std::shared_ptr<IImage> CSpellWindow::createBigSpellBook()
Canvas tmp5 = Canvas(Point(409, 141));
tmp5.draw(img, Point(0, 0), Rect(100, 38 + 45, 509 - 15, 400 - 38));
canvas.drawScaled(tmp5, Point(90, 45), Point(615, 415));
// carpet
Canvas tmp6 = Canvas(Point(590, 59));
tmp6.draw(img, Point(0, 0), Rect(15, 484, 590, 59));
canvas.drawScaled(tmp6, Point(0, 545), Point(800, 59));
// remove bookmarks
for (int i = 0; i < 56; i++)
canvas.draw(Canvas(canvas, Rect(i < 30 ? 268 : 327, 464, 1, 46)), Point(269 + i, 464));
for (int i = 0; i < 56; i++)
canvas.draw(Canvas(canvas, Rect(469, 464, 1, 42)), Point(470 + i, 464));
for (int i = 0; i < 57; i++)
canvas.draw(Canvas(canvas, Rect(i < 30 ? 564 : 630, 464, 1, 44)), Point(565 + i, 464));
for (int i = 0; i < 56; i++)
canvas.draw(Canvas(canvas, Rect(656, 464, 1, 47)), Point(657 + i, 464));
// draw bookmarks
canvas.draw(img, Point(278, 464), Rect(220, 405, 37, 47));
canvas.draw(img, Point(481, 465), Rect(354, 406, 37, 41));
canvas.draw(img, Point(575, 465), Rect(417, 406, 37, 45));
canvas.draw(img, Point(667, 465), Rect(478, 406, 37, 47));
return GH.renderHandler().createImage(canvas.getInternalSurface());
}
void CSpellWindow::searchInput()
{
if(searchBox)
searchBoxDescription->setEnabled(searchBox->getText().empty());
processSpells();
int cp = 0;
// spellbook last page battle index is not reset after battle, so this needs to stay here
vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1));
setCurrentPage(cp);
computeSpellsPerArea();
}
void CSpellWindow::processSpells()
{
mySpells.clear();
//initializing castable spells
mySpells.reserve(CGI->spellh->objects.size());
for(const CSpell * spell : CGI->spellh->objects)
{
bool searchTextFound = !searchBox || boost::algorithm::contains(boost::algorithm::to_lower_copy(spell->getNameTranslated()), boost::algorithm::to_lower_copy(searchBox->getText()));
if(!spell->isCreatureAbility() && myHero->canCastThisSpell(spell) && searchTextFound)
mySpells.push_back(spell);
}
std::sort(mySpells.begin(), mySpells.end(), spellsorter);
//initializing sizes of spellbook's parts
for(auto & elem : sitesPerTabAdv)
elem = 0;
for(auto & elem : sitesPerTabBattle)
elem = 0;
for(const auto spell : mySpells)
{
int * sitesPerOurTab = spell->isCombat() ? sitesPerTabBattle : sitesPerTabAdv;
++sitesPerOurTab[4];
spell->forEachSchool([&sitesPerOurTab](const SpellSchool & school, bool & stop)
{
++sitesPerOurTab[school];
});
}
if(sitesPerTabAdv[4] % spellsPerPage == 0)
sitesPerTabAdv[4]/=spellsPerPage;
else
sitesPerTabAdv[4] = sitesPerTabAdv[4]/spellsPerPage + 1;
for(int v=0; v<4; ++v)
{
if(sitesPerTabAdv[v] <= spellsPerPage - 2)
sitesPerTabAdv[v] = 1;
else
{
if((sitesPerTabAdv[v] - spellsPerPage - 2) % spellsPerPage == 0)
sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 1;
else
sitesPerTabAdv[v] = (sitesPerTabAdv[v] - spellsPerPage - 2) / spellsPerPage + 2;
}
}
if(sitesPerTabBattle[4] % spellsPerPage == 0)
sitesPerTabBattle[4]/=spellsPerPage;
else
sitesPerTabBattle[4] = sitesPerTabBattle[4]/spellsPerPage + 1;
for(int v=0; v<4; ++v)
{
if(sitesPerTabBattle[v] <= spellsPerPage - 2)
sitesPerTabBattle[v] = 1;
else
{
if((sitesPerTabBattle[v] - spellsPerPage - 2) % spellsPerPage == 0)
sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 1;
else
sitesPerTabBattle[v] = (sitesPerTabBattle[v] - spellsPerPage - 2) / spellsPerPage + 2;
}
}
}
void CSpellWindow::fexitb()
{
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab;
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbokLastPageAdvmap) = currentPage;
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage;
close();
}
@ -595,7 +647,7 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
auto guard = vstd::makeScopeGuard([this]()
{
owner->myInt->localState->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab;
owner->myInt->localState->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage;
});
if(mySpell->getTargetType() == spells::AimType::LOCATION)

View File

@ -26,6 +26,8 @@ class CLabel;
class CGStatusBar;
class CPlayerInterface;
class CSpellWindow;
class CTextInput;
class TransparentFilledRectangle;
/// The spell window
class CSpellWindow : public CWindowObject
@ -80,6 +82,10 @@ class CSpellWindow : public CWindowObject
std::vector<std::shared_ptr<InteractiveArea>> interactiveAreas;
std::shared_ptr<CTextInput> searchBox;
std::shared_ptr<TransparentFilledRectangle> searchBoxRectangle;
std::shared_ptr<CLabel> searchBoxDescription;
bool isBigSpellbook;
int spellsPerPage;
int offL;
@ -99,6 +105,8 @@ class CSpellWindow : public CWindowObject
const CGHeroInstance * myHero; //hero whose spells are presented
CPlayerInterface * myInt;
void processSpells();
void searchInput();
void computeSpellsPerArea(); //recalculates spellAreas::mySpell
void setCurrentPage(int value);

View File

@ -331,7 +331,7 @@ void CTradeWindow::setMode(EMarketMode Mode)
}
}
void CTradeWindow::artifactSelected(CHeroArtPlace *slot)
void CTradeWindow::artifactSelected(CArtPlace * slot)
{
assert(mode == EMarketMode::ARTIFACT_RESOURCE);
items[1][0]->setArtInstance(slot->getArt());

View File

@ -40,7 +40,7 @@ public:
void getPositionsFor(std::vector<Rect> &poss, bool Left, EType type) const;
void setMode(EMarketMode Mode); //mode setter
void artifactSelected(CHeroArtPlace *slot); //used when selling artifacts -> called when user clicked on artifact slot
void artifactSelected(CArtPlace * slot); //used when selling artifacts -> called when user clicked on artifact slot
virtual void selectionChanged(bool side) = 0; //true == left
virtual Point selectionOffset(bool Left) const = 0;
virtual std::string updateSlotSubtitle(bool Left) const = 0;

View File

@ -409,7 +409,9 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
box = std::make_shared<CComponentBox>(comps, Rect(75, 300, pos.w - 150, 100));
}
portrait = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsLarge"), hero->getIconIndex(), 0, 170, 66);
portrait = std::make_shared<CHeroArea>(170, 66, hero);
portrait->addClickCallback(nullptr);
portrait->addRClickCallback([hero](){ GH.windows().createAndPushWindow<CRClickPopupInt>(std::make_shared<CHeroWindow>(hero)); });
ok = std::make_shared<CButton>(Point(297, 413), AnimationPath::builtin("IOKAY"), CButton::tooltip(), std::bind(&CLevelWindow::close, this), EShortcut::GLOBAL_ACCEPT);
//%s has gained a level.

View File

@ -35,6 +35,7 @@ class CGStatusBar;
class CTextBox;
class CGarrisonInt;
class CGarrisonSlot;
class CHeroArea;
enum class EUserEvent;
@ -129,7 +130,7 @@ public:
/// Raised up level window where you can select one out of two skills
class CLevelWindow : public CWindowObject
{
std::shared_ptr<CAnimImage> portrait;
std::shared_ptr<CHeroArea> portrait;
std::shared_ptr<CButton> ok;
std::shared_ptr<CLabel> mainTitle;
std::shared_ptr<CLabel> levelTitle;

View File

@ -1,6 +1,6 @@
set(VCMI_VERSION_MAJOR 1)
set(VCMI_VERSION_MINOR 4)
set(VCMI_VERSION_PATCH 1)
set(VCMI_VERSION_PATCH 2)
add_definitions(
-DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR}
-DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR}

View File

@ -1873,6 +1873,7 @@
}
],
"index" : 127,
"class" : "SPECIAL",
"type" : ["HERO"]
},
"armageddonsBlade":
@ -1917,6 +1918,7 @@
}
],
"index" : 128,
"class" : "SPECIAL",
"type" : ["HERO"]
},
"angelicAlliance":

View File

@ -935,5 +935,16 @@
"grassHills" : { "index" :208, "handler": "static", "types" : { "object" : { "index" : 0} } },
"roughHills" : { "index" :209, "handler": "static", "types" : { "object" : { "index" : 0} } },
"subterraneanRocks" : { "index" :210, "handler": "static", "types" : { "object" : { "index" : 0} } },
"swampFoliage" : { "index" :211, "handler": "static", "types" : { "object" : { "index" : 0} } }
"swampFoliage" : { "index" :211, "handler": "static", "types" : { "object" : { "index" : 0} } },
/// special object to handle invalid / unknown objects on some user-made maps
"nothing" : {
"index" : 0,
"handler": "generic",
"types" : {
"nothing" : {
"index" : 0
}
}
}
}

View File

@ -615,7 +615,7 @@
},
"infoBarCreatureManagement": {
"type" : "boolean",
"default" : false
"default" : true
},
"enableLargeSpellbook" : {
"type": "boolean",

View File

@ -1221,7 +1221,7 @@
"effects" : {
"attacksNearestCreature" : {
"type" : "ATTACKS_NEAREST_CREATURE",
"duration" : "UNTIL_ATTACK"
"duration" : "UNTIL_OWN_ATTACK"
}
}
},

View File

@ -85,7 +85,8 @@
"N", "N", "N",
"N", "N", "N"
],
"mapping" : { "normal" : "49-72", "dirt" : "21-44", "sand" : "0-23", "water" : "20-32", "rock": "0-7", "hota" : "77-117" }
"decoration" : true,
"mapping" : { "normal" : "49-56,57-72", "dirt" : "21-28,29-44", "sand" : "0-11,12-23", "water" : "20-32", "rock": "0-7", "hota" : "77-101,102-117" }
},
// Mixed transitions
{

View File

@ -222,7 +222,7 @@
{
"name": "chessFieldTurn",
"callback": "parseAndSetTimer_turn",
"help": "vcmi.optionsTab.chessFieldTurn.help"
"help": "vcmi.optionsTab.chessFieldTurnAccumulate.help"
},
{
"name": "chessFieldBattle",
@ -232,7 +232,7 @@
{
"name": "chessFieldUnit",
"callback": "parseAndSetTimer_unit",
"help": "vcmi.optionsTab.chessFieldUnit.help"
"help": "vcmi.optionsTab.chessFieldUnitAccumulate.help"
}
]
},

View File

@ -1,34 +0,0 @@
{
"items":
[
{ //backgound color
"type": "drawRect",
"rect": {"x": 4, "y": 4, "w": 72, "h": 24},
"color": [10, 10, 10, 255]
},
{ //clocks icon
"type": "image",
"image": "VCMI/BATTLEQUEUE/STATESSMALL",
"frame": 1,
"position": {"x": 4, "y": 6}
},
{ //timer field label
"name": "timer",
"type": "label",
"font": "big",
"alignment": "left",
"color": "yellow",
"text": "",
"position": {"x": 26, "y": 2}
},
],
"variables":
{
"notificationTime": [0, 1, 2, 3, 4, 5, 20],
"notificationSound": "WE5",
"textColorFromPlayerColor": true
}
}

8
debian/changelog vendored
View File

@ -1,9 +1,15 @@
vcmi (1.4.1) jammy; urgency=medium
vcmi (1.4.2) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 22 Dec 2023 16:00:00 +0200
vcmi (1.4.1) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Tue, 12 Dec 2023 16:00:00 +0200
vcmi (1.4.0) jammy; urgency=medium
* New upstream release

View File

@ -1,5 +1,6 @@
[![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.4.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.4.1)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project

View File

@ -14,3 +14,4 @@ Bonus may have any of these durations. They acts in disjunction.
- UNTIL_ATTACK: removed after attack and counterattacks are performed
- STACK_GETS_TURN: removed when stack gets its turn - used for defensive stance
- COMMANDER_KILLED
- UNTIL_OWN_ATTACK: removed after attack (not counterattack) is performed

View File

@ -36,7 +36,7 @@ Gives specific creature in every slot, with optional amount. Examples:
### Movement points
`nwcnebuchadnezzar` or `vcminahar` or `vcmimove` - give 1000000 movement points and free ship boarding for 1 day
`nwcnebuchadnezzar` or `vcminahar` or `vcmimove` - give unlimited (or specified amount of) movement points and free ship boarding
Alternative usage: `vcmimove <amount>` - gives specified amount of movement points
### Resources
@ -57,11 +57,23 @@ Alternative usage: `vcmilevel <amount>` - advances hero by specified number of l
- `vcmiolorin` or `vcmiexp` - gives selected hero 10000 experience
Alternative usage: `vcmiexp <amount>` - gives selected hero specified amount of experience
### Luck and morale
`nwcfollowthewhiterabbit` or `vcmiluck` - the currently selected hero permanently gains maximum luck
`nwcmorpheus` or `vcmimorale` - the currently selected hero permanently gains maximum morale
### Puzzle map
`nwcoracle` or `vcmiobelisk` - reveals the puzzle map
### Finishing the game
`nwcredpill` or `vcmisilmaril` or `vcmiwin` - player wins
`nwcbluepill` or `vcmimelkor` or `vcmilose` - player loses
### Misc
`nwctheone` or `vcmigod` - reveals the whole map, gives 5 archangels in each empty slot, unlimited movement points and permanent flight
## Using cheat codes on other players
By default, all cheat codes apply to current player. Alternatively, it is possible to specify player that you want to target:

View File

@ -5,7 +5,7 @@ VCMI_LIB_NAMESPACE_BEGIN
namespace vstd
{
DLL_LINKAGE std::string getFormattedDateTime(std::time_t dt);
DLL_LINKAGE std::string getFormattedDateTime(std::time_t dt, std::string format);
DLL_LINKAGE std::string getDateTimeISO8601Basic(std::time_t dt);
}

View File

@ -1,56 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<?xml version='1.0' encoding='utf-8'?>
<component type="desktop">
<id>eu.vcmi.VCMI</id>
<metadata_license>CC-BY-SA-4.0</metadata_license>
<project_license>GPL-2.0-or-later</project_license>
<name>VCMI</name>
<summary>Open-source game engine for Heroes of Might and Magic III</summary>
<summary xml:lang="cs">Herní engine s otevřeným kódem pro Heroes of Might and Magic III</summary>
<summary xml:lang="de">Open-Source-Spielengine für Heroes of Might and Magic III</summary>
<developer_name>VCMI Team</developer_name>
<developer_name xml:lang="cs">Tým VCMI</developer_name>
<metadata_license>CC-BY-SA-4.0</metadata_license>
<project_license>GPL-2.0-or-later</project_license>
<description>
<p>VCMI is an open-source engine for Heroes of Might and Magic III with new possibilities. Years of intensive work resulted in an impressive amount of features. Among the current features are:</p>
<p xml:lang="cs">VCMI je engine s otevřeným kódem a novými možnostmi pro Heroes of Might and Magic III. Roky usilovné práce vyústily v úchvatném počtu nových funkcí. Mezi současnými funkcemi jsou:</p>
<p xml:lang="de">VCMI ist eine Open-Source-Engine für Heroes of Might and Magic III mit neuen Möglichkeiten. Jahrelange intensive Arbeit führte zu einer beeindruckenden Anzahl von Features. Zu den aktuellen Features gehören:</p>
<ul>
<li>Complete gameplay mechanics</li>
<li xml:lang="cs">Kompletní herní mechaniky</li>
<li xml:lang="de">Vollständige Spielmechanik</li>
<li>Almost all objects, abilities, spells and other content</li>
<li xml:lang="cs">Skoro všechny předměty, schopnosti, kouzla a ostatní obsah</li>
<li xml:lang="de">Fast alle Objekte, Fähigkeiten, Zaubersprüche und andere Inhalte</li>
<li>Basic battle AI and adventure AI</li>
<li xml:lang="cs">Základní AI boje a mapy světa</li>
<li xml:lang="de">Grundlegende Kampf- und Abenteuer-KI</li>
<li>Many GUI improvements: high resolutions, stack queue, creature window</li>
<li xml:lang="cs">Mnoho vylepšení rozhraní: vyšší rozlišení, fronta oddílů a okno bojovníků</li>
<li xml:lang="de">Viele GUI-Verbesserungen: Hohe Auflösungen, Truppenwarteschlange, Kreaturenfenster</li>
<li>Advanced and easy mod support - add new towns, creatures, heroes, artifacts and spells without limits or conflicts</li>
<li xml:lang="cs">Pokročilá a jednoduchá podpora modifikací - přidání nových měst, bojovníků, hrdinů, artefaktů a kouzel bez limitů a konfliktů</li>
<li xml:lang="de">Erweiterte und einfache Mod-Unterstützung - füge neue Städte, Kreaturen, Helden, Artefakte und Zaubersprüche ohne Einschränkungen oder Konflikte hinzu</li>
<li>Launcher for easy configuration - download mods from our server and install them immediately!</li>
<li xml:lang="cs">Spouštěč pro jednoduché nastavení - stahujte modifikace z našeho serveru a hned je instalujte!</li>
<li xml:lang="de">Launcher für einfache Konfiguration - Mods von unserem Server herunterladen und sofort installieren!</li>
<li>Random map generator that supports objects added by mods</li>
<li xml:lang="cs">Náhodný generátor map, který podporuje předměty přidané modifikacemi</li>
<li xml:lang="de">Zufallsgenerator für Karten, der von Mods hinzugefügte Objekte unterstützt</li>
</ul>
<p>Note: In order to play the game using VCMI you need to own data files for Heroes of Might and Magic III: The Shadow of Death.</p>
<p xml:lang="cs">Poznámka: pokud chcete hrát hru přes VCMI, musíte vlastnit datové soubory Heroes of Might and Magic III: The Shadow of Death.</p>
<p xml:lang="de">Hinweis: Um das Spiel mit VCMI spielen zu können, sind die Originaldateien für Heroes of Might and Magic III: The Shadow of Death erforderlich.</p>
<p>If you want help, please check our forum, bug tracker or GitHub page.</p>
<p xml:lang="cs">Pokud chcete pomoct, prosíme, podívejte se na naše fórum nebo GitHub.</p>
<p xml:lang="de">Wird Hilfe benötigt, besucht bitte unser Forum, den Bugtracker oder die GitHub-Seite.</p>
</description>
<screenshots>
<screenshot type="default">
<image>https://play-lh.googleusercontent.com/-ZErg8hUyc3TScVQZYh30KzaUDWmnIGRcN3WPCQimdviOJVoMkjDdIW19WqX0KZO7mk=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/-ZErg8hUyc3TScVQZYh30KzaUDWmnIGRcN3WPCQimdviOJVoMkjDdIW19WqX0KZO7mk=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/8Uuuir7_0pDCPohacS7nd6CQuvaOO7nKmKWo-A-EysRY8uIK5qLsv0cpMxT3AFu2DA=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/8Uuuir7_0pDCPohacS7nd6CQuvaOO7nKmKWo-A-EysRY8uIK5qLsv0cpMxT3AFu2DA=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/993tSmsQh1qgb1_XQUxD1wmS8Zuiydf0LsQK_nU2fAiJl62n0_Vl_5JMVK-J9lvVeQ=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/993tSmsQh1qgb1_XQUxD1wmS8Zuiydf0LsQK_nU2fAiJl62n0_Vl_5JMVK-J9lvVeQ=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/N3fn4cqX2iN0t9jzEo0vgEMBpUYWyRgOz8AtDsMLmF-BTyxO4l2uoAderEIhXPvPsg=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/N3fn4cqX2iN0t9jzEo0vgEMBpUYWyRgOz8AtDsMLmF-BTyxO4l2uoAderEIhXPvPsg=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/9wSQsPlMPSBAzekEcw1OVpzeOvYiHrYRYHgpa1aRk31pvR752gu_cUZws5x8JMCACw=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/9wSQsPlMPSBAzekEcw1OVpzeOvYiHrYRYHgpa1aRk31pvR752gu_cUZws5x8JMCACw=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/nU_MSwmEhiQ14Sx9xI3QFF1FhL7BYTDMbEYQg-XglSYwIMpYZDpP92_ybTtu4cVJSxo=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/nU_MSwmEhiQ14Sx9xI3QFF1FhL7BYTDMbEYQg-XglSYwIMpYZDpP92_ybTtu4cVJSxo=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/rhI5moPCKQpYdjnRaJJ_CdIxSAXUkrH7ddghuPWaW0vcyaltd-ZGZG4Kl6v3YKGVlzU=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/rhI5moPCKQpYdjnRaJJ_CdIxSAXUkrH7ddghuPWaW0vcyaltd-ZGZG4Kl6v3YKGVlzU=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/1zecL5SvayVkNoIHijeS_rm0Yr_XyHbp_dmSx70NXhjckvnezwvWkpINYBHAy4o8EaM=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/1zecL5SvayVkNoIHijeS_rm0Yr_XyHbp_dmSx70NXhjckvnezwvWkpINYBHAy4o8EaM=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/e5ibvuWm442dYN2z_pAwDC3Ktc7XOY6FEI4N7BkFB7DqGi3Q-Vl46KqoJ_EFSrXRNw=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/e5ibvuWm442dYN2z_pAwDC3Ktc7XOY6FEI4N7BkFB7DqGi3Q-Vl46KqoJ_EFSrXRNw=w2560-h1440</image>
</screenshot>
<screenshot>
<image>https://play-lh.googleusercontent.com/wD6xJK2nuzVyKUFODfQs2SEbUuSyEglojpp_FE6GeAuzdA_R44Dp6gTyN70KCwpRtD9M=w2560-h1440</image>
<image type="source">https://play-lh.googleusercontent.com/wD6xJK2nuzVyKUFODfQs2SEbUuSyEglojpp_FE6GeAuzdA_R44Dp6gTyN70KCwpRtD9M=w2560-h1440</image>
</screenshot>
</screenshots>
<releases>
<release version="1.4.2" date="2023-12-22" type="stable"/>
<release version="1.4.1" date="2023-12-12" type="stable"/>
<release version="1.4.0" date="2023-12-08" type="stable"/>
<release version="1.3.2" date="2023-09-15" type="stable"/>
<release version="1.3.1" date="2023-08-18" type="stable"/>
<release version="1.3.0" date="2023-08-04" type="stable"/>
<release version="1.2.1" date="2023-04-28" type="stable"/>
<release version="1.2.0" date="2023-04-14" type="stable"/>
<release version="1.1.1" date="2023-02-03" type="stable"/>
<release version="1.1.0" date="2022-12-23" type="stable"/>
<release version="1.0.0" date="2022-09-11" type="stable"/>
<release version="0.99" date="2016-11-01" type="stable"/>
</releases>
<url type="homepage">https://vcmi.eu/</url>
<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
<url type="faq">https://vcmi.eu/faq/</url>
@ -58,33 +96,15 @@
<url type="translate">https://github.com/vcmi/vcmi/blob/master/docs/modders/Translations.md</url>
<url type="contact">https://discord.gg/chBT42V</url>
<url type="vcs-browser">https://github.com/vcmi/vcmi</url>
<recommends>
<control>keyboard</control>
<control>pointing</control>
<control>touch</control>
</recommends>
<categories>
<category>Game</category>
<category>StrategyGame</category>
</categories>
<releases>
<release version="1.4.1" date="2023-12-22" />
<release version="1.4.0" date="2023-12-08" />
<release version="1.3.2" date="2023-09-15" />
<release version="1.3.1" date="2023-08-18" />
<release version="1.3.0" date="2023-08-04" />
<release version="1.2.1" date="2023-04-28" />
<release version="1.2.0" date="2023-04-14" />
<release version="1.1.1" date="2023-02-03" />
<release version="1.1.0" date="2022-12-23" />
<release version="1.0.0" date="2022-09-11" />
<release version="0.99" date="2016-11-01" />
</releases>
<keywords>
<keyword translate="no">heroes3</keyword>
<keyword translate="no">homm3</keyword>
</keywords>
<developer_name>VCMI Team</developer_name>
<recommends>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</recommends>
<content_rating type="oars-1.1">
<content_attribute id="violence-cartoon">moderate</content_attribute>
<content_attribute id="violence-fantasy">moderate</content_attribute>
@ -98,4 +118,8 @@
<binary>vcmilauncher</binary>
<binary>vcmiserver</binary>
</provides>
<keywords>
<keyword>heroes3</keyword>
<keyword>homm3</keyword>
</keywords>
</component>

View File

@ -136,14 +136,14 @@ void MainWindow::detectPreferredLanguage()
for (auto const & vcmiLang : Languages::getLanguageList())
if (vcmiLang.tagIETF == userLang.toStdString())
selectedLanguage = vcmiLang.identifier;
}
logGlobal->info("Selected language: %s", selectedLanguage);
if (!selectedLanguage.empty())
{
Settings node = settings.write["general"]["language"];
node->String() = selectedLanguage;
if (!selectedLanguage.empty())
{
logGlobal->info("Selected language: %s", selectedLanguage);
Settings node = settings.write["general"]["language"];
node->String() = selectedLanguage;
return;
}
}
}

View File

@ -591,7 +591,7 @@ void CModListView::downloadFile(QString file, QString url, QString description,
this, SLOT(downloadFinished(QStringList,QStringList,QStringList)));
connect(manager.get(), SIGNAL(extractionProgress(qint64,qint64)),
this, SLOT(downloadProgress(qint64,qint64)));
this, SLOT(extractionProgress(qint64,qint64)));
connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
@ -613,6 +613,14 @@ void CModListView::downloadProgress(qint64 current, qint64 max)
ui->progressBar->setValue(current / (1024 * 1024));
}
void CModListView::extractionProgress(qint64 current, qint64 max)
{
// display progress, in extracted files
ui->progressBar->setVisible(true);
ui->progressBar->setMaximum(max);
ui->progressBar->setValue(current);
}
void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors)
{
QString title = tr("Download failed");

View File

@ -98,6 +98,7 @@ private slots:
void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);
void modSelected(const QModelIndex & current, const QModelIndex & previous);
void downloadProgress(qint64 current, qint64 max);
void extractionProgress(qint64 current, qint64 max);
void downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors);
void modelReset();
void hideProgressBar();

View File

@ -24,7 +24,9 @@ namespace
{
QString detectModArchive(QString path, QString modName, std::vector<std::string> & filesToExtract)
{
filesToExtract = ZipArchive::listFiles(qstringToPath(path));
ZipArchive archive(qstringToPath(path));
filesToExtract = archive.listFiles();
QString modDirName;
@ -285,14 +287,23 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
if(!modDirName.size())
return addError(modname, "Mod archive is invalid or corrupted");
auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesToExtract]()
std::atomic<int> filesCounter = 0;
auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesCounter, &filesToExtract]()
{
return ZipArchive::extract(qstringToPath(archivePath), qstringToPath(destDir), filesToExtract);
ZipArchive archive(qstringToPath(archivePath));
for (auto const & file : filesToExtract)
{
if (!archive.extract(qstringToPath(destDir), file))
return false;
++filesCounter;
}
return true;
});
while(futureExtract.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready)
{
emit extractionProgress(0, 0);
emit extractionProgress(filesCounter, filesToExtract.size());
qApp->processEvents();
}

File diff suppressed because it is too large Load Diff

View File

@ -93,6 +93,16 @@ int AFactionMember::getPrimSkillLevel(PrimarySkill id) const
int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
{
int32_t maxGoodMorale = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
int32_t maxBadMorale = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
if(getBonusBearer()->hasBonusOfType(BonusType::MAX_MORALE))
{
if(bonusList && !bonusList->empty())
bonusList = std::make_shared<const BonusList>();
return maxGoodMorale;
}
static const auto unaffectedByMoraleSelector = Selector::type()(BonusType::NON_LIVING).Or(Selector::type()(BonusType::UNDEAD))
.Or(Selector::type()(BonusType::SIEGE_WEAPON)).Or(Selector::type()(BonusType::NO_MORALE));
@ -109,14 +119,21 @@ int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
static const std::string cachingStrMor = "type_MORALE";
bonusList = getBonusBearer()->getBonuses(moraleSelector, cachingStrMor);
int32_t maxGoodMorale = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
int32_t maxBadMorale = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE).size();
return std::clamp(bonusList->totalValue(), maxBadMorale, maxGoodMorale);
}
int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
{
int32_t maxGoodLuck = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
int32_t maxBadLuck = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
if(getBonusBearer()->hasBonusOfType(BonusType::MAX_LUCK))
{
if(bonusList && !bonusList->empty())
bonusList = std::make_shared<const BonusList>();
return maxGoodLuck;
}
if(getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
{
if(bonusList && !bonusList->empty())
@ -128,9 +145,6 @@ int AFactionMember::luckValAndBonusList(TConstBonusListPtr & bonusList) const
static const std::string cachingStrLuck = "type_LUCK";
bonusList = getBonusBearer()->getBonuses(luckSelector, cachingStrLuck);
int32_t maxGoodLuck = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_LUCK_DICE).size();
int32_t maxBadLuck = - (int32_t) VLC->settings()->getVector(EGameSettings::COMBAT_BAD_LUCK_DICE).size();
return std::clamp(bonusList->totalValue(), maxBadLuck, maxGoodLuck);
}

View File

@ -625,7 +625,7 @@ void CArtHandler::makeItCommanderArt(CArtifact * a, bool onlyCommander)
a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(slot));
}
bool CArtHandler::legalArtifact(const ArtifactID & id)
bool CArtHandler::legalArtifact(const ArtifactID & id) const
{
auto art = id.toArtifact();
//assert ( (!art->constituents) || art->constituents->size() ); //artifacts is not combined or has some components
@ -648,18 +648,6 @@ bool CArtHandler::legalArtifact(const ArtifactID & id)
return false;
}
void CArtHandler::initAllowedArtifactsList(const std::set<ArtifactID> & allowed)
{
allowedArtifacts.clear();
for (ArtifactID i : allowed)
{
if (legalArtifact(ArtifactID(i)))
allowedArtifacts.push_back(i.toArtifact());
//keep im mind that artifact can be worn by more than one type of bearer
}
}
std::set<ArtifactID> CArtHandler::getDefaultAllowed() const
{
std::set<ArtifactID> allowedArtifacts;

View File

@ -141,15 +141,11 @@ public:
class DLL_LINKAGE CArtHandler : public CHandlerBase<ArtifactID, Artifact, CArtifact, ArtifactService>
{
public:
/// List of artifacts allowed on the map
std::vector<const CArtifact *> allowedArtifacts;
void addBonuses(CArtifact *art, const JsonNode &bonusList);
static CArtifact::EartClass stringToClass(const std::string & className); //TODO: rework EartClass to make this a constructor
bool legalArtifact(const ArtifactID & id);
void initAllowedArtifactsList(const std::set<ArtifactID> & allowed);
bool legalArtifact(const ArtifactID & id) const;
static void makeItCreatureArt(CArtifact * a, bool onlyCreature = true);
static void makeItCommanderArt(CArtifact * a, bool onlyCommander = true);

View File

@ -64,10 +64,7 @@ SpellID CScrollArtifactInstance::getScrollSpellID() const
auto artInst = static_cast<const CArtifactInstance*>(this);
const auto bonus = artInst->getBonusLocalFirst(Selector::type()(BonusType::SPELL));
if(!bonus)
{
logMod->warn("Warning: %s doesn't bear any spell!", artInst->nodeName());
return SpellID::NONE;
}
return bonus->subtype.as<SpellID>();
}
@ -165,6 +162,11 @@ bool CArtifactInstance::isCombined() const
return artType->isCombined();
}
bool CArtifactInstance::isScroll() const
{
return artType->isScroll();
}
void CArtifactInstance::putAt(CArtifactSet & set, const ArtifactPosition slot)
{
auto placementMap = set.putArtifact(slot, this);

View File

@ -87,6 +87,7 @@ public:
bool canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot = ArtifactPosition::FIRST_AVAILABLE,
bool assumeDestRemoved = false) const;
bool isCombined() const;
bool isScroll() const;
void putAt(CArtifactSet & set, const ArtifactPosition slot);
void removeFrom(CArtifactSet & set, const ArtifactPosition slot);
void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot);

View File

@ -721,6 +721,11 @@ bool CGameInfoCallback::isPlayerMakingTurn(PlayerColor player) const
return gs->actingPlayers.count(player);
}
CGameInfoCallback::CGameInfoCallback():
gs(nullptr)
{
}
CGameInfoCallback::CGameInfoCallback(CGameState * GS):
gs(GS)
{

View File

@ -134,7 +134,7 @@ class DLL_LINKAGE CGameInfoCallback : public IGameInfoCallback
protected:
CGameState * gs;//todo: replace with protected const getter, only actual Server and Client objects should hold game state
CGameInfoCallback() = default;
CGameInfoCallback();
CGameInfoCallback(CGameState * GS);
bool hasAccess(std::optional<PlayerColor> playerId) const;

View File

@ -118,11 +118,10 @@ void GameSettings::load(const JsonNode & input)
const JsonNode & GameSettings::getValue(EGameSettings option) const
{
assert(option < EGameSettings::OPTIONS_COUNT);
auto index = static_cast<size_t>(option);
assert(!gameSettings[index].isNull());
return gameSettings[index];
assert(!gameSettings.at(index).isNull());
return gameSettings.at(index);
}
VCMI_LIB_NAMESPACE_END

View File

@ -310,63 +310,73 @@ JsonMap & JsonNode::Struct()
const bool boolDefault = false;
bool JsonNode::Bool() const
{
if (getType() == JsonType::DATA_NULL)
return boolDefault;
assert(getType() == JsonType::DATA_BOOL);
return std::get<bool>(data);
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
if (getType() == JsonType::DATA_BOOL)
return std::get<bool>(data);
return boolDefault;
}
const double floatDefault = 0;
double JsonNode::Float() const
{
if(getType() == JsonType::DATA_NULL)
return floatDefault;
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
if(getType() == JsonType::DATA_FLOAT)
return std::get<double>(data);
if(getType() == JsonType::DATA_INTEGER)
return static_cast<double>(std::get<si64>(data));
assert(getType() == JsonType::DATA_FLOAT);
return std::get<double>(data);
return floatDefault;
}
const si64 integetDefault = 0;
const si64 integerDefault = 0;
si64 JsonNode::Integer() const
{
if(getType() == JsonType::DATA_NULL)
return integetDefault;
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
if(getType() == JsonType::DATA_INTEGER)
return std::get<si64>(data);
if(getType() == JsonType::DATA_FLOAT)
return static_cast<si64>(std::get<double>(data));
assert(getType() == JsonType::DATA_INTEGER);
return std::get<si64>(data);
return integerDefault;
}
const std::string stringDefault = std::string();
const std::string & JsonNode::String() const
{
if (getType() == JsonType::DATA_NULL)
return stringDefault;
assert(getType() == JsonType::DATA_STRING);
return std::get<std::string>(data);
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING);
if (getType() == JsonType::DATA_STRING)
return std::get<std::string>(data);
return stringDefault;
}
const JsonVector vectorDefault = JsonVector();
const JsonVector & JsonNode::Vector() const
{
if (getType() == JsonType::DATA_NULL)
return vectorDefault;
assert(getType() == JsonType::DATA_VECTOR);
return std::get<JsonVector>(data);
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR);
if (getType() == JsonType::DATA_VECTOR)
return std::get<JsonVector>(data);
return vectorDefault;
}
const JsonMap mapDefault = JsonMap();
const JsonMap & JsonNode::Struct() const
{
if (getType() == JsonType::DATA_NULL)
return mapDefault;
assert(getType() == JsonType::DATA_STRUCT);
return std::get<JsonMap>(data);
assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT);
if (getType() == JsonType::DATA_STRUCT)
return std::get<JsonMap>(data);
return mapDefault;
}
JsonNode & JsonNode::operator[](const std::string & child)

View File

@ -384,8 +384,9 @@ namespace JsonRandom
ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng, const Variables & variables)
{
std::set<ArtifactID> allowedArts;
for (auto const * artifact : VLC->arth->allowedArtifacts)
allowedArts.insert(artifact->getId());
for(const auto & artifact : VLC->arth->objects)
if (IObjectInterface::cb->isAllowed(artifact->getId()) && VLC->arth->legalArtifact(artifact->getId()))
allowedArts.insert(artifact->getId());
std::set<ArtifactID> potentialPicks = filterKeys(value, allowedArts, variables);

View File

@ -68,6 +68,9 @@ struct Options
/// primary IETF language tag
std::string tagIETF;
/// DateTime format
std::string dateTimeFormat;
/// Ruleset for plural forms in this language
EPluralForms pluralForms = EPluralForms::NONE;
@ -79,27 +82,27 @@ inline const auto & getLanguageList()
{
static const std::array<Options, 20> languages
{ {
{ "czech", "Czech", "Čeština", "CP1250", "cs", EPluralForms::CZ_3, true },
{ "chinese", "Chinese", "简体中文", "GBK", "zh", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese
{ "english", "English", "English", "CP1252", "en", EPluralForms::EN_2, true },
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", EPluralForms::EN_2, true },
{ "french", "French", "Français", "CP1252", "fr", EPluralForms::FR_2, true },
{ "german", "German", "Deutsch", "CP1252", "de", EPluralForms::EN_2, true },
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", EPluralForms::EN_2, true },
{ "italian", "Italian", "Italiano", "CP1250", "it", EPluralForms::EN_2, true },
{ "korean", "Korean", "한국어", "CP949", "ko", EPluralForms::VI_1, true },
{ "polish", "Polish", "Polski", "CP1250", "pl", EPluralForms::PL_3, true },
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese
{ "russian", "Russian", "Русский", "CP1251", "ru", EPluralForms::UK_3, true },
{ "spanish", "Spanish", "Español", "CP1252", "es", EPluralForms::EN_2, true },
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", EPluralForms::EN_2, true },
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", EPluralForms::EN_2, true },
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", EPluralForms::UK_3, true },
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", EPluralForms::VI_1, true }, // Fan translation uses special encoding
{ "czech", "Czech", "Čeština", "CP1250", "cs", "%d.%m.%Y %T", EPluralForms::CZ_3, true },
{ "chinese", "Chinese", "简体中文", "GBK", "zh", "%F %T", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese
{ "english", "English", "English", "CP1252", "en", "%F %T", EPluralForms::EN_2, true }, // English uses international date/time format here
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", "%d.%m.%Y %T", EPluralForms::EN_2, true },
{ "french", "French", "Français", "CP1252", "fr", "%d/%m/%Y %T", EPluralForms::FR_2, true },
{ "german", "German", "Deutsch", "CP1252", "de", "%d.%m.%Y %T", EPluralForms::EN_2, true },
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "%Y. %m. %d. %T", EPluralForms::EN_2, true },
{ "italian", "Italian", "Italiano", "CP1250", "it", "%d/%m/%Y %T", EPluralForms::EN_2, true },
{ "korean", "Korean", "한국어", "CP949", "ko", "%F %T", EPluralForms::VI_1, true },
{ "polish", "Polish", "Polski", "CP1250", "pl", "%d.%m.%Y %T", EPluralForms::PL_3, true },
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", "%d/%m/%Y %T", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese
{ "russian", "Russian", "Русский", "CP1251", "ru", "%d.%m.%Y %T", EPluralForms::UK_3, true },
{ "spanish", "Spanish", "Español", "CP1252", "es", "%d/%m/%Y %T", EPluralForms::EN_2, true },
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", "%F %T", EPluralForms::EN_2, true },
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", "%d.%m.%Y %T", EPluralForms::EN_2, true },
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "%d.%m.%Y %T", EPluralForms::UK_3, true },
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "%d/%m/%Y %T", EPluralForms::VI_1, true }, // Fan translation uses special encoding
{ "other_cp1250", "Other (East European)", "", "CP1250", "", EPluralForms::NONE, false },
{ "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", EPluralForms::NONE, false },
{ "other_cp1252", "Other (West European)", "", "CP1252", "", EPluralForms::NONE, false }
{ "other_cp1250", "Other (East European)", "", "CP1250", "", "", EPluralForms::NONE, false },
{ "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", "", EPluralForms::NONE, false },
{ "other_cp1252", "Other (West European)", "", "CP1252", "", "", EPluralForms::NONE, false }
} };
static_assert(languages.size() == static_cast<size_t>(ELanguages::COUNT), "Languages array is missing a value!");

View File

@ -11,6 +11,10 @@
#include "TextOperations.h"
#include "CGeneralTextHandler.h"
#include "Languages.h"
#include "CConfigHandler.h"
#include <vstd/DateUtils.h>
#include <boost/locale.hpp>
@ -210,4 +214,9 @@ std::string TextOperations::escapeString(std::string input)
return input;
}
std::string TextOperations::getFormattedDateTimeLocal(std::time_t dt)
{
return vstd::getFormattedDateTime(dt, Languages::getLanguageOptions(settings["general"]["language"].String()).dateTimeFormat);
}
VCMI_LIB_NAMESPACE_END

View File

@ -56,6 +56,9 @@ namespace TextOperations
/// replaces all symbols that normally need escaping with appropriate escape sequences
std::string escapeString(std::string input);
/// get formatted DateTime depending on the language selected
DLL_LINKAGE std::string getFormattedDateTimeLocal(std::time_t dt);
};

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