mirror of
https://github.com/vcmi/vcmi.git
synced 2025-05-13 22:06:58 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
ffd8758017
35
.github/workflows/github.yml
vendored
35
.github/workflows/github.yml
vendored
@ -38,6 +38,7 @@ jobs:
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
before_install: macos.sh
|
||||
@ -50,6 +51,7 @@ jobs:
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: dmg
|
||||
before_install: macos.sh
|
||||
@ -62,6 +64,7 @@ jobs:
|
||||
os: macos-13
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: ipa
|
||||
before_install: macos.sh
|
||||
@ -69,7 +72,7 @@ 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
|
||||
@ -77,10 +80,19 @@ jobs:
|
||||
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-x86
|
||||
- platform: mingw_x86_64
|
||||
os: ubuntu-24.04
|
||||
test: 0
|
||||
pack: 1
|
||||
upload: 1
|
||||
pack_type: Release
|
||||
extension: exe
|
||||
cmake_args: -G Ninja
|
||||
@ -101,6 +113,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 +122,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 +150,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 +250,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 +291,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 +319,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 +328,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' || startsWith(github.ref, 'refs/heads/features/')) }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -z '${{ env.ANDROID_APK_PATH }}' ] ; then
|
||||
|
@ -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"
|
||||
|
7
CI/install_vcpkg_dependencies.sh
Normal file
7
CI/install_vcpkg_dependencies.sh
Normal 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
|
@ -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",
|
||||
|
5
Global.h
5
Global.h
@ -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
|
||||
|
@ -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...",
|
||||
|
||||
@ -687,5 +703,7 @@
|
||||
"core.bonus.DISINTEGRATE.name": "Disintegrate",
|
||||
"core.bonus.DISINTEGRATE.description": "No corpse remains after death",
|
||||
"core.bonus.INVINCIBLE.name": "Invincible",
|
||||
"core.bonus.INVINCIBLE.description": "Cannot be affected by anything"
|
||||
"core.bonus.INVINCIBLE.description": "Cannot be affected by anything",
|
||||
"core.bonus.PRISM_HEX_ATTACK_BREATH.name": "Prism Breath",
|
||||
"core.bonus.PRISM_HEX_ATTACK_BREATH.description": "Prism Breath Attack (three directions)"
|
||||
}
|
||||
|
66
Mods/vcmi/config/vcmi/spells.json
Normal file
66
Mods/vcmi/config/vcmi/spells.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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} гексів"
|
||||
}
|
||||
|
@ -127,6 +127,7 @@
|
||||
|
||||
"factions" : [ "config/vcmi/towerFactions" ],
|
||||
"creatures" : [ "config/vcmi/towerCreature" ],
|
||||
"spells" : [ "config/vcmi/spells" ],
|
||||
|
||||
"translations" : [
|
||||
"config/vcmi/english.json"
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -581,13 +581,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
|
||||
@ -596,7 +596,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';
|
||||
|
@ -393,7 +393,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)
|
||||
@ -905,14 +905,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)
|
||||
|
@ -189,6 +189,7 @@ class CStackWindow : public CWindowObject
|
||||
void init();
|
||||
|
||||
std::string generateStackExpDescription();
|
||||
std::string getCommanderSkillDescription(int skillIndex, int skillLevel);
|
||||
|
||||
public:
|
||||
// for battles
|
||||
|
@ -192,18 +192,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 +261,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 +269,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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -548,6 +548,14 @@
|
||||
}
|
||||
},
|
||||
|
||||
"PRISM_HEX_ATTACK_BREATH":
|
||||
{
|
||||
"graphics":
|
||||
{
|
||||
"icon": "zvs/Lib1.res/PrismBreath"
|
||||
}
|
||||
},
|
||||
|
||||
"THREE_HEADED_ATTACK":
|
||||
{
|
||||
"graphics":
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
29
config/objects/lighthouse.json
Normal file
29
config/objects/lighthouse.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"summonDemons" : {
|
||||
"summonDemons" : {
|
||||
"type": "ability",
|
||||
"targetType" : "CREATURE",
|
||||
"name": "Summon Demons",
|
||||
"name": "",
|
||||
"school" : {},
|
||||
"level": 2,
|
||||
"power": 50,
|
||||
@ -46,11 +46,11 @@
|
||||
"bonus.GARGOYLE" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"firstAid" : {
|
||||
},
|
||||
"firstAid" : {
|
||||
"targetType" : "CREATURE",
|
||||
"type": "ability",
|
||||
"name": "First Aid",
|
||||
"name": "",
|
||||
"school" : {},
|
||||
"level": 1,
|
||||
"power": 10,
|
||||
@ -106,7 +106,7 @@
|
||||
"catapultShot" : {
|
||||
"targetType" : "LOCATION",
|
||||
"type": "ability",
|
||||
"name": "Catapult shot",
|
||||
"name": "",
|
||||
"school" : {},
|
||||
"level": 1,
|
||||
"power": 1,
|
||||
@ -187,7 +187,7 @@
|
||||
"cyclopsShot" : {
|
||||
"targetType" : "LOCATION",
|
||||
"type": "ability",
|
||||
"name": "Siege shot",
|
||||
"name": "",
|
||||
"school" : {},
|
||||
"level": 1,
|
||||
"power": 1,
|
||||
|
@ -502,6 +502,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)
|
||||
|
@ -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`
|
||||
|
39
docs/modders/Map_Objects/Flaggable.md
Normal file
39
docs/modders/Map_Objects/Flaggable.md
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -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)
|
||||
|
@ -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`:
|
||||
```
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -122,7 +122,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 << "])";
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -180,6 +180,7 @@ class JsonNode;
|
||||
BONUS_NAME(RESOURCES_TOWN_MULTIPLYING_BOOST) /*Bonus that does not account for propagation and gives extra resources per day with amount multiplied by number of owned towns. val - base resource amount to be multiplied times number of owned towns, subtype - resource type*/ \
|
||||
BONUS_NAME(DISINTEGRATE) /* after death no corpse remains */ \
|
||||
BONUS_NAME(INVINCIBLE) /* cannot be target of attacks or spells */ \
|
||||
BONUS_NAME(PRISM_HEX_ATTACK_BREATH) /*eg. dragons*/ \
|
||||
/* end of list */
|
||||
|
||||
|
||||
|
@ -378,7 +378,7 @@ void CBonusSystemNode::propagateBonus(const std::shared_ptr<Bonus> & b, const CB
|
||||
? source.getUpdatedBonus(b, b->propagationUpdater)
|
||||
: b;
|
||||
bonuses.push_back(propagated);
|
||||
logBonus->trace("#$# %s #propagated to# %s", propagated->Description(), nodeName());
|
||||
logBonus->trace("#$# %s #propagated to# %s", propagated->Description(nullptr), nodeName());
|
||||
}
|
||||
|
||||
TNodes lchildren;
|
||||
@ -392,9 +392,9 @@ void CBonusSystemNode::unpropagateBonus(const std::shared_ptr<Bonus> & b)
|
||||
if(b->propagator->shouldBeAttached(this))
|
||||
{
|
||||
if (bonuses -= b)
|
||||
logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(), nodeName());
|
||||
logBonus->trace("#$# %s #is no longer propagated to# %s", b->Description(nullptr), nodeName());
|
||||
else
|
||||
logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(), nodeName());
|
||||
logBonus->warn("Attempt to remove #$# %s, which is not propagated to %s", b->Description(nullptr), nodeName());
|
||||
|
||||
bonuses.remove_if([b](const auto & bonus)
|
||||
{
|
||||
|
@ -136,6 +136,9 @@ si64 CCompressedStream::readMore(ui8 *data, si64 size)
|
||||
{
|
||||
if (inflateState->avail_in == 0)
|
||||
{
|
||||
if (gzipStream == nullptr)
|
||||
throw std::runtime_error("Potentially truncated gzip file");
|
||||
|
||||
//inflate ran out of available data or was not initialized yet
|
||||
// get new input data and update state accordingly
|
||||
si64 availSize = gzipStream->read(compressedBuffer.data(), compressedBuffer.size());
|
||||
|
@ -158,40 +158,58 @@ bool JsonParser::extractEscaping(std::string & str)
|
||||
|
||||
switch(input[pos])
|
||||
{
|
||||
case '\r':
|
||||
if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON5 && input.size() > pos && input[pos+1] == '\n')
|
||||
{
|
||||
pos += 2;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON5)
|
||||
{
|
||||
pos += 1;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '\"':
|
||||
str += '\"';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case '\\':
|
||||
str += '\\';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case 'b':
|
||||
str += '\b';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case 'f':
|
||||
str += '\f';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case 'n':
|
||||
str += '\n';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case 'r':
|
||||
str += '\r';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case 't':
|
||||
str += '\t';
|
||||
break;
|
||||
pos++;
|
||||
return true;
|
||||
case '/':
|
||||
str += '/';
|
||||
break;
|
||||
default:
|
||||
return error("Unknown escape sequence!", true);
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return error("Unknown escape sequence!", true);
|
||||
}
|
||||
|
||||
bool JsonParser::extractString(std::string & str)
|
||||
{
|
||||
//TODO: JSON5 - line breaks escaping
|
||||
|
||||
if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
|
||||
{
|
||||
if(input[pos] != '\"')
|
||||
@ -216,27 +234,30 @@ bool JsonParser::extractString(std::string & str)
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
if(input[pos] == '\\') // Escaping
|
||||
else if(input[pos] == '\\') // Escaping
|
||||
{
|
||||
str.append(&input[first], pos - first);
|
||||
pos++;
|
||||
if(pos == input.size())
|
||||
break;
|
||||
|
||||
extractEscaping(str);
|
||||
first = pos + 1;
|
||||
first = pos;
|
||||
}
|
||||
if(input[pos] == '\n') // end-of-line
|
||||
else if(input[pos] == '\n') // end-of-line
|
||||
{
|
||||
str.append(&input[first], pos - first);
|
||||
return error("Closing quote not found!", true);
|
||||
}
|
||||
if(static_cast<unsigned char>(input[pos]) < ' ') // control character
|
||||
else if(static_cast<unsigned char>(input[pos]) < ' ') // control character
|
||||
{
|
||||
str.append(&input[first], pos - first);
|
||||
first = pos + 1;
|
||||
pos++;
|
||||
first = pos;
|
||||
error("Illegal character in the string!", true);
|
||||
}
|
||||
pos++;
|
||||
else
|
||||
pos++;
|
||||
}
|
||||
return error("Unterminated string!");
|
||||
}
|
||||
|
@ -23,17 +23,21 @@
|
||||
#include "../mapObjectConstructors/CRewardableConstructor.h"
|
||||
#include "../mapObjectConstructors/CommonConstructors.h"
|
||||
#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
|
||||
#include "../mapObjectConstructors/FlaggableInstanceConstructor.h"
|
||||
#include "../mapObjectConstructors/HillFortInstanceConstructor.h"
|
||||
#include "../mapObjectConstructors/ShipyardInstanceConstructor.h"
|
||||
|
||||
#include "../mapObjects/CGCreature.h"
|
||||
#include "../mapObjects/CGPandoraBox.h"
|
||||
#include "../mapObjects/CQuest.h"
|
||||
#include "../mapObjects/ObjectTemplate.h"
|
||||
#include "../mapObjects/CGMarket.h"
|
||||
#include "../mapObjects/MiscObjects.h"
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../mapObjects/CGMarket.h"
|
||||
#include "../mapObjects/CGPandoraBox.h"
|
||||
#include "../mapObjects/CGTownInstance.h"
|
||||
#include "../mapObjects/CQuest.h"
|
||||
#include "../mapObjects/FlaggableMapObject.h"
|
||||
#include "../mapObjects/MiscObjects.h"
|
||||
#include "../mapObjects/ObjectTemplate.h"
|
||||
#include "../mapObjects/ObstacleSetHandler.h"
|
||||
|
||||
#include "../modding/IdentifierStorage.h"
|
||||
#include "../modding/CModHandler.h"
|
||||
#include "../modding/ModScope.h"
|
||||
@ -57,6 +61,7 @@ CObjectClassesHandler::CObjectClassesHandler()
|
||||
SET_HANDLER_CLASS("town", CTownInstanceConstructor);
|
||||
SET_HANDLER_CLASS("bank", CBankInstanceConstructor);
|
||||
SET_HANDLER_CLASS("boat", BoatInstanceConstructor);
|
||||
SET_HANDLER_CLASS("flaggable", FlaggableInstanceConstructor);
|
||||
SET_HANDLER_CLASS("market", MarketInstanceConstructor);
|
||||
SET_HANDLER_CLASS("hillFort", HillFortInstanceConstructor);
|
||||
SET_HANDLER_CLASS("shipyard", ShipyardInstanceConstructor);
|
||||
@ -82,7 +87,6 @@ CObjectClassesHandler::CObjectClassesHandler()
|
||||
SET_HANDLER("garrison", CGGarrison);
|
||||
SET_HANDLER("heroPlaceholder", CGHeroPlaceholder);
|
||||
SET_HANDLER("keymaster", CGKeymasterTent);
|
||||
SET_HANDLER("lighthouse", CGLighthouse);
|
||||
SET_HANDLER("magi", CGMagi);
|
||||
SET_HANDLER("mine", CGMine);
|
||||
SET_HANDLER("obelisk", CGObelisk);
|
||||
|
60
lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp
Normal file
60
lib/mapObjectConstructors/FlaggableInstanceConstructor.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* FlaggableInstanceConstructor.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "FlaggableInstanceConstructor.h"
|
||||
|
||||
#include "../json/JsonBonus.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../VCMI_Lib.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
void FlaggableInstanceConstructor::initTypeData(const JsonNode & config)
|
||||
{
|
||||
for (const auto & bonusJson : config["bonuses"].Struct())
|
||||
providedBonuses.push_back(JsonUtils::parseBonus(bonusJson.second));
|
||||
|
||||
if (!config["message"].isNull())
|
||||
{
|
||||
std::string message = config["message"].String();
|
||||
if (!message.empty() && message.at(0) == '@')
|
||||
{
|
||||
visitMessageTextID = message.substr(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
visitMessageTextID = TextIdentifier(getBaseTextID(), "onVisit").get();
|
||||
VLC->generaltexth->registerString( config.getModScope(), visitMessageTextID, config["message"]);
|
||||
}
|
||||
}
|
||||
|
||||
dailyIncome = ResourceSet(config["dailyIncome"]);
|
||||
}
|
||||
|
||||
void FlaggableInstanceConstructor::initializeObject(FlaggableMapObject * flaggable) const
|
||||
{
|
||||
}
|
||||
|
||||
const std::string & FlaggableInstanceConstructor::getVisitMessageTextID() const
|
||||
{
|
||||
return visitMessageTextID;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<Bonus>> & FlaggableInstanceConstructor::getProvidedBonuses() const
|
||||
{
|
||||
return providedBonuses;
|
||||
}
|
||||
|
||||
const ResourceSet & FlaggableInstanceConstructor::getDailyIncome() const
|
||||
{
|
||||
return dailyIncome;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
41
lib/mapObjectConstructors/FlaggableInstanceConstructor.h
Normal file
41
lib/mapObjectConstructors/FlaggableInstanceConstructor.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* FlaggableInstanceConstructor.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CDefaultObjectTypeHandler.h"
|
||||
|
||||
#include "../ResourceSet.h"
|
||||
#include "../bonuses/Bonus.h"
|
||||
#include "../mapObjects/FlaggableMapObject.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class FlaggableInstanceConstructor final : public CDefaultObjectTypeHandler<FlaggableMapObject>
|
||||
{
|
||||
/// List of bonuses that are provided by every map object of this type
|
||||
std::vector<std::shared_ptr<Bonus>> providedBonuses;
|
||||
|
||||
/// ID of message to show on hero visit
|
||||
std::string visitMessageTextID;
|
||||
|
||||
/// Amount of resources granted by this object to owner every day
|
||||
ResourceSet dailyIncome;
|
||||
|
||||
protected:
|
||||
void initTypeData(const JsonNode & config) override;
|
||||
void initializeObject(FlaggableMapObject * object) const override;
|
||||
|
||||
public:
|
||||
const std::string & getVisitMessageTextID() const;
|
||||
const std::vector<std::shared_ptr<Bonus>> & getProvidedBonuses() const;
|
||||
const ResourceSet & getDailyIncome() const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -144,7 +144,7 @@ void CBank::onHeroVisit(const CGHeroInstance * h) const
|
||||
bd.player = h->getOwner();
|
||||
bd.text.appendLocalString(EMetaText::ADVOB_TXT, 32);
|
||||
bd.components = getPopupComponents(h->getOwner());
|
||||
bd.text.replaceRawString(getObjectName());
|
||||
bd.text.replaceTextID(getObjectHandler()->getNameTextID());
|
||||
cb->showBlockingDialog(this, &bd);
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
|
||||
if (!bankConfig)
|
||||
{
|
||||
iw.text.appendRawString(VLC->generaltexth->advobtxt[33]);// This was X, now is completely empty
|
||||
iw.text.replaceRawString(getObjectName());
|
||||
iw.text.replaceTextID(getObjectHandler()->getNameTextID());
|
||||
cb->showInfoDialog(&iw);
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
|
||||
const auto growth = b->val * (base + castleBonus) / 100;
|
||||
if (growth)
|
||||
{
|
||||
ret.entries.emplace_back(growth, b->Description(growth));
|
||||
ret.entries.emplace_back(growth, b->Description(cb, growth));
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
|
||||
// Note: bonus uses 1-based levels (Pikeman is level 1), town list uses 0-based (Pikeman in 0-th creatures entry)
|
||||
TConstBonusListPtr bonuses = getBonuses(Selector::typeSubtype(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1)));
|
||||
for(const auto & b : *bonuses)
|
||||
ret.entries.emplace_back(b->val, b->Description());
|
||||
ret.entries.emplace_back(b->val, b->Description(cb));
|
||||
|
||||
int dwellingBonus = 0;
|
||||
if(const PlayerState *p = cb->getPlayerState(tempOwner, false))
|
||||
|
105
lib/mapObjects/FlaggableMapObject.cpp
Normal file
105
lib/mapObjects/FlaggableMapObject.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* FlaggableMapObject.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "FlaggableMapObject.h"
|
||||
|
||||
#include "../IGameCallback.h"
|
||||
#include "CGHeroInstance.h"
|
||||
#include "../networkPacks/PacksForClient.h"
|
||||
#include "../mapObjectConstructors/FlaggableInstanceConstructor.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
const IOwnableObject * FlaggableMapObject::asOwnable() const
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
ResourceSet FlaggableMapObject::dailyIncome() const
|
||||
{
|
||||
return getFlaggableHandler()->getDailyIncome();
|
||||
}
|
||||
|
||||
std::vector<CreatureID> FlaggableMapObject::providedCreatures() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void FlaggableMapObject::onHeroVisit( const CGHeroInstance * h ) const
|
||||
{
|
||||
if (cb->getPlayerRelations(h->getOwner(), getOwner()) != PlayerRelations::ENEMIES)
|
||||
return; // H3 behavior - revisiting owned Lighthouse is a no-op
|
||||
|
||||
if (getOwner().isValidPlayer())
|
||||
takeBonusFrom(getOwner());
|
||||
|
||||
cb->setOwner(this, h->getOwner()); //not ours? flag it!
|
||||
|
||||
InfoWindow iw;
|
||||
iw.player = h->getOwner();
|
||||
iw.text.appendTextID(getFlaggableHandler()->getVisitMessageTextID());
|
||||
cb->showInfoDialog(&iw);
|
||||
|
||||
giveBonusTo(h->getOwner());
|
||||
}
|
||||
|
||||
void FlaggableMapObject::initObj(vstd::RNG & rand)
|
||||
{
|
||||
if(getOwner().isValidPlayer())
|
||||
{
|
||||
// FIXME: This is dirty hack
|
||||
giveBonusTo(getOwner(), true);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FlaggableInstanceConstructor> FlaggableMapObject::getFlaggableHandler() const
|
||||
{
|
||||
return std::dynamic_pointer_cast<FlaggableInstanceConstructor>(getObjectHandler());
|
||||
}
|
||||
|
||||
void FlaggableMapObject::giveBonusTo(const PlayerColor & player, bool onInit) const
|
||||
{
|
||||
for (auto const & bonus : getFlaggableHandler()->getProvidedBonuses())
|
||||
{
|
||||
GiveBonus gb(GiveBonus::ETarget::PLAYER);
|
||||
gb.id = player;
|
||||
gb.bonus = *bonus;
|
||||
|
||||
// FIXME: better place for this code?
|
||||
gb.bonus.duration = BonusDuration::PERMANENT;
|
||||
gb.bonus.source = BonusSource::OBJECT_INSTANCE;
|
||||
gb.bonus.sid = BonusSourceID(id);
|
||||
|
||||
// FIXME: This is really dirty hack
|
||||
// Proper fix would be to make FlaggableMapObject into bonus system node
|
||||
// Unfortunately this will cause saves breakage
|
||||
if(onInit)
|
||||
gb.applyGs(cb->gameState());
|
||||
else
|
||||
cb->sendAndApply(gb);
|
||||
}
|
||||
}
|
||||
|
||||
void FlaggableMapObject::takeBonusFrom(const PlayerColor & player) const
|
||||
{
|
||||
RemoveBonus rb(GiveBonus::ETarget::PLAYER);
|
||||
rb.whoID = player;
|
||||
rb.source = BonusSource::OBJECT_INSTANCE;
|
||||
rb.id = BonusSourceID(id);
|
||||
cb->sendAndApply(rb);
|
||||
}
|
||||
|
||||
void FlaggableMapObject::serializeJsonOptions(JsonSerializeFormat& handler)
|
||||
{
|
||||
serializeJsonOwner(handler);
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
41
lib/mapObjects/FlaggableMapObject.h
Normal file
41
lib/mapObjects/FlaggableMapObject.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* FlaggableMapObject.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "CGObjectInstance.h"
|
||||
#include "IOwnableObject.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct Bonus;
|
||||
class FlaggableInstanceConstructor;
|
||||
|
||||
class DLL_LINKAGE FlaggableMapObject : public CGObjectInstance, public IOwnableObject
|
||||
{
|
||||
std::shared_ptr<FlaggableInstanceConstructor> getFlaggableHandler() const;
|
||||
|
||||
void giveBonusTo(const PlayerColor & player, bool onInit = false) const;
|
||||
void takeBonusFrom(const PlayerColor & player) const;
|
||||
|
||||
public:
|
||||
using CGObjectInstance::CGObjectInstance;
|
||||
|
||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||
void initObj(vstd::RNG & rand) override;
|
||||
|
||||
const IOwnableObject * asOwnable() const final;
|
||||
ResourceSet dailyIncome() const override;
|
||||
std::vector<CreatureID> providedCreatures() const override;
|
||||
|
||||
protected:
|
||||
void serializeJsonOptions(JsonSerializeFormat & handler) override;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -1311,75 +1311,6 @@ void CGObelisk::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
|
||||
}
|
||||
}
|
||||
|
||||
const IOwnableObject * CGLighthouse::asOwnable() const
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
ResourceSet CGLighthouse::dailyIncome() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<CreatureID> CGLighthouse::providedCreatures() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
|
||||
{
|
||||
if(h->tempOwner != tempOwner)
|
||||
{
|
||||
PlayerColor oldOwner = tempOwner;
|
||||
cb->setOwner(this,h->tempOwner); //not ours? flag it!
|
||||
h->showInfoDialog(69);
|
||||
giveBonusTo(h->tempOwner);
|
||||
|
||||
if(oldOwner.isValidPlayer()) //remove bonus from old owner
|
||||
{
|
||||
RemoveBonus rb(GiveBonus::ETarget::PLAYER);
|
||||
rb.whoID = oldOwner;
|
||||
rb.source = BonusSource::OBJECT_INSTANCE;
|
||||
rb.id = BonusSourceID(id);
|
||||
cb->sendAndApply(rb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CGLighthouse::initObj(vstd::RNG & rand)
|
||||
{
|
||||
if(tempOwner.isValidPlayer())
|
||||
{
|
||||
// FIXME: This is dirty hack
|
||||
giveBonusTo(tempOwner, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CGLighthouse::giveBonusTo(const PlayerColor & player, bool onInit) const
|
||||
{
|
||||
GiveBonus gb(GiveBonus::ETarget::PLAYER);
|
||||
gb.bonus.type = BonusType::MOVEMENT;
|
||||
gb.bonus.val = 500;
|
||||
gb.id = player;
|
||||
gb.bonus.duration = BonusDuration::PERMANENT;
|
||||
gb.bonus.source = BonusSource::OBJECT_INSTANCE;
|
||||
gb.bonus.sid = BonusSourceID(id);
|
||||
gb.bonus.subtype = BonusCustomSubtype::heroMovementSea;
|
||||
|
||||
// FIXME: This is really dirty hack
|
||||
// Proper fix would be to make CGLighthouse into bonus system node
|
||||
// Unfortunately this will cause saves breakage
|
||||
if(onInit)
|
||||
gb.applyGs(cb->gameState());
|
||||
else
|
||||
cb->sendAndApply(gb);
|
||||
}
|
||||
|
||||
void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler)
|
||||
{
|
||||
serializeJsonOwner(handler);
|
||||
}
|
||||
|
||||
void HillFort::onHeroVisit(const CGHeroInstance * h) const
|
||||
{
|
||||
cb->showObjectWindow(this, EOpenWindowMode::HILL_FORT_WINDOW, h, false);
|
||||
|
@ -413,28 +413,6 @@ protected:
|
||||
void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CGLighthouse : public CGObjectInstance, public IOwnableObject
|
||||
{
|
||||
public:
|
||||
using CGObjectInstance::CGObjectInstance;
|
||||
|
||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||
void initObj(vstd::RNG & rand) override;
|
||||
|
||||
const IOwnableObject * asOwnable() const final;
|
||||
ResourceSet dailyIncome() const override;
|
||||
std::vector<CreatureID> providedCreatures() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<CGObjectInstance&>(*this);
|
||||
}
|
||||
void giveBonusTo(const PlayerColor & player, bool onInit = false) const;
|
||||
|
||||
protected:
|
||||
void serializeJsonOptions(JsonSerializeFormat & handler) override;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CGTerrainPatch : public CGObjectInstance
|
||||
{
|
||||
public:
|
||||
|
@ -208,6 +208,9 @@ void CMapLoaderH3M::readHeader()
|
||||
|
||||
// optimization - load mappings only once to avoid slow parsing of map headers for map list
|
||||
static const std::map<EMapFormat, MapIdentifiersH3M> identifierMappers = generateMappings();
|
||||
if (!identifierMappers.count(mapHeader->version))
|
||||
throw std::runtime_error("Unsupported map format! Format ID " + std::to_string(static_cast<int>(mapHeader->version)));
|
||||
|
||||
const MapIdentifiersH3M & identifierMapper = identifierMappers.at(mapHeader->version);
|
||||
|
||||
reader->setIdentifierRemapper(identifierMapper);
|
||||
@ -1478,9 +1481,9 @@ CGObjectInstance * CMapLoaderH3M::readShipyard(const int3 & mapPosition, std::sh
|
||||
return object;
|
||||
}
|
||||
|
||||
CGObjectInstance * CMapLoaderH3M::readLighthouse(const int3 & mapPosition)
|
||||
CGObjectInstance * CMapLoaderH3M::readLighthouse(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate)
|
||||
{
|
||||
auto * object = new CGLighthouse(map->cb);
|
||||
auto * object = readGeneric(mapPosition, objectTemplate);
|
||||
setOwnerAndValidate(mapPosition, object, reader->readPlayer32());
|
||||
return object;
|
||||
}
|
||||
@ -1618,7 +1621,7 @@ CGObjectInstance * CMapLoaderH3M::readObject(std::shared_ptr<const ObjectTemplat
|
||||
return readPyramid(mapPosition, objectTemplate);
|
||||
|
||||
case Obj::LIGHTHOUSE:
|
||||
return readLighthouse(mapPosition);
|
||||
return readLighthouse(mapPosition, objectTemplate);
|
||||
|
||||
case Obj::CREATURE_BANK:
|
||||
case Obj::DERELICT_SHIP:
|
||||
|
@ -208,7 +208,7 @@ private:
|
||||
CGObjectInstance * readPyramid(const int3 & position, std::shared_ptr<const ObjectTemplate> objTempl);
|
||||
CGObjectInstance * readQuestGuard(const int3 & position);
|
||||
CGObjectInstance * readShipyard(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate);
|
||||
CGObjectInstance * readLighthouse(const int3 & mapPosition);
|
||||
CGObjectInstance * readLighthouse(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate);
|
||||
CGObjectInstance * readGeneric(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
|
||||
CGObjectInstance * readBank(const int3 & position, std::shared_ptr<const ObjectTemplate> objectTemplate);
|
||||
|
||||
|
@ -1014,8 +1014,6 @@ void CMapLoaderJson::readTerrain()
|
||||
const JsonNode underground = getFromArchive(TERRAIN_FILE_NAMES[1]);
|
||||
readTerrainLevel(underground, 1);
|
||||
}
|
||||
|
||||
map->calculateWaterContent();
|
||||
}
|
||||
|
||||
CMapLoaderJson::MapObjectLoader::MapObjectLoader(CMapLoaderJson * _owner, JsonMap::value_type & json):
|
||||
|
@ -410,9 +410,11 @@ bool MapReaderH3M::readBool()
|
||||
int8_t MapReaderH3M::readInt8Checked(int8_t lowerLimit, int8_t upperLimit)
|
||||
{
|
||||
int8_t result = readInt8();
|
||||
assert(result >= lowerLimit);
|
||||
assert(result <= upperLimit);
|
||||
return std::clamp(result, lowerLimit, upperLimit);
|
||||
int8_t resultClamped = std::clamp(result, lowerLimit, upperLimit);
|
||||
if (result != resultClamped)
|
||||
logGlobal->warn("Map contains out of range value %d! Expected %d-%d", static_cast<int>(result), static_cast<int>(lowerLimit), static_cast<int>(upperLimit));
|
||||
|
||||
return resultClamped;
|
||||
}
|
||||
|
||||
uint8_t MapReaderH3M::readUInt8()
|
||||
|
@ -446,8 +446,8 @@ void CModHandler::loadTranslation(const TModID & modName)
|
||||
JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]);
|
||||
JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]);
|
||||
|
||||
VLC->generaltexth->loadTranslationOverrides(modName, baseTranslation);
|
||||
VLC->generaltexth->loadTranslationOverrides(modName, extraTranslation);
|
||||
VLC->generaltexth->loadTranslationOverrides(modName, modBaseLanguage, baseTranslation);
|
||||
VLC->generaltexth->loadTranslationOverrides(modName, preferredLanguage, extraTranslation);
|
||||
}
|
||||
|
||||
void CModHandler::load()
|
||||
|
@ -152,41 +152,55 @@ std::unique_ptr<CMap> CMapGenerator::generate()
|
||||
return std::move(map->mapInstance);
|
||||
}
|
||||
|
||||
std::string CMapGenerator::getMapDescription() const
|
||||
MetaString CMapGenerator::getMapDescription() const
|
||||
{
|
||||
assert(map);
|
||||
const TextIdentifier mainPattern("vcmi", "randomMap", "description");
|
||||
const TextIdentifier isHuman("vcmi", "randomMap", "description", "isHuman");
|
||||
const TextIdentifier townChoiceIs("vcmi", "randomMap", "description", "townChoice");
|
||||
const std::array waterContent = {
|
||||
TextIdentifier("vcmi", "randomMap", "description", "water", "none"),
|
||||
TextIdentifier("vcmi", "randomMap", "description", "water", "normal"),
|
||||
TextIdentifier("vcmi", "randomMap", "description", "water", "islands")
|
||||
};
|
||||
const std::array monsterStrength = {
|
||||
TextIdentifier("vcmi", "randomMap", "description", "monster", "weak"),
|
||||
TextIdentifier("vcmi", "randomMap", "description", "monster", "normal"),
|
||||
TextIdentifier("vcmi", "randomMap", "description", "monster", "strong")
|
||||
};
|
||||
|
||||
const std::string waterContentStr[3] = { "none", "normal", "islands" };
|
||||
const std::string monsterStrengthStr[3] = { "weak", "normal", "strong" };
|
||||
|
||||
int monsterStrengthIndex = mapGenOptions.getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0
|
||||
const auto * mapTemplate = mapGenOptions.getMapTemplate();
|
||||
int monsterStrengthIndex = mapGenOptions.getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0
|
||||
|
||||
if(!mapTemplate)
|
||||
throw rmgException("Map template for Random Map Generator is not found. Could not start the game.");
|
||||
MetaString result = MetaString::createFromTextID(mainPattern.get());
|
||||
|
||||
std::stringstream ss;
|
||||
ss << boost::str(boost::format(std::string("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") % mapTemplate->getName() %
|
||||
map->width() % map->height() % static_cast<int>(map->levels()) % static_cast<int>(mapGenOptions.getHumanOrCpuPlayerCount()) %
|
||||
static_cast<int>(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] %
|
||||
monsterStrengthStr[monsterStrengthIndex]);
|
||||
result.replaceRawString(mapTemplate->getName());
|
||||
result.replaceNumber(map->width());
|
||||
result.replaceNumber(map->height());
|
||||
result.replaceNumber(map->levels());
|
||||
result.replaceNumber(mapGenOptions.getHumanOrCpuPlayerCount());
|
||||
result.replaceNumber(mapGenOptions.getCompOnlyPlayerCount());
|
||||
result.replaceTextID(waterContent.at(mapGenOptions.getWaterContent()).get());
|
||||
result.replaceTextID(monsterStrength.at(monsterStrengthIndex).get());
|
||||
|
||||
for(const auto & pair : mapGenOptions.getPlayersSettings())
|
||||
{
|
||||
const auto & pSettings = pair.second;
|
||||
|
||||
if(pSettings.getPlayerType() == EPlayerType::HUMAN)
|
||||
{
|
||||
ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor().getNum()] << " is human";
|
||||
result.appendTextID(isHuman.get());
|
||||
result.replaceName(pSettings.getColor());
|
||||
}
|
||||
|
||||
if(pSettings.getStartingTown() != FactionID::RANDOM)
|
||||
{
|
||||
ss << ", " << GameConstants::PLAYER_COLOR_NAMES[pSettings.getColor().getNum()]
|
||||
<< " town choice is " << (*VLC->townh)[pSettings.getStartingTown()]->getNameTranslated();
|
||||
result.appendTextID(townChoiceIs.get());
|
||||
result.replaceName(pSettings.getColor());
|
||||
result.replaceName(pSettings.getStartingTown());
|
||||
}
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
return result;
|
||||
}
|
||||
|
||||
void CMapGenerator::addPlayerInfo()
|
||||
@ -451,7 +465,7 @@ void CMapGenerator::addHeaderInfo()
|
||||
m.height = mapGenOptions.getHeight();
|
||||
m.twoLevel = mapGenOptions.getHasTwoLevels();
|
||||
m.name.appendLocalString(EMetaText::GENERAL_TXT, 740);
|
||||
m.description.appendRawString(getMapDescription());
|
||||
m.description = getMapDescription();
|
||||
m.difficulty = EMapDifficulty::NORMAL;
|
||||
addPlayerInfo();
|
||||
m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE);
|
||||
|
@ -10,14 +10,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../GameConstants.h"
|
||||
#include "CMapGenOptions.h"
|
||||
#include "../int3.h"
|
||||
#include "CRmgTemplate.h"
|
||||
#include "../LoadProgress.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class MetaString;
|
||||
class CRmgTemplate;
|
||||
class CMapGenOptions;
|
||||
class JsonNode;
|
||||
@ -93,7 +91,7 @@ private:
|
||||
/// Generation methods
|
||||
void loadConfig();
|
||||
|
||||
std::string getMapDescription() const;
|
||||
MetaString getMapDescription() const;
|
||||
|
||||
void initPrisonsRemaining();
|
||||
void initQuestArtsRemaining();
|
||||
|
@ -20,14 +20,17 @@
|
||||
#include "../gameState/CGameState.h"
|
||||
#include "../gameState/CGameStateCampaign.h"
|
||||
#include "../gameState/TavernHeroesPool.h"
|
||||
|
||||
#include "../mapObjects/CGCreature.h"
|
||||
#include "../mapObjects/CGDwelling.h"
|
||||
#include "../mapObjects/CGMarket.h"
|
||||
#include "../mapObjects/CGPandoraBox.h"
|
||||
#include "../mapObjects/CGTownInstance.h"
|
||||
#include "../mapObjects/CQuest.h"
|
||||
#include "../mapObjects/FlaggableMapObject.h"
|
||||
#include "../mapObjects/MiscObjects.h"
|
||||
#include "../mapObjects/TownBuildingInstance.h"
|
||||
|
||||
#include "../mapping/CMap.h"
|
||||
#include "../networkPacks/PacksForClient.h"
|
||||
#include "../networkPacks/PacksForClientBattle.h"
|
||||
@ -73,7 +76,7 @@ void registerTypes(Serializer &s)
|
||||
s.template registerType<CGSirens>(15);
|
||||
s.template registerType<CGShipyard>(16);
|
||||
s.template registerType<CGDenOfthieves>(17);
|
||||
s.template registerType<CGLighthouse>(18);
|
||||
s.template registerType<FlaggableMapObject>(18);
|
||||
s.template registerType<CGTerrainPatch>(19);
|
||||
s.template registerType<HillFort>(20);
|
||||
s.template registerType<CGMarket>(21);
|
||||
|
@ -139,9 +139,7 @@ CGeneralTextHandler::CGeneralTextHandler():
|
||||
// pseudo-array, that don't have H3 file with same name
|
||||
seerEmpty (*this, "core.seerhut.empty" ),
|
||||
seerNames (*this, "core.seerhut.names" ),
|
||||
capColors (*this, "vcmi.capitalColors" ),
|
||||
znpc00 (*this, "vcmi.znpc00" ), // technically - wog
|
||||
qeModCommands (*this, "vcmi.quickExchange" )
|
||||
capColors (*this, "vcmi.capitalColors" )
|
||||
{
|
||||
readToVector("core.vcdesc", "DATA/VCDESC.TXT" );
|
||||
readToVector("core.lcdesc", "DATA/LCDESC.TXT" );
|
||||
@ -166,10 +164,6 @@ CGeneralTextHandler::CGeneralTextHandler():
|
||||
readToVector("core.mineevnt", "DATA/MINEEVNT.TXT" );
|
||||
readToVector("core.xtrainfo", "DATA/XTRAINFO.TXT" );
|
||||
|
||||
static const std::string QE_MOD_COMMANDS = "DATA/QECOMMANDS.TXT";
|
||||
if (CResourceHandler::get()->existsResource(TextPath::builtin(QE_MOD_COMMANDS)))
|
||||
readToVector("vcmi.quickExchange", QE_MOD_COMMANDS);
|
||||
|
||||
{
|
||||
CLegacyConfigParser parser(TextPath::builtin("DATA/RANDTVRN.TXT"));
|
||||
parser.endLine();
|
||||
@ -298,11 +292,6 @@ CGeneralTextHandler::CGeneralTextHandler():
|
||||
scenariosCountPerCampaign.push_back(region);
|
||||
}
|
||||
}
|
||||
if (VLC->engineSettings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
|
||||
{
|
||||
if(CResourceHandler::get()->existsResource(TextPath::builtin("DATA/ZNPC00.TXT")))
|
||||
readToVector("vcmi.znpc00", "DATA/ZNPC00.TXT" );
|
||||
}
|
||||
}
|
||||
|
||||
int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t count) const
|
||||
|
@ -62,8 +62,6 @@ public:
|
||||
LegacyTextContainer fcommands; // fort screen
|
||||
LegacyTextContainer tavernInfo;
|
||||
|
||||
LegacyTextContainer qeModCommands;
|
||||
|
||||
LegacyHelpContainer zelp;
|
||||
|
||||
//objects
|
||||
@ -75,8 +73,6 @@ public:
|
||||
|
||||
//sec skills
|
||||
LegacyTextContainer levels;
|
||||
//commanders
|
||||
LegacyTextContainer znpc00; //more or less useful content of that file
|
||||
|
||||
std::vector<std::string> findStringsWithPrefix(const std::string & prefix);
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "CArtHandler.h"
|
||||
#include "CCreatureHandler.h"
|
||||
#include "CCreatureSet.h"
|
||||
#include "entities/faction/CFaction.h"
|
||||
#include "texts/CGeneralTextHandler.h"
|
||||
#include "CSkillHandler.h"
|
||||
#include "GameConstants.h"
|
||||
@ -387,6 +388,11 @@ void MetaString::replaceName(const ArtifactID & id)
|
||||
replaceTextID(id.toEntity(VLC)->getNameTextID());
|
||||
}
|
||||
|
||||
void MetaString::replaceName(const FactionID & id)
|
||||
{
|
||||
replaceTextID(id.toEntity(VLC)->getNameTextID());
|
||||
}
|
||||
|
||||
void MetaString::replaceName(const MapObjectID& id)
|
||||
{
|
||||
replaceTextID(VLC->objtypeh->getObjectName(id, 0));
|
||||
|
@ -21,6 +21,7 @@ class MapObjectSubID;
|
||||
class PlayerColor;
|
||||
class SecondarySkill;
|
||||
class SpellID;
|
||||
class FactionID;
|
||||
class GameResID;
|
||||
using TQuantity = si32;
|
||||
|
||||
@ -97,6 +98,7 @@ public:
|
||||
void replacePositiveNumber(int64_t txt);
|
||||
|
||||
void replaceName(const ArtifactID & id);
|
||||
void replaceName(const FactionID& id);
|
||||
void replaceName(const MapObjectID& id);
|
||||
void replaceName(const PlayerColor& id);
|
||||
void replaceName(const SecondarySkill& id);
|
||||
|
@ -22,7 +22,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
std::recursive_mutex TextLocalizationContainer::globalTextMutex;
|
||||
|
||||
void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized)
|
||||
void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language)
|
||||
{
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
@ -42,6 +42,11 @@ void TextLocalizationContainer::registerStringOverride(const std::string & modCo
|
||||
entry.identifierModContext = modContext;
|
||||
entry.baseStringModContext = modContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (language == VLC->generaltexth->getPreferredLanguage())
|
||||
entry.overriden = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -127,10 +132,10 @@ void TextLocalizationContainer::registerString(const std::string & identifierMod
|
||||
}
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const JsonNode & config)
|
||||
void TextLocalizationContainer::loadTranslationOverrides(const std::string & modContext, const std::string & language, const JsonNode & config)
|
||||
{
|
||||
for(const auto & node : config.Struct())
|
||||
registerStringOverride(modContext, node.first, node.second.String());
|
||||
registerStringOverride(modContext, node.first, node.second.String(), language);
|
||||
}
|
||||
|
||||
bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const
|
||||
@ -140,15 +145,18 @@ bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) con
|
||||
return stringsLocalizations.count(UID.get());
|
||||
}
|
||||
|
||||
void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage) const
|
||||
void TextLocalizationContainer::exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage, bool onlyMissing) const
|
||||
{
|
||||
std::lock_guard globalLock(globalTextMutex);
|
||||
|
||||
for (auto const & subContainer : subContainers)
|
||||
subContainer->exportAllTexts(storage);
|
||||
subContainer->exportAllTexts(storage, onlyMissing);
|
||||
|
||||
for (auto const & entry : stringsLocalizations)
|
||||
{
|
||||
if (onlyMissing && entry.second.overriden)
|
||||
continue;
|
||||
|
||||
std::string textToWrite;
|
||||
std::string modName = entry.second.baseStringModContext;
|
||||
|
||||
|
@ -32,6 +32,8 @@ protected:
|
||||
/// Different from identifierModContext if mod has modified object from another mod (e.g. rebalance mods)
|
||||
std::string baseStringModContext;
|
||||
|
||||
bool overriden = false;
|
||||
|
||||
template <typename Handler>
|
||||
void serialize(Handler & h)
|
||||
{
|
||||
@ -47,7 +49,7 @@ protected:
|
||||
std::vector<const TextLocalizationContainer *> subContainers;
|
||||
|
||||
/// add selected string to internal storage as high-priority strings
|
||||
void registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized);
|
||||
void registerStringOverride(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language);
|
||||
|
||||
std::string getModLanguage(const std::string & modContext);
|
||||
|
||||
@ -57,7 +59,7 @@ protected:
|
||||
public:
|
||||
/// Loads translation from provided json
|
||||
/// Any entries loaded by this will have priority over texts registered normally
|
||||
void loadTranslationOverrides(const std::string & modContext, JsonNode const & file);
|
||||
void loadTranslationOverrides(const std::string & modContext, const std::string & language, JsonNode const & file);
|
||||
|
||||
/// add selected string to internal storage
|
||||
void registerString(const std::string & modContext, const TextIdentifier & UID, const JsonNode & localized);
|
||||
@ -77,7 +79,7 @@ public:
|
||||
|
||||
/// Debug method, returns all currently stored texts
|
||||
/// Format: [mod ID][string ID] -> human-readable text
|
||||
void exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage) const;
|
||||
void exportAllTexts(std::map<std::string, std::map<std::string, std::string>> & storage, bool onlyMissing) const;
|
||||
|
||||
/// Add or override subcontainer which can store identifiers
|
||||
void addSubContainer(const TextLocalizationContainer & container);
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include "../Global.h"
|
||||
|
||||
#define VCMI_EDITOR_VERSION "0.2"
|
||||
#define VCMI_EDITOR_NAME "VCMI Map Editor"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
@ -60,7 +60,7 @@ Initializer::Initializer(CGObjectInstance * o, const PlayerColor & pl) : default
|
||||
INIT_OBJ_TYPE(CGHeroPlaceholder);
|
||||
INIT_OBJ_TYPE(CGHeroInstance);
|
||||
INIT_OBJ_TYPE(CGSignBottle);
|
||||
INIT_OBJ_TYPE(CGLighthouse);
|
||||
INIT_OBJ_TYPE(FlaggableMapObject);
|
||||
//INIT_OBJ_TYPE(CRewardableObject);
|
||||
//INIT_OBJ_TYPE(CGPandoraBox);
|
||||
//INIT_OBJ_TYPE(CGEvent);
|
||||
@ -108,7 +108,7 @@ void Initializer::initialize(CGShipyard * o)
|
||||
o->tempOwner = defaultPlayer;
|
||||
}
|
||||
|
||||
void Initializer::initialize(CGLighthouse * o)
|
||||
void Initializer::initialize(FlaggableMapObject * o)
|
||||
{
|
||||
if(!o) return;
|
||||
|
||||
@ -172,10 +172,12 @@ void Initializer::initialize(CGTownInstance * o)
|
||||
if(lvl > 2) o->addBuilding(BuildingID::CASTLE);
|
||||
if(lvl > 3) o->addBuilding(BuildingID::CAPITOL);
|
||||
|
||||
for(auto const & spell : VLC->spellh->objects) //add all regular spells to town
|
||||
if(o->possibleSpells.empty())
|
||||
{
|
||||
if(!spell->isSpecial() && !spell->isCreatureAbility())
|
||||
o->possibleSpells.push_back(spell->id);
|
||||
for(auto const & spellId : VLC->spellh->getDefaultAllowed()) //add all regular spells to town
|
||||
{
|
||||
o->possibleSpells.push_back(spellId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,7 +246,7 @@ void Inspector::updateProperties(CGDwelling * o)
|
||||
}
|
||||
}
|
||||
|
||||
void Inspector::updateProperties(CGLighthouse * o)
|
||||
void Inspector::updateProperties(FlaggableMapObject * o)
|
||||
{
|
||||
if(!o) return;
|
||||
|
||||
@ -492,7 +494,7 @@ void Inspector::updateProperties()
|
||||
UPDATE_OBJ_PROPERTIES(CGHeroPlaceholder);
|
||||
UPDATE_OBJ_PROPERTIES(CGHeroInstance);
|
||||
UPDATE_OBJ_PROPERTIES(CGSignBottle);
|
||||
UPDATE_OBJ_PROPERTIES(CGLighthouse);
|
||||
UPDATE_OBJ_PROPERTIES(FlaggableMapObject);
|
||||
UPDATE_OBJ_PROPERTIES(CRewardableObject);
|
||||
UPDATE_OBJ_PROPERTIES(CGPandoraBox);
|
||||
UPDATE_OBJ_PROPERTIES(CGEvent);
|
||||
@ -540,7 +542,7 @@ void Inspector::setProperty(const QString & key, const QVariant & value)
|
||||
SET_PROPERTIES(CGHeroInstance);
|
||||
SET_PROPERTIES(CGShipyard);
|
||||
SET_PROPERTIES(CGSignBottle);
|
||||
SET_PROPERTIES(CGLighthouse);
|
||||
SET_PROPERTIES(FlaggableMapObject);
|
||||
SET_PROPERTIES(CRewardableObject);
|
||||
SET_PROPERTIES(CGPandoraBox);
|
||||
SET_PROPERTIES(CGEvent);
|
||||
@ -553,7 +555,7 @@ void Inspector::setProperty(CArmedInstance * o, const QString & key, const QVari
|
||||
if(!o) return;
|
||||
}
|
||||
|
||||
void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVariant & value)
|
||||
void Inspector::setProperty(FlaggableMapObject * o, const QString & key, const QVariant & value)
|
||||
{
|
||||
if(!o) return;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "../lib/GameConstants.h"
|
||||
#include "../lib/mapObjects/CGCreature.h"
|
||||
#include "../lib/mapObjects/MapObjects.h"
|
||||
#include "../lib/mapObjects/FlaggableMapObject.h"
|
||||
#include "../lib/mapObjects/CRewardableObject.h"
|
||||
#include "../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../lib/ResourceSet.h"
|
||||
@ -48,7 +49,7 @@ public:
|
||||
DECLARE_OBJ_TYPE(CGHeroInstance);
|
||||
DECLARE_OBJ_TYPE(CGCreature);
|
||||
DECLARE_OBJ_TYPE(CGSignBottle);
|
||||
DECLARE_OBJ_TYPE(CGLighthouse);
|
||||
DECLARE_OBJ_TYPE(FlaggableMapObject);
|
||||
//DECLARE_OBJ_TYPE(CRewardableObject);
|
||||
//DECLARE_OBJ_TYPE(CGEvent);
|
||||
//DECLARE_OBJ_TYPE(CGPandoraBox);
|
||||
@ -78,7 +79,7 @@ protected:
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CGHeroInstance);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CGCreature);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CGSignBottle);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CGLighthouse);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(FlaggableMapObject);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CRewardableObject);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CGPandoraBox);
|
||||
DECLARE_OBJ_PROPERTY_METHODS(CGEvent);
|
||||
|
@ -182,6 +182,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
console = new CConsoleHandler();
|
||||
logConfig = new CBasicLogConfigurator(logPath, console);
|
||||
logConfig->configureDefault();
|
||||
logGlobal->info("Starting map editor of '%s'", GameConstants::VCMI_VERSION);
|
||||
logGlobal->info("The log file will be saved to %s", logPath);
|
||||
|
||||
//init
|
||||
@ -317,7 +318,7 @@ void MainWindow::setStatusMessage(const QString & status)
|
||||
|
||||
void MainWindow::setTitle()
|
||||
{
|
||||
QString title = QString("%1%2 - %3 (v%4)").arg(filename, unsaved ? "*" : "", VCMI_EDITOR_NAME, VCMI_EDITOR_VERSION);
|
||||
QString title = QString("%1%2 - %3 (%4)").arg(filename, unsaved ? "*" : "", VCMI_EDITOR_NAME, GameConstants::VCMI_VERSION.c_str());
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ void MapController::repairMap(CMap * map) const
|
||||
dynamic_cast<CGTownInstance*>(obj.get()) ||
|
||||
dynamic_cast<CGGarrison*>(obj.get()) ||
|
||||
dynamic_cast<CGShipyard*>(obj.get()) ||
|
||||
dynamic_cast<CGLighthouse*>(obj.get()) ||
|
||||
dynamic_cast<FlaggableMapObject*>(obj.get()) ||
|
||||
dynamic_cast<CGHeroInstance*>(obj.get()))
|
||||
obj->tempOwner = PlayerColor::NEUTRAL;
|
||||
}
|
||||
@ -369,6 +369,7 @@ void MapController::pasteFromClipboard(int level)
|
||||
if (!canPlaceObject(level, obj, errorMsg))
|
||||
{
|
||||
errors.push_back(std::move(errorMsg));
|
||||
continue;
|
||||
}
|
||||
auto newPos = objUniquePtr->pos + shift;
|
||||
if(_map->isInTheMap(newPos))
|
||||
@ -380,8 +381,8 @@ void MapController::pasteFromClipboard(int level)
|
||||
_scenes[level]->selectionObjectsView.selectObject(obj);
|
||||
_mapHandler->invalidate(obj);
|
||||
}
|
||||
|
||||
QMessageBox::warning(main, QObject::tr("Can't place object"), errors.join('\n'));
|
||||
if(!errors.isEmpty())
|
||||
QMessageBox::warning(main, QObject::tr("Can't place object"), errors.join('\n'));
|
||||
|
||||
_scenes[level]->objectsView.draw();
|
||||
_scenes[level]->passabilityView.update();
|
||||
|
@ -28,10 +28,12 @@
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/modding/IdentifierStorage.h"
|
||||
#include "../../lib/networkPacks/PacksForClient.h"
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
#include "../../lib/CPlayerState.h"
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
BattleProcessor::BattleProcessor(CGameHandler * gameHandler)
|
||||
: gameHandler(gameHandler)
|
||||
@ -156,16 +158,24 @@ BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray<const CArmedIns
|
||||
{
|
||||
const auto & t = *gameHandler->getTile(tile);
|
||||
TerrainId terrain = t.terType->getId();
|
||||
if (gameHandler->gameState()->map->isCoastalTile(tile)) //coastal tile is always ground
|
||||
if (town)
|
||||
terrain = town->getNativeTerrain();
|
||||
else if (gameHandler->gameState()->map->isCoastalTile(tile)) //coastal tile is always ground
|
||||
terrain = ETerrainId::SAND;
|
||||
|
||||
BattleField terType = gameHandler->gameState()->battleGetBattlefieldType(tile, gameHandler->getRandomGenerator());
|
||||
if (heroes[BattleSide::ATTACKER] && heroes[BattleSide::ATTACKER]->boat && heroes[BattleSide::DEFENDER] && heroes[BattleSide::DEFENDER]->boat)
|
||||
terType = BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.ship_to_ship"));
|
||||
BattleField battlefieldType = gameHandler->gameState()->battleGetBattlefieldType(tile, gameHandler->getRandomGenerator());
|
||||
|
||||
if (town)
|
||||
{
|
||||
const TerrainType* terrainData = VLC->terrainTypeHandler->getById(terrain);
|
||||
battlefieldType = BattleField(*RandomGeneratorUtil::nextItem(terrainData->battleFields, gameHandler->getRandomGenerator()));
|
||||
}
|
||||
else if (heroes[BattleSide::ATTACKER] && heroes[BattleSide::ATTACKER]->boat && heroes[BattleSide::DEFENDER] && heroes[BattleSide::DEFENDER]->boat)
|
||||
battlefieldType = BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.ship_to_ship"));
|
||||
|
||||
//send info about battles
|
||||
BattleStart bs;
|
||||
bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, layout, town);
|
||||
bs.info = BattleInfo::setupBattle(tile, terrain, battlefieldType, armies, heroes, layout, town);
|
||||
bs.battleID = gameHandler->gameState()->nextBattleID;
|
||||
|
||||
engageIntoBattle(bs.info->getSide(BattleSide::ATTACKER).color);
|
||||
|
Loading…
x
Reference in New Issue
Block a user