1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-08 22:26:51 +02:00

Merge branch 'develop' into mechanical_bonus

This commit is contained in:
Ivan Savenko
2024-10-31 15:14:51 +02:00
committed by GitHub
143 changed files with 2549 additions and 1663 deletions

View File

@@ -3,7 +3,6 @@ name: VCMI
on:
push:
branches:
- features/*
- beta
- master
- develop
@@ -38,6 +37,7 @@ jobs:
os: macos-13
test: 0
pack: 1
upload: 1
pack_type: Release
extension: dmg
before_install: macos.sh
@@ -50,6 +50,7 @@ jobs:
os: macos-13
test: 0
pack: 1
upload: 1
pack_type: Release
extension: dmg
before_install: macos.sh
@@ -62,6 +63,7 @@ jobs:
os: macos-13
test: 0
pack: 1
upload: 1
pack_type: Release
extension: ipa
before_install: macos.sh
@@ -69,14 +71,23 @@ jobs:
conan_profile: ios-arm64
conan_prebuilts: dependencies-ios
conan_options: --options with_apple_system_libs=True
- platform: msvc
- platform: msvc-x64
os: windows-latest
test: 0
pack: 1
upload: 1
pack_type: RelWithDebInfo
extension: exe
before_install: msvc.sh
preset: windows-msvc-release
- platform: msvc-x86
os: windows-latest
test: 0
pack: 1
pack_type: RelWithDebInfo
extension: exe
before_install: msvc.sh
preset: windows-msvc-release
preset: windows-msvc-release-x86
- platform: mingw_x86_64
os: ubuntu-24.04
test: 0
@@ -101,6 +112,7 @@ jobs:
conan_prebuilts: dependencies-mingw-x86
- platform: android-32
os: ubuntu-24.04
upload: 1
extension: apk
preset: android-conan-ninja-release
before_install: android.sh
@@ -109,6 +121,7 @@ jobs:
artifact_platform: armeabi-v7a
- platform: android-64
os: ubuntu-24.04
upload: 1
extension: apk
preset: android-conan-ninja-release
before_install: android.sh
@@ -136,6 +149,10 @@ jobs:
if: "${{ matrix.conan_prebuilts != '' }}"
run: source '${{github.workspace}}/CI/install_conan_dependencies.sh' '${{matrix.conan_prebuilts}}'
- name: Install vcpkg Dependencies
if: ${{ startsWith(matrix.platform, 'msvc') }}
run: source '${{github.workspace}}/CI/install_vcpkg_dependencies.sh' '${{matrix.platform}}'
# ensure the ccache for each PR is separate so they don't interfere with each other
# fall back to ccache of the vcmi/vcmi repo if no PR-specific ccache is found
- name: ccache for PRs
@@ -232,11 +249,11 @@ jobs:
elif [[ (${{matrix.preset}} == android-conan-ninja-release) && (${{github.ref}} != 'refs/heads/master') ]]
then
cmake -DENABLE_CCACHE:BOOL=ON -DANDROID_GRADLE_PROPERTIES="applicationIdSuffix=.daily;signingConfig=dailySigning;applicationLabel=VCMI daily" --preset ${{ matrix.preset }}
elif [[ ${{matrix.platform}} != msvc ]]
elif [[ ${{startsWith(matrix.platform, 'msvc') }} ]]
then
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
else
cmake --preset ${{ matrix.preset }}
else
cmake -DENABLE_CCACHE:BOOL=ON --preset ${{ matrix.preset }}
fi
- name: Build
@@ -273,12 +290,14 @@ jobs:
sleep 3
((counter++))
done
rm -rf _CPack_Packages
- name: Artifacts
if: ${{ matrix.pack == 1 }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
compression-level: 0
path: |
${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.${{ matrix.extension }}
@@ -299,6 +318,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
compression-level: 0
path: |
${{ env.ANDROID_APK_PATH }}
@@ -307,19 +327,21 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - aab
compression-level: 0
path: |
${{ env.ANDROID_AAB_PATH }}
- name: Upload debug symbols
if: ${{ matrix.platform == 'msvc' }}
if: ${{ startsWith(matrix.platform, 'msvc') }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }} - symbols
compression-level: 9
path: |
${{github.workspace}}/**/*.pdb
- name: Upload build
if: ${{ (matrix.pack == 1 || startsWith(matrix.platform, 'android')) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/features/')) && matrix.platform != 'msvc' && matrix.platform != 'mingw-32' }}
if: ${{ (matrix.upload == 1) && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') }}
continue-on-error: true
run: |
if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then

View File

@@ -46,6 +46,7 @@ const std::vector<std::vector<std::string>> contributors = {
{ "Developing", "", "vmarkovtsev", "" },
{ "Developing", "Tom Zielinski", "Warmonger", "Warmonger@vp.pl" },
{ "Developing", "Xiaomin Ding", "", "dingding303@gmail.com" },
{ "Developing", "Fenghuang Rumeng", "kdmcser", "zqtndfj@gmail.com" },
{ "Testing", "Ben Yan", "by003", "benyan9110@gmail.com," },
{ "Testing", "", "Misiokles", "" },

View File

@@ -1,10 +1,17 @@
curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \
"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.7/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
#!/usr/bin/env bash
#rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug
#mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
#cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
MSVC_INSTALL_PATH=$(vswhere -latest -property installationPath)
echo "MSVC_INSTALL_PATH = $MSVC_INSTALL_PATH"
echo "Installed toolset versions:"
ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC"
DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
dirname "$DUMPBIN_DIR" > $GITHUB_PATH
TOOLS_DIR=$(ls -vr "$MSVC_INSTALL_PATH/VC/Tools/MSVC/" | head -1)
DUMPBIN_PATH="$MSVC_INSTALL_PATH/VC/Tools/MSVC/$TOOLS_DIR/bin/Hostx64/x64/dumpbin.exe"
# This command should work as well, but for some reason it is *extremely* slow on the Github CI (~7 minutes)
#DUMPBIN_PATH=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
echo "TOOLS_DIR = $TOOLS_DIR"
echo "DUMPBIN_PATH = $DUMPBIN_PATH"
dirname "$DUMPBIN_PATH" > "$GITHUB_PATH"

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
RELEASE_TAG="v1.8"
FILENAME="dependencies-$1"
DOWNLOAD_URL="https://github.com/vcmi/vcmi-deps-windows/releases/download/$RELEASE_TAG/$FILENAME.txz"
curl -L "$DOWNLOAD_URL" | tar -xf - --xz

View File

@@ -154,6 +154,19 @@
}
},
{
"name": "windows-msvc-release-x86",
"displayName": "Windows x86 RelWithDebInfo",
"description": "VCMI RelWithDebInfo build",
"inherits": "default-release",
"generator": "Visual Studio 17 2022",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"CMAKE_POLICY_DEFAULT_CMP0091": "NEW",
"FORCE_BUNDLED_MINIZIP": "ON",
"CMAKE_GENERATOR_PLATFORM": "WIN32"
}
},
{
"name": "windows-msvc-release-ccache",
"displayName": "Windows x64 RelWithDebInfo with ccache",
@@ -382,6 +395,11 @@
"configurePreset": "windows-msvc-release",
"inherits": "default-release"
},
{
"name": "windows-msvc-release-x86",
"configurePreset": "windows-msvc-release-x86",
"inherits": "default-release"
},
{
"name": "windows-msvc-release-ccache",
"configurePreset": "windows-msvc-release-ccache",

View File

@@ -7,15 +7,29 @@
* Implemented adventure map overlay accessible via Alt key that highlights all interactive objects on screen
* Implemented xBRZ upscaling filter
* It is now possible to import data from Heroes Chronicles (gog.com installer only) as custom campaigns
* Added simple support for spell research feature from HotA that can be enabled via mod or game configuration editing
* Implemented automatic selection of interface scaling. Selecting interface scaling manually will restore old behavior
* VCMI will now launch in fullscreen on desktop systems. Use F4 hotkey or toggle option in settings to restore old behavior
### General
* Saved game size reduced by approximately 3 times, especially for large maps or games with a large number of mods.
* Mods that modify game texts, such as descriptions of secondary skills, will now correctly override translation mods
* Game will now correctly restore information such as hero path, order of heroes and towns, and list of sleeping heroes on loading a save game
* Added translation for missing texts, such as random map descriptions, quick exchange buttons, wog commander abilities, moat names
### Multiplayer
* Added option to start vcmi server on randomly selected TCP port
* Fixed potential desynchronization between server and clients on randomization of map objects if client and server run on different operating systems
* Fixed possible freeze on receiving turn in multiplayer when player has town window opened
* Fixed possible freeze if player is attacked by another player on first day of the week
* If player disconnects from a multiplayer game, all other players will now receive notification in form of popup message instead of chat message
* Fixed potentially missing disconnection notification in multiplayer if player disconnects due to connection loss
* Game will now correctly show turn timers and simultaneous turns state on loading game
### Stability
* Fixed possible crash on connecting bluetooth mouse during gameplay on Android
* VCMI will now write more detailed information to log file on crash due to uncaught exception
* Fixed crash on transfer of multiple artifacts in a backpack to another hero on starting next campaign scenario without hero that held these artifacts before
### Mechanics
* Arrow tower will now prefer to attack more units that are viewed most dangerous instead of simply attacking top-most unit
@@ -24,15 +38,38 @@
* Map events and town events are now triggered on start of turn of player affected by event, in line with H3 instead of triggering on start of new day for all players
* Neutral towns should now have initial garrison and weekly growth of garrison identical to H3
* It is now possible to buy a new war machine in town if hero has different war machine in the slot
* Fixed possible integer overflow if hero has unit with total AI value of over 2^31
* Unicorn Glade in Rampart now correctly requires Dendroid Arches and not Homestead
* Game will no longer place obstacles on ship-to-ship battles, in line with H3
* Game will now place obstacles in battles in villages (towns without forts)
* Battles in villages (towns without forts) now always occur on battlefield of native terrain
* Fixed pathfinding through subterranean gates located on right edge of the map or next to terra incognita
* Chain Lightning will now skip over creatures that are immune to this spell instead of targeting them but dealing no damage
* Commanders spell resistance now uses golem-like logic which reduces damage instead of using dwarf-style change to block spell
* It is now possible to target empty hex for shooters with area attack, such as Magog or Lich
### Interface
* Added option to drag map with right-click
* Added hotkeys to reorder list of owned towns or heroes
* The number of units resurrected using the Life Drain ability is now written to the combat log.
### Video / Audio
* Fixed playback of audio stream with different formats from video files in some Heroes 3 versions
* Video playback will not be replaced by a black square when another dialogue box is on top of the video.
* When resuming video playback, the video will now be continued instead of being restarted.
* Reduced video decompression artefacts for video formats that store RGB rather than YUV data.
* Intro videos are now played inside a frame on resolutions higher than 800x600 instead of filling entire screen
* Re-enabled idle animations for Conflux creatures in battles
* .webm video with vp8 / vp9 codec are now supported on every platform
* It is now possible to provide external audio stream for a video file (e.g. for translations)
* It is now possible to provide external subtitles for a video file
* Game will now correctly resume playback of terrain music on closing scenario information window in campaigns instead of playing main theme
* Background music theme will now play at lower volume while intro speech in campaign intro / outro is playing
* Added workaround for playback of corrupted `BladeFWCampaign.mp3` music file
* Fixed computation of audio length for formats other than .wav. This fixes incorrect text scrolling speed in campaign intro/outro
* Game will now use Noto family true type font to display characters not preset in Heroes III fonts
* Added option to scale all in-game fonts when scalable true type fonts are in use
### Interface
* It is now possible to search for a map object using Ctrl+F hotkey
* Added option to drag map with right-click
* Added hotkeys to reorder list of owned towns or heroes
* The number of units resurrected using the Life Drain ability is now written to the combat log.
* Fixed order of popup dialogs after battle.
* Right-click on wandering monster on adventure map will now also show creature level and faction it belongs to
* Added additional information to map right-click popup dialog: map author, map creation date, map version
@@ -47,13 +84,22 @@
* Fixed hero path not updating correctly after hiring or dismissing creatures
* Fixed missing description of a stack artifact when accessed through unit window
* Fixed text overflow on campaign scenario window if campaign name is too long
* Intro videos are now played inside a frame on resolutions higher than 800x600 instead of filling entire screen
* Recruiting hero in town will now play "new construction" sound
* Game will now correctly update displayed hero path when hiring or dismissing creatures that give movement penalty
* Game will now show level, faction and attack range of wandering monsters in right-click popup window
* Hovering over owned hero will now show movement points information in status bar
* Quick backpack window is now also accessible via Shift+mouse click, similar to HD Mod
* It is now possible to sort artifacts in backpack by cost, slot, or rarity class
* Fixed incorrect display of names of VCMI maps in scenario selection if multiple VCMI map are present in list
### Random Maps Generator
* Implemented connection option 'forcePortal'
* It is now possible to connect zone to itself using pair of portals
* It is now possible for a random map template to change game settings
* Road settings will now be correctly loaded when opening random map setup tab
* Added support for banning objects per zones
* Added support for customizing objects frequency, value, and count per zone
* Fixed values of Pandora Boxes with creatures to be in line with H3:SoD
### Campaigns
* It is now possible to use .zip archive for VCMI campaigns instead of raw gzip stream
@@ -65,6 +111,7 @@
* Added support for custom region definitions (such as background images) for VCMI campaigns
### AI
* VCMI will now use BattleAI for battles with neutral enemies by default
* Fixed bug where BattleAI attempts to move double-wide unit to an unreachable hex
* Fixed several cases where Nullkiller AI can count same dangerous object twice, doubling expected army loss.
* Nullkiller is now capable of visiting configurable objects from mods
@@ -74,6 +121,8 @@
* Fixed case where BattleAI will go around the map to attack ranged units if direct path is blocked by another unit
* Fixed evaluation of effects of waiting if unit is under haste effect by Battle AI
* Battle AI can now use location spells
* Battle AI will now correctly avoid moving flying units into dangerous obstacles such as moat
* Fixed possible crash on AI attempting to visit town that is already being visited by this hero
### Launcher
* Added Swedish translation
@@ -81,13 +130,23 @@
### Map Editor
* Implemented tracking of building requirements for Building Dialog
* Added build/demolish/enable/disable all buildings options to Building Dialog in town properties
* Implemented configuration of patrol radius for heroes
* It is now possible to set spells allowed or required to be present in Mages Guild
* It is now possible to add timed events to a town
* Fixed editor not marking mod as dependency if spells from mod are used in town Mages Guild or in hero starting spells
* It is now possible to choose road types for random map generation in editor
* Validator will now warn in case if map has players with no heroes or towns
* Fixed broken transparency handling on some adventure map objects from mods
* Fixed duplicated list of spells in Mage Guild in copy-pasted towns
* Removed separate versioning of map editor. Map editor now has same version as VCMI
* Timed events interfaces now counts days from 1, instead of from 0
### Modding
* Added support for configurable flaggable objects that can provide bonuses or daily income to owning player
* Added support for soft dependencies for mods, that only affect mod loading order (and as result - override order), without requiring dependent mod or allowing access to its identifiers
* It is now possible to provide translations for mods that modify strings from original game, such as secondary skill descriptions
* It is now possible to embed json data directly into mod.json instead of using list of files
* Implemented detection of potential conflicts between mods. To enable, open Launcher and set "Mod Validation" option to "Full"
* Fixed multiple issues with configurable town buildings
* Added documentation for configurable town buildings. See docs/Moddders/Entities_Format/Town_Buildings_Format.md
* Replaced some of hardcoded town buildings with configurable buildings. These building types are now deprecated and will be removed in future.
@@ -95,7 +154,9 @@
* It is now possible to add guards to a configurable objects. All H3 creature banks are now implemented as configurable object.
* It is now possible to define starting position of units in a guarded configurable object
* Added `canCastWithoutSkip` parameter to a spell. If such spell is cast by a creature, its turn will not end after a spellcast
* Added `castOnlyOnSelf` parameter to a spell. Creature that can cast this spell can only cast it on themselves
* Mod can now provide pregenerated assets in place of autogenerated, such as large spellbook.
* Added support for 'fused' artifacts, as alternative to combined artifacts
* Added support for custom music and opening sound for a battlefield
* Added support for multiple music tracks for towns
* Added support for multiple music tracks for terrains on adventure map
@@ -108,11 +169,12 @@
* Town building can now define provided fortifications - health of walls, towers, presence of moat, identifier of creature shooter on tower
* Added DISINTEGRATE bonus
* Added INVINCIBLE bonus
* Added PRISM_HEX_ATTACK_BREATH bonus
* Added THIEVES_GUILD_ACCESS bonus that changes amount of information available in thieves guild
* TimesStackLevelUpdater now supports commanders
* Black market restock period setting now correctly restocks on specified date instead of restocking on all dates other than specified one
* Game now supports vp8 and vp9 encoding for video files on all platforms
* Json Validator will now attempt to detect typos when encountering unknown property in Json
* Added `translate missing` command that will export only untranslated strings into `translationsMissing` directory, separated per mod
# 1.5.6 -> 1.5.7

View File

@@ -154,7 +154,10 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#endif
#define BOOST_THREAD_DONT_PROVIDE_THREAD_DESTRUCTOR_CALLS_TERMINATE_IF_JOINABLE 1
//need to link boost thread dynamically to avoid https://stackoverflow.com/questions/35978572/boost-thread-interupt-does-not-work-when-crossing-a-dll-boundary
#define BOOST_THREAD_USE_DLL //for example VCAI::finish() may freeze on thread join after interrupt when linking this statically
//for example VCAI::finish() may freeze on thread join after interrupt when linking this statically
#ifndef BOOST_THREAD_USE_DLL
# define BOOST_THREAD_USE_DLL
#endif
#define BOOST_BIND_NO_PLACEHOLDERS
#if BOOST_VERSION >= 106600

View File

@@ -15,6 +15,8 @@
"vcmi.adventureMap.monsterLevel" : "\n\n%TOWN%LEVEL级%ATTACK_TYPE生物",
"vcmi.adventureMap.monsterMeleeType" : "近战",
"vcmi.adventureMap.monsterRangedType" : "远程",
"vcmi.adventureMap.search.hover" : "搜索地图物体",
"vcmi.adventureMap.search.help" : "选择要再地图上搜索的物体。",
"vcmi.adventureMap.confirmRestartGame" : "你想要重新开始游戏吗?",
"vcmi.adventureMap.noTownWithMarket" : "没有足够的市场。",
@@ -40,6 +42,12 @@
"vcmi.heroOverview.secondarySkills" : "初始技能",
"vcmi.heroOverview.spells" : "魔法",
"vcmi.quickExchange.moveUnit" : "移动生物",
"vcmi.quickExchange.moveAllUnits" : "移动所有生物",
"vcmi.quickExchange.swapAllUnits" : "交换生物",
"vcmi.quickExchange.moveAllArtifacts" : "移动所有宝物",
"vcmi.quickExchange.swapAllArtifacts" : "交换宝物",
"vcmi.radialWheel.mergeSameUnit" : "合并相同生物",
"vcmi.radialWheel.fillSingleUnit" : "单个生物填充空格",
"vcmi.radialWheel.splitSingleUnit" : "分割单个生物",
@@ -59,8 +67,25 @@
"vcmi.radialWheel.moveDown" : "下移",
"vcmi.radialWheel.moveBottom" : "移到底端",
"vcmi.randomMap.description" : "这是随机生成的地图。\\n模版为%s,大小为%dx%d,层数为%d,人类玩家数量为%d,电脑玩家数量为%d,水域面积%s,怪物等级%s,VCMI地图",
"vcmi.randomMap.description.isHuman" : ", %s为人类玩家",
"vcmi.randomMap.description.townChoice" : ", %s的城镇选择为%s",
"vcmi.randomMap.description.water.none" : "无",
"vcmi.randomMap.description.water.normal" : "普通",
"vcmi.randomMap.description.water.islands" : "岛屿",
"vcmi.randomMap.description.monster.weak" : "弱",
"vcmi.randomMap.description.monster.normal" : "普通",
"vcmi.randomMap.description.monster.strong" : "强",
"vcmi.spellBook.search" : "搜索中...",
"vcmi.spellResearch.canNotAfford" : "你没有足够的资源来将{%SPELL1}替换为{%SPELL2}。但你依然可以弃掉此法术,继续法术研究。",
"vcmi.spellResearch.comeAgain" : "今日已研究过法术,请明日再来。",
"vcmi.spellResearch.pay" : "你想将{%SPELL1}替换为{%SPELL2}吗?或者弃掉此法术,继续法术研究?",
"vcmi.spellResearch.research" : "研究此法术",
"vcmi.spellResearch.skip" : "跳过此法术",
"vcmi.spellResearch.abort" : "中止",
"vcmi.mainMenu.serverConnecting" : "连接中...",
"vcmi.mainMenu.serverAddressEnter" : "使用地址:",
"vcmi.mainMenu.serverConnectionFailed" : "连接失败",
@@ -145,10 +170,12 @@
"vcmi.client.errors.invalidMap" : "{非法地图或战役}\n\n启动游戏失败,选择的地图或者战役,无效或被污染。原因:\n%s",
"vcmi.client.errors.missingCampaigns" : "{找不到数据文件}\n\n没有找到战役数据文件!你可能使用了不完整或损坏的英雄无敌3数据文件,请重新安装数据文件。",
"vcmi.server.errors.disconnected" : "{网络错误}\n\n与游戏服务器的连接已断开!",
"vcmi.server.errors.playerLeft" : "{玩家离开}\n\n%s玩家已断开游戏!", //%s -> player color
"vcmi.server.errors.existingProcess" : "一个VCMI进程已经在运行,启动新进程前请结束它。",
"vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}",
"vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}",
"vcmi.server.errors.modNoDependency" : "读取mod包 {'%s'}失败!\n 需要的mod {'%s'} 没有安装或无效!\n",
"vcmi.server.errors.modDependencyLoop" : "读取mod包 {'%s'}失败!\n 这个mod可能存在循环(软)依赖!",
"vcmi.server.errors.modConflict" : "读取的mod包 {'%s'}无法运行!\n 与另一个mod {'%s'}冲突!\n",
"vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!",
@@ -342,6 +369,13 @@
"vcmi.heroWindow.openCommander.help" : "显示该英雄指挥官详细信息",
"vcmi.heroWindow.openBackpack.hover" : "开启宝物背包界面",
"vcmi.heroWindow.openBackpack.help" : "用更大的界面显示所有获得的宝物",
"vcmi.heroWindow.sortBackpackByCost.hover" : "按价格排序",
"vcmi.heroWindow.sortBackpackByCost.help" : "将行囊里的宝物按价格排序。.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "按装备槽排序",
"vcmi.heroWindow.sortBackpackBySlot.help" : "将行囊里的宝物按装备槽排序。",
"vcmi.heroWindow.sortBackpackByClass.hover" : "按类型排序",
"vcmi.heroWindow.sortBackpackByClass.help" : "将行囊里的宝物按装备槽排序:低级宝物、中级宝物、高级宝物、圣物。",
"vcmi.heroWindow.fusingArtifact.fusing" : "你已拥有融合%s所需的全部组件,想现在进行融合吗?{所有组件在融合后将被消耗。}",
"vcmi.tavernWindow.inviteHero" : "邀请英雄",
@@ -670,5 +704,7 @@
"core.bonus.DISINTEGRATE.name": "解体",
"core.bonus.DISINTEGRATE.description": "死亡后不会留下尸体",
"core.bonus.INVINCIBLE.name": "无敌",
"core.bonus.INVINCIBLE.description": "不受任何效果影响"
"core.bonus.INVINCIBLE.description": "不受任何效果影响",
"core.bonus.PRISM_HEX_ATTACK_BREATH.name": "棱光吐息",
"core.bonus.PRISM_HEX_ATTACK_BREATH.description": "攻击后向三方向扩散攻击"
}

View File

@@ -3,20 +3,20 @@
"vcmi.adventureMap.monsterThreat.levels.0" : "Bez námahy",
"vcmi.adventureMap.monsterThreat.levels.1" : "Velmi slabá",
"vcmi.adventureMap.monsterThreat.levels.2" : "Slabá",
"vcmi.adventureMap.monsterThreat.levels.3" : "Trochu slabší",
"vcmi.adventureMap.monsterThreat.levels.4" : "Podobná",
"vcmi.adventureMap.monsterThreat.levels.5" : "Trochu silnější",
"vcmi.adventureMap.monsterThreat.levels.3" : "O něco slabší",
"vcmi.adventureMap.monsterThreat.levels.4" : "Rovnocenná",
"vcmi.adventureMap.monsterThreat.levels.5" : "O něco silnější",
"vcmi.adventureMap.monsterThreat.levels.6" : "Silná",
"vcmi.adventureMap.monsterThreat.levels.7" : "Velmi silná",
"vcmi.adventureMap.monsterThreat.levels.8" : "Výzva",
"vcmi.adventureMap.monsterThreat.levels.9" : "Převažující",
"vcmi.adventureMap.monsterThreat.levels.10" : "Smrtelná",
"vcmi.adventureMap.monsterThreat.levels.11" : "Nemožná",
"vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL %TOWN %ATTACK_TYPE jednotka",
"vcmi.adventureMap.monsterMeleeType" : "útok zblízka",
"vcmi.adventureMap.monsterRangedType" : "útok na dálku",
"vcmi.adventureMap.search.hover" : "Prohledat mapový objekt",
"vcmi.adventureMap.search.help" : "Vyberte objekt na mapě pro prohledání.",
"vcmi.adventureMap.monsterThreat.levels.9" : "Převaha",
"vcmi.adventureMap.monsterThreat.levels.10" : "Smrtící",
"vcmi.adventureMap.monsterThreat.levels.11" : "Nehratelná",
"vcmi.adventureMap.monsterLevel" : "\n\nÚroveň %LEVEL, %TOWN\nJednotka %ATTACK_TYPE",
"vcmi.adventureMap.monsterMeleeType" : "útočí zblízka",
"vcmi.adventureMap.monsterRangedType" : "útočí na dálku",
"vcmi.adventureMap.search.hover" : "Prohledat objekt",
"vcmi.adventureMap.search.help" : "Vyberte objekt na mapě k prohledání.",
"vcmi.adventureMap.confirmRestartGame" : "Jste si jisti, že chcete restartovat hru?",
"vcmi.adventureMap.noTownWithMarket" : "Nejsou dostupné žádne tržnice!",
@@ -42,6 +42,12 @@
"vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti",
"vcmi.heroOverview.spells" : "Kouzla",
"vcmi.quickExchange.moveUnit" : "Přesunout jednotku",
"vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky",
"vcmi.quickExchange.swapAllUnits" : "Vyměnit armády",
"vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty",
"vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt",
"vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky",
"vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou",
"vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku",
@@ -60,8 +66,18 @@
"vcmi.radialWheel.moveUp" : "Posunout výše",
"vcmi.radialWheel.moveDown" : "Posunout níže",
"vcmi.radialWheel.moveBottom" : "Přesunout dolů",
"vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa",
"vcmi.randomMap.description.isHuman" : ", %s je lidský hráč",
"vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s",
"vcmi.randomMap.description.water.none" : "žádná",
"vcmi.randomMap.description.water.normal" : "normální",
"vcmi.randomMap.description.water.islands" : "ostrovy",
"vcmi.randomMap.description.monster.weak" : "nízká",
"vcmi.randomMap.description.monster.normal" : "normální",
"vcmi.randomMap.description.monster.strong" : "vysoká",
"vcmi.spellBook.search" : "hledat...",
"vcmi.spellBook.search" : "Hledat",
"vcmi.spellResearch.canNotAfford" : "Nemáte dostatek prostředků k nahrazení {%SPELL1} za {%SPELL2}. Stále však můžete toto kouzlo zrušit a pokračovat ve výzkumu kouzel.",
"vcmi.spellResearch.comeAgain" : "Výzkum už byl dnes proveden. Vraťte se zítra.",
@@ -86,7 +102,7 @@
"vcmi.lobby.sortDate" : "Řadit mapy dle data změny",
"vcmi.lobby.backToLobby" : "Vrátit se do lobby",
"vcmi.lobby.author" : "Autor",
"vcmi.lobby.handicap" : "Handicap",
"vcmi.lobby.handicap" : "Postih",
"vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).",
"vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.",
"vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.",
@@ -215,7 +231,7 @@
"vcmi.systemOptions.resolutionButton.hover" : "Rozlišení: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Vybrat rozlišení}\n\nZmění rozlišení herní obrazovky.",
"vcmi.systemOptions.resolutionMenu.hover" : "Vybrat rozlišení",
"vcmi.systemOptions.resolutionMenu.help" : "Změnit rozlišení herní obrazovky.",
"vcmi.systemOptions.resolutionMenu.help" : "Změní rozlišení herní obrazovky.",
"vcmi.systemOptions.scalingButton.hover" : "Škálování rozhraní: %p%",
"vcmi.systemOptions.scalingButton.help" : "{Škálování rozhraní}\n\nZmění škálování herního rozhraní",
"vcmi.systemOptions.scalingMenu.hover" : "Vybrat škálování rozhraní",
@@ -244,14 +260,14 @@
"vcmi.adventureOptions.forceMovementInfo.help" : "{Vždy zobrazit cenu pohybu}\n\nVždy zobrazit informace o bodech pohybu v panelu informací. (Místo zobrazení pouze při stisknuté klávese ALT).",
"vcmi.adventureOptions.showGrid.hover" : "Zobrazit mřížku",
"vcmi.adventureOptions.showGrid.help" : "{Zobrazit mřížku}\n\nZobrazit překrytí mřížkou, zvýrazňuje hranice mezi dlaždicemi mapy světa.",
"vcmi.adventureOptions.borderScroll.hover" : "Posouvání okraji",
"vcmi.adventureOptions.borderScroll.help" : "{Posouvání okraji}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.",
"vcmi.adventureOptions.borderScroll.hover" : "Posouvání okrajem obrazovky",
"vcmi.adventureOptions.borderScroll.help" : "{Posouvání okrajem obrazovky}\n\nPosouvat mapu světa, když je kurzor na okraji obrazovky. Může být zakázáno držením klávesy CTRL.",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Správa jednotek v informačním panelu",
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Správa jednotek v informačním panelu}\n\nUmožňuje přeskupovat jednotky v informačním panelu namísto procházení standardních informací.",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Posouvání mapy levým kliknutím",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Posouvání mapy levým kliknutím}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.",
"vcmi.adventureOptions.rightButtonDrag.hover" : "Přetahování pravým tlačítkem",
"vcmi.adventureOptions.rightButtonDrag.help" : "{Přetahování pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.",
"vcmi.adventureOptions.leftButtonDrag.hover" : "Posun levým tlač.",
"vcmi.adventureOptions.leftButtonDrag.help" : "{Posun levým tlačítkem}\n\nPosouvání mapy tažením myši se stisknutým levým tlačítkem.",
"vcmi.adventureOptions.rightButtonDrag.hover" : "Posun pravým tlač.",
"vcmi.adventureOptions.rightButtonDrag.help" : "{Posun pravým tlačítkem}\n\nKdyž je povoleno, pohyb myší se stisknutým pravým tlačítkem bude posouvat pohled na mapě dobrodružství.",
"vcmi.adventureOptions.smoothDragging.hover" : "Plynulé posouvání mapy",
"vcmi.adventureOptions.smoothDragging.help" : "{Plynulé posouvání mapy}\n\nPokud je tato možnost aktivována, posouvání mapy bude plynulé.",
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Přeskočit efekty mizení",
@@ -288,8 +304,8 @@
"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Zobrazit okno statistik hrdinů}\n\nTrvale zapne okno statistiky hrdinů, které ukazuje hlavní schopnosti a magickou energii.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Přeskočit úvodní hudbu",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Přeskočit úvodní hudbu}\n\nPovolí akce při úvodní hudbě přehrávané při začátku každé bitvy.",
"vcmi.battleOptions.endWithAutocombat.hover": "Ukončit bitvu",
"vcmi.battleOptions.endWithAutocombat.help": "{Ukončit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.",
"vcmi.battleOptions.endWithAutocombat.hover": "Přeskočit bitvu",
"vcmi.battleOptions.endWithAutocombat.help": "{Přeskočit bitvu}\n\nAutomatický boj okamžitě dohraje bitvu do konce.",
"vcmi.battleOptions.showQuickSpell.hover": "Zobrazit rychlý panel kouzel",
"vcmi.battleOptions.showQuickSpell.help": "{Zobrazit rychlý panel kouzel}\n\nZobrazí panel pro rychlý výběr kouzel.",
@@ -308,7 +324,7 @@
"vcmi.battleWindow.damageEstimation.kills" : "%d zahyne",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne",
"vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu",
"vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ",
"vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu",
"vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.",
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
@@ -318,7 +334,7 @@
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!",
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s bylo zabito přesnými zásahy!",
"vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete ukončit bitvu s automatickým bojem?",
"vcmi.battleWindow.endWithAutocombat" : "Opravdu chcete dokončit bitvu automatickým bojem?",
"vcmi.battleResultsWindow.applyResultsLabel" : "Použít výsledek bitvy",
@@ -344,7 +360,7 @@
"vcmi.townStructure.bank.borrow" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Máme pro vás speciální nabídku. Můžete si vzít půjčku 2500 zlata na 5 dní. Každý den budete muset splácet 500 zlata.\"",
"vcmi.townStructure.bank.payBack" : "Vstupujete do banky. Bankéř vás spatří a říká: \"Již jste si vzali půjčku. Nejprve ji splaťte, než si vezmete další.\"",
"vcmi.logicalExpressions.anyOf" : "Něco z následujících:",
"vcmi.logicalExpressions.anyOf" : "Nějaké z následujících:",
"vcmi.logicalExpressions.allOf" : "Všechny následující:",
"vcmi.logicalExpressions.noneOf" : "Žádné z následujících:",
@@ -381,7 +397,7 @@
"vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest",
"vcmi.optionsTab.turnOptions.hover" : "Možnosti tahu",
"vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo tahů a nastavení souběžných tahů",
"vcmi.optionsTab.turnOptions.help" : "Vyberte odpočítávadlo a nastavení souběžných tahů",
"vcmi.optionsTab.chessFieldBase.hover" : "Základní časovač",
"vcmi.optionsTab.chessFieldTurn.hover" : "Časovač tahu",
@@ -404,7 +420,7 @@
"vcmi.optionsTab.simturnsMax.help" : "Hrát souběžně po určený počet dní nebo do setkání s jiným hráčem",
"vcmi.optionsTab.simturnsAI.help" : "{Souběžné tahy AI}\nExperimentální volba. Dovoluje AI hráčům hrát souběžně s lidskými hráči, když jsou souběžné tahy povoleny.",
"vcmi.optionsTab.turnTime.select" : "Vyberte šablonu nastavení časovače",
"vcmi.optionsTab.turnTime.select" : "Šablona nastavení časovače",
"vcmi.optionsTab.turnTime.unlimited" : "Neomezený čas tahu",
"vcmi.optionsTab.turnTime.classic.1" : "Klasický časovač: 1 minuta",
"vcmi.optionsTab.turnTime.classic.2" : "Klasický časovač: 2 minuty",
@@ -419,7 +435,7 @@
"vcmi.optionsTab.turnTime.chess.2" : "Šachová: 02:00 + 01:00 + 00:15 + 00:00",
"vcmi.optionsTab.turnTime.chess.1" : "Šachová: 01:00 + 01:00 + 00:00 + 00:00",
"vcmi.optionsTab.simturns.select" : "Vyberte šablonu souběžných tahů",
"vcmi.optionsTab.simturns.select" : "Šablona souběžných tahů",
"vcmi.optionsTab.simturns.none" : "Bez souběžných tahů",
"vcmi.optionsTab.simturns.tillContactMax" : "Souběžně: Do setkání",
"vcmi.optionsTab.simturns.tillContact1" : "Souběžně: 1 týden, přerušit při setkání",
@@ -687,5 +703,7 @@
"core.bonus.DISINTEGRATE.name": "Rozpad",
"core.bonus.DISINTEGRATE.description": "Po smrti nezůstane žádné tělo",
"core.bonus.INVINCIBLE.name": "Neporazitelný",
"core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem"
}
"core.bonus.INVINCIBLE.description": "Nelze ovlivnit žádným efektem",
"core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Trojitý dech",
"core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Útok trojitým dechem (útok přes 3 směry)"
}

View File

@@ -42,6 +42,12 @@
"vcmi.heroOverview.secondarySkills" : "Secondary Skills",
"vcmi.heroOverview.spells" : "Spells",
"vcmi.quickExchange.moveUnit" : "Move Unit",
"vcmi.quickExchange.moveAllUnits" : "Move All Units",
"vcmi.quickExchange.swapAllUnits" : "Swap Armies",
"vcmi.quickExchange.moveAllArtifacts" : "Move All Artifacts",
"vcmi.quickExchange.swapAllArtifacts" : "Swap Artifact",
"vcmi.radialWheel.mergeSameUnit" : "Merge same creatures",
"vcmi.radialWheel.fillSingleUnit" : "Fill with single creatures",
"vcmi.radialWheel.splitSingleUnit" : "Split off single creature",
@@ -60,6 +66,16 @@
"vcmi.radialWheel.moveUp" : "Move up",
"vcmi.radialWheel.moveDown" : "Move down",
"vcmi.radialWheel.moveBottom" : "Move to bottom",
"vcmi.randomMap.description" : "Map created by the Random Map Generator.\nTemplate was %s, size %dx%d, levels %d, players %d, computers %d, water %s, monster %s, VCMI map",
"vcmi.randomMap.description.isHuman" : ", %s is human",
"vcmi.randomMap.description.townChoice" : ", %s town choice is %s",
"vcmi.randomMap.description.water.none" : "none",
"vcmi.randomMap.description.water.normal" : "normal",
"vcmi.randomMap.description.water.islands" : "islands",
"vcmi.randomMap.description.monster.weak" : "weak",
"vcmi.randomMap.description.monster.normal" : "normal",
"vcmi.randomMap.description.monster.strong" : "strong",
"vcmi.spellBook.search" : "search...",
@@ -159,6 +175,7 @@
"vcmi.server.errors.modsToEnable" : "{Following mods are required}",
"vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}",
"vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n",
"vcmi.server.errors.modDependencyLoop" : "Failed to load mod {'%s'}!\n It maybe in a (soft) dependency loop.",
"vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n",
"vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!",
@@ -535,7 +552,9 @@
"core.seerhut.quest.reachDate.visit.3" : "Closed till %s.",
"core.seerhut.quest.reachDate.visit.4" : "Closed till %s.",
"core.seerhut.quest.reachDate.visit.5" : "Closed till %s.",
"mapObject.core.hillFort.object.description" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town.",
"core.bonus.ADDITIONAL_ATTACK.name": "Double Strike",
"core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice",
"core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations",
@@ -689,5 +708,7 @@
"core.bonus.INVINCIBLE.name": "Invincible",
"core.bonus.INVINCIBLE.description": "Cannot be affected by anything",
"core.bonus.MECHANICAL.name": "Mechanical",
"core.bonus.MECHANICAL.description": "Immunity to many effects, repairable"
"core.bonus.MECHANICAL.description": "Immunity to many effects, repairable",
"core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Prism Breath",
"core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Prism Breath Attack (three directions)"
}

View File

@@ -532,7 +532,9 @@
"core.seerhut.quest.reachDate.visit.3" : "Zamknięte do %s.",
"core.seerhut.quest.reachDate.visit.4" : "Zamknięte do %s.",
"core.seerhut.quest.reachDate.visit.5" : "Zamknięte do %s.",
"mapObject.core.hillFort.object.description" : "Ulepsza jednostki. Koszt ulepszenia dla poziomów 1 - 4 jest bardziej korzystny niż w mieście.",
"core.bonus.ADDITIONAL_ATTACK.name": "Podwójne Uderzenie",
"core.bonus.ADDITIONAL_ATTACK.description": "Atakuje dwa razy",
"core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowy odwet",

View File

@@ -15,6 +15,8 @@
"vcmi.adventureMap.monsterLevel" : "\n\nNível %LEVEL, unidade %TOWN de ataque %ATTACK_TYPE",
"vcmi.adventureMap.monsterMeleeType" : "corpo a corpo",
"vcmi.adventureMap.monsterRangedType" : "à distância",
"vcmi.adventureMap.search.hover" : "Procurar objeto no mapa",
"vcmi.adventureMap.search.help" : "Selecione o objeto para procurar no mapa.",
"vcmi.adventureMap.confirmRestartGame" : "Tem certeza de que deseja reiniciar o jogo?",
"vcmi.adventureMap.noTownWithMarket" : "Não há mercados disponíveis!",
@@ -40,6 +42,12 @@
"vcmi.heroOverview.secondarySkills" : "Habilid. Secundárias",
"vcmi.heroOverview.spells" : "Feitiços",
"vcmi.quickExchange.moveUnit" : "Mover Unidade",
"vcmi.quickExchange.moveAllUnits" : "Mover Todas as Unidades",
"vcmi.quickExchange.swapAllUnits" : "Trocar Exércitos",
"vcmi.quickExchange.moveAllArtifacts" : "Mover Todos os Artefatos",
"vcmi.quickExchange.swapAllArtifacts" : "Trocar Artefato",
"vcmi.radialWheel.mergeSameUnit" : "Mesclar criaturas iguais",
"vcmi.radialWheel.fillSingleUnit" : "Preencher com criaturas únicas",
"vcmi.radialWheel.splitSingleUnit" : "Dividir uma criatura única",
@@ -58,6 +66,16 @@
"vcmi.radialWheel.moveUp" : "Mover para cima",
"vcmi.radialWheel.moveDown" : "Mover para baixo",
"vcmi.radialWheel.moveBottom" : "Mover para o fundo",
"vcmi.randomMap.description" : "Mapa criado pelo Gerador de Mapas Aleatórios.\nO modelo foi %s, tamanho %dx%d, níveis %d, jogadores %d, computadores %d, água %s, monstros %s, mapa VCMI",
"vcmi.randomMap.description.isHuman" : ", %s é humano",
"vcmi.randomMap.description.townChoice" : ", a escolha de cidade de %s é %s",
"vcmi.randomMap.description.water.none" : "nenhuma",
"vcmi.randomMap.description.water.normal" : "normal",
"vcmi.randomMap.description.water.islands" : "ilhas",
"vcmi.randomMap.description.monster.weak" : "fraco",
"vcmi.randomMap.description.monster.normal" : "normal",
"vcmi.randomMap.description.monster.strong" : "forte",
"vcmi.spellBook.search" : "Procurar...",
@@ -94,8 +112,8 @@
"vcmi.lobby.login.connecting" : "Conectando...",
"vcmi.lobby.login.error" : "Erro de conexão: %s",
"vcmi.lobby.login.create" : "Nova Conta",
"vcmi.lobby.login.login" : "Login",
"vcmi.lobby.login.as" : "Logar como %s",
"vcmi.lobby.login.login" : "Entrar",
"vcmi.lobby.login.as" : "Entrar como %s",
"vcmi.lobby.header.rooms" : "Salas de Jogo - %d",
"vcmi.lobby.header.channels" : "Canais de Bate-papo",
"vcmi.lobby.header.chat.global" : "Bate-papo Global do Jogo - %s", // %s -> nome do idioma
@@ -156,9 +174,9 @@
"vcmi.server.errors.existingProcess" : "Outro processo do servidor VCMI está em execução. Por favor, termine-o antes de iniciar um novo jogo.",
"vcmi.server.errors.modsToEnable" : "{Os seguintes mods são necessários}",
"vcmi.server.errors.modsToDisable" : "{Os seguintes mods devem ser desativados}",
"vcmi.server.errors.modNoDependency" : "Falha ao carregar o mod {'%s'}!\n Ele depende do mod {'%s'} que não está ativo!\n",
"vcmi.server.errors.modConflict" : "Falha ao carregar o mod {'%s'}!\n Conflita com o mod ativo {'%s'}!\n",
"vcmi.server.errors.unknownEntity" : "Falha ao carregar o salvamento! Entidade desconhecida '%s' encontrada no jogo salvo! O salvamento pode não ser compatível com a versão atualmente instalada dos mods!",
"vcmi.server.errors.modNoDependency" : "Falha ao carregar o mod {'%s'}!\n Ele depende do mod {'%s'}, que não está ativo!\n",
"vcmi.server.errors.modConflict" : "Falha ao carregar o mod {'%s'}!\n Conflito com o mod ativo {'%s'}!\n",
"vcmi.server.errors.unknownEntity" : "Falha ao carregar o jogo salvo! Entidade desconhecida '%s' encontrada no jogo salvo! O jogo salvo pode não ser compatível com a versão atualmente instalada dos mods!",
"vcmi.dimensionDoor.seaToLandError" : "Não é possível teleportar do mar para a terra ou vice-versa com uma Porta Dimensional.",
@@ -211,11 +229,11 @@
"vcmi.systemOptions.fullscreenExclusive.hover" : "Tela Cheia (exclusiva)",
"vcmi.systemOptions.fullscreenExclusive.help" : "{Tela Cheia}\n\nSe selecionado, o VCMI será executado em modo de tela cheia exclusiva. Neste modo, o jogo mudará a resolução do monitor para a resolução selecionada.",
"vcmi.systemOptions.resolutionButton.hover" : "Resolução: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Selecionar Resolução}\n\nMuda a resolução da tela do jogo.",
"vcmi.systemOptions.resolutionButton.help" : "{Seleciona a Resolução}\n\nMuda a resolução da tela do jogo.",
"vcmi.systemOptions.resolutionMenu.hover" : "Selecionar Resolução",
"vcmi.systemOptions.resolutionMenu.help" : "Muda a resolução da tela do jogo.",
"vcmi.systemOptions.scalingButton.hover" : "Escala da Interface: %p%",
"vcmi.systemOptions.scalingButton.help" : "{Escala da Interface}\n\nAlterar escala da interface do jogo.",
"vcmi.systemOptions.scalingButton.help" : "{Escala da Interface}\n\nAltera a escala da interface do jogo.",
"vcmi.systemOptions.scalingMenu.hover" : "Selecionar Escala da Interface",
"vcmi.systemOptions.scalingMenu.help" : "Altera a escala da interface do jogo.",
"vcmi.systemOptions.longTouchButton.hover" : "Intervalo de Toque Longo: %d ms", // Translation note: "ms" = "milliseconds"
@@ -224,15 +242,15 @@
"vcmi.systemOptions.longTouchMenu.help" : "Muda a duração do intervalo de toque longo.",
"vcmi.systemOptions.longTouchMenu.entry" : "%d milissegundos",
"vcmi.systemOptions.framerateButton.hover" : "Mostrar FPS",
"vcmi.systemOptions.framerateButton.help" : "{Mostrar FPS}\n\nAtiva ou desativa a visibilidade do contador de Quadros Por Segundo no canto da janela do jogo.",
"vcmi.systemOptions.hapticFeedbackButton.hover" : "Resposta tátil",
"vcmi.systemOptions.hapticFeedbackButton.help" : "{Resposta tátil}\n\nAtiva ou desativa a resposta tátil nos toques na tela.",
"vcmi.systemOptions.framerateButton.help" : "{Mostra os Quadros Por Segundo}\n\nAtiva ou desativa a visibilidade do contador de Quadros Por Segundo no canto da janela do jogo.",
"vcmi.systemOptions.hapticFeedbackButton.hover" : "Resposta Tátil",
"vcmi.systemOptions.hapticFeedbackButton.help" : "{Resposta Tátil}\n\nAtiva ou desativa a resposta tátil nos toques na tela.",
"vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Aprimoramentos da Interface",
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Aprimoramentos da Interface}\n\nAtiva ou desativa várias melhorias de interface. Como um botão de mochila etc. Desative para ter uma experiência mais clássica.",
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Aprimoramentos da Interface}\n\nAtiva ou desativa várias melhorias de interface, como um botão de mochila etc. Desative para ter uma experiência mais clássica.",
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Grimório Grande",
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Grimório Grande}\n\nAtiva um grimório maior que comporta mais feitiços por página. A animação de mudança de página do grimório não funciona com esta configuração ativada.",
"vcmi.systemOptions.audioMuteFocus.hover" : "Silenciar na inatividade",
"vcmi.systemOptions.audioMuteFocus.help" : "{Silenciar na inatividade}\n\nSilencia o áudio quando a janela está inativa. As exceções são mensagens no jogo e som de novo turno.",
"vcmi.systemOptions.audioMuteFocus.hover" : "Silenciar na Inatividade",
"vcmi.systemOptions.audioMuteFocus.help" : "{Silencia o Áudio na Inatividade}\n\nSilencia o áudio quando a janela está inativa. As exceções são mensagens no jogo e som de novo turno.",
"vcmi.adventureOptions.infoBarPick.hover" : "Mensagens no Painel de Informações",
"vcmi.adventureOptions.infoBarPick.help" : "{Mostra as Mensagens no Painel de Informações}\n\nSempre que possível, as mensagens do jogo provenientes de objetos no mapa serão mostradas no painel de informações, em vez de aparecerem em uma janela separada.",
@@ -241,7 +259,7 @@
"vcmi.adventureOptions.forceMovementInfo.hover" : "Sempre Mostrar o Custo de Movimento",
"vcmi.adventureOptions.forceMovementInfo.help" : "{Sempre Mostrar o Custo de Movimento}\n\nSempre mostra os dados de pontos de movimento na barra de status (em vez de apenas visualizá-los enquanto você mantém pressionada a tecla ALT).",
"vcmi.adventureOptions.showGrid.hover" : "Mostrar Grade",
"vcmi.adventureOptions.showGrid.help" : "{Mostrar Grade}\n\nMostra a sobreposição da grade, destacando as fronteiras entre as telhas do mapa de aventura.",
"vcmi.adventureOptions.showGrid.help" : "{Mostra a Grade}\n\nMostra a sobreposição da grade, destacando as fronteiras entre os hexágonos do mapa de aventura.",
"vcmi.adventureOptions.borderScroll.hover" : "Rolagem de Borda",
"vcmi.adventureOptions.borderScroll.help" : "{Rolagem de Borda}\n\nFaz o mapa de aventura rolar quando o cursor está adjacente à borda da janela. Pode ser desativado mantendo pressionada a tecla CTRL.",
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Gerenciar Criaturas no Painel de Info.",
@@ -253,7 +271,7 @@
"vcmi.adventureOptions.smoothDragging.hover" : "Arrastar Suavemente o Mapa",
"vcmi.adventureOptions.smoothDragging.help" : "{Arrasta o Mapa Suavemente}\n\nQuando ativado, o arrasto do mapa tem um efeito de movimento moderno.",
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Omitir Efeitos de Desvanecimento",
"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Omitir Efeitos de Desvanecimento}\n\nQuando ativado, omite o desvanecimento de objetos e efeitos semelhantes (coleta de recursos, embarque em navios etc). Torna a interface do usuário mais reativa em alguns casos em detrimento da estética. Especialmente útil em jogos PvP. Para obter velocidade de movimento máxima, o pulo está ativo independentemente desta configuração.",
"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Omite os Efeitos de Desvanecimento}\n\nQuando ativado, omite o desvanecimento de objetos e efeitos semelhantes (coleta de recursos, embarque em navios etc.). Torna a interface do usuário mais reativa em alguns casos, em detrimento da estética. Especialmente útil em jogos PvP. Para obter velocidade de movimento máxima, o pulo está ativo independentemente desta configuração.",
"vcmi.adventureOptions.mapScrollSpeed1.hover": "",
"vcmi.adventureOptions.mapScrollSpeed5.hover": "",
"vcmi.adventureOptions.mapScrollSpeed6.hover": "",
@@ -261,7 +279,7 @@
"vcmi.adventureOptions.mapScrollSpeed5.help" : "Define a velocidade de rolagem do mapa como muito rápida.",
"vcmi.adventureOptions.mapScrollSpeed6.help" : "Define a velocidade de rolagem do mapa como instantânea.",
"vcmi.adventureOptions.hideBackground.hover" : "Ocultar Fundo",
"vcmi.adventureOptions.hideBackground.help" : "{Ocultar Fundo}\n\nOculta o mapa de aventura no fundo e mostra uma textura em vez disso.",
"vcmi.adventureOptions.hideBackground.help" : "{Oculta o Fundo}\n\nOculta o mapa de aventura no fundo e mostra uma textura em vez disso.",
"vcmi.battleOptions.queueSizeLabel.hover": "Mostrar Fila de Ordem de Turno",
"vcmi.battleOptions.queueSizeNoneButton.hover": "DESL.",
@@ -269,7 +287,7 @@
"vcmi.battleOptions.queueSizeSmallButton.hover": "PEQU.",
"vcmi.battleOptions.queueSizeBigButton.hover": "GRAN.",
"vcmi.battleOptions.queueSizeNoneButton.help": "Não exibir Fila de Ordem de Turno.",
"vcmi.battleOptions.queueSizeAutoButton.help": "Ajusta automaticamente o tamanho da fila de ordem de turno com base na resolução do jogo (o tamanho PEQUENO é usado ao jogar o jogo em uma resolução com altura inferior a 700 pixels, o tamanho GRANDE é usado caso contrário).",
"vcmi.battleOptions.queueSizeAutoButton.help": "Ajusta automaticamente o tamanho da fila de ordem de turno com base na resolução do jogo (o tamanho PEQUENO é usado ao jogar em uma resolução com altura inferior a 700 pixels; o tamanho GRANDE é usado caso contrário).",
"vcmi.battleOptions.queueSizeSmallButton.help": "Define o tamanho da fila de ordem de turno como PEQUENO.",
"vcmi.battleOptions.queueSizeBigButton.help": "Define o tamanho da fila de ordem de turno como GRANDE (não suportado se a altura da resolução do jogo for inferior a 700 pixels).",
"vcmi.battleOptions.animationsSpeed1.hover": "",
@@ -281,7 +299,7 @@
"vcmi.battleOptions.movementHighlightOnHover.hover": "Destacar Movimento ao Passar o Mouse",
"vcmi.battleOptions.movementHighlightOnHover.help": "{Destaca o Movimento ao Passar o Mouse}\n\nDestaca o alcance de movimento da unidade quando você passa o mouse sobre ela.",
"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Mostrar Limites de Alcance de Atiradores",
"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Mostra o Limites de Alcance dos Atiradores ao Passar o Mouse}\n\nMostra os limites de alcance do atirador quando você passa o mouse sobre ele.",
"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Mostra os Limites de Alcance dos Atiradores ao Passar o Mouse}\n\nMostra os limites de alcance do atirador quando você passa o mouse sobre ele.",
"vcmi.battleOptions.showStickyHeroInfoWindows.hover": "Mostrar Janelas de Estatísticas de Heróis",
"vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Mostra as Janelas de Estatísticas de Heróis}\n\nAlterna permanentemente as janelas de estatísticas dos heróis que mostram estatísticas primárias e pontos de feitiço.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Pular Música de Introdução",
@@ -289,7 +307,7 @@
"vcmi.battleOptions.endWithAutocombat.hover": "Terminar a batalha",
"vcmi.battleOptions.endWithAutocombat.help": "{Termina a batalha}\n\nO Combate Automático reproduz a batalha até o final instantâneo.",
"vcmi.battleOptions.showQuickSpell.hover": "Mostrar Painel de Feitiço Rápido",
"vcmi.battleOptions.showQuickSpell.help": "{Mostrar Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços",
"vcmi.battleOptions.showQuickSpell.help": "{Mostra o Painel de Feitiço Rápido}\n\nMostra um painel para seleção rápida de feitiços.",
"vcmi.adventureMap.revisitObject.hover" : "Revisitar Objeto",
"vcmi.adventureMap.revisitObject.help" : "{Revisitar Objeto}\n\nSe um herói estiver atualmente em um Objeto do Mapa, ele pode revisitar o local.",
@@ -334,9 +352,9 @@
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Mostrar Produção Semanal de Criaturas",
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Mostrar Produção Semanal de Criaturas}\n\nMostra a produção semanal das criaturas em vez da quantidade disponível no resumo da cidade (canto inferior esquerdo da tela da cidade).",
"vcmi.otherOptions.compactTownCreatureInfo.hover" : "Informações Compactas de Criaturas",
"vcmi.otherOptions.compactTownCreatureInfo.help" : "{Informações Compactas de Criaturas}\n\nMostra informações menores para criaturas da cidade no resumo da cidade (canto inferior esquerdo da tela da cidade).",
"vcmi.otherOptions.compactTownCreatureInfo.help" : "{Informações Compactas de Criaturas}\n\nMostra informações reduzidas para criaturas da cidade no resumo da cidade (canto inferior esquerdo da tela da cidade).",
"vcmi.townHall.missingBase" : "A construção base %s deve ser construída primeiro",
"vcmi.townHall.missingBase" : "A construção base %s deve ser feita primeiro.",
"vcmi.townHall.noCreaturesToRecruit" : "Não há criaturas para recrutar!",
"vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode pegar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"",
@@ -351,11 +369,12 @@
"vcmi.heroWindow.openBackpack.hover" : "Abrir janela da mochila de artefatos",
"vcmi.heroWindow.openBackpack.help" : "Abre a janela que facilita o gerenciamento da mochila de artefatos.",
"vcmi.heroWindow.sortBackpackByCost.hover" : "Ordenar por custo",
"vcmi.heroWindow.sortBackpackByCost.help" : "Ordenar artefatos na mochila por custo.",
"vcmi.heroWindow.sortBackpackByCost.help" : "Ordena artefatos na mochila por custo.",
"vcmi.heroWindow.sortBackpackBySlot.hover" : "Ordenar por espaço",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Ordenar artefatos na mochila por espaço equipado.",
"vcmi.heroWindow.sortBackpackBySlot.help" : "Ordena artefatos na mochila por espaço equipado.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Ordenar por classe",
"vcmi.heroWindow.sortBackpackByClass.help" : "Ordenar artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia",
"vcmi.heroWindow.sortBackpackByClass.help" : "Ordena artefatos na mochila por classe de artefato. Tesouro, Menor, Maior, Relíquia.",
"vcmi.heroWindow.fusingArtifact.fusing" : "Você possui todos os componentes necessários para a fusão de %s. Deseja realizar a fusão? {Todos os componentes serão consumidos após a fusão.}",
"vcmi.tavernWindow.inviteHero" : "Convidar herói",
@@ -366,7 +385,7 @@
"vcmi.creatureWindow.showSkills.hover" : "Alternar para visualização de habilidades",
"vcmi.creatureWindow.showSkills.help" : "Exibe todas as habilidades aprendidas do comandante.",
"vcmi.creatureWindow.returnArtifact.hover" : "Devolver artefato",
"vcmi.creatureWindow.returnArtifact.help" : "Clique neste botão para devolver o artefato para a mochila do herói.",
"vcmi.creatureWindow.returnArtifact.help" : "Clique neste botão para devolver o artefato à mochila do herói.",
"vcmi.questLog.hideComplete.hover" : "Ocultar missões completas",
"vcmi.questLog.hideComplete.help" : "Oculta todas as missões completas.",
@@ -378,7 +397,7 @@
"vcmi.randomMapTab.widgets.roadTypesLabel" : "Tipos de Estrada",
"vcmi.optionsTab.turnOptions.hover" : "Opções de Turno",
"vcmi.optionsTab.turnOptions.help" : "Selecione as opções de cronômetro do turno e turnos simultâneos",
"vcmi.optionsTab.turnOptions.help" : "Selecione as opções de cronômetro do turno e turnos simultâneos.",
"vcmi.optionsTab.chessFieldBase.hover" : "Cronômetro Base",
"vcmi.optionsTab.chessFieldTurn.hover" : "Cronôm. Turno",
@@ -388,8 +407,8 @@
"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Usado fora de combate ou quando o {Cronômetro da Batalha} se esgota. Restaurado a cada turno. O tempo restante é adicionado ao {Tempo Base} no final do turno.",
"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Usado fora de combate ou quando o {Cronômetro da Batalha} se esgota. Restaurado a cada turno. Qualquer tempo não utilizado é perdido.",
"vcmi.optionsTab.chessFieldBattle.help" : "Usado em batalhas com a IA ou em combates PvP quando o {Cronômetro da Unidade} se esgota. Restaurado no início de cada combate.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Usado ao selecionar ação da unidade em combates PvP. O tempo restante é adicionado ao {Cronômetro da Batalha} no final do turno da unidade.",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Usado ao selecionar ação da unidade em combates PvP. Restaurado no início do turno de cada unidade. Qualquer tempo não utilizado é perdido.",
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Usado ao selecionar a ação da unidade em combates PvP. O tempo restante é adicionado ao {Cronômetro da Batalha} no final do turno da unidade.",
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Usado ao selecionar a ação da unidade em combates PvP. Restaurado no início do turno de cada unidade. Qualquer tempo não utilizado é perdido.",
"vcmi.optionsTab.accumulate" : "Acumular",
@@ -520,9 +539,9 @@
"core.seerhut.quest.reachDate.hover.3" : "(Não retorne antes de %s)",
"core.seerhut.quest.reachDate.hover.4" : "(Não retorne antes de %s)",
"core.seerhut.quest.reachDate.hover.5" : "(Não retorne antes de %s)",
"core.seerhut.quest.reachDate.receive.0" : "Estou ocupado. Não volte antes de %s",
"core.seerhut.quest.reachDate.receive.1" : "Estou ocupado. Não volte antes de %s",
"core.seerhut.quest.reachDate.receive.2" : "Estou ocupado. Não volte antes de %s",
"core.seerhut.quest.reachDate.receive.0" : "Estou ocupado. Não volte antes de %s.",
"core.seerhut.quest.reachDate.receive.1" : "Estou ocupado. Não volte antes de %s.",
"core.seerhut.quest.reachDate.receive.2" : "Estou ocupado. Não volte antes de %s.",
"core.seerhut.quest.reachDate.receive.3" : "Fechado até %s.",
"core.seerhut.quest.reachDate.receive.4" : "Fechado até %s.",
"core.seerhut.quest.reachDate.receive.5" : "Fechado até %s.",
@@ -532,7 +551,9 @@
"core.seerhut.quest.reachDate.visit.3" : "Fechado até %s.",
"core.seerhut.quest.reachDate.visit.4" : "Fechado até %s.",
"core.seerhut.quest.reachDate.visit.5" : "Fechado até %s.",
"mapObject.core.hillFort.object.description" : "Atualiza criaturas. O custo de atualização para os níveis 1 a 4 é mais vantajoso do que na cidade associada.",
"core.bonus.ADDITIONAL_ATTACK.name" : "Ataque Duplo",
"core.bonus.ADDITIONAL_ATTACK.description" : "Ataca duas vezes",
"core.bonus.ADDITIONAL_RETALIATION.name" : "Contra-ataques Adicionais",
@@ -570,7 +591,7 @@
"core.bonus.ENCHANTER.name" : "Encantador",
"core.bonus.ENCHANTER.description" : "Pode lançar ${subtype.spell} em massa a cada turno",
"core.bonus.ENCHANTED.name" : "Encantado",
"core.bonus.ENCHANTED.description" : "Afetado por ${subtype.spell} permanente",
"core.bonus.ENCHANTED.description" : "Afetado por ${subtype.spell} permanentemente",
"core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Ignorar Ataque (${val}%)",
"core.bonus.ENEMY_ATTACK_REDUCTION.description" : "Ao ser atacado, ${val}% do ataque do agressor é ignorado",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Ignorar Defesa (${val}%)",
@@ -662,7 +683,7 @@
"core.bonus.SPELL_LIKE_ATTACK.name" : "Ataque Similar a Feitiço",
"core.bonus.SPELL_LIKE_ATTACK.description" : "Ataques com ${subtype.spell}",
"core.bonus.SPELL_RESISTANCE_AURA.name" : "Aura de Resistência",
"core.bonus.SPELL_RESISTANCE_AURA.description" : "Pilhas próximas ganham ${val}% de resistência a magia",
"core.bonus.SPELL_RESISTANCE_AURA.description" : "Pilhas próximas ganham ${val}% de resistência à magia",
"core.bonus.SUMMON_GUARDIANS.name" : "Invocar Guardas",
"core.bonus.SUMMON_GUARDIANS.description" : "No início da batalha, invoca ${subtype.creature} (${val}%)",
"core.bonus.SYNERGY_TARGET.name" : "Alvo Sinergizável",
@@ -675,8 +696,8 @@
"core.bonus.TRANSMUTATION.description" : "${val}% de chance de transformar a unidade atacada em um tipo diferente",
"core.bonus.UNDEAD.name" : "Morto-vivo",
"core.bonus.UNDEAD.description" : "A criatura é um Morto-vivo",
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Contra-ataques Ilimitadas",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Pode contra-atacar contra um número ilimitado de ataques",
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Contra-ataques Ilimitados",
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Pode contra-atacar um número ilimitado de vezes",
"core.bonus.WATER_IMMUNITY.name" : "Imunidade à Água",
"core.bonus.WATER_IMMUNITY.description" : "Imune a todos os feitiços da escola de magia da Água",
"core.bonus.WIDE_BREATH.name" : "Sopro Amplo",
@@ -684,5 +705,27 @@
"core.bonus.DISINTEGRATE.name": "Desintegrar",
"core.bonus.DISINTEGRATE.description": "Nenhum corpo permanece após a morte",
"core.bonus.INVINCIBLE.name": "Invencível",
"core.bonus.INVINCIBLE.description": "Não pode ser afetado por nada"
"core.bonus.INVINCIBLE.description": "Não pode ser afetado por nada",
"spell.core.castleMoat.name": "Fosso",
"spell.core.castleMoatTrigger.name": "Fosso",
"spell.core.catapultShot.name": "Disparo de Catapulta",
"spell.core.cyclopsShot.name": "Tiro de Cerco",
"spell.core.dungeonMoat.name": "Óleo Fervente",
"spell.core.dungeonMoatTrigger.name": "Óleo Fervente",
"spell.core.fireWallTrigger.name": "Parede de Fogo",
"spell.core.firstAid.name": "Primeiros Socorros",
"spell.core.fortressMoat.name": "Alcatrão Fervente",
"spell.core.fortressMoatTrigger.name": "Alcatrão Fervente",
"spell.core.infernoMoat.name": "Lava",
"spell.core.infernoMoatTrigger.name": "Lava",
"spell.core.landMineTrigger.name": "Mina Terrestre",
"spell.core.necropolisMoat.name": "Cemitério",
"spell.core.necropolisMoatTrigger.name": "Cemitério",
"spell.core.rampartMoat.name": "Espraiamento",
"spell.core.rampartMoatTrigger.name": "Espraiamento",
"spell.core.strongholdMoat.name": "Estacas de Madeira",
"spell.core.strongholdMoatTrigger.name": "Estacas de Madeira",
"spell.core.summonDemons.name": "Invocar Demônios",
"spell.core.towerMoat.name": "Mina Terrestre"
}

View File

@@ -203,7 +203,7 @@
"mapObject.core.creatureBank.dragonFlyHive.name" : "Улей летучих змиев",
"mapObject.core.creatureBank.dwarvenTreasury.name" : "Сокровищница гномов",
"mapObject.core.creatureBank.griffinConservatory.name" : "Консерватория грифонов",
"mapObject.core.creatureBank.inpCache.name" : "Яма бесов",
"mapObject.core.creatureBank.impCache.name" : "Яма бесов",
"mapObject.core.creatureBank.medusaStore.name" : "Склады медуз",
"mapObject.core.creatureBank.nagaBank.name" : "Хранилище наг",
"mapObject.core.crypt.crypt.name" : "Склеп",

View File

@@ -0,0 +1,66 @@
{
"core:summonDemons" : {
"name": "Summon Demons"
},
"core:firstAid" : {
"name": "First Aid"
},
"core:catapultShot" : {
"name": "Catapult shot"
},
"core:cyclopsShot" : {
"name": "Siege shot"
},
"core:fireWallTrigger" : {
"name" : "Fire Wall"
},
"core:landMineTrigger" : {
"name" : "Land Mine",
},
"core:castleMoatTrigger" : {
"name": "Moat"
},
"core:castleMoat": {
"name": "Moat"
},
"core:rampartMoatTrigger" : {
"name": "Brambles"
},
"core:rampartMoat": {
"name": "Brambles"
},
"core:towerMoat": {
"name": "Land Mine"
},
"core:infernoMoatTrigger" : {
"name": "Lava"
},
"core:infernoMoat": {
"name": "Lava"
},
"core:necropolisMoatTrigger" : {
"name": "Boneyard"
},
"core:necropolisMoat": {
"name": "Boneyard"
},
"core:dungeonMoatTrigger" : {
"name": "Boiling Oil"
},
"core:dungeonMoat": {
"name": "Boiling Oil"
},
"core:strongholdMoatTrigger" : {
"name": "Wooden Spikes"
},
"core:strongholdMoat": {
"name": "Wooden Spikes"
},
"core:fortressMoatTrigger" : {
"name": "Boiling Tar"
},
"core:fortressMoat": {
"name": "Boiling Tar"
}
}

View File

@@ -15,6 +15,8 @@
"vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN",
"vcmi.adventureMap.monsterMeleeType" : "närstrid",
"vcmi.adventureMap.monsterRangedType" : "fjärrstrid",
"vcmi.adventureMap.search.hover" : "Sök kartobjekt",
"vcmi.adventureMap.search.help" : "Välj objekt för att söka på kartan.",
"vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?",
"vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!",
@@ -356,8 +358,9 @@
"vcmi.heroWindow.sortBackpackBySlot.help" : "Sorterar artefakter i ryggsäcken efter utrustad plats.",
"vcmi.heroWindow.sortBackpackByClass.hover" : "Sortera efter klass",
"vcmi.heroWindow.sortBackpackByClass.help" : "Sorterar artefakter i ryggsäcken efter artefaktklass (skatt, mindre, större, relik)",
"vcmi.heroWindow.fusingArtifact.fusing" : "Du har alla komponenterna som behövs för en sammanslagning av %s. Vill du utföra sammanslagningen? {Alla komponenter kommer att förbrukas vid sammanslagningen.}",
"vcmi.tavernWindow.inviteHero" : "Bjud in hjälte",
"vcmi.tavernWindow.inviteHero" : "Bjud in hjälte",
"vcmi.commanderWindow.artifactMessage" : "Vill du återlämna denna artefakt till hjälten?",
@@ -574,7 +577,7 @@
"core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)",
"core.bonus.ENEMY_ATTACK_REDUCTION.description" : "Ignorerar ${val}% av angriparens attack.",
"core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Förbigå försvar (${val}%)",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "Din attack ignorerar ${val}% av fiendens försvar.",
"core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "Attacker ignorerar ${val}% av fiendens försvar.",
"core.bonus.FIRE_IMMUNITY.name" : "Eld-immunitet",
"core.bonus.FIRE_IMMUNITY.description" : "Immun mot alla eldmagi-trollformler.",
"core.bonus.FIRE_SHIELD.name" : "Eldsköld (${val}%)",
@@ -594,7 +597,7 @@
"core.bonus.GARGOYLE.name" : "Stenfigur",
"core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas.",
"core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från inkommande attacker.",
"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar skadan från fiendens attacker.",
"core.bonus.HATE.name" : "Hatar: ${subtype.creature}",
"core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtype.creature}.",
"core.bonus.HEALER.name" : "Helare",
@@ -644,7 +647,7 @@
"core.bonus.REVENGE.name" : "Hämndlysten",
"core.bonus.REVENGE.description" : "Vållar mer skada om den själv blivit skadad.",
"core.bonus.SHOOTER.name" : "Distans-attack",
"core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd.",
"core.bonus.SHOOTER.description" : "Skjuter/attackerar på avstånd.",
"core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten",
"core.bonus.SHOOTS_ALL_ADJACENT.description" : "Distans-attack drabbar alla inom räckhåll.",
"core.bonus.SOUL_STEAL.name" : "Själtjuv",

View File

@@ -612,18 +612,5 @@
"core.bonus.WIDE_BREATH.name" : "Широкий подих",
"core.bonus.WIDE_BREATH.description" : "Атака широким подихом",
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Обмежена дальність стрільби",
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів",
"vcmi.stackExperience.description" : "» S t a c k E x p e r i e n c e D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",
"vcmi.stackExperience.rank.0" : "Початковий",
"vcmi.stackExperience.rank.1" : "Новачок",
"vcmi.stackExperience.rank.2" : "Підготовлений",
"vcmi.stackExperience.rank.3" : "Досвідчений",
"vcmi.stackExperience.rank.4" : "Випробуваний",
"vcmi.stackExperience.rank.5" : "Ветеран",
"vcmi.stackExperience.rank.6" : "Адепт",
"vcmi.stackExperience.rank.7" : "Експерт",
"vcmi.stackExperience.rank.8" : "Еліта",
"vcmi.stackExperience.rank.9" : "Майстер",
"vcmi.stackExperience.rank.10" : "Профі"
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Не може стріляти по цілях на відстані більше ${val} гексів"
}

View File

@@ -127,6 +127,7 @@
"factions" : [ "config/vcmi/towerFactions" ],
"creatures" : [ "config/vcmi/towerCreature" ],
"spells" : [ "config/vcmi/spells" ],
"translations" : [
"config/vcmi/english.json"

View File

@@ -116,8 +116,8 @@ set(vcmiclientcommon_SRCS
globalLobby/GlobalLobbyWindow.cpp
widgets/Buttons.cpp
widgets/CArtPlace.cpp
widgets/CComponent.cpp
widgets/CComponentHolder.cpp
widgets/CExchangeController.cpp
widgets/CGarrisonInt.cpp
widgets/CreatureCostBox.cpp
@@ -327,8 +327,8 @@ set(vcmiclientcommon_HEADERS
globalLobby/GlobalLobbyWindow.h
widgets/Buttons.h
widgets/CArtPlace.h
widgets/CComponent.h
widgets/CComponentHolder.h
widgets/CExchangeController.h
widgets/CGarrisonInt.h
widgets/CreatureCostBox.h

View File

@@ -185,12 +185,12 @@ void ClientCommandManager::handleRedrawCommand()
GH.windows().totalRedraw();
}
void ClientCommandManager::handleTranslateGameCommand()
void ClientCommandManager::handleTranslateGameCommand(bool onlyMissing)
{
std::map<std::string, std::map<std::string, std::string>> textsByMod;
VLC->generaltexth->exportAllTexts(textsByMod);
VLC->generaltexth->exportAllTexts(textsByMod, onlyMissing);
const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation";
const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / ( onlyMissing ? "translationMissing" : "translation");
boost::filesystem::create_directories(outPath);
for(const auto & modEntry : textsByMod)
@@ -254,13 +254,20 @@ void ClientCommandManager::handleTranslateMapsCommand()
logGlobal->info("Loading campaigns for export");
for (auto const & campaignName : campaignList)
{
loadedCampaigns.push_back(CampaignHandler::getCampaign(campaignName.getName()));
for (auto const & part : loadedCampaigns.back()->allScenarios())
loadedCampaigns.back()->getMap(part, nullptr);
try
{
loadedCampaigns.push_back(CampaignHandler::getCampaign(campaignName.getName()));
for (auto const & part : loadedCampaigns.back()->allScenarios())
loadedCampaigns.back()->getMap(part, nullptr);
}
catch(std::exception & e)
{
logGlobal->warn("Campaign %s is invalid. Message: %s", campaignName.getName(), e.what());
}
}
std::map<std::string, std::map<std::string, std::string>> textsByMod;
VLC->generaltexth->exportAllTexts(textsByMod);
VLC->generaltexth->exportAllTexts(textsByMod, false);
const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / "translation";
boost::filesystem::create_directories(outPath);
@@ -591,7 +598,10 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
handleRedrawCommand();
else if(message=="translate" || message=="translate game")
handleTranslateGameCommand();
handleTranslateGameCommand(false);
else if(message=="translate missing")
handleTranslateGameCommand(true);
else if(message=="translate maps")
handleTranslateMapsCommand();

View File

@@ -46,7 +46,7 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
void handleRedrawCommand();
// Extracts all translateable game texts into Translation directory, separating files on per-mod basis
void handleTranslateGameCommand();
void handleTranslateGameCommand(bool onlyMissing);
// Extracts all translateable texts from maps and campaigns into Translation directory, separating files on per-mod basis
void handleTranslateMapsCommand();

View File

@@ -111,7 +111,7 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
if(key.repeat != 0)
return; // ignore periodic event resends
std::string keyName = SDL_GetKeyName(key.keysym.sym);
std::string keyName = getKeyNameWithModifiers(SDL_GetKeyName(key.keysym.sym));
logGlobal->trace("keyboard: key '%s' released", keyName);
if (SDL_IsTextInputActive() == SDL_TRUE)

View File

@@ -99,7 +99,7 @@ CBonusSelection::CBonusSelection()
int availableSpace = videoButtonActive ? 225 : 285;
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), availableSpace );
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 286, 108), 1);
labelChooseBonus = std::make_shared<CLabel>(475, 432, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]);
groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));

View File

@@ -188,7 +188,7 @@ void CLobbyScreen::toggleMode(bool host)
return;
auto buttonColor = host ? Colors::WHITE : Colors::ORANGE;
buttonSelect->setTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor);
buttonSelect->setTextOverlay(" " + CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor);
buttonOptions->setTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor);
if (buttonTurnOptions)
@@ -199,7 +199,7 @@ void CLobbyScreen::toggleMode(bool host)
if(buttonRMG)
{
buttonRMG->setTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
buttonRMG->setTextOverlay(" " + CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
buttonRMG->block(!host);
}
buttonSelect->block(!host);

View File

@@ -187,8 +187,8 @@ InfoCard::InfoCard()
iconsVictoryCondition = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRVICT"), 0, 0, 24, 302);
iconsLossCondition = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRLOSS"), 0, 0, 24, 359);
labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, "", 290);
labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, "", 290);
labelDifficulty = std::make_shared<CLabel>(62, 472, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
labelDifficultyPercent = std::make_shared<CLabel>(311, 472, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);

View File

@@ -540,7 +540,7 @@ CMultiPlayers::CMultiPlayers(const std::vector<std::string> & playerNames, ESele
std::string text = CGI->generaltexth->allTexts[446];
boost::replace_all(text, "\t", "\n");
textTitle = std::make_shared<CTextBox>(text, Rect(25, 20, 315, 50), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE); //HOTSEAT Please enter names
textTitle = std::make_shared<CTextBox>(text, Rect(25, 10, 315, 60), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE); //HOTSEAT Please enter names
for(int i = 0; i < inputNames.size(); i++)
{

View File

@@ -265,7 +265,12 @@ void MusicEntry::load(const AudioPath & musicURI)
try
{
auto * musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
std::unique_ptr<CInputStream> stream = CResourceHandler::get()->load(currentName);
if(musicURI.getName() == "BLADEFWCAMPAIGN") // handle defect MP3 file - ffprobe says: Skipping 52 bytes of junk at 0.
stream->seek(52);
auto * musicFile = MakeSDLRWops(std::move(stream));
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
}
catch(std::exception & e)

View File

@@ -99,9 +99,7 @@ static AtlasLayout doAtlasPacking(const std::map<int, Point> & images)
void CBitmapFont::loadFont(const ResourcePath & resource, std::unordered_map<CodePoint, EntryFNT> & loadedChars)
{
auto data = CResourceHandler::get()->load(resource)->readAll();
std::string modName = VLC->modh->findResourceOrigin(resource);
std::string modLanguage = VLC->modh->getModLanguage(modName);
std::string modEncoding = Languages::getLanguageOptions(modLanguage).encoding;
std::string modEncoding = VLC->modh->findResourceEncoding(resource);
height = data.first[5];

View File

@@ -60,11 +60,9 @@ bool FontChain::bitmapFontsPrioritized(const std::string & bitmapFontName) const
if (!vstd::isAlmostEqual(1.0, settings["video"]["fontScalingFactor"].Float()))
return false; // If player requested non-100% scaling - use scalable fonts
std::string modName = CGI->modh->findResourceOrigin(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT));
std::string fontLanguage = CGI->modh->getModLanguage(modName);
std::string gameLanguage = CGI->generaltexth->getPreferredLanguage();
std::string fontEncoding = Languages::getLanguageOptions(fontLanguage).encoding;
std::string gameEncoding = Languages::getLanguageOptions(gameLanguage).encoding;
std::string fontEncoding = CGI->modh->findResourceEncoding(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT));
// player uses language with different encoding than his bitmap fonts
// for example, Polish language with English fonts or Chinese language which can't use H3 fonts at all

View File

@@ -22,6 +22,8 @@
CArtifactsOfHeroAltar::CArtifactsOfHeroAltar(const Point & position)
{
init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
enableGesture();
// The backpack is in the altar window above and to the right
for(auto & slot : backpack)

View File

@@ -40,6 +40,8 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack()
visibleCapacityMax = visibleCapacityMax > backpackCap ? backpackCap : visibleCapacityMax;
initAOHbackpack(visibleCapacityMax, backpackCap < 0 || visibleCapacityMax < backpackCap);
setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
}
void CArtifactsOfHeroBackpack::onSliderMoved(int newVal)
@@ -83,9 +85,7 @@ void CArtifactsOfHeroBackpack::initAOHbackpack(size_t slots, bool slider)
slotSizeWithMargin * (artPlaceIdx / slotsColumnsMax));
backpackSlotsBackgrounds.emplace_back(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/artifactSlotEmpty"), pos));
artPlace = std::make_shared<CArtPlace>(pos);
artPlace->setArtifact(nullptr);
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
artPlaceIdx++;
}
@@ -126,12 +126,11 @@ size_t CArtifactsOfHeroBackpack::calcRows(size_t slots)
CArtifactsOfHeroQuickBackpack::CArtifactsOfHeroQuickBackpack(const ArtifactPosition filterBySlot)
: CArtifactsOfHeroBackpack(0, 0)
{
assert(ArtifactUtils::checkIfSlotValid(*getHero(), filterBySlot));
if(!ArtifactUtils::isSlotEquipment(filterBySlot))
return;
this->filterBySlot = filterBySlot;
setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
}
void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
@@ -174,6 +173,7 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
slotsColumnsMax = ceilf(sqrtf(requiredSlots));
slotsRowsMax = calcRows(requiredSlots);
initAOHbackpack(requiredSlots, false);
setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
auto artPlace = backpack.begin();
for(auto & art : filteredArts)
setSlotData(*artPlace++, curHero->getArtPos(art.second));

View File

@@ -63,18 +63,14 @@ void CArtifactsOfHeroBase::init(
auto artPlace = std::make_shared<CArtPlace>(Point(403 + 46 * s, 365));
backpack.push_back(artPlace);
}
for(auto artPlace : artWorn)
for(auto & artPlace : artWorn)
{
artPlace.second->slot = artPlace.first;
artPlace.second->setArtifact(nullptr);
artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE));
}
for(auto artPlace : backpack)
for(const auto & artPlace : backpack)
{
artPlace->setArtifact(nullptr);
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
}
leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(),
[scrollCallback](){scrollCallback(true);}, EShortcut::MOVE_LEFT);
@@ -89,31 +85,56 @@ void CArtifactsOfHeroBase::init(
setRedrawParent(true);
}
void CArtifactsOfHeroBase::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
void CArtifactsOfHeroBase::setClickPressedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const
{
if(artPlace.isLocked())
for(const auto & [slot, artPlace] : artWorn)
artPlace->setClickPressedCallback(callback);
for(const auto & artPlace : backpack)
artPlace->setClickPressedCallback(callback);
}
void CArtifactsOfHeroBase::setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const
{
for(const auto & [slot, artPlace] : artWorn)
artPlace->setShowPopupCallback(callback);
for(const auto & artPlace : backpack)
artPlace->setShowPopupCallback(callback);
}
void CArtifactsOfHeroBase::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
{
auto ownedPlace = getArtPlace(cursorPosition);
assert(ownedPlace != nullptr);
if(ownedPlace->isLocked())
return;
if(clickPressedCallback)
clickPressedCallback(artPlace, cursorPosition);
clickPressedCallback(*ownedPlace, cursorPosition);
}
void CArtifactsOfHeroBase::showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
void CArtifactsOfHeroBase::showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
{
if(artPlace.isLocked())
auto ownedPlace = getArtPlace(cursorPosition);
assert(ownedPlace != nullptr);
if(ownedPlace->isLocked())
return;
if(showPopupCallback)
showPopupCallback(artPlace, cursorPosition);
showPopupCallback(*ownedPlace, cursorPosition);
}
void CArtifactsOfHeroBase::gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
void CArtifactsOfHeroBase::gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
{
if(artPlace.isLocked())
auto ownedPlace = getArtPlace(cursorPosition);
assert(ownedPlace != nullptr);
if(ownedPlace->isLocked())
return;
if(gestureCallback)
gestureCallback(artPlace, cursorPosition);
gestureCallback(*ownedPlace, cursorPosition);
}
void CArtifactsOfHeroBase::setHero(const CGHeroInstance * hero)
@@ -154,26 +175,12 @@ void CArtifactsOfHeroBase::unmarkSlots()
CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const ArtifactPosition & slot)
{
if(ArtifactUtils::isSlotEquipment(slot))
{
if(artWorn.find(slot) == artWorn.end())
{
logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
return nullptr;
}
if(ArtifactUtils::isSlotEquipment(slot) && artWorn.find(slot) != artWorn.end())
return artWorn[slot];
}
if(ArtifactUtils::isSlotBackpack(slot))
{
for(ArtPlacePtr artPlace : backpack)
if(artPlace->slot == slot)
return artPlace;
return nullptr;
}
else
{
return nullptr;
}
if(ArtifactUtils::isSlotBackpack(slot) && slot - ArtifactPosition::BACKPACK_START < backpack.size())
return(backpack[slot - ArtifactPosition::BACKPACK_START]);
logGlobal->error("CArtifactsOfHero::getArtPlace: invalid slot %d", slot);
return nullptr;
}
CArtifactsOfHeroBase::ArtPlacePtr CArtifactsOfHeroBase::getArtPlace(const Point & cursorPosition)
@@ -260,7 +267,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
if(auto slotInfo = curHero->getSlot(slot))
{
artPlace->lockSlot(slotInfo->locked);
artPlace->setArtifact(slotInfo->artifact);
artPlace->setArtifact(slotInfo->artifact->getTypeId(), slotInfo->artifact->getScrollSpellID());
if(slotInfo->locked || slotInfo->artifact->isCombined())
return;
@@ -285,7 +292,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
}
else
{
artPlace->setArtifact(nullptr);
artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
}
}

View File

@@ -9,7 +9,7 @@
*/
#pragma once
#include "CArtPlace.h"
#include "CComponentHolder.h"
#include "Scrollable.h"
#include "../gui/Shortcut.h"
@@ -33,9 +33,9 @@ public:
CArtifactsOfHeroBase();
virtual void putBackPickedArtifact();
virtual void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
virtual void showPopupArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
virtual void gestureArtPlace(CArtPlace & artPlace, const Point & cursorPosition);
virtual void clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition);
virtual void showPopupArtPlace(CComponentHolder & artPlace, const Point & cursorPosition);
virtual void gestureArtPlace(CComponentHolder & artPlace, const Point & cursorPosition);
virtual void setHero(const CGHeroInstance * hero);
virtual const CGHeroInstance * getHero() const;
virtual void scrollBackpack(bool left);
@@ -50,6 +50,8 @@ public:
void enableGesture();
const CArtifactInstance * getArt(const ArtifactPosition & slot) const;
void enableKeyboardShortcuts();
void setClickPressedArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const;
void setShowPopupArtPlacesCallback(const CArtPlace::ClickFunctor & callback) const;
const CGHeroInstance * curHero;
ArtPlaceMap artWorn;

View File

@@ -29,15 +29,15 @@ CArtifactsOfHeroKingdom::CArtifactsOfHeroKingdom(ArtPlaceMap ArtWorn, std::vecto
for(auto artPlace : artWorn)
{
artPlace.second->slot = artPlace.first;
artPlace.second->setArtifact(nullptr);
artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace.second->setArtifact(ArtifactID(ArtifactID::NONE));
artPlace.second->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
artPlace.second->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
}
enableGesture();
for(auto artPlace : backpack)
{
artPlace->setArtifact(nullptr);
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPrassedArtPlace, this, _1, _2));
artPlace->setArtifact(ArtifactID(ArtifactID::NONE));
artPlace->setClickPressedCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
artPlace->setShowPopupCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
}
leftBackpackRoll->addCallback(std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, -1));

View File

@@ -21,6 +21,8 @@
CArtifactsOfHeroMain::CArtifactsOfHeroMain(const Point & position)
{
init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
setShowPopupArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::showPopupArtPlace, this, _1, _2));
enableGesture();
}

View File

@@ -15,25 +15,28 @@
CArtifactsOfHeroMarket::CArtifactsOfHeroMarket(const Point & position, const int selectionWidth)
{
init(position, std::bind(&CArtifactsOfHeroBase::scrollBackpack, this, _1));
setClickPressedArtPlacesCallback(std::bind(&CArtifactsOfHeroBase::clickPressedArtPlace, this, _1, _2));
for(const auto & [slot, artPlace] : artWorn)
artPlace->setSelectionWidth(selectionWidth);
for(auto artPlace : backpack)
artPlace->setSelectionWidth(selectionWidth);
};
void CArtifactsOfHeroMarket::clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition)
void CArtifactsOfHeroMarket::clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition)
{
if(artPlace.isLocked())
auto ownedPlace = getArtPlace(cursorPosition);
assert(ownedPlace != nullptr);
if(ownedPlace->isLocked())
return;
if(const auto art = getArt(artPlace.slot))
if(const auto art = getArt(ownedPlace->slot))
{
if(onSelectArtCallback && art->artType->isTradable())
{
unmarkSlots();
artPlace.selectSlot(true);
onSelectArtCallback(&artPlace);
onSelectArtCallback(ownedPlace.get());
}
else
{

View File

@@ -18,5 +18,5 @@ public:
std::function<void()> onClickNotTradableCallback;
CArtifactsOfHeroMarket(const Point & position, const int selectionWidth);
void clickPrassedArtPlace(CArtPlace & artPlace, const Point & cursorPosition) override;
void clickPressedArtPlace(CComponentHolder & artPlace, const Point & cursorPosition) override;
};

View File

@@ -1,5 +1,5 @@
/*
* CArtPlace.cpp, part of VCMI engine
* CComponentHolder.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@@ -8,14 +8,14 @@
*
*/
#include "StdInc.h"
#include "CArtPlace.h"
#include "CComponentHolder.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "CComponent.h"
#include "Images.h"
#include "../windows/GUIClasses.h"
#include "../render/Canvas.h"
#include "../render/Colors.h"
#include "../render/IRenderHandler.h"
@@ -28,27 +28,97 @@
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/networkPacks/ArtifactLocation.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CSkillHandler.h"
void CArtPlace::setInternals(const CArtifactInstance * artInst)
CComponentHolder::CComponentHolder(const Rect & area, const Point & selectionOversize)
: SelectableSlot(area, selectionOversize)
{
ourArt = artInst;
if(!artInst)
setClickPressedCallback([this](const CComponentHolder &, const Point & cursorPosition)
{
if(text.size())
LRClickableAreaWTextComp::clickPressed(cursorPosition);
});
setShowPopupCallback([this](const CComponentHolder &, const Point & cursorPosition)
{
if(text.size())
LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
});
}
void CComponentHolder::setClickPressedCallback(const ClickFunctor & callback)
{
clickPressedCallback = callback;
}
void CComponentHolder::setShowPopupCallback(const ClickFunctor & callback)
{
showPopupCallback = callback;
}
void CComponentHolder::setGestureCallback(const ClickFunctor & callback)
{
gestureCallback = callback;
}
void CComponentHolder::clickPressed(const Point & cursorPosition)
{
if(clickPressedCallback)
clickPressedCallback(*this, cursorPosition);
}
void CComponentHolder::showPopupWindow(const Point & cursorPosition)
{
if(showPopupCallback)
showPopupCallback(*this, cursorPosition);
}
void CComponentHolder::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
{
if(!on)
return;
if(gestureCallback)
gestureCallback(*this, initialPosition);
}
CArtPlace::CArtPlace(Point position, const ArtifactID & artId, const SpellID & spellId)
: CComponentHolder(Rect(position, Point(44, 44)), Point(1, 1))
, locked(false)
, imageIndex(0)
{
OBJECT_CONSTRUCTION;
image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), 0);
setArtifact(artId, spellId);
moveSelectionForeground();
}
void CArtPlace::setArtifact(const SpellID & newSpellId)
{
setArtifact(ArtifactID::SPELL_SCROLL, newSpellId);
}
void CArtPlace::setArtifact(const ArtifactID & newArtId, const SpellID & newSpellId)
{
artId = newArtId;
if(artId == ArtifactID::NONE)
{
image->disable();
text.clear();
hoverText = CGI->generaltexth->allTexts[507];
lockSlot(false);
return;
}
imageIndex = artInst->artType->getIconIndex();
if(artInst->getTypeId() == ArtifactID::SPELL_SCROLL)
const auto artType = artId.toArtifact();
imageIndex = artType->getIconIndex();
if(artId == ArtifactID::SPELL_SCROLL)
{
auto spellID = artInst->getScrollSpellID();
assert(spellID.num >= 0);
spellId = newSpellId;
assert(spellId.num > 0);
if(settings["general"]["enableUiEnhancements"].Bool())
{
imageIndex = spellID.num;
imageIndex = spellId.num;
if(component.type != ComponentType::SPELL_SCROLL)
{
image->setScale(Point(pos.w, 34));
@@ -58,7 +128,7 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
}
// Add spell component info (used to provide a pic in r-click popup)
component.type = ComponentType::SPELL_SCROLL;
component.subType = spellID;
component.subType = spellId;
}
else
{
@@ -69,47 +139,33 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
image->moveTo(Point(pos.x, pos.y));
}
component.type = ComponentType::ARTIFACT;
component.subType = artInst->getTypeId();
component.subType = artId;
}
image->enable();
text = artInst->getDescription();
lockSlot(locked);
text = artType->getDescriptionTranslated();
if(artType->isScroll())
ArtifactUtils::insertScrrollSpellName(text, spellId);
}
CArtPlace::CArtPlace(Point position, const CArtifactInstance * art)
: SelectableSlot(Rect(position, Point(44, 44)), Point(1, 1))
, ourArt(art)
, locked(false)
ArtifactID CArtPlace::getArtifactId() const
{
OBJECT_CONSTRUCTION;
imageIndex = 0;
if(locked)
imageIndex = ArtifactID::ART_LOCK;
else if(ourArt)
imageIndex = ourArt->artType->getIconIndex();
image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
image->disable();
moveSelectionForeground();
return artId;
}
const CArtifactInstance * CArtPlace::getArt() const
{
return ourArt;
}
CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art)
: CArtPlace(position, art),
CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot,
const ArtifactID & artId, const SpellID & spellId)
: CArtPlace(position, artId, spellId),
commanderOwner(commanderOwner),
commanderSlotID(artSlot.num)
{
setArtifact(art);
}
void CCommanderArtPlace::returnArtToHeroCallback()
{
ArtifactPosition artifactPos = commanderSlotID;
ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, getArt()->getTypeId());
ArtifactPosition freeSlot = ArtifactUtils::getArtBackpackPosition(commanderOwner, getArtifactId());
if(freeSlot == ArtifactPosition::PRE_FIRST)
{
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("core.genrltxt.152"));
@@ -120,10 +176,10 @@ void CCommanderArtPlace::returnArtToHeroCallback()
src.creature = SlotID::COMMANDER_SLOT_PLACEHOLDER;
ArtifactLocation dst(commanderOwner->id, freeSlot);
if(getArt()->canBePutAt(commanderOwner, freeSlot, true))
if(getArtifactId().toArtifact()->canBePutAt(commanderOwner, freeSlot, true))
{
LOCPLINT->cb->swapArtifacts(src, dst);
setArtifact(nullptr);
setArtifact(ArtifactID(ArtifactID::NONE));
parent->redraw();
}
}
@@ -131,29 +187,35 @@ void CCommanderArtPlace::returnArtToHeroCallback()
void CCommanderArtPlace::clickPressed(const Point & cursorPosition)
{
if(getArt() && text.size())
if(getArtifactId() != ArtifactID::NONE && text.size())
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this]() { returnArtToHeroCallback(); }, []() {});
}
void CCommanderArtPlace::showPopupWindow(const Point & cursorPosition)
{
if(getArt() && text.size())
if(getArtifactId() != ArtifactID::NONE && text.size())
CArtPlace::showPopupWindow(cursorPosition);
}
void CArtPlace::lockSlot(bool on)
{
if(locked == on)
return;
locked = on;
if(on)
{
image->setFrame(ArtifactID::ART_LOCK);
else if(ourArt)
hoverText = CGI->generaltexth->allTexts[507];
}
else if(artId != ArtifactID::NONE)
{
image->setFrame(imageIndex);
auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[1]);
hoverText.replaceName(artId);
this->hoverText = hoverText.toString();
}
else
image->setFrame(0);
{
hoverText = CGI->generaltexth->allTexts[507];
}
}
bool CArtPlace::isLocked() const
@@ -161,60 +223,6 @@ bool CArtPlace::isLocked() const
return locked;
}
void CArtPlace::clickPressed(const Point & cursorPosition)
{
if(clickPressedCallback)
clickPressedCallback(*this, cursorPosition);
}
void CArtPlace::showPopupWindow(const Point & cursorPosition)
{
if(showPopupCallback)
showPopupCallback(*this, cursorPosition);
}
void CArtPlace::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
{
if(!on)
return;
if(gestureCallback)
gestureCallback(*this, initialPosition);
}
void CArtPlace::setArtifact(const CArtifactInstance * art)
{
setInternals(art);
if(art)
{
image->setFrame(locked ? static_cast<int>(ArtifactID::ART_LOCK) : imageIndex);
if(locked) // Locks should appear as empty.
hoverText = CGI->generaltexth->allTexts[507];
else
hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % ourArt->artType->getNameTranslated());
}
else
{
lockSlot(false);
}
}
void CArtPlace::setClickPressedCallback(const ClickFunctor & callback)
{
clickPressedCallback = callback;
}
void CArtPlace::setShowPopupCallback(const ClickFunctor & callback)
{
showPopupCallback = callback;
}
void CArtPlace::setGestureCallback(const ClickFunctor & callback)
{
gestureCallback = callback;
}
void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts)
{
for(auto [combinedId, availableArts] : arts)
@@ -252,3 +260,55 @@ void CArtPlace::addCombinedArtInfo(const std::map<const ArtifactID, std::vector<
text += info.toString();
}
}
CSecSkillPlace::CSecSkillPlace(const Point & position, const ImageSize & imageSize, const SecondarySkill & newSkillId, const uint8_t level)
: CComponentHolder(Rect(position, Point()), Point())
{
OBJECT_CONSTRUCTION;
auto imagePath = AnimationPath::builtin("SECSKILL");
if(imageSize == ImageSize::MEDIUM)
imagePath = AnimationPath::builtin("SECSK32");
if(imageSize == ImageSize::SMALL)
imagePath = AnimationPath::builtin("SECSK82");
image = std::make_shared<CAnimImage>(imagePath, 0);
component.type = ComponentType::SEC_SKILL;
pos.w = image->pos.w;
pos.h = image->pos.h;
setSkill(newSkillId, level);
}
void CSecSkillPlace::setSkill(const SecondarySkill & newSkillId, const uint8_t level)
{
skillId = newSkillId;
component.subType = newSkillId;
setLevel(level);
}
void CSecSkillPlace::setLevel(const uint8_t level)
{
// 0 - none
// 1 - base
// 2 - advanced
// 3 - expert
assert(level <= 3);
if(skillId != SecondarySkill::NONE && level > 0)
{
const auto secSkill = skillId.toSkill();
image->setFrame(secSkill->getIconIndex(level - 1));
image->enable();
auto hoverText = MetaString::createFromRawString(CGI->generaltexth->heroscrn[21]);
hoverText.replaceRawString(CGI->generaltexth->levels[level - 1]);
hoverText.replaceTextID(secSkill->getNameTextID());
this->hoverText = hoverText.toString();
component.value = level;
text = secSkill->getDescriptionTranslated(level);
}
else
{
image->disable();
hoverText.clear();
text.clear();
}
}

View File

@@ -1,5 +1,5 @@
/*
* CArtPlace.h, part of VCMI engine
* CComponentHolder.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@@ -13,37 +13,43 @@
class CAnimImage;
class CArtPlace : public SelectableSlot
class CComponentHolder : public SelectableSlot
{
public:
using ClickFunctor = std::function<void(CArtPlace&, const Point&)>;
using ClickFunctor = std::function<void(CComponentHolder&, const Point&)>;
ArtifactPosition slot;
CArtPlace(Point position, const CArtifactInstance * art = nullptr);
const CArtifactInstance * getArt() const;
void lockSlot(bool on);
bool isLocked() const;
void setArtifact(const CArtifactInstance * art);
ClickFunctor clickPressedCallback;
ClickFunctor showPopupCallback;
ClickFunctor gestureCallback;
std::shared_ptr<CAnimImage> image;
CComponentHolder(const Rect & area, const Point & selectionOversize);
void setClickPressedCallback(const ClickFunctor & callback);
void setShowPopupCallback(const ClickFunctor & callback);
void setGestureCallback(const ClickFunctor & callback);
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
};
class CArtPlace : public CComponentHolder
{
public:
ArtifactPosition slot;
CArtPlace(Point position, const ArtifactID & newArtId = ArtifactID::NONE, const SpellID & newSpellId = SpellID::NONE);
void setArtifact(const SpellID & newSpellId);
void setArtifact(const ArtifactID & newArtId, const SpellID & newSpellId = SpellID::NONE);
ArtifactID getArtifactId() const;
void lockSlot(bool on);
bool isLocked() const;
void addCombinedArtInfo(const std::map<const ArtifactID, std::vector<ArtifactID>> & arts);
private:
const CArtifactInstance * ourArt;
ArtifactID artId;
SpellID spellId;
bool locked;
int imageIndex;
std::shared_ptr<CAnimImage> image;
ClickFunctor clickPressedCallback;
ClickFunctor showPopupCallback;
ClickFunctor gestureCallback;
protected:
void setInternals(const CArtifactInstance * artInst);
int32_t imageIndex;
};
class CCommanderArtPlace : public CArtPlace
@@ -55,7 +61,26 @@ private:
void returnArtToHeroCallback();
public:
CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot, const CArtifactInstance * art = nullptr);
CCommanderArtPlace(Point position, const CGHeroInstance * commanderOwner, ArtifactPosition artSlot,
const ArtifactID & artId = ArtifactID::NONE, const SpellID & spellId = SpellID::NONE);
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
};
class CSecSkillPlace : public CComponentHolder
{
public:
enum class ImageSize
{
LARGE,
MEDIUM,
SMALL
};
CSecSkillPlace(const Point & position, const ImageSize & imageSize, const SecondarySkill & skillId = SecondarySkill::NONE, const uint8_t level = 0);
void setSkill(const SecondarySkill & newSkillId, const uint8_t level = 0);
void setLevel(const uint8_t level);
private:
SecondarySkill skillId;
};

View File

@@ -582,13 +582,13 @@ void MoraleLuckBox::set(const AFactionMember * node)
else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
{
auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
text += "\n" + noMorale->Description();
text += "\n" + noMorale->Description(LOCPLINT->cb.get());
component.value = 0;
}
else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
{
auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
text += "\n" + noLuck->Description();
text += "\n" + noLuck->Description(LOCPLINT->cb.get());
component.value = 0;
}
else
@@ -597,7 +597,7 @@ void MoraleLuckBox::set(const AFactionMember * node)
for(auto & bonus : * modifierList)
{
if(bonus->val) {
const std::string& description = bonus->Description();
const std::string& description = bonus->Description(LOCPLINT->cb.get());
//arraytxt already contains \n
if (description.size() && description[0] != '\n')
addInfo += '\n';

View File

@@ -58,7 +58,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
CAltarArtifacts::onSlotClickPressed(altarSlot, offerTradePanel);
});
offerTradePanel->updateSlotsCallback = std::bind(&CAltarArtifacts::updateAltarSlots, this);
offerTradePanel->moveTo(pos.topLeft() + Point(315, 52));
offerTradePanel->moveTo(pos.topLeft() + Point(315, 53));
CMarketBase::updateShowcases();
CAltarArtifacts::deselect();

View File

@@ -162,9 +162,9 @@ void CAltarCreatures::makeDeal()
for(int & units : unitsOnAltar)
units = 0;
for(auto heroSlot : offerTradePanel->slots)
for(const auto & heroSlot : offerTradePanel->slots)
{
heroSlot->setType(EType::CREATURE_PLACEHOLDER);
heroSlot->setID(CreatureID::NONE);
heroSlot->subtitle->clear();
}
deselect();
@@ -175,16 +175,16 @@ CMarketBase::MarketShowcasesParams CAltarCreatures::getShowcasesParams() const
std::optional<ShowcaseParams> bidSelected = std::nullopt;
std::optional<ShowcaseParams> offerSelected = std::nullopt;
if(bidTradePanel->isHighlighted())
bidSelected = ShowcaseParams {std::to_string(offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getSelectedItemId())->getIconIndex()};
bidSelected = ShowcaseParams {std::to_string(offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getHighlightedItemId())->getIconIndex()};
if(offerTradePanel->isHighlighted() && offerSlider->getValue() > 0)
offerSelected = ShowcaseParams {offerTradePanel->highlightedSlot->subtitle->getText(), CGI->creatures()->getByIndex(offerTradePanel->getSelectedItemId())->getIconIndex()};
offerSelected = ShowcaseParams {offerTradePanel->highlightedSlot->subtitle->getText(), CGI->creatures()->getByIndex(offerTradePanel->getHighlightedItemId())->getIconIndex()};
return MarketShowcasesParams {bidSelected, offerSelected};
}
void CAltarCreatures::sacrificeAll()
{
std::optional<SlotID> lastSlot;
for(auto heroSlot : bidTradePanel->slots)
for(const auto & heroSlot : bidTradePanel->slots)
{
auto stackCount = hero->getStackCount(SlotID(heroSlot->serial));
if(stackCount > unitsOnAltar[heroSlot->serial])
@@ -211,7 +211,8 @@ void CAltarCreatures::sacrificeAll()
void CAltarCreatures::updateAltarSlot(const std::shared_ptr<CTradeableItem> & slot)
{
auto units = unitsOnAltar[slot->serial];
slot->setType(units > 0 ? EType::CREATURE : EType::CREATURE_PLACEHOLDER);
const auto [oppositeSlot, oppositePanel] = getOpposite(slot);
slot->setID(units > 0 ? oppositeSlot->id : CreatureID::NONE);
slot->subtitle->setText(units > 0 ?
boost::str(boost::format(CGI->generaltexth->allTexts[122]) % std::to_string(hero->calculateXp(units * expPerUnit[slot->serial]))) : "");
}
@@ -234,21 +235,9 @@ void CAltarCreatures::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
if(newSlot == curPanel->highlightedSlot)
return;
auto oppositePanel = bidTradePanel;
curPanel->onSlotClickPressed(newSlot);
if(curPanel->highlightedSlot == bidTradePanel->highlightedSlot)
{
oppositePanel = offerTradePanel;
}
std::shared_ptr<CTradeableItem> oppositeNewSlot;
for(const auto & slot : oppositePanel->slots)
if(slot->serial == newSlot->serial)
{
oppositeNewSlot = slot;
break;
}
assert(oppositeNewSlot);
oppositePanel->onSlotClickPressed(oppositeNewSlot);
auto [oppositeSlot, oppositePanel] = getOpposite(newSlot);
oppositePanel->onSlotClickPressed(oppositeSlot);
highlightingChanged();
redraw();
}
@@ -258,7 +247,7 @@ std::string CAltarCreatures::getTraderText()
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
MetaString message = MetaString::createFromTextID("core.genrltxt.484");
message.replaceNamePlural(CreatureID(bidTradePanel->getSelectedItemId()));
message.replaceNamePlural(CreatureID(bidTradePanel->getHighlightedItemId()));
return message.toString();
}
else
@@ -266,3 +255,22 @@ std::string CAltarCreatures::getTraderText()
return "";
}
}
std::tuple<const std::shared_ptr<CTradeableItem>, std::shared_ptr<TradePanelBase>> CAltarCreatures::getOpposite(
const std::shared_ptr<CTradeableItem> & curSlot)
{
assert(curSlot);
auto oppositePanel = bidTradePanel;
if(vstd::contains(bidTradePanel->slots, curSlot))
oppositePanel = offerTradePanel;
std::shared_ptr<CTradeableItem> oppositeSlot;
for(const auto & slot : oppositePanel->slots)
if (slot->serial == curSlot->serial)
{
oppositeSlot = slot;
break;
}
return std::make_tuple(oppositeSlot, oppositePanel);
}

View File

@@ -33,4 +33,5 @@ private:
void onOfferSliderMoved(int newVal) override;
void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot, std::shared_ptr<TradePanelBase> & curPanel) override;
std::string getTraderText() override;
std::tuple<const std::shared_ptr<CTradeableItem>, std::shared_ptr<TradePanelBase>> getOpposite(const std::shared_ptr<CTradeableItem> & curSlot);
};

View File

@@ -46,7 +46,7 @@ CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance
CArtifactsBuying::onSlotClickPressed(newSlot, offerTradePanel);
}, [this]()
{
CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_ARTIFACT, bidTradePanel->getSelectedItemId());
CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_ARTIFACT, bidTradePanel->getHighlightedItemId());
}, market->availableItemsIds(EMarketMode::RESOURCE_ARTIFACT));
offerTradePanel->deleteSlotsCheck = [this](const std::shared_ptr<CTradeableItem> & slot)
{
@@ -66,10 +66,10 @@ void CArtifactsBuying::deselect()
void CArtifactsBuying::makeDeal()
{
if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero))
if(ArtifactID(offerTradePanel->getHighlightedItemId()).toArtifact()->canBePutAt(hero))
{
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getHighlightedItemId()),
ArtifactID(offerTradePanel->getHighlightedItemId()), offerQty, hero);
CMarketTraderText::makeDeal();
deselect();
}
@@ -84,8 +84,8 @@ CMarketBase::MarketShowcasesParams CArtifactsBuying::getShowcasesParams() const
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
return MarketShowcasesParams
{
ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : bidQty), bidTradePanel->getSelectedItemId()},
ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : offerQty), CGI->artifacts()->getByIndex(offerTradePanel->getSelectedItemId())->getIconIndex()}
ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : bidQty), bidTradePanel->getHighlightedItemId()},
ShowcaseParams {std::to_string(deal->isBlocked() ? 0 : offerQty), CGI->artifacts()->getByIndex(offerTradePanel->getHighlightedItemId())->getIconIndex()}
};
else
return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -95,8 +95,8 @@ void CArtifactsBuying::highlightingChanged()
{
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT);
deal->block(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())) < bidQty || !LOCPLINT->makingTurn);
market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_ARTIFACT);
deal->block(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())) < bidQty || !LOCPLINT->makingTurn);
}
CMarketBase::highlightingChanged();
CMarketTraderText::highlightingChanged();
@@ -107,10 +107,10 @@ std::string CArtifactsBuying::getTraderText()
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
MetaString message = MetaString::createFromTextID("core.genrltxt.267");
message.replaceName(ArtifactID(offerTradePanel->getSelectedItemId()));
message.replaceName(ArtifactID(offerTradePanel->getHighlightedItemId()));
message.replaceNumber(bidQty);
message.replaceTextID(bidQty == 1 ? "core.genrltxt.161" : "core.genrltxt.160");
message.replaceName(GameResID(bidTradePanel->getSelectedItemId()));
message.replaceName(GameResID(bidTradePanel->getHighlightedItemId()));
return message.toString();
}
else

View File

@@ -79,7 +79,7 @@ void CArtifactsSelling::makeDeal()
const auto art = hero->getArt(selectedHeroSlot);
assert(art);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(),
GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
GameResID(offerTradePanel->getHighlightedItemId()), offerQty, hero);
CMarketTraderText::makeDeal();
}
@@ -129,7 +129,7 @@ CMarketBase::MarketShowcasesParams CArtifactsSelling::getShowcasesParams() const
return MarketShowcasesParams
{
std::nullopt,
ShowcaseParams {std::to_string(offerQty), offerTradePanel->getSelectedItemId()}
ShowcaseParams {std::to_string(offerQty), offerTradePanel->getHighlightedItemId()}
};
else
return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -147,7 +147,7 @@ void CArtifactsSelling::highlightingChanged()
const auto art = hero->getArt(selectedHeroSlot);
if(art && offerTradePanel->isHighlighted())
{
market->getOffer(art->getTypeId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
market->getOffer(art->getTypeId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::ARTIFACT_RESOURCE);
deal->block(!LOCPLINT->makingTurn);
}
CMarketBase::highlightingChanged();
@@ -162,7 +162,7 @@ std::string CArtifactsSelling::getTraderText()
MetaString message = MetaString::createFromTextID("core.genrltxt.268");
message.replaceNumber(offerQty);
message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
message.replaceName(GameResID(offerTradePanel->getSelectedItemId()));
message.replaceName(GameResID(offerTradePanel->getHighlightedItemId()));
message.replaceName(art->getTypeId());
return message.toString();
}

View File

@@ -29,7 +29,7 @@ CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance
: CMarketBase(market, hero)
, CResourcesBuying(
[this](const std::shared_ptr<CTradeableItem> & heroSlot){CFreelancerGuild::onSlotClickPressed(heroSlot, offerTradePanel);},
[this](){CMarketBase::updateSubtitlesForBid(EMarketMode::CREATURE_RESOURCE, bidTradePanel->getSelectedItemId());})
[this](){CMarketBase::updateSubtitlesForBid(EMarketMode::CREATURE_RESOURCE, bidTradePanel->getHighlightedItemId());})
, CMarketSlider([this](int newVal){CMarketSlider::onOfferSliderMoved(newVal);})
{
OBJECT_CONSTRUCTION;
@@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal()
{
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
{
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getHighlightedItemId()), bidQty * toTrade, hero);
CMarketTraderText::makeDeal();
deselect();
}
@@ -80,8 +80,8 @@ CMarketBase::MarketShowcasesParams CFreelancerGuild::getShowcasesParams() const
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
return MarketShowcasesParams
{
ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getSelectedItemId())->getIconIndex()},
ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getSelectedItemId()}
ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), CGI->creatures()->getByIndex(bidTradePanel->getHighlightedItemId())->getIconIndex()},
ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getHighlightedItemId()}
};
else
return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -91,7 +91,7 @@ void CFreelancerGuild::highlightingChanged()
{
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::CREATURE_RESOURCE);
market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::CREATURE_RESOURCE);
offerSlider->setAmount((hero->getStackCount(SlotID(bidTradePanel->highlightedSlot->serial)) - (hero->stacksCount() == 1 && hero->needsLastStack() ? 1 : 0)) / bidQty);
offerSlider->scrollTo(0);
offerSlider->block(false);
@@ -109,12 +109,12 @@ std::string CFreelancerGuild::getTraderText()
MetaString message = MetaString::createFromTextID("core.genrltxt.269");
message.replaceNumber(offerQty);
message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
message.replaceName(GameResID(offerTradePanel->getSelectedItemId()));
message.replaceName(GameResID(offerTradePanel->getHighlightedItemId()));
message.replaceNumber(bidQty);
if(bidQty == 1)
message.replaceNameSingular(bidTradePanel->getSelectedItemId());
message.replaceNameSingular(bidTradePanel->getHighlightedItemId());
else
message.replaceNamePlural(bidTradePanel->getSelectedItemId());
message.replaceNamePlural(bidTradePanel->getHighlightedItemId());
return message.toString();
}
else

View File

@@ -93,7 +93,7 @@ void CMarketBase::updateSubtitlesForBid(EMarketMode marketMode, int bidId)
void CMarketBase::updateShowcases()
{
const auto updateSelectedBody = [](const std::shared_ptr<TradePanelBase> & tradePanel, const std::optional<const ShowcaseParams> & params)
const auto updateShowcase = [](const std::shared_ptr<TradePanelBase> & tradePanel, const std::optional<const ShowcaseParams> & params)
{
if(params.has_value())
{
@@ -109,9 +109,9 @@ void CMarketBase::updateShowcases()
const auto params = getShowcasesParams();
if(bidTradePanel)
updateSelectedBody(bidTradePanel, params.bidParams);
updateShowcase(bidTradePanel, params.bidParams);
if(offerTradePanel)
updateSelectedBody(offerTradePanel, params.offerParams);
updateShowcase(offerTradePanel, params.offerParams);
}
void CMarketBase::highlightingChanged()

View File

@@ -60,7 +60,7 @@ void CMarketResources::makeDeal()
{
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
{
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getHighlightedItemId()),
GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero);
CMarketTraderText::makeDeal();
deselect();
@@ -69,11 +69,11 @@ void CMarketResources::makeDeal()
CMarketBase::MarketShowcasesParams CMarketResources::getShowcasesParams() const
{
if(bidTradePanel->highlightedSlot && offerTradePanel->highlightedSlot && bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId())
if(bidTradePanel->highlightedSlot && offerTradePanel->highlightedSlot && bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId())
return MarketShowcasesParams
{
ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), bidTradePanel->getSelectedItemId()},
ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getSelectedItemId()}
ShowcaseParams {std::to_string(bidQty * offerSlider->getValue()), bidTradePanel->getHighlightedItemId()},
ShowcaseParams {std::to_string(offerQty * offerSlider->getValue()), offerTradePanel->getHighlightedItemId()}
};
else
return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -83,10 +83,10 @@ void CMarketResources::highlightingChanged()
{
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
market->getOffer(bidTradePanel->getSelectedItemId(), offerTradePanel->getSelectedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_RESOURCE);
offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())) / bidQty);
market->getOffer(bidTradePanel->getHighlightedItemId(), offerTradePanel->getHighlightedItemId(), bidQty, offerQty, EMarketMode::RESOURCE_RESOURCE);
offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())) / bidQty);
offerSlider->scrollTo(0);
const bool isControlsBlocked = bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId() ? false : true;
const bool isControlsBlocked = bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId() ? false : true;
offerSlider->block(isControlsBlocked);
maxAmount->block(isControlsBlocked);
deal->block(isControlsBlocked || !LOCPLINT->makingTurn);
@@ -97,7 +97,7 @@ void CMarketResources::highlightingChanged()
void CMarketResources::updateSubtitles()
{
CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_RESOURCE, bidTradePanel->getSelectedItemId());
CMarketBase::updateSubtitlesForBid(EMarketMode::RESOURCE_RESOURCE, bidTradePanel->getHighlightedItemId());
if(bidTradePanel->highlightedSlot)
offerTradePanel->slots[bidTradePanel->highlightedSlot->serial]->subtitle->setText(CGI->generaltexth->allTexts[164]); // n/a
}
@@ -105,15 +105,15 @@ void CMarketResources::updateSubtitles()
std::string CMarketResources::getTraderText()
{
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted() &&
bidTradePanel->getSelectedItemId() != offerTradePanel->getSelectedItemId())
bidTradePanel->getHighlightedItemId() != offerTradePanel->getHighlightedItemId())
{
MetaString message = MetaString::createFromTextID("core.genrltxt.157");
message.replaceNumber(offerQty);
message.replaceRawString(offerQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
message.replaceName(GameResID(bidTradePanel->getSelectedItemId()));
message.replaceName(GameResID(bidTradePanel->getHighlightedItemId()));
message.replaceNumber(bidQty);
message.replaceRawString(bidQty == 1 ? CGI->generaltexth->allTexts[161] : CGI->generaltexth->allTexts[160]);
message.replaceName(GameResID(offerTradePanel->getSelectedItemId()));
message.replaceName(GameResID(offerTradePanel->getHighlightedItemId()));
return message.toString();
}
else

View File

@@ -64,8 +64,8 @@ void CTransferResources::makeDeal()
{
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
{
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero);
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getHighlightedItemId()),
PlayerColor(offerTradePanel->getHighlightedItemId()), toTrade, hero);
CMarketTraderText::makeDeal();
deselect();
}
@@ -76,8 +76,8 @@ CMarketBase::MarketShowcasesParams CTransferResources::getShowcasesParams() cons
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
return MarketShowcasesParams
{
ShowcaseParams {std::to_string(offerSlider->getValue()), bidTradePanel->getSelectedItemId()},
ShowcaseParams {CGI->generaltexth->capColors[offerTradePanel->getSelectedItemId()], offerTradePanel->getSelectedItemId()}
ShowcaseParams {std::to_string(offerSlider->getValue()), bidTradePanel->getHighlightedItemId()},
ShowcaseParams {CGI->generaltexth->capColors[offerTradePanel->getHighlightedItemId()], offerTradePanel->getHighlightedItemId()}
};
else
return MarketShowcasesParams {std::nullopt, std::nullopt};
@@ -87,7 +87,7 @@ void CTransferResources::highlightingChanged()
{
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getSelectedItemId())));
offerSlider->setAmount(LOCPLINT->cb->getResourceAmount(GameResID(bidTradePanel->getHighlightedItemId())));
offerSlider->scrollTo(0);
offerSlider->block(false);
maxAmount->block(false);
@@ -102,8 +102,8 @@ std::string CTransferResources::getTraderText()
if(bidTradePanel->isHighlighted() && offerTradePanel->isHighlighted())
{
MetaString message = MetaString::createFromTextID("core.genrltxt.165");
message.replaceName(GameResID(bidTradePanel->getSelectedItemId()));
message.replaceName(PlayerColor(offerTradePanel->getSelectedItemId()));
message.replaceName(GameResID(bidTradePanel->getHighlightedItemId()));
message.replaceName(PlayerColor(offerTradePanel->getHighlightedItemId()));
return message.toString();
}
else

View File

@@ -23,11 +23,11 @@
#include "../../../lib/texts/CGeneralTextHandler.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
CTradeableItem::CTradeableItem(const Rect & area, EType Type, int ID, int Serial)
CTradeableItem::CTradeableItem(const Rect & area, EType Type, int32_t ID, int32_t serial)
: SelectableSlot(area, Point(1, 1))
, type(EType(-1)) // set to invalid, will be corrected in setType
, id(ID)
, serial(Serial)
, serial(serial)
{
OBJECT_CONSTRUCTION;
@@ -65,17 +65,14 @@ void CTradeableItem::setType(EType newType)
subtitle->moveTo(pos.topLeft() + Point(35, 55));
image->moveTo(pos.topLeft() + Point(19, 8));
break;
case EType::CREATURE_PLACEHOLDER:
case EType::CREATURE:
subtitle->moveTo(pos.topLeft() + Point(30, 77));
break;
case EType::PLAYER:
subtitle->moveTo(pos.topLeft() + Point(31, 76));
break;
case EType::ARTIFACT_PLACEHOLDER:
case EType::ARTIFACT_INSTANCE:
image->moveTo(pos.topLeft() + Point(0, 1));
subtitle->moveTo(pos.topLeft() + Point(21, 56));
case EType::ARTIFACT:
subtitle->moveTo(pos.topLeft() + Point(21, 55));
break;
case EType::ARTIFACT_TYPE:
subtitle->moveTo(pos.topLeft() + Point(35, 57));
@@ -85,14 +82,14 @@ void CTradeableItem::setType(EType newType)
}
}
void CTradeableItem::setID(int newID)
void CTradeableItem::setID(int32_t newID)
{
if(id != newID)
{
id = newID;
if(image)
{
int index = getIndex();
const auto index = getIndex();
if(index < 0)
image->disable();
else
@@ -121,8 +118,7 @@ AnimationPath CTradeableItem::getFilename()
case EType::PLAYER:
return AnimationPath::builtin("CREST58");
case EType::ARTIFACT_TYPE:
case EType::ARTIFACT_PLACEHOLDER:
case EType::ARTIFACT_INSTANCE:
case EType::ARTIFACT:
return AnimationPath::builtin("artifact");
case EType::CREATURE:
return AnimationPath::builtin("TWCRPORT");
@@ -142,8 +138,7 @@ int CTradeableItem::getIndex()
case EType::PLAYER:
return id;
case EType::ARTIFACT_TYPE:
case EType::ARTIFACT_INSTANCE:
case EType::ARTIFACT_PLACEHOLDER:
case EType::ARTIFACT:
return CGI->artifacts()->getByIndex(id)->getIconIndex();
case EType::CREATURE:
return CGI->creatures()->getByIndex(id)->getIconIndex();
@@ -169,11 +164,10 @@ void CTradeableItem::hover(bool on)
switch(type)
{
case EType::CREATURE:
case EType::CREATURE_PLACEHOLDER:
GH.statusbar()->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->getNamePluralTranslated()));
break;
case EType::ARTIFACT_TYPE:
case EType::ARTIFACT_PLACEHOLDER:
case EType::ARTIFACT:
if(id < 0)
GH.statusbar()->write(CGI->generaltexth->zelp[582].first);
else
@@ -193,11 +187,9 @@ void CTradeableItem::showPopupWindow(const Point & cursorPosition)
switch(type)
{
case EType::CREATURE:
case EType::CREATURE_PLACEHOLDER:
break;
case EType::ARTIFACT_TYPE:
case EType::ARTIFACT_PLACEHOLDER:
//TODO: it's would be better for market to contain actual CArtifactInstance and not just ids of certain artifact type so we can use getEffectiveDescription.
case EType::ARTIFACT:
if (id >= 0)
CRClickPopup::createAndPush(CGI->artifacts()->getByIndex(id)->getDescriptionTranslated());
break;
@@ -241,7 +233,7 @@ void TradePanelBase::setShowcaseSubtitle(const std::string & text)
showcaseSlot->subtitle->setText(text);
}
int TradePanelBase::getSelectedItemId() const
int32_t TradePanelBase::getHighlightedItemId() const
{
if(highlightedSlot)
return highlightedSlot->id;
@@ -263,7 +255,7 @@ void TradePanelBase::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
bool TradePanelBase::isHighlighted() const
{
return getSelectedItemId() != -1;
return highlightedSlot != nullptr;
}
ResourcesPanel::ResourcesPanel(const CTradeableItem::ClickPressedFunctor & clickPressedCallback,
@@ -339,7 +331,7 @@ CreaturesPanel::CreaturesPanel(const CTradeableItem::ClickPressedFunctor & click
for(const auto & [creatureId, slotId, creaturesNum] : initialSlots)
{
auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(slotsPos[slotId.num], slotDimension),
creaturesNum == 0 ? EType::CREATURE_PLACEHOLDER : EType::CREATURE, creatureId.num, slotId));
EType::CREATURE, creaturesNum == 0 ? -1 : creatureId.num, slotId));
slot->clickPressedCallback = clickPressedCallback;
if(creaturesNum != 0)
slot->subtitle->setText(std::to_string(creaturesNum));
@@ -357,7 +349,7 @@ CreaturesPanel::CreaturesPanel(const CTradeableItem::ClickPressedFunctor & click
for(const auto & srcSlot : srcSlots)
{
auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(slotsPos[srcSlot->serial], srcSlot->pos.dimensions()),
emptySlots ? EType::CREATURE_PLACEHOLDER : EType::CREATURE, srcSlot->id, srcSlot->serial));
EType::CREATURE, emptySlots ? -1 : srcSlot->id, srcSlot->serial));
slot->clickPressedCallback = clickPressedCallback;
slot->subtitle->setText(emptySlots ? "" : srcSlot->subtitle->getText());
slot->setSelectionWidth(selectionWidth);
@@ -372,7 +364,7 @@ ArtifactsAltarPanel::ArtifactsAltarPanel(const CTradeableItem::ClickPressedFunct
int slotNum = 0;
for(auto & altarSlotPos : slotsPos)
{
auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(altarSlotPos, Point(44, 44)), EType::ARTIFACT_PLACEHOLDER, -1, slotNum));
auto slot = slots.emplace_back(std::make_shared<CTradeableItem>(Rect(altarSlotPos, Point(44, 44)), EType::ARTIFACT, -1, slotNum));
slot->clickPressedCallback = clickPressedCallback;
slot->subtitle->clear();
slot->subtitle->moveBy(Point(0, -1));

View File

@@ -16,7 +16,7 @@
enum class EType
{
RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, CREATURE_PLACEHOLDER, ARTIFACT_PLACEHOLDER, ARTIFACT_INSTANCE
RESOURCE, PLAYER, ARTIFACT_TYPE, CREATURE, ARTIFACT
};
class CTradeableItem : public SelectableSlot, public std::enable_shared_from_this<CTradeableItem>
@@ -28,19 +28,19 @@ public:
using ClickPressedFunctor = std::function<void(const std::shared_ptr<CTradeableItem>&)>;
EType type;
int id;
const int serial;
int32_t id;
const int32_t serial;
std::shared_ptr<CLabel> subtitle;
ClickPressedFunctor clickPressedCallback;
void setType(EType newType);
void setID(int newID);
void setID(int32_t newID);
void clear();
void showPopupWindow(const Point & cursorPosition) override;
void hover(bool on) override;
void clickPressed(const Point & cursorPosition) override;
CTradeableItem(const Rect & area, EType Type, int ID, int Serial);
CTradeableItem(const Rect & area, EType Type, int32_t ID, int32_t serial);
};
class TradePanelBase : public CIntObject
@@ -61,7 +61,7 @@ public:
virtual void clearSubtitles();
void updateOffer(CTradeableItem & slot, int, int);
void setShowcaseSubtitle(const std::string & text);
int getSelectedItemId() const;
int32_t getHighlightedItemId() const;
void onSlotClickPressed(const std::shared_ptr<CTradeableItem> & newSlot);
bool isHighlighted() const;
};

View File

@@ -17,14 +17,15 @@
#include "../CPlayerInterface.h"
#include "../render/Canvas.h"
#include "../widgets/Buttons.h"
#include "../widgets/CArtPlace.h"
#include "../widgets/CComponent.h"
#include "../widgets/CComponentHolder.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../widgets/ObjectLists.h"
#include "../windows/InfoWindows.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../battle/BattleInterface.h"
#include "../../CCallback.h"
#include "../../lib/ArtifactUtils.h"
@@ -393,7 +394,7 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
auto getSkillDescription = [this](int skillIndex) -> std::string
{
return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + (parent->info->commander->secondarySkills[skillIndex] * 2)];
return parent->getCommanderSkillDescription(skillIndex, parent->info->commander->secondarySkills[skillIndex]);
};
for(int index = ECommander::ATTACK; index <= ECommander::SPELL_POWER; ++index)
@@ -432,7 +433,9 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
for(auto equippedArtifact : parent->info->commander->artifactsWorn)
{
Point artPos = getArtifactPos(equippedArtifact.first);
auto artPlace = std::make_shared<CCommanderArtPlace>(artPos, parent->info->owner, equippedArtifact.first, equippedArtifact.second.artifact);
const auto commanderArt = equippedArtifact.second.artifact;
assert(commanderArt);
auto artPlace = std::make_shared<CCommanderArtPlace>(artPos, parent->info->owner, equippedArtifact.first, commanderArt->getTypeId());
artifacts.push_back(artPlace);
}
@@ -523,6 +526,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
CRClickPopup::createAndPush(parent->info->creature->getDescriptionTranslated());
});
if(parent->info->stackNode != nullptr && parent->info->commander == nullptr)
{
//normal stack, not a commander and not non-existing stack (e.g. recruitment dialog)
@@ -531,14 +535,22 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
name = std::make_shared<CLabel>(215, 12, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName());
const BattleInterface* battleInterface = LOCPLINT->battleInt.get();
const CStack* battleStack = parent->info->stack;
int dmgMultiply = 1;
if(parent->info->owner && parent->info->stackNode->hasBonusOfType(BonusType::SIEGE_WEAPON))
dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK);
if (battleInterface && battleInterface->getBattle() != nullptr && battleStack->hasBonusOfType(BonusType::SIEGE_WEAPON))
{
// Determine the relevant hero based on the unit side
const auto hero = (battleStack->unitSide() == BattleSide::ATTACKER)
? battleInterface->attackingHeroInstance
: battleInterface->defendingHeroInstance;
dmgMultiply += hero->getPrimSkillLevel(PrimarySkill::ATTACK);
}
icons = std::make_shared<CPicture>(ImagePath::builtin("stackWindow/icons"), 117, 32);
const CStack * battleStack = parent->info->stack;
morale = std::make_shared<MoraleLuckBox>(true, Rect(Point(321, 110), Point(42, 42) ));
luck = std::make_shared<MoraleLuckBox>(false, Rect(Point(375, 110), Point(42, 42) ));
@@ -566,7 +578,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter));
addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter));
addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply);
addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter), parent->info->stackNode->getMaxDamage(shooter));
addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth());
addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), parent->info->stackNode->getMovementRange());
@@ -616,11 +628,11 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
auto art = parent->info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT);
if(art)
{
parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
parent->stackArtifactHelp->component.subType = art->artType->getId();
parent->stackArtifactHelp->text = art->getDescription();
parent->stackArtifact = std::make_shared<CArtPlace>(pos, art->getTypeId());
parent->stackArtifact->setShowPopupCallback([](CComponentHolder & artPlace, const Point & cursorPosition)
{
artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
});
if(parent->info->owner)
{
parent->stackArtifactButton = std::make_shared<CButton>(
@@ -905,14 +917,30 @@ std::string CStackWindow::generateStackExpDescription()
return expText;
}
std::string CStackWindow::getCommanderSkillDescription(int skillIndex, int skillLevel)
{
constexpr std::array skillNames = {
"attack",
"defence",
"health",
"damage",
"speed",
"magic"
};
std::string textID = TextIdentifier("vcmi", "commander", "skill", skillNames.at(skillIndex), skillLevel).get();
return CGI->generaltexth->translate(textID);
}
void CStackWindow::setSelection(si32 newSkill, std::shared_ptr<CCommanderSkillIcon> newIcon)
{
auto getSkillDescription = [this](int skillIndex, bool selected) -> std::string
{
if(selected)
return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + ((info->commander->secondarySkills[skillIndex] + 1) * 2)]; //upgrade description
return getCommanderSkillDescription(skillIndex, info->commander->secondarySkills[skillIndex] + 1); //upgrade description
else
return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + (info->commander->secondarySkills[skillIndex] * 2)];
return getCommanderSkillDescription(skillIndex, info->commander->secondarySkills[skillIndex]);
};
auto getSkillImage = [this](int skillIndex)
@@ -987,8 +1015,7 @@ void CStackWindow::removeStackArtifact(ArtifactPosition pos)
artLoc.creature = info->stackNode->armyObj->findStack(info->stackNode);
LOCPLINT->cb->swapArtifacts(artLoc, ArtifactLocation(info->owner->id, slot));
stackArtifactButton.reset();
stackArtifactHelp.reset();
stackArtifactIcon.reset();
stackArtifact.reset();
redraw();
}
}

View File

@@ -28,6 +28,7 @@ class CTabbedInt;
class CButton;
class CMultiLineLabel;
class CListBox;
class CArtPlace;
class CCommanderArtPlace;
class LRClickableArea;
@@ -156,8 +157,7 @@ class CStackWindow : public CWindowObject
MainSection(CStackWindow * owner, int yOffset, bool showExp, bool showArt);
};
std::shared_ptr<CAnimImage> stackArtifactIcon;
std::shared_ptr<LRClickableAreaWTextComp> stackArtifactHelp;
std::shared_ptr<CArtPlace> stackArtifact;
std::shared_ptr<CButton> stackArtifactButton;
@@ -189,6 +189,7 @@ class CStackWindow : public CWindowObject
void init();
std::string generateStackExpDescription();
std::string getCommanderSkillDescription(int skillIndex, int skillLevel);
public:
// for battles

View File

@@ -82,7 +82,8 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
for(int m=0; m < hero->secSkills.size(); ++m)
secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
secSkills[leftRight].push_back(std::make_shared<CSecSkillPlace>(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::MEDIUM,
hero->secSkills[m].first, hero->secSkills[m].second));
specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
@@ -126,21 +127,6 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
{
const CGHeroInstance * hero = heroInst.at(b);
//secondary skill's clickable areas
for(int g=0; g<hero->secSkills.size(); ++g)
{
SecondarySkill skill = hero->secSkills[g].first;
int level = hero->secSkills[g].second; // <1, 3>
secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) );
secSkillAreas[b][g]->component = Component(ComponentType::SEC_SKILL, skill, level);
secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21];
boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->generaltexth->levels[level - 1]);
boost::algorithm::replace_first(secSkillAreas[b][g]->hoverText, "%s", CGI->skillh->getByIndex(skill)->getNameTranslated());
}
heroAreas[b] = std::make_shared<CHeroArea>(257 + 228 * b, 13, hero);
heroAreas[b]->addClickCallback([this, hero]() -> void
{
@@ -192,18 +178,52 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
if(qeLayout)
{
buttonMoveUnitsFromLeftToRight = std::make_shared<CButton>(Point(325, 118), AnimationPath::builtin("quick-exchange/armRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]), [this](){ this->moveUnitsShortcut(true); });
buttonMoveUnitsFromRightToLeft = std::make_shared<CButton>(Point(425, 118), AnimationPath::builtin("quick-exchange/armLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[1]), [this](){ this->moveUnitsShortcut(false); });
buttonMoveArtifactsFromLeftToRight = std::make_shared<CButton>(Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), [this](){ this->moveArtifactsCallback(true);});
buttonMoveArtifactsFromRightToLeft = std::make_shared<CButton>(Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[3]), [this](){ this->moveArtifactsCallback(false);});
buttonMoveUnitsFromLeftToRight = std::make_shared<CButton>(
Point(325, 118),
AnimationPath::builtin("quick-exchange/armRight.DEF"),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllUnits")),
[this](){ this->moveUnitsShortcut(true); });
exchangeUnitsButton = std::make_shared<CButton>(Point(377, 118), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[2]), [this](){ controller.swapArmy(); });
exchangeArtifactsButton = std::make_shared<CButton>(Point(377, 154), AnimationPath::builtin("quick-exchange/swapAll.DEF"), CButton::tooltip(CGI->generaltexth->qeModCommands[4]), [this](){ this->swapArtifactsCallback(); });
buttonMoveUnitsFromRightToLeft = std::make_shared<CButton>(
Point(425, 118),
AnimationPath::builtin("quick-exchange/armLeft.DEF"),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllUnits")),
[this](){ this->moveUnitsShortcut(false); });
backpackButtonLeft = std::make_shared<CButton>(Point(325, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
buttonMoveArtifactsFromLeftToRight = std::make_shared<CButton>(
Point(325, 154), AnimationPath::builtin("quick-exchange/artRight.DEF"),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllArtifacts")),
[this](){ this->moveArtifactsCallback(true);});
buttonMoveArtifactsFromRightToLeft = std::make_shared<CButton>(
Point(425, 154), AnimationPath::builtin("quick-exchange/artLeft.DEF"),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveAllArtifacts")),
[this](){ this->moveArtifactsCallback(false);});
exchangeUnitsButton = std::make_shared<CButton>(
Point(377, 118),
AnimationPath::builtin("quick-exchange/swapAll.DEF"),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.swapAllUnits")),
[this](){ controller.swapArmy(); });
exchangeArtifactsButton = std::make_shared<CButton>(
Point(377, 154),
AnimationPath::builtin("quick-exchange/swapAll.DEF"),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.swapAllArtifacts")),
[this](){ this->swapArtifactsCallback(); });
backpackButtonLeft = std::make_shared<CButton>(
Point(325, 518),
AnimationPath::builtin("heroBackpack"),
CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
[this](){ this->backpackShortcut(true); });
backpackButtonRight = std::make_shared<CButton>(Point(419, 518), AnimationPath::builtin("heroBackpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
backpackButtonRight = std::make_shared<CButton>(
Point(419, 518),
AnimationPath::builtin("heroBackpack"),
CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
[this](){ this->backpackShortcut(false); });
backpackButtonLeft->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/backpackButtonIcon")));
backpackButtonRight->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("heroWindow/backpackButtonIcon")));
@@ -227,7 +247,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
std::make_shared<CButton>(
Point(484 + 35 * i, 154),
AnimationPath::builtin("quick-exchange/unitLeft.DEF"),
CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")),
std::bind(&CExchangeController::moveStack, &controller, false, SlotID(i))));
moveUnitFromRightToLeftButtons.back()->block(leftHeroBlock);
@@ -235,7 +255,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
std::make_shared<CButton>(
Point(66 + 35 * i, 154),
AnimationPath::builtin("quick-exchange/unitRight.DEF"),
CButton::tooltip(CGI->generaltexth->qeModCommands[1]),
CButton::tooltip(CGI->generaltexth->translate("vcmi.quickExchange.moveUnit")),
std::bind(&CExchangeController::moveStack, &controller, true, SlotID(i))));
moveUnitFromLeftToRightButtons.back()->block(rightHeroBlock);
}
@@ -362,7 +382,7 @@ void CExchangeWindow::update()
int id = hero->secSkills[m].first;
int level = hero->secSkills[m].second;
secSkillIcons[leftRight][m]->setFrame(2 + id * 3 + level);
secSkills[leftRight][m]->setSkill(id, level);
}
expValues[leftRight]->setText(TextOperations::formatMetric(hero->exp, 3));

View File

@@ -19,7 +19,6 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
std::array<std::shared_ptr<CLabel>, 2> titles;
std::vector<std::shared_ptr<CAnimImage>> primSkillImages;//shared for both heroes
std::array<std::vector<std::shared_ptr<CLabel>>, 2> primSkillValues;
std::array<std::vector<std::shared_ptr<CAnimImage>>, 2> secSkillIcons;
std::array<std::shared_ptr<CAnimImage>, 2> specImages;
std::array<std::shared_ptr<CAnimImage>, 2> expImages;
std::array<std::shared_ptr<CLabel>, 2> expValues;
@@ -27,7 +26,7 @@ class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public
std::array<std::shared_ptr<CLabel>, 2> manaValues;
std::vector<std::shared_ptr<LRClickableAreaWTextComp>> primSkillAreas;
std::array<std::vector<std::shared_ptr<LRClickableAreaWTextComp>>, 2> secSkillAreas;
std::array<std::vector<std::shared_ptr<CSecSkillPlace>>, 2> secSkills;
std::array<std::shared_ptr<CHeroArea>, 2> heroAreas;
std::array<std::shared_ptr<LRClickableAreaWText>, 2> specialtyAreas;

View File

@@ -93,10 +93,6 @@ CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero,
if(const auto curHero = arts->getHero())
swapArtifactAndClose(*arts, artPlace.slot, ArtifactLocation(curHero->id, arts->getFilterSlot()));
};
arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition)
{
showArifactInfo(*arts, artPlace, cursorPosition);
};
addSet(arts);
arts->setHero(hero);
addUsedEvents(GESTURE);

View File

@@ -206,7 +206,7 @@ void CHeroOverview::genControls()
i = 0;
for(auto & skill : (*CGI->heroh)[heroIdx]->secSkillsInit)
{
imageSecSkills.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex() * 3 + skill.second + 2, 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)));
imageSecSkills.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex(skill.second + 2), 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)));
labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->levels[skill.second - 1]));
labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->skillh)[skill.first]->getNameTranslated()));
i++;

View File

@@ -152,8 +152,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
{
Rect r = Rect(i%2 == 0 ? 18 : 162, 276 + 48 * (i/2), 136, 42);
secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, ComponentType::SEC_SKILL));
secSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSKILL"), 0, 0, r.x, r.y));
secSkills.emplace_back(std::make_shared<CSecSkillPlace>(r.topLeft(), CSecSkillPlace::ImageSize::LARGE));
int x = (i % 2) ? 212 : 68;
int y = 280 + 48 * (i/2);
@@ -234,20 +233,16 @@ void CHeroWindow::update()
}
//secondary skills support
for(size_t g=0; g< secSkillAreas.size(); ++g)
for(size_t g=0; g< secSkills.size(); ++g)
{
SecondarySkill skill = curHero->secSkills[g].first;
int level = curHero->getSecSkillLevel(skill);
std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated();
std::string skillValue = CGI->generaltexth->levels[level-1];
secSkillAreas[g]->component.subType = skill;
secSkillAreas[g]->component.value = level;
secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName);
secSkillImages[g]->setFrame(skill*3 + level + 2);
secSkillNames[g]->setText(skillName);
secSkillValues[g]->setText(skillValue);
secSkills[g]->setSkill(skill, level);
}
std::ostringstream expstr;

View File

@@ -74,8 +74,7 @@ class CHeroWindow : public CStatusbarWindow, public IGarrisonHolder, public CWin
std::shared_ptr<CLabel> specName;
std::shared_ptr<MoraleLuckBox> morale;
std::shared_ptr<MoraleLuckBox> luck;
std::vector<std::shared_ptr<LRClickableAreaWTextComp>> secSkillAreas;
std::vector<std::shared_ptr<CAnimImage>> secSkillImages;
std::vector< std::shared_ptr<CSecSkillPlace>> secSkills;
std::vector<std::shared_ptr<CLabel>> secSkillNames;
std::vector<std::shared_ptr<CLabel>> secSkillValues;

View File

@@ -583,28 +583,16 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
if(object->ID == Obj::MINE || object->ID == Obj::ABANDONED_MINE)
{
const CGMine * mine = dynamic_cast<const CGMine *>(object);
assert(mine);
minesCount[mine->producedResource]++;
totalIncome += mine->dailyIncome()[EGameResID::GOLD];
}
}
//Heroes can produce gold as well - skill, specialty or arts
std::vector<const CGHeroInstance*> heroes = LOCPLINT->cb->getHeroesInfo(true);
auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID);
for(auto & hero : heroes)
{
totalIncome += hero->dailyIncome()[EGameResID::GOLD];
}
//Add town income of all towns
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
for(auto & town : towns)
{
totalIncome += town->dailyIncome()[EGameResID::GOLD];
}
for(auto & mapObject : ownedObjects)
totalIncome += mapObject->asOwnable()->dailyIncome()[EGameResID::GOLD];
//if player has some modded boosts we want to show that as well
const auto * playerSettings = LOCPLINT->cb->getPlayerSettings(LOCPLINT->playerID);
const auto & towns = LOCPLINT->cb->getTownsInfo(true);
totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_CONSTANT_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * playerSettings->handicap.percentIncome / 100;
totalIncome += LOCPLINT->cb->getPlayerState(LOCPLINT->playerID)->valOfBonuses(BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST, BonusSubtypeID(GameResID(EGameResID::GOLD))) * towns.size() * playerSettings->handicap.percentIncome / 100;

View File

@@ -219,7 +219,6 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE));
artSets.clear();
const auto heroArts = artsSellingMarket->getAOHset();
heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);};
addSet(heroArts);
marketWidget = artsSellingMarket;
initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);

View File

@@ -128,12 +128,6 @@ void CWindowWithArtifacts::showArtifactAssembling(const CArtifactsOfHeroBase & a
}
}
void CWindowWithArtifacts::showArifactInfo(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const
{
if(artsInst.getArt(artPlace.slot) && artPlace.text.size())
artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
}
void CWindowWithArtifacts::showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot,
const Point & cursorPosition) const
{

View File

@@ -31,7 +31,6 @@ public:
bool allowExchange, bool altarTrading, bool closeWindow);
void swapArtifactAndClose(const CArtifactsOfHeroBase & artsInst, const ArtifactPosition & slot, const ArtifactLocation & dstLoc);
void showArtifactAssembling(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const;
void showArifactInfo(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const;
void showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot, const Point & cursorPosition) const;
void activate() override;
void deactivate() override;

View File

@@ -896,28 +896,18 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int
pos.x += X;
pos.y += Y;
icon = std::make_shared<CAnimImage>(AnimationPath::builtin("SECSKILL"), _ID * 3 + 3, 0);
pos.h = icon->pos.h;
pos.w = icon->pos.w;
skill = std::make_shared<CSecSkillPlace>(Point(), CSecSkillPlace::ImageSize::LARGE, _ID, 1);
skill->setClickPressedCallback([this](const CComponentHolder&, const Point& cursorPosition)
{
bool skillKnown = parent->hero->getSecSkillLevel(ID);
bool canLearn = parent->hero->canLearnSkill(ID);
if(!skillKnown && canLearn)
GH.windows().createAndPushWindow<CUnivConfirmWindow>(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000);
});
update();
}
void CUniversityWindow::CItem::clickPressed(const Point & cursorPosition)
{
bool skillKnown = parent->hero->getSecSkillLevel(ID);
bool canLearn = parent->hero->canLearnSkill(ID);
if (!skillKnown && canLearn)
GH.windows().createAndPushWindow<CUnivConfirmWindow>(parent, ID, LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) >= 2000);
}
void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition)
{
CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared<CComponent>(ComponentType::SEC_SKILL, ID, 1));
}
void CUniversityWindow::CItem::update()
{
bool skillKnown = parent->hero->getSecSkillLevel(ID);
@@ -941,14 +931,6 @@ void CUniversityWindow::CItem::update()
level = std::make_shared<CLabel>(22, 57, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->levels[0]);
}
void CUniversityWindow::CItem::hover(bool on)
{
if(on)
GH.statusbar()->write(ID.toEntity(VLC)->getNameTranslated());
else
GH.statusbar()->clear();
}
CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID building, const IMarket * _market, const std::function<void()> & onWindowClosed)
: CWindowObject(PLAYER_COLORED, ImagePath::builtin("UNIVERS1")),
hero(_hero),
@@ -1131,6 +1113,9 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI
statusbar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
garr = std::make_shared<CGarrisonInt>(Point(108, 60), 18, Point(), hero, nullptr);
statusbar->write(VLC->generaltexth->translate(dynamic_cast<const HillFort *>(fort)->getDescriptionToolTip()));
updateGarrisons();
}
@@ -1148,45 +1133,59 @@ void CHillFortWindow::updateGarrisons()
TResources totalSum; // totalSum[resource ID] = value
auto getImgIdx = [](CHillFortWindow::State st) -> std::size_t
{
switch (st)
{
case State::EMPTY:
return 0;
case State::UNAVAILABLE:
case State::ALREADY_UPGRADED:
return 1;
default:
return static_cast<std::size_t>(st);
}
};
for(int i=0; i<slotsCount; i++)
{
std::fill(costs[i].begin(), costs[i].end(), 0);
int newState = getState(SlotID(i));
if(newState != -1)
State newState = getState(SlotID(i));
if(newState != State::EMPTY)
{
UpgradeInfo info;
LOCPLINT->cb->fillUpgradeInfo(hero, SlotID(i), info);
if(info.newID.size())//we have upgrades here - update costs
{
costs[i] = info.cost[0] * hero->getStackCount(SlotID(i));
costs[i] = info.cost.back() * hero->getStackCount(SlotID(i));
totalSum += costs[i];
}
}
currState[i] = newState;
upgrade[i]->setImage(AnimationPath::builtin(currState[i] == -1 ? slotImages[0] : slotImages[currState[i]]));
upgrade[i]->block(currState[i] == -1);
upgrade[i]->setImage(AnimationPath::builtin(slotImages[getImgIdx(currState[i])]));
upgrade[i]->block(currState[i] == State::EMPTY);
upgrade[i]->addHoverText(EButtonState::NORMAL, getTextForSlot(SlotID(i)));
}
//"Upgrade all" slot
int newState = 2;
State newState = State::MAKE_UPGRADE;
{
TResources myRes = LOCPLINT->cb->getResourceAmount();
bool allUpgraded = true;//All creatures are upgraded?
for(int i=0; i<slotsCount; i++)
allUpgraded &= currState[i] == 1 || currState[i] == -1;
allUpgraded &= currState[i] == State::ALREADY_UPGRADED || currState[i] == State::EMPTY || currState[i] == State::UNAVAILABLE;
if(allUpgraded)
newState = 1;
if (allUpgraded)
newState = State::ALREADY_UPGRADED;
if(!totalSum.canBeAfforded(myRes))
newState = 0;
newState = State::UNAFFORDABLE;
}
currState[slotsCount] = newState;
upgradeAll->setImage(AnimationPath::builtin(allImages[newState]));
upgradeAll->setImage(AnimationPath::builtin(allImages[static_cast<std::size_t>(newState)]));
garr->recreateSlots();
@@ -1199,7 +1198,7 @@ void CHillFortWindow::updateGarrisons()
slotLabels[i][j]->setText("");
}
//if can upgrade or can not afford, draw cost
if(currState[i] == 0 || currState[i] == 2)
if(currState[i] == State::UNAFFORDABLE || currState[i] == State::MAKE_UPGRADE)
{
if(costs[i].nonZero())
{
@@ -1244,24 +1243,30 @@ void CHillFortWindow::updateGarrisons()
void CHillFortWindow::makeDeal(SlotID slot)
{
assert(slot.getNum()>=0);
int offset = (slot.getNum() == slotsCount)?2:0;
assert(slot.getNum() >= 0);
int offset = (slot.getNum() == slotsCount) ? 2 : 0;
switch(currState[slot.getNum()])
{
case 0:
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector<std::shared_ptr<CComponent>>(), soundBase::sound_todo);
break;
case 1:
case State::ALREADY_UPGRADED:
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[313 + offset], std::vector<std::shared_ptr<CComponent>>(), soundBase::sound_todo);
break;
case 2:
for(int i=0; i<slotsCount; i++)
case State::UNAFFORDABLE:
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[314 + offset], std::vector<std::shared_ptr<CComponent>>(), soundBase::sound_todo);
break;
case State::UNAVAILABLE:
{
std::string message = VLC->generaltexth->translate(dynamic_cast<const HillFort *>(fort)->getUnavailableUpgradeMessage());
LOCPLINT->showInfoDialog(message, std::vector<std::shared_ptr<CComponent>>(), soundBase::sound_todo);
break;
}
case State::MAKE_UPGRADE:
for(int i = 0; i < slotsCount; i++)
{
if(slot.getNum() ==i || ( slot.getNum() == slotsCount && currState[i] == 2 ))//this is activated slot or "upgrade all"
if(slot.getNum() == i || ( slot.getNum() == slotsCount && currState[i] == State::MAKE_UPGRADE ))//this is activated slot or "upgrade all"
{
UpgradeInfo info;
LOCPLINT->cb->fillUpgradeInfo(hero, SlotID(i), info);
LOCPLINT->cb->upgradeCreature(hero, SlotID(i), info.newID[0]);
LOCPLINT->cb->upgradeCreature(hero, SlotID(i), info.newID.back());
}
}
break;
@@ -1283,22 +1288,28 @@ std::string CHillFortWindow::getTextForSlot(SlotID slot)
return str;
}
int CHillFortWindow::getState(SlotID slot)
CHillFortWindow::State CHillFortWindow::getState(SlotID slot)
{
TResources myRes = LOCPLINT->cb->getResourceAmount();
if(hero->slotEmpty(slot))//no creature here
return -1;
if(hero->slotEmpty(slot))
return State::EMPTY;
UpgradeInfo info;
LOCPLINT->cb->fillUpgradeInfo(hero, slot, info);
if(!info.newID.size())//already upgraded
return 1;
if (info.newID.empty())
{
// Hill Fort may limit level of upgradeable creatures, e.g. mini Hill Fort from HOTA
if (hero->getCreature(slot)->hasUpgrades())
return State::UNAVAILABLE;
if(!(info.cost[0] * hero->getStackCount(slot)).canBeAfforded(myRes))
return 0;
return State::ALREADY_UPGRADED;
}
return 2;//can upgrade
if(!(info.cost.back() * hero->getStackCount(slot)).canBeAfforded(myRes))
return State::UNAFFORDABLE;
return State::MAKE_UPGRADE;
}
CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner):

View File

@@ -46,6 +46,7 @@ class VideoWidget;
class VideoWidgetOnce;
class GraphicalPrimitiveCanvas;
class TransparentFilledRectangle;
class CSecSkillPlace;
enum class EUserEvent;
@@ -370,7 +371,7 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder
{
class CItem final : public CIntObject
{
std::shared_ptr<CAnimImage> icon;
std::shared_ptr<CSecSkillPlace> skill;
std::shared_ptr<CPicture> topBar;
std::shared_ptr<CPicture> bottomBar;
std::shared_ptr<CLabel> name;
@@ -379,9 +380,6 @@ class CUniversityWindow final : public CStatusbarWindow, public IMarketHolder
SecondarySkill ID;//id of selected skill
CUniversityWindow * parent;
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
void hover(bool on) override;
void update();
CItem(CUniversityWindow * _parent, int _ID, int X, int Y);
};
@@ -451,9 +449,11 @@ public:
class CHillFortWindow : public CStatusbarWindow, public IGarrisonHolder
{
private:
static const int slotsCount = 7;
enum class State { UNAFFORDABLE, ALREADY_UPGRADED, MAKE_UPGRADE, EMPTY, UNAVAILABLE };
static constexpr std::size_t slotsCount = 7;
//todo: mithril support
static const int resCount = 7;
static constexpr std::size_t resCount = 7;
const CGObjectInstance * fort;
const CGHeroInstance * hero;
@@ -465,7 +465,7 @@ private:
std::array<std::shared_ptr<CLabel>, resCount> totalLabels;
std::array<std::shared_ptr<CButton>, slotsCount> upgrade;//upgrade single creature
std::array<int, slotsCount + 1> currState;//current state of slot - to avoid calls to getState or updating buttons
std::array<State, slotsCount + 1> currState;//current state of slot - to avoid calls to getState or updating buttons
//there is a place for only 2 resources per slot
std::array< std::array<std::shared_ptr<CAnimImage>, 2>, slotsCount> slotIcons;
@@ -480,7 +480,7 @@ private:
std::string getTextForSlot(SlotID slot);
void makeDeal(SlotID slot);//-1 for upgrading all creatures
int getState(SlotID slot); //-1 = no creature 0=can't upgrade, 1=upgraded, 2=can upgrade
State getState(SlotID slot);
public:
CHillFortWindow(const CGHeroInstance * visitor, const CGObjectInstance * object);
void updateGarrisons() override;//update buttons after garrison changes

View File

@@ -2002,6 +2002,10 @@
{
"type" : "HAS_ANOTHER_BONUS_LIMITER",
"parameters" : [ "GARGOYLE" ]
},
{
"type" : "HAS_ANOTHER_BONUS_LIMITER",
"parameters" : [ "SIEGE_WEAPON" ]
}
]
},

View File

@@ -556,6 +556,14 @@
}
},
"PRISM_HEX_ATTACK_BREATH":
{
"graphics":
{
"icon": "zvs/Lib1.res/PrismBreath"
}
},
"THREE_HEADED_ATTACK":
{
"graphics":

View File

@@ -187,7 +187,7 @@
"bonuses": [
{
"type": "PRIMARY_SKILL",
"subtype": "primarySkill.defence",
"subtype": "primarySkill.attack",
"val": 2
}
],

View File

@@ -199,7 +199,7 @@
"dwellingLvl3": { "id" : 32, "requires" : [ "dwellingLvl1" ] },
"dwellingLvl4": { "id" : 33, "requires" : [ "dwellingLvl3" ] },
"dwellingLvl5": { "id" : 34, "requires" : [ "dwellingLvl3" ] },
"dwellingLvl6": { "id" : 35, "requires" : [ "allOf", [ "dwellingLvl3" ], [ "dwellingLvl4" ] ] },
"dwellingLvl6": { "id" : 35, "requires" : [ "allOf", [ "dwellingLvl4" ], [ "dwellingLvl5" ] ] },
"dwellingLvl7": { "id" : 36, "requires" : [ "allOf", [ "dwellingLvl6" ], [ "mageGuild2" ] ] },
"dwellingUpLvl1": { "id" : 37, "upgrades" : "dwellingLvl1" },

View File

@@ -53,6 +53,7 @@
"config/objects/creatureBanks.json",
"config/objects/dwellings.json",
"config/objects/generic.json",
"config/objects/lighthouse.json",
"config/objects/magicSpring.json",
"config/objects/magicWell.json",
"config/objects/moddables.json",

View File

@@ -198,7 +198,7 @@
{
"index": 91,
"class" : "warlock",
"female": true,
"female": false,
"spellbook": [ "resurrection" ],
"skills":
[

View File

@@ -176,7 +176,7 @@
"mutare":
{
"index": 151,
"class" : "warlock",
"class" : "overlord",
"female": true,
"special" : true,
"spellbook": [ "magicArrow" ],
@@ -220,7 +220,7 @@
"mutareDrake":
{
"index": 153,
"class" : "warlock",
"class" : "overlord",
"female": true,
"special" : true,
"spellbook": [ "magicArrow" ],

View File

@@ -261,7 +261,8 @@
}
]
},
"inpCache" : {
"impCache" : {
"compatibilityIdentifiers" : [ "inpCache" ],
"index" : 3,
"name" : "Imp Cache",
"aiValue" : 1500,

View File

@@ -270,23 +270,6 @@
}
}
},
"lighthouse" : {
"index" :42,
"handler" : "lighthouse",
"base" : {
"sounds" : {
"visit" : ["LIGHTHOUSE"]
}
},
"types" : {
"object" : {
"index" : 0,
"aiValue" : 500,
"rmg" : {
}
}
}
},
"obelisk" : {
"index" :57,
"handler" : "obelisk",
@@ -683,6 +666,7 @@
"object" : {
"index" : 0,
"aiValue" : 7000,
"description" : "",
"rmg" : {
"zoneLimit" : 1,
"value" : 7000,

View File

@@ -0,0 +1,29 @@
{
"lighthouse" : {
"index" :42,
"handler" : "flaggable",
"base" : {
"sounds" : {
"visit" : ["LIGHTHOUSE"]
}
},
"types" : {
"lighthouse" : {
"compatibilityIdentifiers" : [ "object" ],
"index" : 0,
"aiValue" : 500,
"rmg" : {
},
"message" : "@core.advevent.69",
"bonuses" : {
"seaMovement" : {
"type" : "MOVEMENT",
"subtype" : "heroMovementSea",
"val" : 500
}
}
}
}
}
}

View File

@@ -63,10 +63,10 @@
}
]
},
"message" : [ 106, "%s.", 108 ] // No Wisdom
"message" : [ 106, "%s. ", 108 ] // No Wisdom
},
{
"message" : [ 106, "%s.", 109 ] // No spellbook
"message" : [ 106, "%s. ", 109 ] // No spellbook
}
]

View File

@@ -49,7 +49,7 @@
"message" : [ 127, "%s." ] // You learn new spell
}
],
"onVisitedMessage" : [ 127, "%s.", 174 ], // You already known this spell
"onVisitedMessage" : [ 127, "%s. ", 174 ], // You already known this spell
"onEmpty" : [
{
"limiter" : {
@@ -59,10 +59,10 @@
}
]
},
"message" : [ 127, "%s.", 130 ] // No Wisdom
"message" : [ 127, "%s. ", 130 ] // No Wisdom
},
{
"message" : [ 127, "%s.", 131 ] // No spellbook
"message" : [ 127, "%s. ", 131 ] // No spellbook
}
]
}
@@ -118,7 +118,7 @@
"message" : [ 128, "%s." ] // You learn new spell
}
],
"onVisitedMessage" : [ 128, "%s.", 174 ], // You already known this spell
"onVisitedMessage" : [ 128, "%s. ", 174 ], // You already known this spell
"onEmpty" : [
{
"limiter" : {
@@ -128,10 +128,10 @@
}
]
},
"message" : [ 128, "%s.", 130 ] // No Wisdom
"message" : [ 128, "%s. ", 130 ] // No Wisdom
},
{
"message" : [ 128, "%s.", 131 ] // No spellbook
"message" : [ 128, "%s. ", 131 ] // No spellbook
}
]
}
@@ -187,7 +187,7 @@
"message" : [ 129, "%s." ] // You learn new spell
}
],
"onVisitedMessage" : [ 129, "%s.", 174 ], // You already known this spell
"onVisitedMessage" : [ 129, "%s. ", 174 ], // You already known this spell
"onEmpty" : [
{
"limiter" : {
@@ -197,10 +197,10 @@
}
]
},
"message" : [ 129, "%s.", 130 ] // No Wisdom
"message" : [ 129, "%s. ", 130 ] // No Wisdom
},
{
"message" : [ 129, "%s.", 131 ] // No spellbook
"message" : [ 129, "%s. ", 131 ] // No spellbook
}
]
}

View File

@@ -122,6 +122,11 @@
"description" : "List of mods that are required to run this one",
"items" : { "type" : "string" }
},
"softDepends" : {
"type" : "array",
"description" : "List of mods if they are enabled, should be loaded before this one. This mod will overwrite any conflicting items from its soft dependency mods",
"items" : { "type" : "string" }
},
"conflicts" : {
"type" : "array",
"description" : "List of mods that can't be enabled in the same time as this one",

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@
{
"targetType" : "CREATURE",
"type": "combat",
"name": "Land Mine",
"name": "",
"school":
{
"air": false,
@@ -237,7 +237,7 @@
"fireWallTrigger" : {
"targetType" : "CREATURE",
"type": "combat",
"name": "Fire Wall",
"name": "",
"school":
{
"air": false,

View File

@@ -1,8 +1,8 @@
{
"summonDemons" : {
"summonDemons" : {
"type": "ability",
"targetType" : "CREATURE",
"name": "Summon Demons",
"name": "",
"school" : {},
"level": 2,
"power": 50,
@@ -47,11 +47,11 @@
"bonus.GARGOYLE" : "absolute"
}
}
},
"firstAid" : {
},
"firstAid" : {
"targetType" : "CREATURE",
"type": "ability",
"name": "First Aid",
"name": "",
"school" : {},
"level": 1,
"power": 10,
@@ -107,7 +107,7 @@
"catapultShot" : {
"targetType" : "LOCATION",
"type": "ability",
"name": "Catapult shot",
"name": "",
"school" : {},
"level": 1,
"power": 1,
@@ -188,7 +188,7 @@
"cyclopsShot" : {
"targetType" : "LOCATION",
"type": "ability",
"name": "Siege shot",
"name": "",
"school" : {},
"level": 1,
"power": 1,

View File

@@ -506,6 +506,10 @@ Affected unit attacks all adjacent creatures (Hydra). Only directly targeted cre
Affected unit attacks creature located directly behind targeted tile (Dragons). Only directly targeted creature will attempt to retaliate
### PRISM_HEX_ATTACK_BREATH
Like `TWO_HEX_ATTACK_BREATH` but affects also two additional cratures (in triangle form from target tile)
### RETURN_AFTER_STRIKE
Affected unit can return to his starting location after attack (Harpies)

View File

@@ -49,6 +49,7 @@ These are object types that are available for modding and have configurable prop
- `dwelling` - see [Dwelling](Map_Objects/Dwelling.md). Object that allows recruitments of units outside of towns
- `market` - see [Market](Map_Objects/Market.md). Trading resources, artifacts, creatures and such
- `boat` - see [Boat](Map_Objects/Boat.md). Object to move across different terrains, such as water
- `flaggable` - see [Flaggable](Map_Objects/Flaggable.md). Object that can be flagged by a player to provide [Bonus](Bonus_Format.md) or resources
- `hillFort` - TODO: documentation. See config files in vcmi installation for reference
- `shipyard` - TODO: documentation. See config files in vcmi installation for reference
- `terrain` - Defines terrain overlays such as magic grounds. TODO: documentation. See config files in vcmi installation for reference
@@ -60,7 +61,6 @@ These are types that don't have configurable properties, however it is possible
- `generic` - Defines empty object type that provides no functionality. Note that unlike `static`, objects of this type are never used by RMG
- `borderGate`
- `borderGuard`
- `lighthouse`
- `magi`
- `mine`
- `obelisk`

View File

@@ -0,0 +1,39 @@
# Flaggable objects
Flaggable object are those that can be captured by a visiting hero. H3 examples are mines, dwellings, or lighthouse.
Currently, it is possible to make flaggable objects that provide player with:
- Any [Bonus](Bonus_Format.md) supported by bonus system
- Daily resources income (wood, ore, gold, etc)
## Format description
```jsonc
{
"baseObjectName" : {
"name" : "Object name",
"handler" : "flaggable",
"types" : {
"objectName" : {
// Text for message that player will get on capturing this object with a hero
// Alternatively, it is possible to reuse existing string from H3 using form '@core.advevent.69'
"onVisit" : "{Object Name}\r\n\r\nText of messages that player will see on visit.",
// List of bonuses that will be granted to player that owns this object
"bonuses" : {
"firstBonus" : { BONUS FORMAT },
"secondBonus" : { BONUS FORMAT },
},
// Resources that will be given to owner on every day
"dailyIncome" : {
"wood" : 2,
"ore" : 2,
"gold" : 1000
}
}
}
}
}
```

View File

@@ -48,6 +48,12 @@
[
"baseMod"
],
// List of mods if they are enabled, should be loaded before this one. This mod will overwrite any conflicting items from its soft dependency mods.
"softDepends" :
[
"baseMod"
],
// List of mods that can't be enabled in the same time as this one
"conflicts" :

View File

@@ -121,6 +121,7 @@ Below a list of supported commands, with their arguments wrapped in `<>`
#### Extract commands
`translate` - save game texts into json files
`translate missing` - save untranslated game texts into json files
`translate maps` - save map and campaign texts into json files
`get config` - save game objects data into json files
`get scripts` - dumps lua script stuff into files (currently inactive due to scripting disabled for default builds)

View File

@@ -26,7 +26,7 @@ Note: if you don't need in-game videos, you can omit downloading/copying file VI
### Step 2.a: Installing data files with GOG offline installer
If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .exe file first, then the .bin file. This may take a few seconds. Please be patient.
If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .bin file first, then the .exe file. This may take a few seconds. Please be patient.
### Step 2.b: Installing data files with Finder or Windows explorer

View File

@@ -17,7 +17,7 @@ Please report about gameplay problem on forums: [Help & Bugs](https://forum.vcmi
### Step 2.a: Installing data files with GOG offline installer
If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .exe file first, then the .bin file. This may take a few seconds. Please be patient.
If you bought HoMM3 on [GOG](https://www.gog.com/de/game/heroes_of_might_and_magic_3_complete_edition), you can download the files directly from the browser and install them in the launcher. Select the .bin file first, then the .exe file. This may take a few seconds. Please be patient.
### Step 2.b: Installing by the classic way

View File

@@ -136,6 +136,8 @@ After that, start Launcher, switch to Help tab and open "log files directory". Y
If your mod also contains maps or campaigns that you want to translate, then use '/translate maps' command instead.
If you want to update existing translation, you can use '/translate missing' command that will export only strings that were not translated
### Translating mod information
In order to display information in Launcher in language selected by user add following block into your `mod.json`:
```

View File

@@ -838,9 +838,16 @@ void CSettingsView::on_sliderScalingCursor_valueChanged(int value)
void CSettingsView::on_buttonScalingAuto_toggled(bool checked)
{
ui->spinBoxInterfaceScaling->setDisabled(checked);
ui->spinBoxInterfaceScaling->setValue(100);
if (checked)
{
ui->spinBoxInterfaceScaling->hide();
}
else
{
ui->spinBoxInterfaceScaling->show();
ui->spinBoxInterfaceScaling->setValue(100);
}
Settings node = settings.write["video"]["resolution"]["scaling"];
node->Integer() = checked ? 0 : 100;
}

View File

@@ -44,7 +44,7 @@
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="121"/>
<source>Check for updates</source>
<translation>Verificar por atualizações</translation>
<translation>Verificar atualizações</translation>
</message>
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="189"/>
@@ -54,7 +54,7 @@
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="114"/>
<source>Log files directory</source>
<translation>Diretório do arquivo de registro</translation>
<translation>Diretório de arquivos de registro</translation>
</message>
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="107"/>
@@ -79,7 +79,7 @@
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="290"/>
<source>Project homepage</source>
<translation>Página da web do projeto</translation>
<translation>Página do projeto</translation>
</message>
<message>
<location filename="../aboutProject/aboutproject_moc.ui" line="303"/>

View File

@@ -134,14 +134,6 @@ std::string CArtifactInstance::nodeName() const
return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type";
}
std::string CArtifactInstance::getDescription() const
{
std::string text = artType->getDescriptionTranslated();
if(artType->isScroll())
ArtifactUtils::insertScrrollSpellName(text, getScrollSpellID());
return text;
}
ArtifactID CArtifactInstance::getTypeId() const
{
return artType->getId();

View File

@@ -80,7 +80,6 @@ public:
CArtifactInstance();
void setType(const CArtifact * art);
std::string nodeName() const override;
std::string getDescription() const;
ArtifactID getTypeId() const;
ArtifactInstanceID getId() const;
void setId(ArtifactInstanceID id);

View File

@@ -484,7 +484,7 @@ std::vector<ConstTransitivePtr<CGObjectInstance>> CGameInfoCallback::getAllVisit
{
std::vector<ConstTransitivePtr<CGObjectInstance>> ret;
for(auto & obj : gs->map->objects)
if(obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj))
if(obj && obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj))
ret.push_back(obj);
return ret;

View File

@@ -116,6 +116,7 @@ set(lib_MAIN_SRCS
mapObjectConstructors/CommonConstructors.cpp
mapObjectConstructors/CRewardableConstructor.cpp
mapObjectConstructors/DwellingInstanceConstructor.cpp
mapObjectConstructors/FlaggableInstanceConstructor.cpp
mapObjectConstructors/HillFortInstanceConstructor.cpp
mapObjectConstructors/ShipyardInstanceConstructor.cpp
@@ -132,6 +133,7 @@ set(lib_MAIN_SRCS
mapObjects/CObjectHandler.cpp
mapObjects/CQuest.cpp
mapObjects/CRewardableObject.cpp
mapObjects/FlaggableMapObject.cpp
mapObjects/IMarket.cpp
mapObjects/IObjectInterface.cpp
mapObjects/MiscObjects.cpp
@@ -497,6 +499,7 @@ set(lib_MAIN_HEADERS
mapObjectConstructors/CRewardableConstructor.h
mapObjectConstructors/DwellingInstanceConstructor.h
mapObjectConstructors/HillFortInstanceConstructor.h
mapObjectConstructors/FlaggableInstanceConstructor.h
mapObjectConstructors/IObjectInfo.h
mapObjectConstructors/RandomMapInfo.h
mapObjectConstructors/ShipyardInstanceConstructor.h
@@ -515,6 +518,7 @@ set(lib_MAIN_HEADERS
mapObjects/CObjectHandler.h
mapObjects/CQuest.h
mapObjects/CRewardableObject.h
mapObjects/FlaggableMapObject.h
mapObjects/IMarket.h
mapObjects/IObjectInterface.h
mapObjects/IOwnableObject.h

View File

@@ -45,7 +45,12 @@ int32_t CSkill::getIndex() const
int32_t CSkill::getIconIndex() const
{
return getIndex(); //TODO: actual value with skill level
return getIndex() * 3 + 3; // Base master level
}
int32_t CSkill::getIconIndex(uint8_t skillMasterLevel) const
{
return getIconIndex() + skillMasterLevel;
}
std::string CSkill::getNameTextID() const
@@ -122,7 +127,7 @@ CSkill::LevelInfo & CSkill::at(int level)
DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInfo & info)
{
for(int i=0; i < info.effects.size(); i++)
out << (i ? "," : "") << info.effects[i]->Description();
out << (i ? "," : "") << info.effects[i]->Description(nullptr);
return out << "])";
}

View File

@@ -34,6 +34,7 @@ public:
private:
std::vector<LevelInfo> levels; // bonuses provided by basic, advanced and expert level
void addNewBonus(const std::shared_ptr<Bonus> & b, int level);
int32_t getIconIndex() const override;
SecondarySkill id;
std::string modScope;
@@ -50,7 +51,7 @@ public:
};
int32_t getIndex() const override;
int32_t getIconIndex() const override;
int32_t getIconIndex(uint8_t skillMasterLevel) const;
std::string getJsonKey() const override;
std::string getModScope() const override;
void registerIcons(const IconRegistar & cb) const override;

View File

@@ -161,54 +161,45 @@ struct RangeGenerator
BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, const BattleLayout & layout, const CGTownInstance * town)
{
CMP_stack cmpst;
auto * curB = new BattleInfo(layout);
auto * currentBattle = new BattleInfo(layout);
for(auto i : { BattleSide::LEFT_SIDE, BattleSide::RIGHT_SIDE})
curB->sides[i].init(heroes[i], armies[i]);
currentBattle->sides[i].init(heroes[i], armies[i]);
std::vector<CStack*> & stacks = (curB->stacks);
std::vector<CStack*> & stacks = (currentBattle->stacks);
curB->tile = tile;
curB->battlefieldType = battlefieldType;
curB->round = -2;
curB->activeStack = -1;
curB->replayAllowed = false;
if(town)
{
curB->town = town;
curB->terrainType = town->getNativeTerrain();
}
else
{
curB->town = nullptr;
curB->terrainType = terrain;
}
currentBattle->tile = tile;
currentBattle->terrainType = terrain;
currentBattle->battlefieldType = battlefieldType;
currentBattle->round = -2;
currentBattle->activeStack = -1;
currentBattle->replayAllowed = false;
currentBattle->town = town;
//setting up siege obstacles
if (town && town->fortificationsLevel().wallsHealth != 0)
{
auto fortification = town->fortificationsLevel();
curB->si.gateState = EGateState::CLOSED;
currentBattle->si.gateState = EGateState::CLOSED;
curB->si.wallState[EWallPart::GATE] = EWallState::INTACT;
currentBattle->si.wallState[EWallPart::GATE] = EWallState::INTACT;
for(const auto wall : {EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL})
curB->si.wallState[wall] = static_cast<EWallState>(fortification.wallsHealth);
currentBattle->si.wallState[wall] = static_cast<EWallState>(fortification.wallsHealth);
if (fortification.citadelHealth != 0)
curB->si.wallState[EWallPart::KEEP] = static_cast<EWallState>(fortification.citadelHealth);
currentBattle->si.wallState[EWallPart::KEEP] = static_cast<EWallState>(fortification.citadelHealth);
if (fortification.upperTowerHealth != 0)
curB->si.wallState[EWallPart::UPPER_TOWER] = static_cast<EWallState>(fortification.upperTowerHealth);
currentBattle->si.wallState[EWallPart::UPPER_TOWER] = static_cast<EWallState>(fortification.upperTowerHealth);
if (fortification.lowerTowerHealth != 0)
curB->si.wallState[EWallPart::BOTTOM_TOWER] = static_cast<EWallState>(fortification.lowerTowerHealth);
currentBattle->si.wallState[EWallPart::BOTTOM_TOWER] = static_cast<EWallState>(fortification.lowerTowerHealth);
}
//randomize obstacles
if (layout.obstaclesAllowed && !town)
if (layout.obstaclesAllowed && (!town || !town->hasFort()))
{
RandGen r{};
auto ourRand = [&](){ return r.rand(); };
@@ -221,12 +212,12 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
auto appropriateAbsoluteObstacle = [&](int id)
{
const auto * info = Obstacle(id).getInfo();
return info && info->isAbsoluteObstacle && info->isAppropriate(curB->terrainType, battlefieldType);
return info && info->isAbsoluteObstacle && info->isAppropriate(currentBattle->terrainType, battlefieldType);
};
auto appropriateUsualObstacle = [&](int id)
{
const auto * info = Obstacle(id).getInfo();
return info && !info->isAbsoluteObstacle && info->isAppropriate(curB->terrainType, battlefieldType);
return info && !info->isAbsoluteObstacle && info->isAppropriate(currentBattle->terrainType, battlefieldType);
};
if(r.rand(1,100) <= 40) //put cliff-like obstacle
@@ -237,8 +228,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
auto obstPtr = std::make_shared<CObstacleInstance>();
obstPtr->obstacleType = CObstacleInstance::ABSOLUTE_OBSTACLE;
obstPtr->ID = obidgen.getSuchNumber(appropriateAbsoluteObstacle);
obstPtr->uniqueID = static_cast<si32>(curB->obstacles.size());
curB->obstacles.push_back(obstPtr);
obstPtr->uniqueID = static_cast<si32>(currentBattle->obstacles.size());
currentBattle->obstacles.push_back(obstPtr);
for(BattleHex blocked : obstPtr->getBlockedTiles())
blockedTiles.push_back(blocked);
@@ -256,7 +247,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
while(tilesToBlock > 0)
{
RangeGenerator obidgen(0, VLC->obstacleHandler->size() - 1, ourRand);
auto tileAccessibility = curB->getAccessibility();
auto tileAccessibility = currentBattle->getAccessibility();
const int obid = obidgen.getSuchNumber(appropriateUsualObstacle);
const ObstacleInfo &obi = *Obstacle(obid).getInfo();
@@ -290,8 +281,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
auto obstPtr = std::make_shared<CObstacleInstance>();
obstPtr->ID = obid;
obstPtr->pos = posgenerator.getSuchNumber(validPosition);
obstPtr->uniqueID = static_cast<si32>(curB->obstacles.size());
curB->obstacles.push_back(obstPtr);
obstPtr->uniqueID = static_cast<si32>(currentBattle->obstacles.size());
currentBattle->obstacles.push_back(obstPtr);
for(BattleHex blocked : obstPtr->getBlockedTiles())
blockedTiles.push_back(blocked);
@@ -315,7 +306,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
CreatureID cre = warMachineArt->artType->getWarMachine();
if(cre != CreatureID::NONE)
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);
currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);
}
};
@@ -353,7 +344,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
const BattleHex & pos = layout.units.at(side).at(k);
if (pos.isValid())
curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos);
currentBattle->generateNewStack(currentBattle->nextUnitId(), *i->second, side, i->first, pos);
}
}
@@ -362,20 +353,20 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
{
if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive)
{
curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, layout.commanders.at(i));
currentBattle->generateNewStack(currentBattle->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, layout.commanders.at(i));
}
}
if (curB->town)
if (currentBattle->town)
{
if (curB->town->fortificationsLevel().citadelHealth != 0)
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER);
if (currentBattle->town->fortificationsLevel().citadelHealth != 0)
currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER);
if (curB->town->fortificationsLevel().upperTowerHealth != 0)
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER);
if (currentBattle->town->fortificationsLevel().upperTowerHealth != 0)
currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER);
if (curB->town->fortificationsLevel().lowerTowerHealth != 0)
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER);
if (currentBattle->town->fortificationsLevel().lowerTowerHealth != 0)
currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), BattleSide::DEFENDER, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER);
//Moat generating is done on server
}
@@ -390,15 +381,15 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
for(const std::shared_ptr<Bonus> & bonus : bgInfo->bonuses)
{
curB->addNewBonus(bonus);
currentBattle->addNewBonus(bonus);
}
//native terrain bonuses
auto nativeTerrain = std::make_shared<CreatureTerrainLimiter>();
curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID())->addLimiter(nativeTerrain));
curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::ATTACK))->addLimiter(nativeTerrain));
curB->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::DEFENSE))->addLimiter(nativeTerrain));
currentBattle->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::STACKS_SPEED, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID())->addLimiter(nativeTerrain));
currentBattle->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::ATTACK))->addLimiter(nativeTerrain));
currentBattle->addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::PRIMARY_SKILL, BonusSource::TERRAIN_NATIVE, 1, BonusSourceID(), BonusSubtypeID(PrimarySkill::DEFENSE))->addLimiter(nativeTerrain));
//////////////////////////////////////////////////////////////////////////
//tactics
@@ -428,21 +419,21 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
logGlobal->warn("Double tactics is not implemented, only attacker will have tactics!");
if(tacticsSkillDiffAttacker > 0)
{
curB->tacticsSide = BattleSide::ATTACKER;
currentBattle->tacticsSide = BattleSide::ATTACKER;
//bonus specifies distance you can move beyond base row; this allows 100% compatibility with HMM3 mechanics
curB->tacticDistance = 1 + tacticsSkillDiffAttacker;
currentBattle->tacticDistance = 1 + tacticsSkillDiffAttacker;
}
else if(tacticsSkillDiffDefender > 0)
{
curB->tacticsSide = BattleSide::DEFENDER;
currentBattle->tacticsSide = BattleSide::DEFENDER;
//bonus specifies distance you can move beyond base row; this allows 100% compatibility with HMM3 mechanics
curB->tacticDistance = 1 + tacticsSkillDiffDefender;
currentBattle->tacticDistance = 1 + tacticsSkillDiffDefender;
}
else
curB->tacticDistance = 0;
currentBattle->tacticDistance = 0;
}
return curB;
return currentBattle;
}
const CGHeroInstance * BattleInfo::getHero(const PlayerColor & player) const
@@ -885,12 +876,12 @@ void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool fo
if(forceAdd || !sta->hasBonus(Selector::source(BonusSource::SPELL_EFFECT, value.sid).And(Selector::typeSubtypeValueType(value.type, value.subtype, value.valType))))
{
//no such effect or cumulative - add new
logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description());
logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description(nullptr));
sta->addNewBonus(std::make_shared<Bonus>(value));
}
else
{
logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description());
logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description(nullptr));
for(const auto & stackBonus : sta->getExportedBonusList()) //TODO: optimize
{

View File

@@ -1392,7 +1392,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
at.friendlyCreaturePositions.insert(tile);
}
}
else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH))
else if(attacker->hasBonusOfType(BonusType::TWO_HEX_ATTACK_BREATH) || attacker->hasBonusOfType(BonusType::PRISM_HEX_ATTACK_BREATH))
{
auto direction = BattleHex::mutualPosition(attackOriginHex, destinationTile);
@@ -1404,27 +1404,39 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(
direction = BattleHex::mutualPosition(attackOriginHex, defender->occupiedHex(defenderPos));
}
if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation
for(int i = 0; i < 3; i++)
{
BattleHex nextHex = destinationTile.cloneInDirection(direction, false);
if ( defender->doubleWide() )
if(direction != BattleHex::NONE) //only adjacent hexes are subject of dragon breath calculation
{
auto secondHex = destinationTile == defenderPos ? defender->occupiedHex(defenderPos) : defenderPos;
BattleHex nextHex = destinationTile.cloneInDirection(direction, false);
// if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin)
// then dragon breath should target tile on the opposite side of targeted creature
if(BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE)
nextHex = secondHex.cloneInDirection(direction, false);
if ( defender->doubleWide() )
{
auto secondHex = destinationTile == defenderPos ? defender->occupiedHex(defenderPos) : defenderPos;
// if targeted double-wide creature is attacked from above or below ( -> second hex is also adjacent to attack origin)
// then dragon breath should target tile on the opposite side of targeted creature
if(BattleHex::mutualPosition(attackOriginHex, secondHex) != BattleHex::NONE)
nextHex = secondHex.cloneInDirection(direction, false);
}
if (nextHex.isValid())
{
//friendly stacks can also be damaged by Dragon Breath
const auto * st = battleGetUnitByPos(nextHex, true);
if(st != nullptr)
at.friendlyCreaturePositions.insert(nextHex);
}
}
if (nextHex.isValid())
{
//friendly stacks can also be damaged by Dragon Breath
const auto * st = battleGetUnitByPos(nextHex, true);
if(st != nullptr)
at.friendlyCreaturePositions.insert(nextHex);
}
if(!attacker->hasBonusOfType(BonusType::PRISM_HEX_ATTACK_BREATH))
break;
// only needed for prism
int tmpDirection = static_cast<int>(direction) + 2;
if(tmpDirection > static_cast<int>(BattleHex::EDir::LEFT))
tmpDirection -= static_cast<int>(BattleHex::EDir::TOP);
direction = static_cast<BattleHex::EDir>(tmpDirection);
}
}
return at;

View File

@@ -18,8 +18,11 @@
#include "../CCreatureHandler.h"
#include "../CCreatureSet.h"
#include "../CSkillHandler.h"
#include "../IGameCallback.h"
#include "../TerrainHandler.h"
#include "../VCMI_Lib.h"
#include "../mapObjects/CGObjectInstance.h"
#include "../mapObjectConstructors/CObjectClassesHandler.h"
#include "../battle/BattleInfo.h"
#include "../constants/StringConstants.h"
#include "../entities/hero/CHero.h"
@@ -87,7 +90,7 @@ JsonNode CAddInfo::toJsonNode() const
return node;
}
}
std::string Bonus::Description(std::optional<si32> customValue) const
std::string Bonus::Description(const IGameInfoCallback * cb, std::optional<si32> customValue) const
{
MetaString descriptionHelper = description;
auto valueToShow = customValue.value_or(val);
@@ -112,6 +115,10 @@ std::string Bonus::Description(std::optional<si32> customValue) const
case BonusSource::HERO_SPECIAL:
descriptionHelper.appendTextID(sid.as<HeroTypeID>().toEntity(VLC)->getNameTextID());
break;
case BonusSource::OBJECT_INSTANCE:
const auto * object = cb->getObj(sid.as<ObjectInstanceID>());
if (object)
descriptionHelper.appendTextID(VLC->objtypeh->getObjectName(object->ID, object->subID));
}
}

View File

@@ -26,6 +26,7 @@ class IPropagator;
class IUpdater;
class BonusList;
class CSelector;
class IGameInfoCallback;
using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
@@ -177,7 +178,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>, public Se
val += Val;
}
std::string Description(std::optional<si32> customValue = {}) const;
std::string Description(const IGameInfoCallback * cb, std::optional<si32> customValue = {}) const;
JsonNode toJsonNode() const;
std::shared_ptr<Bonus> addLimiter(const TLimiterPtr & Limiter); //returns this for convenient chain-calls

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