From 82eb2f8a2b3cde8bf1485a12f405ec94b015e871 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:30:48 +0300 Subject: [PATCH 01/59] Artifact assembling changes --- client/widgets/CArtifactHolder.cpp | 2 +- client/widgets/CArtifactsOfHeroBase.cpp | 4 +- client/widgets/CWindowWithArtifacts.cpp | 17 +++---- client/widgets/CWindowWithArtifacts.h | 2 +- lib/ArtifactUtils.cpp | 21 ++------ lib/ArtifactUtils.h | 2 +- lib/CArtHandler.cpp | 6 --- lib/CArtHandler.h | 1 - lib/NetPacksLib.cpp | 68 +++++++++++++++++-------- server/CGameHandler.cpp | 16 +++--- 10 files changed, 74 insertions(+), 65 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index b53237c9a..d0d2d5031 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -264,7 +264,7 @@ bool ArtifactUtilsClient::askToAssemble(const CGHeroInstance * hero, const Artif if(hero->tempOwner != LOCPLINT->playerID) return false; - auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId(), ArtifactUtils::isSlotEquipment(slot)); + auto assemblyPossibilities = ArtifactUtils::assemblyPossibilities(hero, art->getTypeId()); if(!assemblyPossibilities.empty()) { auto askThread = new boost::thread([hero, art, slot, assemblyPossibilities]() -> void diff --git a/client/widgets/CArtifactsOfHeroBase.cpp b/client/widgets/CArtifactsOfHeroBase.cpp index b8d175226..d184f69ea 100644 --- a/client/widgets/CArtifactsOfHeroBase.cpp +++ b/client/widgets/CArtifactsOfHeroBase.cpp @@ -261,8 +261,10 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit { arts.insert(std::pair(combinedArt, 0)); for(const auto part : combinedArt->getConstituents()) - if(artSet.hasArt(part->getId(), true)) + { + if(artSet.hasArt(part->getId(), false)) arts.at(combinedArt)++; + } } artPlace->addCombinedArtInfo(arts); } diff --git a/client/widgets/CWindowWithArtifacts.cpp b/client/widgets/CWindowWithArtifacts.cpp index 7725c16c3..8f7b951bb 100644 --- a/client/widgets/CWindowWithArtifacts.cpp +++ b/client/widgets/CWindowWithArtifacts.cpp @@ -244,7 +244,7 @@ void CWindowWithArtifacts::rightClickArtPlaceHero(CArtifactsOfHeroBase & artsIns void CWindowWithArtifacts::artifactRemoved(const ArtifactLocation & artLoc) { - updateSlots(artLoc.slot); + updateSlots(); } void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const ArtifactLocation & destLoc, bool withRedraw) @@ -310,26 +310,23 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const void CWindowWithArtifacts::artifactDisassembled(const ArtifactLocation & artLoc) { - updateSlots(artLoc.slot); + updateSlots(); } void CWindowWithArtifacts::artifactAssembled(const ArtifactLocation & artLoc) { markPossibleSlots(); - updateSlots(artLoc.slot); + updateSlots(); } -void CWindowWithArtifacts::updateSlots(const ArtifactPosition & slot) +void CWindowWithArtifacts::updateSlots() { - auto updateSlotBody = [slot](auto artSetWeak) -> void + auto updateSlotBody = [](auto artSetWeak) -> void { if(const auto artSetPtr = artSetWeak.lock()) { - if(ArtifactUtils::isSlotEquipment(slot)) - artSetPtr->updateWornSlots(); - else if(ArtifactUtils::isSlotBackpack(slot)) - artSetPtr->updateBackpackSlots(); - + artSetPtr->updateWornSlots(); + artSetPtr->updateBackpackSlots(); artSetPtr->redraw(); } }; diff --git a/client/widgets/CWindowWithArtifacts.h b/client/widgets/CWindowWithArtifacts.h index 85067e244..976b1a226 100644 --- a/client/widgets/CWindowWithArtifacts.h +++ b/client/widgets/CWindowWithArtifacts.h @@ -43,7 +43,7 @@ private: std::vector artSets; CloseCallback closeCallback; - void updateSlots(const ArtifactPosition & slot); + void updateSlots(); std::optional> getState(); std::optional findAOHbyRef(CArtifactsOfHeroBase & artsInst); void markPossibleSlots(); diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index eeb94c2c6..24bd404f3 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -116,7 +116,7 @@ DLL_LINKAGE bool ArtifactUtils::isBackpackFreeSlots(const CArtifactSet * target, } DLL_LINKAGE std::vector ArtifactUtils::assemblyPossibilities( - const CArtifactSet * artSet, const ArtifactID & aid, bool equipped) + const CArtifactSet * artSet, const ArtifactID & aid) { std::vector arts; const auto * art = aid.toArtifact(); @@ -130,23 +130,10 @@ DLL_LINKAGE std::vector ArtifactUtils::assemblyPossibilities( for(const auto constituent : artifact->getConstituents()) //check if all constituents are available { - if(equipped) + if(!artSet->hasArt(constituent->getId(), false, false, false)) { - // Search for equipped arts - if(!artSet->hasArt(constituent->getId(), true, false, false)) - { - possible = false; - break; - } - } - else - { - // Search in backpack - if(!artSet->hasArtBackpack(constituent->getId())) - { - possible = false; - break; - } + possible = false; + break; } } if(possible) diff --git a/lib/ArtifactUtils.h b/lib/ArtifactUtils.h index 2e70999b6..4b30f946d 100644 --- a/lib/ArtifactUtils.h +++ b/lib/ArtifactUtils.h @@ -36,7 +36,7 @@ namespace ArtifactUtils DLL_LINKAGE bool isSlotBackpack(const ArtifactPosition & slot); DLL_LINKAGE bool isSlotEquipment(const ArtifactPosition & slot); DLL_LINKAGE bool isBackpackFreeSlots(const CArtifactSet * target, const size_t reqSlots = 1); - DLL_LINKAGE std::vector assemblyPossibilities(const CArtifactSet * artSet, const ArtifactID & aid, bool equipped); + DLL_LINKAGE std::vector assemblyPossibilities(const CArtifactSet * artSet, const ArtifactID & aid); DLL_LINKAGE CArtifactInstance * createScroll(const SpellID & sid); DLL_LINKAGE CArtifactInstance * createNewArtifactInstance(CArtifact * art); DLL_LINKAGE CArtifactInstance * createNewArtifactInstance(const ArtifactID & aid); diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 5eeebb5e4..f135c45fb 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -820,12 +820,6 @@ ArtifactPosition CArtifactSet::getArtPos(const ArtifactID & aid, bool onlyWorn, return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0]; } -ArtifactPosition CArtifactSet::getArtBackpackPos(const ArtifactID & aid) const -{ - const auto result = getBackpackArtPositions(aid); - return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0]; -} - std::vector CArtifactSet::getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const { std::vector result; diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index dad6e88e5..7201b95fa 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -260,7 +260,6 @@ public: /// (if more than one such artifact lower ID is returned) ArtifactPosition getArtPos(const ArtifactID & aid, bool onlyWorn = true, bool allowLocked = true) const; ArtifactPosition getArtPos(const CArtifactInstance *art) const; - ArtifactPosition getArtBackpackPos(const ArtifactID & aid) const; std::vector getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const; std::vector getBackpackArtPositions(const ArtifactID & aid) const; const CArtifactInstance * getArtByInstanceId(const ArtifactInstanceID & artInstId) const; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 4b5fd4740..4b2a57eeb 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1907,43 +1907,69 @@ void BulkMoveArtifacts::applyGs(CGameState * gs) void AssembledArtifact::applyGs(CGameState *gs) { CArtifactSet * artSet = al.getHolderArtSet(); - [[maybe_unused]] const CArtifactInstance *transformedArt = al.getArt(); + const CArtifactInstance * transformedArt = al.getArt(); assert(transformedArt); - bool combineEquipped = !ArtifactUtils::isSlotBackpack(al.slot); - assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(artSet, transformedArt->artType->getId(), combineEquipped), [=](const CArtifact * art)->bool + assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(artSet, transformedArt->getTypeId()), [=](const CArtifact * art)->bool { return art->getId() == builtArt->getId(); })); + const auto transformedArtSlot = artSet->getSlotByInstance(transformedArt); auto * combinedArt = new CArtifactInstance(builtArt); gs->map->addNewArtifactInstance(combinedArt); - // Retrieve all constituents - for(const CArtifact * constituent : builtArt->getConstituents()) - { - ArtifactPosition pos = combineEquipped ? artSet->getArtPos(constituent->getId(), true, false) : - artSet->getArtBackpackPos(constituent->getId()); - assert(pos != ArtifactPosition::PRE_FIRST); - CArtifactInstance * constituentInstance = artSet->getArt(pos); - //move constituent from hero to be part of new, combined artifact - constituentInstance->removeFrom(ArtifactLocation(al.artHolder, pos)); - if(combineEquipped) + // Find slots for all involved artifacts + std::vector slotsInvolved; + for(const auto constituent : builtArt->getConstituents()) + { + ArtifactPosition slot; + if(transformedArt->getTypeId() == constituent->getId()) + slot = transformedArtSlot; + else + slot = artSet->getArtPos(constituent->getId(), false, false); + + assert(slot != ArtifactPosition::PRE_FIRST); + slotsInvolved.emplace_back(slot); + } + std::sort(slotsInvolved.begin(), slotsInvolved.end(), std::greater<>()); + + // Find a slot for combined artifact + al.slot = transformedArtSlot; + for(const auto slot : slotsInvolved) + { + if(ArtifactUtils::isSlotEquipment(transformedArtSlot)) { + + if(ArtifactUtils::isSlotBackpack(slot)) + { + al.slot = ArtifactPosition::BACKPACK_START; + break; + } + if(!vstd::contains(combinedArt->artType->getPossibleSlots().at(artSet->bearerType()), al.slot) - && vstd::contains(combinedArt->artType->getPossibleSlots().at(artSet->bearerType()), pos)) - al.slot = pos; - if(al.slot == pos) - pos = ArtifactPosition::PRE_FIRST; + && vstd::contains(combinedArt->artType->getPossibleSlots().at(artSet->bearerType()), slot)) + al.slot = slot; } else { - al.slot = std::min(al.slot, pos); - pos = ArtifactPosition::PRE_FIRST; + if(ArtifactUtils::isSlotBackpack(slot)) + al.slot = std::min(al.slot, slot); } - combinedArt->addPart(constituentInstance, pos); } - //put new combined artifacts + // Delete parts from hero + for(const auto slot : slotsInvolved) + { + const auto constituentInstance = artSet->getArt(slot); + constituentInstance->removeFrom(ArtifactLocation(al.artHolder, slot)); + + if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot) + combinedArt->addPart(constituentInstance, slot); + else + combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST); + } + + // Put new combined artifacts combinedArt->putAt(al); } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3ae1fdfd5..7493125a4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2787,7 +2787,7 @@ bool CGameHandler::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID * @param assembleTo If assemble is true, this represents the artifact ID of the combination * artifact to assemble to. Otherwise it's not used. */ -bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) +bool CGameHandler::assembleArtifacts(ObjectInstanceID heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) { const CGHeroInstance * hero = getHero(heroID); const CArtifactInstance * destArtifact = hero->getArt(artifactSlot); @@ -2795,23 +2795,27 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition if(!destArtifact) COMPLAIN_RET("assembleArtifacts: there is no such artifact instance!"); + const auto dstLoc = ArtifactLocation(hero, artifactSlot); if(assemble) { CArtifact * combinedArt = VLC->arth->objects[assembleTo]; if(!combinedArt->isCombined()) COMPLAIN_RET("assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts!"); - if (!vstd::contains(ArtifactUtils::assemblyPossibilities(hero, destArtifact->getTypeId(), - ArtifactUtils::isSlotEquipment(artifactSlot)), combinedArt)) + if(!vstd::contains(ArtifactUtils::assemblyPossibilities(hero, destArtifact->getTypeId()), combinedArt)) { COMPLAIN_RET("assembleArtifacts: It's impossible to assemble requested artifact!"); } - + if(!destArtifact->canBePutAt(dstLoc) + && !destArtifact->canBePutAt(ArtifactLocation(hero, ArtifactPosition::BACKPACK_START))) + { + COMPLAIN_RET("assembleArtifacts: It's impossible to give the artholder requested artifact!"); + } if(ArtifactUtils::checkSpellbookIsNeeded(hero, assembleTo, artifactSlot)) giveHeroNewArtifact(hero, VLC->arth->objects[ArtifactID::SPELLBOOK], ArtifactPosition::SPELLBOOK); AssembledArtifact aa; - aa.al = ArtifactLocation(hero, artifactSlot); + aa.al = dstLoc; aa.builtArt = combinedArt; sendAndApply(&aa); } @@ -2825,7 +2829,7 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition COMPLAIN_RET("assembleArtifacts: Artifact being attempted to disassemble but backpack is full!"); DisassembledArtifact da; - da.al = ArtifactLocation(hero, artifactSlot); + da.al = dstLoc; sendAndApply(&da); } From 89409da0c0b10ec6d02a3be949348c79a34264b7 Mon Sep 17 00:00:00 2001 From: SoundSSGood <87084363+SoundSSGood@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:34:02 +0300 Subject: [PATCH 02/59] Reduced number of assembling asks --- server/CGameHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 7493125a4..032eac0d4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2683,7 +2683,7 @@ bool CGameHandler::moveArtifact(const ArtifactLocation &al1, const ArtifactLocat } MoveArtifact ma(&src, &dst); - if(dst.slot == ArtifactPosition::TRANSITION_POS) + if(src.artHolder == dst.artHolder) ma.askAssemble = false; sendAndApply(&ma); } From 73ea52c615b356cbf92d7883a7ea29b36be049b3 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sat, 23 Sep 2023 23:55:29 +0200 Subject: [PATCH 03/59] Enable VSync to prevent screen tearing while scrolling across map Fixes #2935 --- client/renderSDL/ScreenHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index a513454b8..f4b261b31 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -275,7 +275,7 @@ void ScreenHandler::initializeWindow() } //create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible - mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0); + mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), SDL_RENDERER_PRESENTVSYNC); if(mainRenderer == nullptr) throw std::runtime_error("Unable to create renderer\n"); @@ -570,4 +570,4 @@ bool ScreenHandler::hasFocus() { ui32 flags = SDL_GetWindowFlags(mainWindow); return flags & SDL_WINDOW_INPUT_FOCUS; -} \ No newline at end of file +} From 23d1638d702a1444363d72f70c0a65163c7eac9b Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 26 Sep 2023 16:06:01 +0200 Subject: [PATCH 04/59] Add VSync to settings --- client/renderSDL/ScreenHandler.cpp | 10 +- config/schemas/settings.json | 7 +- launcher/settingsView/csettingsview_moc.cpp | 7 + launcher/settingsView/csettingsview_moc.h | 2 + launcher/settingsView/csettingsview_moc.ui | 961 ++++++++++---------- 5 files changed, 508 insertions(+), 479 deletions(-) diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index f4b261b31..1371c51c7 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -274,8 +274,14 @@ void ScreenHandler::initializeWindow() handleFatalError(message, true); } - //create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible - mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), SDL_RENDERER_PRESENTVSYNC); + // create first available renderer if no preferred one is set + // use no SDL_RENDERER_SOFTWARE or SDL_RENDERER_ACCELERATED flag, so HW accelerated will be preferred but SW renderer will also be possible + uint32_t rendererFlags = 0; + if(settings["video"]["vsync"].Bool()) + { + rendererFlags |= SDL_RENDERER_PRESENTVSYNC; + } + mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), rendererFlags); if(mainRenderer == nullptr) throw std::runtime_error("Unable to create renderer\n"); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index ee7c69213..d0dedb7fe 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -149,7 +149,8 @@ "driver", "displayIndex", "showfps", - "targetfps" + "targetfps", + "vsync" ], "properties" : { "resolution" : { @@ -207,6 +208,10 @@ "targetfps" : { "type" : "number", "default" : 60 + }, + "vsync" : { + "type" : "boolean", + "default" : true } } }, diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 9f00a369f..9eea8ee6d 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -83,6 +83,7 @@ void CSettingsView::loadSettings() ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float()); ui->spinBoxFramerateLimit->setValue(settings["video"]["targetfps"].Float()); + ui->checkBoxVSync->setChecked(settings["video"]["vsync"].Bool()); ui->spinBoxReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100)); ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String())); @@ -494,6 +495,12 @@ void CSettingsView::on_spinBoxFramerateLimit_valueChanged(int arg1) node->Float() = arg1; } +void CSettingsView::on_checkBoxVSync_stateChanged(int arg1) +{ + Settings node = settings.write["video"]["vsync"]; + node->Bool() = arg1; +} + void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1) { Settings node = settings.write["server"]["playerAI"]; diff --git a/launcher/settingsView/csettingsview_moc.h b/launcher/settingsView/csettingsview_moc.h index 5cc14c505..466901389 100644 --- a/launcher/settingsView/csettingsview_moc.h +++ b/launcher/settingsView/csettingsview_moc.h @@ -62,6 +62,8 @@ private slots: void on_spinBoxFramerateLimit_valueChanged(int arg1); + void on_checkBoxVSync_stateChanged(int arg1); + void on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1); void on_comboBoxAlliedPlayerAI_currentTextChanged(const QString &arg1); diff --git a/launcher/settingsView/csettingsview_moc.ui b/launcher/settingsView/csettingsview_moc.ui index 186689a6c..deae0833c 100644 --- a/launcher/settingsView/csettingsview_moc.ui +++ b/launcher/settingsView/csettingsview_moc.ui @@ -42,7 +42,6 @@ - 75 true @@ -107,20 +106,208 @@ 0 - -197 + -356 610 - 768 + 873 - - + + + + Heroes III Translation + + + + + + + + true + + + + General + + + + + + + Fullscreen + + + + + + + Interface Scaling + + + + + + + Autosave prefix + + + + + + + Adventure Map Allies + + + + + + + + true + + + + Video + + + + + + + 20 + + + 1000 + + + 10 + + + + + + + + true + + + + Artificial Intelligence + + + + + + + empty = map name prefix + + + + + + + true + + + + + + true + + + + + + + + + Autosave limit (0 = off) + + + + + + + + + + Additional repository + + + + + + + % + + + 0 + + + 25 + + + 1 + + + 0 + + + + + + + Show intro + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + Enemy AI in battles + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + false @@ -140,470 +327,13 @@ - - - - Display index - - - - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - Autosave limit (0 = off) - - - - - - - Fullscreen - - - - - - - Network port - - - - - - - - 75 - true - - - - General - - - - - - - Autosave - - - - - - - - Hardware - - - - - Software - - - - - - - - Show intro - - - - - - - - - - true - - - - - - - true - - - - - - true - - - - - - - 20 - - - 1000 - - - 10 - - - - - - - - - - Framerate Limit - - - - - - - - - - Adventure Map Enemies - - - - - - - Default repository - - - - - - - - - - 1 - - - - Off - - - - - On - - - - - - - - - 75 - true - - - - Video - - - - - - - Autosave prefix - - - - - - - Resolution - - - - + - - - - - - - true - - - - - - - Cursor - - - - - - - 1 - - - - Off - - - - - On - - - - - - - - Refresh now - - - - - - - - - - - - - - Heroes III Translation - - - - - - - 1 - - - - Off - - - - - On - - - - - - - - Enemy AI in battles - - - - - - - - 75 - true - - - - Mod Repositories - - - - - - - Additional repository - - - - - - - false - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - 50 - - - 400 - - - 10 - - - - - - - Heroes III Data Language - - - - - - - - - - Neutral AI in battles - - - - - - - Interface Scaling - - - - - - - 1024 - - - 65535 - - - 3030 - - - - - - - - - - - - - - Friendly AI in battles - - - - - - - VCMI Language - - - - - - - - - - Check on startup - - - - - - - BattleAI - - - - BattleAI - - - - - StupidAI - - - - - - - - Adventure Map Allies - - - - - - - - 75 - true - - - - Artificial Intelligence - - - - - - - VCAI - - - - VCAI - - - - - Nullkiller - - - - - - - - empty = map name prefix - - - @@ -635,6 +365,266 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use + + + + + + + Display index + + + + + + + VCAI + + + + VCAI + + + + + Nullkiller + + + + + + + + + + + + + + + Default repository + + + + + + + Heroes III Data Language + + + + + + + + + + true + + + + + + + Framerate Limit + + + + + + + Friendly AI in battles + + + + + + + VCMI Language + + + + + + + + + + true + + + + + + + + + + 50 + + + 400 + + + 10 + + + + + + + + + + + true + + + + Mod Repositories + + + + + + + + + + + + + + Resolution + + + + + + + 1024 + + + 65535 + + + 3030 + + + + + + + Autosave + + + + + + + Cursor + + + + + + + + Hardware + + + + + Software + + + + + + + + + + + Network port + + + + + + + false + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + 1 + + + + Off + + + + + On + + + + + + + + Refresh now + + + + + + + BattleAI + + + + BattleAI + + + + + StupidAI + + + + + + + + Check on startup + + + + + + + Neutral AI in battles + + + @@ -642,22 +632,41 @@ Fullscreen Exclusive Mode - game will cover entirety of your screen and will use - - - - % + + + + Adventure Map Enemies - - 0 - - - 25 - - + + + + + 1 - - 0 + + + Off + + + + + On + + + + + + + + VSync + + + + + + + From bfa5ef7990f7213a4877d400c5f3f86a4483ba10 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 26 Sep 2023 17:28:44 +0200 Subject: [PATCH 05/59] Don't sleep in FramerateManager::framerateDelay() if VSync is enabled --- client/gui/FramerateManager.cpp | 16 +++++++++++----- client/gui/FramerateManager.h | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client/gui/FramerateManager.cpp b/client/gui/FramerateManager.cpp index 59aa653e7..0e81ea8c8 100644 --- a/client/gui/FramerateManager.cpp +++ b/client/gui/FramerateManager.cpp @@ -11,22 +11,28 @@ #include "StdInc.h" #include "FramerateManager.h" +#include "../../lib/CConfigHandler.h" + FramerateManager::FramerateManager(int targetFrameRate) : targetFrameTime(Duration(boost::chrono::seconds(1)) / targetFrameRate) , lastFrameIndex(0) , lastFrameTimes({}) - , lastTimePoint (Clock::now()) + , lastTimePoint(Clock::now()) + , vsyncEnabled(settings["video"]["vsync"].Bool()) { boost::range::fill(lastFrameTimes, targetFrameTime); } void FramerateManager::framerateDelay() { - Duration timeSpentBusy = Clock::now() - lastTimePoint; + if(!vsyncEnabled) + { + Duration timeSpentBusy = Clock::now() - lastTimePoint; - // FPS is higher than it should be, then wait some time - if(timeSpentBusy < targetFrameTime) - boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); + // FPS is higher than it should be, then wait some time + if(timeSpentBusy < targetFrameTime) + boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); + } // compute actual timeElapsed taking into account actual sleep interval // limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint) diff --git a/client/gui/FramerateManager.h b/client/gui/FramerateManager.h index 818e55f94..d653bd667 100644 --- a/client/gui/FramerateManager.h +++ b/client/gui/FramerateManager.h @@ -25,6 +25,8 @@ class FramerateManager /// index of last measured frome in lastFrameTimes array ui32 lastFrameIndex; + bool vsyncEnabled; + public: FramerateManager(int targetFramerate); From e33127a1f74aa1e05b8991593a6d58241ec76bea Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 26 Sep 2023 17:38:41 +0200 Subject: [PATCH 06/59] Launcher setting: Disable spinBoxFramerateLimit if VSync is enabled --- launcher/settingsView/csettingsview_moc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index 9eea8ee6d..5ac6648c2 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -83,6 +83,7 @@ void CSettingsView::loadSettings() ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float()); ui->spinBoxFramerateLimit->setValue(settings["video"]["targetfps"].Float()); + ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); ui->checkBoxVSync->setChecked(settings["video"]["vsync"].Bool()); ui->spinBoxReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100)); @@ -499,6 +500,7 @@ void CSettingsView::on_checkBoxVSync_stateChanged(int arg1) { Settings node = settings.write["video"]["vsync"]; node->Bool() = arg1; + ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool()); } void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1) From e2eeec96a9f9a07e2d5a0a74328c076c08753bc9 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 26 Sep 2023 22:53:45 +0200 Subject: [PATCH 07/59] Fix stuttering animations when using high frame rate limit or vsync --- client/gui/FramerateManager.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/client/gui/FramerateManager.cpp b/client/gui/FramerateManager.cpp index 0e81ea8c8..5291c3920 100644 --- a/client/gui/FramerateManager.cpp +++ b/client/gui/FramerateManager.cpp @@ -12,27 +12,36 @@ #include "FramerateManager.h" #include "../../lib/CConfigHandler.h" +#include FramerateManager::FramerateManager(int targetFrameRate) - : targetFrameTime(Duration(boost::chrono::seconds(1)) / targetFrameRate) - , lastFrameIndex(0) + : lastFrameIndex(0) , lastFrameTimes({}) , lastTimePoint(Clock::now()) , vsyncEnabled(settings["video"]["vsync"].Bool()) { + if(vsyncEnabled) + { + static int display_in_use = 0; + SDL_DisplayMode mode; + SDL_GetCurrentDisplayMode(display_in_use, &mode); + int displayRefreshRate = mode.refresh_rate; + logGlobal->info("Display refresh rate is %d", displayRefreshRate); + targetFrameTime = Duration(boost::chrono::seconds(1)) / displayRefreshRate; + } else + { + targetFrameTime = Duration(boost::chrono::seconds(1)) / targetFrameRate; + } boost::range::fill(lastFrameTimes, targetFrameTime); } void FramerateManager::framerateDelay() { - if(!vsyncEnabled) - { - Duration timeSpentBusy = Clock::now() - lastTimePoint; + Duration timeSpentBusy = Clock::now() - lastTimePoint; - // FPS is higher than it should be, then wait some time - if(timeSpentBusy < targetFrameTime) - boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); - } + // FPS is higher than it should be, then wait some time + if(timeSpentBusy < targetFrameTime) + boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); // compute actual timeElapsed taking into account actual sleep interval // limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint) From ecc7648ef878728014d608bc36a969aacb6a9d1d Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 26 Sep 2023 23:05:48 +0200 Subject: [PATCH 08/59] Use display index from settings --- client/gui/FramerateManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/gui/FramerateManager.cpp b/client/gui/FramerateManager.cpp index 5291c3920..8dc4a075f 100644 --- a/client/gui/FramerateManager.cpp +++ b/client/gui/FramerateManager.cpp @@ -22,7 +22,7 @@ FramerateManager::FramerateManager(int targetFrameRate) { if(vsyncEnabled) { - static int display_in_use = 0; + static int display_in_use = settings["video"]["displayIndex"].Integer(); SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(display_in_use, &mode); int displayRefreshRate = mode.refresh_rate; From 0d11c5197dfd63b8b691b54e5a916718cd6510b6 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Tue, 26 Sep 2023 23:48:53 +0200 Subject: [PATCH 09/59] Remove superfluous vsyncEnabled attribute --- client/gui/FramerateManager.cpp | 3 +-- client/gui/FramerateManager.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/client/gui/FramerateManager.cpp b/client/gui/FramerateManager.cpp index 8dc4a075f..a23db55c4 100644 --- a/client/gui/FramerateManager.cpp +++ b/client/gui/FramerateManager.cpp @@ -18,9 +18,8 @@ FramerateManager::FramerateManager(int targetFrameRate) : lastFrameIndex(0) , lastFrameTimes({}) , lastTimePoint(Clock::now()) - , vsyncEnabled(settings["video"]["vsync"].Bool()) { - if(vsyncEnabled) + if(settings["video"]["vsync"].Bool()) { static int display_in_use = settings["video"]["displayIndex"].Integer(); SDL_DisplayMode mode; diff --git a/client/gui/FramerateManager.h b/client/gui/FramerateManager.h index d653bd667..818e55f94 100644 --- a/client/gui/FramerateManager.h +++ b/client/gui/FramerateManager.h @@ -25,8 +25,6 @@ class FramerateManager /// index of last measured frome in lastFrameTimes array ui32 lastFrameIndex; - bool vsyncEnabled; - public: FramerateManager(int targetFramerate); From 764fbebb356cd7eb33148eaf58c315b96ccd592d Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Wed, 27 Sep 2023 15:12:50 +0300 Subject: [PATCH 10/59] make script executable --- client/icons/generate_icns.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 client/icons/generate_icns.py diff --git a/client/icons/generate_icns.py b/client/icons/generate_icns.py old mode 100644 new mode 100755 From 0db567da37a6ef1a2f6fe15070de5328d752b50b Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Wed, 27 Sep 2023 15:25:41 +0300 Subject: [PATCH 11/59] improve layout of some dev docs --- docs/developers/Building_Android.md | 28 ++++---- docs/developers/Building_iOS.md | 30 ++++---- docs/developers/Building_macOS.md | 103 +++++++++++++--------------- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/docs/developers/Building_Android.md b/docs/developers/Building_Android.md index cbd6a14f7..6800dc7e2 100644 --- a/docs/developers/Building_Android.md +++ b/docs/developers/Building_Android.md @@ -10,17 +10,17 @@ The following instructions apply to **v1.2 and later**. For earlier versions the 2. JDK 11, not necessarily from Oracle 3. Android command line tools or Android Studio for your OS: https://developer.android.com/studio/ 4. Android NDK version **r25c (25.2.9519653)**, there're multiple ways to obtain it: -- - install with Android Studio -- - install with `sdkmanager` command line tool -- - download from https://developer.android.com/ndk/downloads -- - download with Conan, see [#NDK and Conan](#ndk-and-conan) -5. (optional) Ninja: download from your package manager or from https://github.com/ninja-build/ninja/releases + - install with Android Studio + - install with `sdkmanager` command line tool + - download from https://developer.android.com/ndk/downloads + - download with Conan, see [#NDK and Conan](#ndk-and-conan) +5. (optional) Ninja: download from your package manager or from https://github.com/ninja-build/ninja/releases ## Obtaining source code Clone https://github.com/vcmi/vcmi with submodules. Example for command line: -```sh +``` git clone --recurse-submodules https://github.com/vcmi/vcmi.git ``` @@ -30,24 +30,24 @@ We use Conan package manager to build/consume dependencies, find detailed usage branch](https://github.com/vcmi/vcmi/tree/master/docs/conan.md). On the step where you need to replace **PROFILE**, choose: -- `android-32` to build for 32-bit architecture (armeabi-v7a) -- `android-64` to build for 64-bit architecture (aarch64-v8a) +- `android-32` to build for 32-bit architecture (armeabi-v7a) +- `android-64` to build for 64-bit architecture (aarch64-v8a) ### NDK and Conan Conan must be aware of the NDK location when you execute `conan install`. There're multiple ways to achieve that as written in the [Conan docs](https://docs.conan.io/1/integrations/cross_platform/android.html): -- the easiest is to download NDK from Conan (option 1 in the docs), then all the magic happens automatically. You need to create your own Conan profile that imports our Android profile and adds 2 new lines (you can of course just copy everything from our profile into yours without including) and then pass this new profile to `conan install`: +- the easiest is to download NDK from Conan (option 1 in the docs), then all the magic happens automatically. You need to create your own Conan profile that imports our Android profile and adds 2 new lines (you can of course just copy everything from our profile into yours without including) and then pass this new profile to `conan install`: -```sh +``` include(/path/to/vcmi/CI/conan/android-64) [tool_requires] android-ndk/r25c ``` -- to use an already installed NDK, you can simply pass it on the command line to `conan install`: +- to use an already installed NDK, you can simply pass it on the command line to `conan install`: -```sh +``` conan install -c tools.android:ndk_path=/path/to/ndk ... ``` @@ -59,7 +59,7 @@ Building for Android is a 2-step process. First, native C++ code is compiled to This is a traditional CMake project, you can build it from command line or some IDE. You're not required to pass any custom options (except Conan toolchain file), defaults are already good. If you wish to use your own CMake presets, inherit them from our `build-with-conan` preset. Example: -```sh +``` cmake -S . -B ../build -G Ninja -D CMAKE_BUILD_TYPE=Debug --toolchain ... cmake --build ../build ``` @@ -83,4 +83,4 @@ cd android APK will appear in `android/vcmi-app/build/outputs/apk/debug` directory which you can then install to your device with `adb install -r /path/to/apk` (adb command is from Android command line tools). -If you wish to build and install to your device in single action, use `installDebug` instead of `assembleDebug`. \ No newline at end of file +If you wish to build and install to your device in single action, use `installDebug` instead of `assembleDebug`. diff --git a/docs/developers/Building_iOS.md b/docs/developers/Building_iOS.md index 7b06136b4..22a67d85a 100644 --- a/docs/developers/Building_iOS.md +++ b/docs/developers/Building_iOS.md @@ -2,22 +2,24 @@ ## Requirements -1. **macOS** -2. Xcode: -3. CMake 3.21+: `brew install --cask cmake` or get from +1. **macOS** +2. Xcode: +3. CMake 3.21+: `brew install --cask cmake` or get from ## Obtaining source code Clone with submodules. Example for command line: -`git clone --recurse-submodules https://github.com/vcmi/vcmi.git` +``` +git clone --recurse-submodules https://github.com/vcmi/vcmi.git +``` ## Obtaining dependencies There are 2 ways to get prebuilt dependencies: -- [Conan package manager](https://github.com/vcmi/vcmi/tree/develop/docs/conan.md) - recommended. Note that the link points to the cutting-edge state in `develop` branch, for the latest release check the same document in the [master branch (https://github.com/vcmi/vcmi/tree/master/docs/conan.md). -- [legacy manually built libraries](https://github.com/vcmi/vcmi-ios-deps) - can be used if you have Xcode 11/12 or to build for simulator / armv7 device +- [Conan package manager](https://github.com/vcmi/vcmi/tree/develop/docs/conan.md) - recommended. Note that the link points to the cutting-edge state in `develop` branch, for the latest release check the same document in the [master branch (https://github.com/vcmi/vcmi/tree/master/docs/conan.md). +- [legacy manually built libraries](https://github.com/vcmi/vcmi-ios-deps) - can be used if you have Xcode 11/12 or to build for simulator / armv7 device ## Configuring project @@ -25,15 +27,17 @@ Only Xcode generator (`-G Xcode`) is supported! As a minimum, you must pass the following variables to CMake: -- `BUNDLE_IDENTIFIER_PREFIX`: unique bundle identifier prefix, something like `com.MY-NAME` -- (if using legacy dependencies) `CMAKE_PREFIX_PATH`: path to the downloaded dependencies, e.g. `~/Downloads/vcmi-ios-depends/build/iphoneos` +- `BUNDLE_IDENTIFIER_PREFIX`: unique bundle identifier prefix, something like `com.MY-NAME` +- (if using legacy dependencies) `CMAKE_PREFIX_PATH`: path to the downloaded dependencies, e.g. `~/Downloads/vcmi-ios-depends/build/iphoneos` There're a few [CMake presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html): for device (Conan and legacy dependencies) and for simulator, named `ios-device-conan`, `ios-device` and `ios-simulator` respectively. You can also create your local "user preset" to avoid typing variables each time, see example [here](https://gist.github.com/kambala-decapitator/59438030c34b53aed7d3895aaa48b718). Open terminal and `cd` to the directory with source code. Configuration example for device with Conan: -`cmake --preset ios-device-conan \` -` -D BUNDLE_IDENTIFIER_PREFIX=com.MY-NAME` +``` +cmake --preset ios-device-conan \ + -D BUNDLE_IDENTIFIER_PREFIX=com.MY-NAME +``` By default build directory containing Xcode project will appear at `../build-ios-device-conan`, but you can change it with `-B` option. @@ -53,7 +57,9 @@ You must also install game files, see [Installation on iOS](../players/Installat ### From command line -`cmake --build `` --target vcmiclient -- -quiet` +``` +cmake --build --target vcmiclient -- -quiet +``` You can pass additional xcodebuild options after the `--`. Here `-quiet` is passed to reduce amount of output. @@ -67,4 +73,4 @@ Invoke `cpack` after building: `cpack -C Release` -This will generate file with extension **ipa** if you use CMake 3.25+and **zip** otherwise (simply change extension to ipa). \ No newline at end of file +This will generate file with extension **ipa** if you use CMake 3.25+and **zip** otherwise (simply change extension to ipa). diff --git a/docs/developers/Building_macOS.md b/docs/developers/Building_macOS.md index a84297852..d6557e5f2 100644 --- a/docs/developers/Building_macOS.md +++ b/docs/developers/Building_macOS.md @@ -2,18 +2,20 @@ # Requirements -1. C++ toolchain, either of: - - Xcode Command Line Tools (aka CLT): `sudo xcode-select --install` - - Xcode IDE: - - (not tested) other C++ compilers, e.g. gcc/clang from [Homebrew](https://brew.sh/) -2. CMake: `brew install --cask cmake` or get from -3. (optional) Ninja: `brew install ninja` or get from +1. C++ toolchain, either of: + - Xcode Command Line Tools (aka CLT): `sudo xcode-select --install` + - Xcode IDE: + - (not tested) other C++ compilers, e.g. gcc/clang from [Homebrew](https://brew.sh/) +2. CMake: `brew install --cask cmake` or get from +3. (optional) Ninja: `brew install ninja` or get from # Obtaining source code Clone with submodules. Example for command line: -`git clone --recurse-submodules https://github.com/vcmi/vcmi.git` +``` +git clone --recurse-submodules https://github.com/vcmi/vcmi.git +``` # Obtaining dependencies @@ -25,51 +27,48 @@ Please find detailed instructions in [VCMI repository](https://github.com/vcmi/v On the step where you need to replace **PROFILE**, choose: -- if you're on an Intel Mac: `macos-intel` -- if you're on an Apple Silicon Mac: `macos-arm` +- if you're on an Intel Mac: `macos-intel` +- if you're on an Apple Silicon Mac: `macos-arm` Note: if you wish to build 1.0 release in non-`Release` configuration, you should define `USE_CONAN_WITH_ALL_CONFIGS=1` environment variable when executing `conan install`. ## Homebrew -1. [Install Homebrew](https://brew.sh/) -2. Install dependencies: -`brew install boost minizip sdl2 sdl2_image sdl2_mixer sdl2_ttf tbb` -3. If you want to watch in-game videos, also install FFmpeg: -`brew install ffmpeg@4` -4. Install Qt dependency in either of the ways (note that you can skip this if you're not going to build Launcher): - - `brew install qt@5` for Qt 5 or `brew install qt` for Qt 6 - - using [Qt Online Installer](https://www.qt.io/download) - choose **Go open source** +1. [Install Homebrew](https://brew.sh/) +2. Install dependencies: `brew install boost minizip sdl2 sdl2_image sdl2_mixer sdl2_ttf tbb` +3. If you want to watch in-game videos, also install FFmpeg: `brew install ffmpeg@4` +4. Install Qt dependency in either of the ways (note that you can skip this if you're not going to build Launcher): + - `brew install qt@5` for Qt 5 or `brew install qt` for Qt 6 + - using [Qt Online Installer](https://www.qt.io/download) - choose **Go open source** # Preparing build environment This applies only to Xcode-based toolchain. If `xcrun -f clang` prints errors, then use either of the following ways: -- select an Xcode instance from Xcode application - Preferences - Locations - Command Line Tools -- use `xcode-select` utility to set Xcode or Xcode Command Line Tools path: for example, - `sudo xcode-select -s /Library/Developer/CommandLineTools` -- set `DEVELOPER_DIR` environment variable pointing to Xcode or Xcode Command Line Tools path: for example, `export DEVELOPER_DIR=/Applications/Xcode.app` +- select an Xcode instance from Xcode application - Preferences - Locations - Command Line Tools +- use `xcode-select` utility to set Xcode or Xcode Command Line Tools path: for example, `sudo xcode-select -s /Library/Developer/CommandLineTools` +- set `DEVELOPER_DIR` environment variable pointing to Xcode or Xcode Command Line Tools path: for example, `export DEVELOPER_DIR=/Applications/Xcode.app` # Configuring project for building Note that if you wish to use Qt Creator IDE, you should skip this step and configure respective variables inside the IDE. -1. In Terminal `cd` to the source code directory -2. Start assembling CMake invocation: type `cmake -S . -B BUILD_DIR` where *BUILD_DIR* can be any path, **don't press Return** -3. Decide which CMake generator you want to use: - - Makefiles: no extra option needed or pass `-G 'Unix Makefiles'` - - Ninja (if you have installed it): pass `-G Ninja` - - Xcode IDE (if you have installed it): pass `-G Xcode` -4. If you picked Makefiles or Ninja, pick desired *build type* - either of Debug / RelWithDebInfo / Release / MinSizeRel - and pass it in `CMAKE_BUILD_TYPE` option, for example: `-D CMAKE_BUILD_TYPE=Release`. If you don't pass this option, `RelWithDebInfo` will be used. -5. If you don't want to build Launcher, pass `-D ENABLE_LAUNCHER=OFF` -6. You can also pass `-Wno-dev` if you're not interested in CMake developer warnings -7. Next step depends on the dependency manager you have picked: - - Conan: pass `-D CMAKE_TOOLCHAIN_FILE=conan-generated/conan_toolchain.cmake` where **conan-generated** must be replaced with your directory choice - - Homebrew: if you installed FFmpeg or Qt 5, you need to pass `-D "CMAKE_PREFIX_PATH="` variable. See below what you can insert after `=` (but **before the closing quote**), multiple values must be separated with `;` (semicolon): - - if you installed FFmpeg, insert `$(brew --prefix ffmpeg@4)` - - if you installed Qt 5 from Homebrew, insert:`$(brew --prefix qt@5)` - - if you installed Qt from Online Installer, insert your path to Qt directory, for example: `/Users/kambala/dev/Qt-libs/5.15.2/Clang64` - - example for FFmpeg + Qt 5: `-D "CMAKE_PREFIX_PATH=$(brew --prefix ffmpeg@4);$(brew --prefix qt@5)"` +1. In Terminal `cd` to the source code directory +2. Start assembling CMake invocation: type `cmake -S . -B BUILD_DIR` where *BUILD_DIR* can be any path, **don't press Return** +3. Decide which CMake generator you want to use: + - Makefiles: no extra option needed or pass `-G 'Unix Makefiles'` + - Ninja (if you have installed it): pass `-G Ninja` + - Xcode IDE (if you have installed it): pass `-G Xcode` +4. If you picked Makefiles or Ninja, pick desired *build type* - either of Debug / RelWithDebInfo / Release / MinSizeRel - and pass it in `CMAKE_BUILD_TYPE` option, for example: `-D CMAKE_BUILD_TYPE=Release`. If you don't pass this option, `RelWithDebInfo` will be used. +5. If you don't want to build Launcher, pass `-D ENABLE_LAUNCHER=OFF` +6. You can also pass `-Wno-dev` if you're not interested in CMake developer warnings +7. Next step depends on the dependency manager you have picked: + - Conan: pass `-D CMAKE_TOOLCHAIN_FILE=conan-generated/conan_toolchain.cmake` where **conan-generated** must be replaced with your directory choice + - Homebrew: if you installed FFmpeg or Qt 5, you need to pass `-D "CMAKE_PREFIX_PATH="` variable. See below what you can insert after `=` (but **before the closing quote**), multiple values must be separated with `;` (semicolon): + - if you installed FFmpeg, insert `$(brew --prefix ffmpeg@4)` + - if you installed Qt 5 from Homebrew, insert:`$(brew --prefix qt@5)` + - if you installed Qt from Online Installer, insert your path to Qt directory, for example: `/Users/kambala/dev/Qt-libs/5.15.2/Clang64` + - example for FFmpeg + Qt 5: `-D "CMAKE_PREFIX_PATH=$(brew --prefix ffmpeg@4);$(brew --prefix qt@5)"` 8. now press Return # Building project @@ -82,10 +81,10 @@ Open `VCMI.xcodeproj` from the build directory, select `vcmiclient` scheme and h ## From command line -`cmake --build ` +`cmake --build ` -- If using Makefiles generator, you'd want to utilize all your CPU cores by appending `-- -j$(sysctl -n hw.ncpu)` to the above -- If using Xcode generator, you can also choose which configuration to build by appending `--config ` to the above, for example: `--config Debug` +- If using Makefiles generator, you'd want to utilize all your CPU cores by appending `-- -j$(sysctl -n hw.ncpu)` to the above +- If using Xcode generator, you can also choose which configuration to build by appending `--config ` to the above, for example: `--config Debug` # Packaging project into DMG file @@ -97,27 +96,23 @@ If you use Conan, it's expected that you use **conan-generated** directory at st You can run VCMI from DMG, but it's will also work from your IDE be it Xcode or Qt Creator. -Alternatively you can run binaries directly from "bin" directory: +Alternatively you can run binaries directly from the **bin** directory: - BUILD_DIR/bin/vcmilauncher - BUILD_DIR/bin/vcmiclient - BUILD_DIR/bin/vcmiserver +- BUILD_DIR/bin/vcmilauncher +- BUILD_DIR/bin/vcmiclient +- BUILD_DIR/bin/vcmiserver -CMake include commands to copy all needed assets from source directory into "bin" on each build. They'll work when you build from Xcode too. +CMake include commands to copy all needed assets from source directory into the **bin** directory on each build. They'll work when you build from Xcode too. # Some useful debugging tips Anyone who might want to debug builds, but new to macOS could find following commands useful: -- To attach DMG file from command line use -`hdiutil attach vcmi-1.0.dmg` -- Detach volume: -`hdiutil detach /Volumes/vcmi-1.0` -- To view dependency paths -`otool -L /Volumes/vcmi-1.0/VCMI.app/Contents/MacOS/vcmiclient` -- To display load commands such as LC_RPATH -`otool -l /Volumes/vcmi-1.0/VCMI.app/Contents/MacOS/vcmiclient` +- To attach DMG file from command line use `hdiutil attach vcmi-1.0.dmg` +- Detach volume: `hdiutil detach /Volumes/vcmi-1.0` +- To view dependency paths: `otool -L /Volumes/vcmi-1.0/VCMI.app/Contents/MacOS/vcmiclient` +- To display load commands such as `LC_RPATH`: `otool -l /Volumes/vcmi-1.0/VCMI.app/Contents/MacOS/vcmiclient` # Troubleshooting -In case of troubles you can always consult our CI build scripts or contact the dev team via slack \ No newline at end of file +In case of troubles you can always consult our CI build scripts or contact the dev team via slack. From f52562eeb7d0db41a7de2c6e82da2dd0512eec71 Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Wed, 27 Sep 2023 16:09:52 +0200 Subject: [PATCH 12/59] Fix freezing of hero and long enemy turns without sleeping in FramerateManager::framerateDelay() --- client/gui/CGuiHandler.cpp | 3 +-- client/gui/FramerateManager.cpp | 27 +++++++++++---------------- client/gui/FramerateManager.h | 2 ++ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index ae4f9bdab..4bda96ae6 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -129,11 +129,10 @@ void CGuiHandler::renderFrame() CCS->curh->render(); - SDL_RenderPresent(mainRenderer); - windows().onFrameRendered(); } + SDL_RenderPresent(mainRenderer); framerate().framerateDelay(); // holds a constant FPS } diff --git a/client/gui/FramerateManager.cpp b/client/gui/FramerateManager.cpp index a23db55c4..26757f96f 100644 --- a/client/gui/FramerateManager.cpp +++ b/client/gui/FramerateManager.cpp @@ -15,22 +15,12 @@ #include FramerateManager::FramerateManager(int targetFrameRate) - : lastFrameIndex(0) + : targetFrameTime(Duration(boost::chrono::seconds(1)) / targetFrameRate) + , lastFrameIndex(0) , lastFrameTimes({}) , lastTimePoint(Clock::now()) + , vsyncEnabled(settings["video"]["vsync"].Bool()) { - if(settings["video"]["vsync"].Bool()) - { - static int display_in_use = settings["video"]["displayIndex"].Integer(); - SDL_DisplayMode mode; - SDL_GetCurrentDisplayMode(display_in_use, &mode); - int displayRefreshRate = mode.refresh_rate; - logGlobal->info("Display refresh rate is %d", displayRefreshRate); - targetFrameTime = Duration(boost::chrono::seconds(1)) / displayRefreshRate; - } else - { - targetFrameTime = Duration(boost::chrono::seconds(1)) / targetFrameRate; - } boost::range::fill(lastFrameTimes, targetFrameTime); } @@ -38,9 +28,14 @@ void FramerateManager::framerateDelay() { Duration timeSpentBusy = Clock::now() - lastTimePoint; - // FPS is higher than it should be, then wait some time - if(timeSpentBusy < targetFrameTime) - boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); + if(!vsyncEnabled) + { + // if FPS is higher than it should be, then wait some time + if(timeSpentBusy < targetFrameTime) + { + boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy); + } + } // compute actual timeElapsed taking into account actual sleep interval // limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint) diff --git a/client/gui/FramerateManager.h b/client/gui/FramerateManager.h index 818e55f94..d653bd667 100644 --- a/client/gui/FramerateManager.h +++ b/client/gui/FramerateManager.h @@ -25,6 +25,8 @@ class FramerateManager /// index of last measured frome in lastFrameTimes array ui32 lastFrameIndex; + bool vsyncEnabled; + public: FramerateManager(int targetFramerate); From 03c099d4fd6c1d48a364cff061a9ebe76dfc0e72 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 27 Sep 2023 22:53:13 +0200 Subject: [PATCH 13/59] First steps --- client/CPlayerInterface.cpp | 4 +- client/CServerHandler.cpp | 2 +- client/lobby/CBonusSelection.cpp | 69 +++++++++++++---------------- client/lobby/CSelectionBase.cpp | 4 +- client/lobby/RandomMapTab.cpp | 4 +- client/lobby/SelectionTab.cpp | 10 ++--- client/mainmenu/CCampaignScreen.cpp | 2 +- lib/StartInfo.cpp | 4 +- lib/campaign/CampaignHandler.cpp | 10 ++--- lib/campaign/CampaignState.cpp | 10 ++--- lib/campaign/CampaignState.h | 11 ++--- lib/mapObjects/CGHeroInstance.cpp | 5 --- lib/mapping/CMapHeader.h | 4 +- lib/mapping/CMapInfo.cpp | 18 ++++---- lib/mapping/CMapInfo.h | 4 +- lib/mapping/MapFormatH3M.cpp | 6 +-- lib/mapping/MapFormatJson.cpp | 4 +- lib/rmg/CMapGenerator.cpp | 4 +- 18 files changed, 83 insertions(+), 92 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 09bdc174f..63019f00e 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -239,7 +239,7 @@ void CPlayerInterface::performAutosave() prefix = settings["general"]["savePrefix"].String(); if(prefix.empty()) { - std::string name = cb->getMapHeader()->name; + std::string name = cb->getMapHeader()->name.toString(); int txtlen = TextOperations::getUnicodeCharactersCount(name); TextOperations::trimRightUnicode(name, std::max(0, txtlen - 15)); @@ -1718,7 +1718,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won) if(!ps->checkVanquished()) param.allDefeated = false; } - param.scenarioName = cb->getMapHeader()->name; + param.scenarioName = cb->getMapHeader()->name.toString(); param.playerName = cb->getStartInfo()->playerInfos.find(*cb->getPlayerID())->second.name; HighScoreCalculation highScoreCalc; highScoreCalc.parameters.push_back(param); diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 952b6feb1..3ffbb8023 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -701,7 +701,7 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared highScoreCalc->isCampaign = true; highScoreCalc->parameters.clear(); } - param.campaignName = cs->getName(); + param.campaignName = cs->getNameTranslated(); highScoreCalc->parameters.push_back(param); GH.dispatchMainThread([ourCampaign, this]() diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index d25778e9e..e63167a0c 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -79,9 +79,9 @@ CBonusSelection::CBonusSelection() iconsMapSizes = std::make_shared(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 735, 26); labelCampaignDescription = std::make_shared(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]); - campaignDescription = std::make_shared(getCampaign()->getDescription(), Rect(480, 86, 286, 117), 1); + campaignDescription = std::make_shared(getCampaign()->getDescriptionTranslated(), Rect(480, 86, 286, 117), 1); - mapName = std::make_shared(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName()); + mapName = std::make_shared(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated()); labelMapDescription = std::make_shared(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]); mapDescription = std::make_shared("", Rect(480, 278, 292, 108), 1); @@ -146,18 +146,18 @@ void CBonusSelection::createBonusesIcons() std::string picName = bonusPics[bonusType]; size_t picNumber = bonDescs[i].info2; - std::string desc; + MetaString desc; switch(bonDescs[i].type) { case CampaignBonusType::SPELL: - desc = CGI->generaltexth->allTexts[715]; - boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated()); + desc.appendLocalString(EMetaText::GENERAL_TXT, 715); + desc.replaceLocalString(EMetaText::SPELL_NAME, bonDescs[i].info2); break; case CampaignBonusType::MONSTER: picNumber = bonDescs[i].info2 + 2; - desc = CGI->generaltexth->allTexts[717]; - boost::algorithm::replace_first(desc, "%d", std::to_string(bonDescs[i].info3)); - boost::algorithm::replace_first(desc, "%s", CGI->creatures()->getByIndex(bonDescs[i].info2)->getNamePluralTranslated()); + desc.appendLocalString(EMetaText::GENERAL_TXT, 717); + desc.replaceNumber(bonDescs[i].info3); + desc.replaceLocalString(EMetaText::CRE_PL_NAMES, bonDescs[i].info2); break; case CampaignBonusType::BUILDING: { @@ -182,17 +182,16 @@ void CBonusSelection::createBonusesIcons() picNumber = -1; if(vstd::contains((*CGI->townh)[faction]->town->buildings, buildID)) - desc = (*CGI->townh)[faction]->town->buildings.find(buildID)->second->getNameTranslated(); - + desc.appendTextID((*CGI->townh)[faction]->town->buildings.find(buildID)->second->getNameTextID()); break; } case CampaignBonusType::ARTIFACT: - desc = CGI->generaltexth->allTexts[715]; - boost::algorithm::replace_first(desc, "%s", CGI->artifacts()->getByIndex(bonDescs[i].info2)->getNameTranslated()); + desc.appendLocalString(EMetaText::GENERAL_TXT, 715); + desc.replaceLocalString(EMetaText::ART_NAMES, bonDescs[i].info2); break; case CampaignBonusType::SPELL_SCROLL: - desc = CGI->generaltexth->allTexts[716]; - boost::algorithm::replace_first(desc, "%s", CGI->spells()->getByIndex(bonDescs[i].info2)->getNameTranslated()); + desc.appendLocalString(EMetaText::GENERAL_TXT, 716); + desc.replaceLocalString(EMetaText::ART_NAMES, bonDescs[i].info2); break; case CampaignBonusType::PRIMARY_SKILL: { @@ -211,7 +210,7 @@ void CBonusSelection::createBonusesIcons() } } picNumber = leadingSkill; - desc = CGI->generaltexth->allTexts[715]; + desc.appendLocalString(EMetaText::GENERAL_TXT, 715); std::string substitute; //text to be printed instead of %s for(int v = 0; v < toPrint.size(); ++v) @@ -224,14 +223,13 @@ void CBonusSelection::createBonusesIcons() } } - boost::algorithm::replace_first(desc, "%s", substitute); + desc.replaceRawString(substitute); break; } case CampaignBonusType::SECONDARY_SKILL: - desc = CGI->generaltexth->allTexts[718]; - - boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level - boost::algorithm::replace_first(desc, "%s", CGI->skillh->getByIndex(bonDescs[i].info2)->getNameTranslated()); //skill name + desc.appendLocalString(EMetaText::GENERAL_TXT, 718); + desc.replaceTextID(TextIdentifier("core", "genrltxt", "levels", bonDescs[i].info3 - 1).get()); + desc.replaceLocalString(EMetaText::SEC_SKILL_NAME, bonDescs[i].info2); picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1; break; @@ -258,18 +256,17 @@ void CBonusSelection::createBonusesIcons() } picNumber = serialResID; - desc = CGI->generaltexth->allTexts[717]; - boost::algorithm::replace_first(desc, "%d", std::to_string(bonDescs[i].info2)); - std::string replacement; + desc.appendLocalString(EMetaText::GENERAL_TXT, 717); + desc.replaceNumber(bonDescs[i].info2); + if(serialResID <= 6) { - replacement = CGI->generaltexth->restypes[serialResID]; + desc.replaceLocalString(EMetaText::RES_NAMES, serialResID); } else { - replacement = CGI->generaltexth->allTexts[714 + serialResID]; + desc.replaceLocalString(EMetaText::GENERAL_TXT, 714 + serialResID); } - boost::algorithm::replace_first(desc, "%s", replacement); break; } case CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO: @@ -278,31 +275,29 @@ void CBonusSelection::createBonusesIcons() if(!superhero) logGlobal->warn("No superhero! How could it be transferred?"); picNumber = superhero ? superhero->portrait : 0; - desc = CGI->generaltexth->allTexts[719]; - - boost::algorithm::replace_first(desc, "%s", getCampaign()->scenario(static_cast(bonDescs[i].info2)).scenarioName); + desc.appendLocalString(EMetaText::GENERAL_TXT, 719); + desc.replaceRawString(getCampaign()->scenario(static_cast(bonDescs[i].info2)).scenarioName.toString()); break; } case CampaignBonusType::HERO: - desc = CGI->generaltexth->allTexts[718]; - boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color - + desc.appendLocalString(EMetaText::GENERAL_TXT, 718); + desc.replaceTextID(TextIdentifier("core", "genrltxt", "capColors", bonDescs[i].info1).get()); if(bonDescs[i].info2 == 0xFFFF) { - boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name + desc.replaceLocalString(EMetaText::GENERAL_TXT, 101); picNumber = -1; picName = "CBONN1A3.BMP"; } else { - boost::algorithm::replace_first(desc, "%s", CGI->heroh->objects[bonDescs[i].info2]->getNameTranslated()); + desc.replaceTextID(CGI->heroh->objects[bonDescs[i].info2]->getNameTextID()); } break; } - std::shared_ptr bonusButton = std::make_shared(Point(475 + i * 68, 455), AnimationPath(), CButton::tooltip(desc, desc)); + std::shared_ptr bonusButton = std::make_shared(Point(475 + i * 68, 455), AnimationPath(), CButton::tooltip(desc.toString(), desc.toString())); if(picNumber != -1) picName += ":" + std::to_string(picNumber); @@ -355,8 +350,8 @@ void CBonusSelection::updateAfterStateChange() if(!CSH->mi) return; iconsMapSizes->setFrame(CSH->mi->getMapSizeIconId()); - mapName->setText(CSH->mi->getName()); - mapDescription->setText(CSH->mi->getDescription()); + mapName->setText(CSH->mi->getNameTranslated()); + mapDescription->setText(CSH->mi->getDescriptionTranslated()); for(size_t i = 0; i < difficultyIcons.size(); i++) { if(i == CSH->si->difficulty) diff --git a/client/lobby/CSelectionBase.cpp b/client/lobby/CSelectionBase.cpp index 4982d8f76..dbc8793f4 100644 --- a/client/lobby/CSelectionBase.cpp +++ b/client/lobby/CSelectionBase.cpp @@ -200,8 +200,8 @@ void InfoCard::changeSelection() return; labelSaveDate->setText(mapInfo->date); - mapName->setText(mapInfo->getName()); - mapDescription->setText(mapInfo->getDescription()); + mapName->setText(mapInfo->getNameTranslated()); + mapDescription->setText(mapInfo->getDescriptionTranslated()); mapDescription->label->scrollTextTo(0, false); if(mapDescription->slider) diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index aaaacea86..b5bdd9a61 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -163,8 +163,8 @@ void RandomMapTab::updateMapInfoByHost() mapInfo->isRandomMap = true; mapInfo->mapHeader = std::make_unique(); mapInfo->mapHeader->version = EMapFormat::VCMI; - mapInfo->mapHeader->name = CGI->generaltexth->allTexts[740]; - mapInfo->mapHeader->description = CGI->generaltexth->allTexts[741]; + mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740); + mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741); mapInfo->mapHeader->difficulty = 1; // Normal mapInfo->mapHeader->height = mapGenOptions->getHeight(); mapInfo->mapHeader->width = mapGenOptions->getWidth(); diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 34375db26..9e0f87052 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -111,11 +111,11 @@ bool mapSorter::operator()(const std::shared_ptr aaa, const std::sh return (a->victoryIconIndex < b->victoryIconIndex); break; case _name: //by name - return boost::ilexicographical_compare(a->name, b->name); + return boost::ilexicographical_compare(a->name.toString(), b->name.toString()); case _fileName: //by filename return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI); default: - return boost::ilexicographical_compare(a->name, b->name); + return boost::ilexicographical_compare(a->name.toString(), b->name.toString()); } } else //if we are sorting campaigns @@ -125,9 +125,9 @@ bool mapSorter::operator()(const std::shared_ptr aaa, const std::sh case _numOfMaps: //by number of maps in campaign return aaa->campaign->scenariosCount() < bbb->campaign->scenariosCount(); case _name: //by name - return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName()); + return boost::ilexicographical_compare(aaa->campaign->getNameTranslated(), bbb->campaign->getNameTranslated()); default: - return boost::ilexicographical_compare(aaa->campaign->getName(), bbb->campaign->getName()); + return boost::ilexicographical_compare(aaa->campaign->getNameTranslated(), bbb->campaign->getNameTranslated()); } } } @@ -367,7 +367,7 @@ void SelectionTab::showPopupWindow(const Point & cursorPosition) if(!curItems[py]->isFolder) { - std::string text = boost::str(boost::format("{%1%}\r\n\r\n%2%:\r\n%3%") % curItems[py]->getName() % CGI->generaltexth->translate("vcmi.lobby.filename") % curItems[py]->fullFileURI); + std::string text = boost::str(boost::format("{%1%}\r\n\r\n%2%:\r\n%3%") % curItems[py]->getNameTranslated() % CGI->generaltexth->translate("vcmi.lobby.filename") % curItems[py]->fullFileURI); if(curItems[py]->date != "") text += boost::str(boost::format("\r\n\r\n%1%:\r\n%2%") % CGI->generaltexth->translate("vcmi.lobby.creationDate") % curItems[py]->date); diff --git a/client/mainmenu/CCampaignScreen.cpp b/client/mainmenu/CCampaignScreen.cpp index 004539454..4078908e1 100644 --- a/client/mainmenu/CCampaignScreen.cpp +++ b/client/mainmenu/CCampaignScreen.cpp @@ -105,7 +105,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const status = CCampaignScreen::ENABLED; auto header = CampaignHandler::getHeader(campFile); - hoverText = header->getName(); + hoverText = header->getNameTranslated(); if(persistentStorage["completedCampaigns"][header->getFilename()].Bool()) status = CCampaignScreen::COMPLETED; diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index f3abd2d92..7e14bb8bb 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -62,8 +62,8 @@ PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId) std::string StartInfo::getCampaignName() const { - if(!campState->getName().empty()) - return campState->getName(); + if(!campState->getNameTranslated().empty()) + return campState->getNameTranslated(); else return VLC->generaltexth->allTexts[508]; } diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index 63fae7c05..ad1e9146f 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -134,7 +134,7 @@ std::string CampaignHandler::readLocalizedString(CBinaryReader & reader, std::st return ""; VLC->generaltexth->registerString(modName, stringID, input); - return VLC->generaltexth->translate(stringID.get()); + return stringID.get(); } void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader, std::string filename, std::string modName, std::string encoding) @@ -149,8 +149,8 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader ret.version = CampaignVersion::VCMI; ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]); ret.numberOfScenarios = reader["scenarios"].Vector().size(); - ret.name = reader["name"].String(); - ret.description = reader["description"].String(); + ret.name.appendTextID(reader["name"].String()); + ret.description.appendTextID(reader["description"].String()); ret.difficultyChoosenByPlayer = reader["allowDifficultySelection"].Bool(); ret.music = AudioPath::fromJson(reader["music"]); ret.filename = filename; @@ -383,8 +383,8 @@ void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader ret.version = static_cast(reader.readUInt32()); ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19] ret.loadLegacyData(campId); - ret.name = readLocalizedString(reader, filename, modName, encoding, "name"); - ret.description = readLocalizedString(reader, filename, modName, encoding, "description"); + ret.name.appendTextID(readLocalizedString(reader, filename, modName, encoding, "name")); + ret.description.appendTextID(readLocalizedString(reader, filename, modName, encoding, "description")); if (ret.version > CampaignVersion::RoE) ret.difficultyChoosenByPlayer = reader.readInt8(); else diff --git a/lib/campaign/CampaignState.cpp b/lib/campaign/CampaignState.cpp index f8bfe0271..082ddf7ee 100644 --- a/lib/campaign/CampaignState.cpp +++ b/lib/campaign/CampaignState.cpp @@ -134,14 +134,14 @@ bool CampaignHeader::formatVCMI() const return version == CampaignVersion::VCMI; } -std::string CampaignHeader::getDescription() const +std::string CampaignHeader::getDescriptionTranslated() const { - return description; + return description.toString(); } -std::string CampaignHeader::getName() const +std::string CampaignHeader::getNameTranslated() const { - return name; + return name.toString(); } std::string CampaignHeader::getFilename() const @@ -267,7 +267,7 @@ void CampaignState::setCurrentMapAsConquered(std::vector heroe return a->getHeroStrength() > b->getHeroStrength(); }); - logGlobal->info("Scenario %d of campaign %s (%s) has been completed", static_cast(*currentMap), getFilename(), getName()); + logGlobal->info("Scenario %d of campaign %s (%s) has been completed", static_cast(*currentMap), getFilename(), getNameTranslated()); mapsConquered.push_back(*currentMap); auto reservedHeroes = getReservedHeroes(); diff --git a/lib/campaign/CampaignState.h b/lib/campaign/CampaignState.h index 53b6d8e4c..fc5f1cbdf 100644 --- a/lib/campaign/CampaignState.h +++ b/lib/campaign/CampaignState.h @@ -10,6 +10,7 @@ #pragma once #include "../lib/GameConstants.h" +#include "../lib/MetaString.h" #include "../lib/filesystem/ResourcePath.h" #include "CampaignConstants.h" #include "CampaignScenarioPrologEpilog.h" @@ -74,8 +75,8 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable CampaignVersion version = CampaignVersion::NONE; CampaignRegions campaignRegions; - std::string name; - std::string description; + MetaString name; + MetaString description; AudioPath music; std::string filename; std::string modName; @@ -90,8 +91,8 @@ public: bool playerSelectedDifficulty() const; bool formatVCMI() const; - std::string getDescription() const; - std::string getName() const; + std::string getDescriptionTranslated() const; + std::string getNameTranslated() const; std::string getFilename() const; std::string getModName() const; std::string getEncoding() const; @@ -176,7 +177,7 @@ struct DLL_LINKAGE CampaignTravel struct DLL_LINKAGE CampaignScenario { std::string mapName; //*.h3m - std::string scenarioName; //from header. human-readble + MetaString scenarioName; //from header std::set preconditionRegions; //what we need to conquer to conquer this one (stored as bitfield in h3c) ui8 regionColor = 0; ui8 difficulty = 0; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index e82688891..9b2361c72 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1030,8 +1030,6 @@ si32 CGHeroInstance::manaLimit() const std::string CGHeroInstance::getNameTranslated() const { - if (!nameCustom.empty()) - return nameCustom; return VLC->generaltexth->translate(getNameTextID()); } @@ -1049,9 +1047,6 @@ std::string CGHeroInstance::getNameTextID() const std::string CGHeroInstance::getBiographyTranslated() const { - if (!biographyCustom.empty()) - return biographyCustom; - return VLC->generaltexth->translate(getBiographyTextID()); } diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 64ea09b7b..3c43b03e4 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -223,8 +223,8 @@ public: si32 height; /// The default value is 72. si32 width; /// The default value is 72. bool twoLevel; /// The default value is true. - std::string name; - std::string description; + MetaString name; + MetaString description; ui8 difficulty; /// The default value is 1 representing a normal map difficulty. /// Specifies the maximum level to reach for a hero. A value of 0 states that there is no /// maximum level for heroes. This is the default value. diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index e36ba25c2..f037ef7d4 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -100,12 +100,12 @@ void CMapInfo::countPlayers() amountOfHumanPlayersInSave++; } -std::string CMapInfo::getName() const +std::string CMapInfo::getNameTranslated() const { - if(campaign && !campaign->getName().empty()) - return campaign->getName(); - else if(mapHeader && mapHeader->name.length()) - return mapHeader->name; + if(campaign && !campaign->getNameTranslated().empty()) + return campaign->getNameTranslated(); + else if(mapHeader && !mapHeader->name.empty()) + return mapHeader->name.toString(); else return VLC->generaltexth->allTexts[508]; } @@ -121,16 +121,16 @@ std::string CMapInfo::getNameForList() const } else { - return getName(); + return getNameTranslated(); } } -std::string CMapInfo::getDescription() const +std::string CMapInfo::getDescriptionTranslated() const { if(campaign) - return campaign->getDescription(); + return campaign->getDescriptionTranslated(); else - return mapHeader->description; + return mapHeader->description.toString(); } int CMapInfo::getMapSizeIconId() const diff --git a/lib/mapping/CMapInfo.h b/lib/mapping/CMapInfo.h index 33d3874a1..6ac7ccea9 100644 --- a/lib/mapping/CMapInfo.h +++ b/lib/mapping/CMapInfo.h @@ -50,9 +50,9 @@ public: void campaignInit(); void countPlayers(); // TODO: Those must be on client-side - std::string getName() const; + std::string getNameTranslated() const; std::string getNameForList() const; - std::string getDescription() const; + std::string getDescriptionTranslated() const; int getMapSizeIconId() const; int getMapSizeFormatIconId() const; std::string getMapSizeName() const; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 9da89c494..751de6ddc 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -180,8 +180,8 @@ void CMapLoaderH3M::readHeader() mapHeader->areAnyPlayers = reader->readBool(); mapHeader->height = mapHeader->width = reader->readInt32(); mapHeader->twoLevel = reader->readBool(); - mapHeader->name = readLocalizedString("header.name"); - mapHeader->description = readLocalizedString("header.description"); + mapHeader->name.appendTextID(readLocalizedString("header.name")); + mapHeader->description.appendTextID(readLocalizedString("header.description")); mapHeader->difficulty = reader->readInt8(); if(features.levelAB) @@ -2275,7 +2275,7 @@ std::string CMapLoaderH3M::readLocalizedString(const TextIdentifier & stringIden return ""; VLC->generaltexth->registerString(modName, fullIdentifier, mapString); - return VLC->generaltexth->translate(fullIdentifier.get()); + return fullIdentifier.get(); } void CMapLoaderH3M::afterRead() diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 66de3989e..ba3c044ff 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -413,8 +413,8 @@ void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std void CMapFormatJson::serializeHeader(JsonSerializeFormat & handler) { - handler.serializeString("name", mapHeader->name); - handler.serializeString("description", mapHeader->description); + handler.serializeStruct("name", mapHeader->name); + handler.serializeStruct("description", mapHeader->description); handler.serializeInt("heroLevelLimit", mapHeader->levelLimit, 0); //todo: support arbitrary percentage diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 30dcd24fe..4a3e919d8 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -407,8 +407,8 @@ void CMapGenerator::addHeaderInfo() m.width = mapGenOptions.getWidth(); m.height = mapGenOptions.getHeight(); m.twoLevel = mapGenOptions.getHasTwoLevels(); - m.name = VLC->generaltexth->allTexts[740]; - m.description = getMapDescription(); + m.name.appendLocalString(EMetaText::GENERAL_TXT, 740); + m.description.appendRawString(getMapDescription()); m.difficulty = 1; addPlayerInfo(); m.waterMap = (mapGenOptions.getWaterContent() != EWaterContent::EWaterContent::NONE); From ab373f08abbc1747d70e2d32ed19e9662b975dee Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 27 Sep 2023 23:11:11 +0200 Subject: [PATCH 14/59] Use meta string for messages --- lib/MetaString.cpp | 2 +- lib/mapObjects/CGPandoraBox.cpp | 8 ++++---- lib/mapObjects/CGPandoraBox.h | 2 +- lib/mapObjects/MiscObjects.cpp | 30 +++++++++++++++--------------- lib/mapObjects/MiscObjects.h | 6 +++--- lib/mapping/MapFormatH3M.cpp | 6 +++--- lib/mapping/MapFormatH3M.h | 3 ++- 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/MetaString.cpp b/lib/MetaString.cpp index 3c9eac22c..93665a0e9 100644 --- a/lib/MetaString.cpp +++ b/lib/MetaString.cpp @@ -102,7 +102,7 @@ void MetaString::clear() bool MetaString::empty() const { - return message.empty(); + return message.empty() || toString().empty(); } std::string MetaString::getLocalString(const std::pair & txt) const diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index de2a00448..ece2b57bc 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -35,7 +35,7 @@ void CGPandoraBox::init() { i.reward.removeObject = true; if(!message.empty() && i.message.empty()) - i.message = MetaString::createFromRawString(message); + i.message = message; } } @@ -209,7 +209,7 @@ void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler) { CRewardableObject::serializeJsonOptions(handler); - handler.serializeString("guardMessage", message); + handler.serializeStruct("guardMessage", message); if(!handler.saving) { @@ -297,7 +297,7 @@ void CGEvent::init() { i.reward.removeObject = removeAfterVisit; if(!message.empty() && i.message.empty()) - i.message = MetaString::createFromRawString(message); + i.message = message; } } @@ -327,7 +327,7 @@ void CGEvent::activated( const CGHeroInstance * h ) const InfoWindow iw; iw.player = h->tempOwner; if(!message.empty()) - iw.text.appendRawString(message); + iw.text = message; else iw.text.appendLocalString(EMetaText::ADVOB_TXT, 16); cb->showInfoDialog(&iw); diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index 3d4cf3540..4bbd5b2af 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -19,7 +19,7 @@ struct InfoWindow; class DLL_LINKAGE CGPandoraBox : public CRewardableObject { public: - std::string message; + MetaString message; void initObj(CRandomGenerator & rand) override; void onHeroVisit(const CGHeroInstance * h) const override; diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 5b265795a..818521339 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -268,7 +268,7 @@ void CGResource::onHeroVisit( const CGHeroInstance * h ) const { BlockingDialog ynd(true,false); ynd.player = h->getOwner(); - ynd.text.appendRawString(message); + ynd.text = message; cb->showBlockingDialog(&ynd); } else @@ -288,7 +288,7 @@ void CGResource::collectRes(const PlayerColor & player) const if(!message.empty()) { sii.type = EInfoWindowMode::AUTO; - sii.text.appendRawString(message); + sii.text = message; } else { @@ -320,7 +320,7 @@ void CGResource::serializeJsonOptions(JsonSerializeFormat & handler) if(!handler.saving && !handler.getCurrent()["guards"].Vector().empty()) CCreatureSet::serializeJson(handler, "guards", 7); handler.serializeInt("amount", amount, 0); - handler.serializeString("guardMessage", message); + handler.serializeStruct("guardMessage", message); } bool CGTeleport::isEntrance() const @@ -728,8 +728,8 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const case Obj::ARTIFACT: { iw.components.emplace_back(Component::EComponentType::ARTIFACT, subID, 0, 0); - if(message.length()) - iw.text.appendRawString(message); + if(!message.empty()) + iw.text = message; else iw.text.appendLocalString(EMetaText::ART_EVNTS, subID); } @@ -738,8 +738,8 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const { int spellID = storedArtifact->getScrollSpellID(); iw.components.emplace_back(Component::EComponentType::SPELL, spellID, 0, 0); - if(message.length()) - iw.text.appendRawString(message); + if(!message.empty()) + iw.text = message; else { iw.text.appendLocalString(EMetaText::ADVOB_TXT,135); @@ -764,8 +764,8 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const { BlockingDialog ynd(true,false); ynd.player = h->getOwner(); - if(message.length()) - ynd.text.appendRawString(message); + if(!message.empty()) + ynd.text = message; else { // TODO: Guard text is more complex in H3, see mantis issue 2325 for details @@ -779,11 +779,11 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const break; case Obj::SPELL_SCROLL: { - if(message.length()) + if(!message.empty()) { BlockingDialog ynd(true,false); ynd.player = h->getOwner(); - ynd.text.appendRawString(message); + ynd.text = message; cb->showBlockingDialog(&ynd); } else @@ -828,7 +828,7 @@ void CGArtifact::afterAddToMap(CMap * map) void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler) { - handler.serializeString("guardMessage", message); + handler.serializeStruct("guardMessage", message); CArmedInstance::serializeJsonOptions(handler); if(!handler.saving && !handler.getCurrent()["guards"].Vector().empty()) CCreatureSet::serializeJson(handler, "guards", 7); @@ -1046,7 +1046,7 @@ void CGSignBottle::initObj(CRandomGenerator & rand) { auto vector = VLC->generaltexth->findStringsWithPrefix("core.randsign"); std::string messageIdentifier = *RandomGeneratorUtil::nextItem(vector, rand); - message = VLC->generaltexth->translate(messageIdentifier); + message.appendTextID(TextIdentifier("core", "randsign", messageIdentifier).get()); } if(ID == Obj::OCEAN_BOTTLE) @@ -1059,7 +1059,7 @@ void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const { InfoWindow iw; iw.player = h->getOwner(); - iw.text.appendRawString(message); + iw.text = message; cb->showInfoDialog(&iw); if(ID == Obj::OCEAN_BOTTLE) @@ -1068,7 +1068,7 @@ void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const void CGSignBottle::serializeJsonOptions(JsonSerializeFormat& handler) { - handler.serializeString("text", message); + handler.serializeStruct("text", message); } void CGScholar::onHeroVisit( const CGHeroInstance * h ) const diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index d35479513..1b4397794 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -43,7 +43,7 @@ public: class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles { public: - std::string message; + MetaString message; void onHeroVisit(const CGHeroInstance * h) const override; void initObj(CRandomGenerator & rand) override; @@ -119,7 +119,7 @@ class DLL_LINKAGE CGArtifact : public CArmedInstance { public: CArtifactInstance * storedArtifact = nullptr; - std::string message; + MetaString message; void onHeroVisit(const CGHeroInstance * h) const override; void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; @@ -149,7 +149,7 @@ public: static constexpr ui32 RANDOM_AMOUNT = 0; ui32 amount = RANDOM_AMOUNT; //0 if random - std::string message; + MetaString message; void onHeroVisit(const CGHeroInstance * h) const override; void initObj(CRandomGenerator & rand) override; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 751de6ddc..90b6b8dfc 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1135,7 +1135,7 @@ CGObjectInstance * CMapLoaderH3M::readMonster(const int3 & mapPosition, const Ob CGObjectInstance * CMapLoaderH3M::readSign(const int3 & mapPosition) { auto * object = new CGSignBottle(); - object->message = readLocalizedString(TextIdentifier("sign", mapPosition.x, mapPosition.y, mapPosition.z, "message")); + object->message.appendTextID(readLocalizedString(TextIdentifier("sign", mapPosition.x, mapPosition.y, mapPosition.z, "message"))); reader->skipZero(4); return object; } @@ -2247,12 +2247,12 @@ void CMapLoaderH3M::readEvents() } } -void CMapLoaderH3M::readMessageAndGuards(std::string & message, CCreatureSet * guards, const int3 & position) +void CMapLoaderH3M::readMessageAndGuards(MetaString & message, CCreatureSet * guards, const int3 & position) { bool hasMessage = reader->readBool(); if(hasMessage) { - message = readLocalizedString(TextIdentifier("guards", position.x, position.y, position.z, "message")); + message.appendTextID(readLocalizedString(TextIdentifier("guards", position.x, position.y, position.z, "message"))); bool hasGuards = reader->readBool(); if(hasGuards) readCreatureSet(guards, 7); diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 8b7a10a08..6a0180b54 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGHeroInstance; class MapReaderH3M; +class MetaString; class CArtifactInstance; class CGObjectInstance; class CGSeerHut; @@ -215,7 +216,7 @@ private: /** * read optional message and optional guards */ - void readMessageAndGuards(std::string & message, CCreatureSet * guards, const int3 & position); + void readMessageAndGuards(MetaString & message, CCreatureSet * guards, const int3 & position); /// reads string from input stream and converts it to unicode std::string readBasicString(); From 0ac893b80f57cc75dd1a7a73953ca1bb21c72731 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 27 Sep 2023 23:22:45 +0200 Subject: [PATCH 15/59] Rumors meta string --- client/windows/GUIClasses.cpp | 3 +-- lib/CGameInfoCallback.cpp | 19 ++++++++++--------- lib/mapping/CMap.cpp | 2 +- lib/mapping/CMap.h | 3 ++- lib/mapping/MapFormatH3M.cpp | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 419dea97f..8bf9052c1 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -456,8 +456,7 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj) heroDescription = std::make_shared("", Rect(30, 373, 233, 35), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); heroesForHire = std::make_shared(145, 283, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[38]); - auto rumorText = boost::str(boost::format(CGI->generaltexth->allTexts[216]) % LOCPLINT->cb->getTavernRumor(tavernObj)); - rumor = std::make_shared(rumorText, Rect(32, 188, 330, 66), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); + rumor = std::make_shared(LOCPLINT->cb->getTavernRumor(tavernObj), Rect(32, 188, 330, 66), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); statusbar = CGStatusBar::create(std::make_shared(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26)); cancel = std::make_shared(Point(310, 428), AnimationPath::builtin("ICANCEL.DEF"), CButton::tooltip(CGI->generaltexth->tavernInfo[7]), std::bind(&CTavernWindow::close, this), EShortcut::GLOBAL_CANCEL); diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index b50d59e9b..bc3dfebe3 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -649,33 +649,34 @@ EPlayerStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bool verbos std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTavern) const { - std::string text; + MetaString text; + text.appendLocalString(EMetaText::GENERAL_TXT, 216); + std::string extraText; if(gs->rumor.type == RumorState::TYPE_NONE) - return text; + return text.toString(); auto rumor = gs->rumor.last[gs->rumor.type]; switch(gs->rumor.type) { case RumorState::TYPE_SPECIAL: + text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first); if(rumor.first == RumorState::RUMOR_GRAIL) - extraText = VLC->generaltexth->arraytxt[158 + rumor.second]; + text.replaceTextID(TextIdentifier("core", "genrltxt", "arraytxt", 158 + rumor.second).get()); else - extraText = VLC->generaltexth->capColors[rumor.second]; - - text = boost::str(boost::format(VLC->generaltexth->allTexts[rumor.first]) % extraText); + text.replaceTextID(TextIdentifier("core", "genrltxt", "capitalColors", rumor.second).get()); break; case RumorState::TYPE_MAP: - text = gs->map->rumors[rumor.first].text; + text.replaceRawString(gs->map->rumors[rumor.first].text.toString()); break; case RumorState::TYPE_RAND: - text = VLC->generaltexth->tavernRumors[rumor.first]; + text.replaceTextID(TextIdentifier("core", "genrltxt", "randtvrn", rumor.first).get()); break; } - return text; + return text.toString(); } PlayerRelations CGameInfoCallback::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 811a52e02..71feca889 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -32,7 +32,7 @@ VCMI_LIB_NAMESPACE_BEGIN void Rumor::serializeJson(JsonSerializeFormat & handler) { handler.serializeString("name", name); - handler.serializeString("text", text); + handler.serializeStruct("text", text); } DisposedHero::DisposedHero() : heroId(0), portrait(255) diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index c3cd0bfdb..abed40d06 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -11,6 +11,7 @@ #pragma once #include "CMapHeader.h" +#include "../MetaString.h" #include "../mapObjects/MiscObjects.h" // To serialize static props #include "../mapObjects/CQuest.h" // To serialize static props #include "../mapObjects/CGTownInstance.h" // To serialize static props @@ -36,7 +37,7 @@ struct TeleportChannel; struct DLL_LINKAGE Rumor { std::string name; - std::string text; + MetaString text; Rumor() = default; ~Rumor() = default; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 90b6b8dfc..0d1e00ed5 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -807,7 +807,7 @@ void CMapLoaderH3M::readRumors() { Rumor ourRumor; ourRumor.name = readBasicString(); - ourRumor.text = readLocalizedString(TextIdentifier("header", "rumor", it, "text")); + ourRumor.text.appendTextID(readLocalizedString(TextIdentifier("header", "rumor", it, "text"))); map->rumors.push_back(ourRumor); } } From f9f79255c57ba0e78e8b6e0a4e3058b9aae4ad50 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 27 Sep 2023 23:25:19 +0200 Subject: [PATCH 16/59] Creature message meta string --- lib/mapObjects/CGCreature.cpp | 4 ++-- lib/mapObjects/CGCreature.h | 3 ++- lib/mapping/MapFormatH3M.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/mapObjects/CGCreature.cpp b/lib/mapObjects/CGCreature.cpp index a2891c2cb..21ddb93f6 100644 --- a/lib/mapObjects/CGCreature.cpp +++ b/lib/mapObjects/CGCreature.cpp @@ -108,7 +108,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const { InfoWindow iw; iw.player = h->tempOwner; - iw.text.appendRawString(message); + iw.text = message; iw.type = EInfoWindowMode::MODAL; cb->showInfoDialog(&iw); } @@ -578,7 +578,7 @@ void CGCreature::serializeJsonOptions(JsonSerializeFormat & handler) handler.serializeBool("noGrowing", notGrowingTeam); handler.serializeBool("neverFlees", neverFlees); - handler.serializeString("rewardMessage", message); + handler.serializeStruct("rewardMessage", message); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGCreature.h b/lib/mapObjects/CGCreature.h index 76df95aed..ac599465a 100644 --- a/lib/mapObjects/CGCreature.h +++ b/lib/mapObjects/CGCreature.h @@ -11,6 +11,7 @@ #include "CArmedInstance.h" #include "../ResourceSet.h" +#include "../MetaString.h" VCMI_LIB_NAMESPACE_BEGIN @@ -27,7 +28,7 @@ public: ui32 identifier; //unique code for this monster (used in missions) si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) - std::string message; //message printed for attacking hero + MetaString message; //message printed for attacking hero TResources resources; // resources given to hero that has won with monsters ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none bool neverFlees; //if true, the troops will never flee diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 0d1e00ed5..271e8e068 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1099,7 +1099,7 @@ CGObjectInstance * CMapLoaderH3M::readMonster(const int3 & mapPosition, const Ob bool hasMessage = reader->readBool(); if(hasMessage) { - object->message = readLocalizedString(TextIdentifier("monster", mapPosition.x, mapPosition.y, mapPosition.z, "message")); + object->message.appendTextID(readLocalizedString(TextIdentifier("monster", mapPosition.x, mapPosition.y, mapPosition.z, "message"))); reader->readResourses(object->resources); object->gainedArtifact = reader->readArtifact(); } From 00c8c2eb82181fc13df9b1555ce518205ac53fcc Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 27 Sep 2023 23:28:17 +0200 Subject: [PATCH 17/59] Event message meta string --- lib/mapping/CMap.cpp | 2 +- lib/mapping/CMapDefines.h | 3 ++- lib/mapping/MapFormatH3M.cpp | 4 ++-- server/CGameHandler.cpp | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 71feca889..fd9062164 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -59,7 +59,7 @@ bool CMapEvent::earlierThanOrEqual(const CMapEvent & other) const void CMapEvent::serializeJson(JsonSerializeFormat & handler) { handler.serializeString("name", name); - handler.serializeString("message", message); + handler.serializeStruct("message", message); handler.serializeInt("players", players); handler.serializeInt("humanAffected", humanAffected); handler.serializeInt("computerAffected", computerAffected); diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index c0bce891a..250e2eab4 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -13,6 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN #include "../ResourceSet.h" +#include "../MetaString.h" class TerrainType; class RiverType; @@ -33,7 +34,7 @@ public: bool earlierThanOrEqual(const CMapEvent & other) const; std::string name; - std::string message; + MetaString message; TResources resources; ui8 players; // affected players, bit field? ui8 humanAffected; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 271e8e068..828bfb3e9 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2155,7 +2155,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt { CCastleEvent event; event.name = readBasicString(); - event.message = readLocalizedString(TextIdentifier("town", position.x, position.y, position.z, "event", eventID, "description")); + event.message.appendTextID(readLocalizedString(TextIdentifier("town", position.x, position.y, position.z, "event", eventID, "description"))); reader->readResourses(event.resources); @@ -2225,7 +2225,7 @@ void CMapLoaderH3M::readEvents() { CMapEvent event; event.name = readBasicString(); - event.message = readLocalizedString(TextIdentifier("event", eventID, "description")); + event.message.appendTextID(readLocalizedString(TextIdentifier("event", eventID, "description"))); reader->readResourses(event.resources); event.players = reader->readUInt8(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 744152a9a..4d91d2378 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3216,7 +3216,7 @@ void CGameHandler::handleTimeEvents() //prepare dialog InfoWindow iw; iw.player = color; - iw.text.appendRawString(ev.message); + iw.text = ev.message; for (int i=0; i Date: Wed, 27 Sep 2023 23:49:27 +0200 Subject: [PATCH 18/59] Seerhut meta strings --- lib/mapObjects/CQuest.cpp | 54 ++++++++++++++++++------------------ lib/mapObjects/CQuest.h | 3 +- lib/mapping/MapFormatH3M.cpp | 6 ++-- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 377786a5a..bf0b3f833 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -181,20 +181,20 @@ bool CQuest::checkQuest(const CGHeroInstance * h) const void CQuest::getVisitText(MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const { - std::string text; + MetaString text; bool failRequirements = (h ? !checkQuest(h) : true); if(firstVisit) { isCustom = isCustomFirst; text = firstVisitText; - iwText.appendRawString(text); + iwText.appendRawString(text.toString()); } else if(failRequirements) { isCustom = isCustomNext; text = nextVisitText; - iwText.appendRawString(text); + iwText.appendRawString(text.toString()); } switch (missionType) { @@ -223,7 +223,7 @@ void CQuest::getVisitText(MetaString &iwText, std::vector &components case MISSION_KILL_HERO: components.emplace_back(Component::EComponentType::HERO_PORTRAIT, heroPortrait, 0, 0); if(!isCustom) - addReplacements(iwText, text); + addReplacements(iwText, text.toString()); break; case MISSION_HERO: //FIXME: portrait may not match hero, if custom portrait was set in map editor @@ -236,7 +236,7 @@ void CQuest::getVisitText(MetaString &iwText, std::vector &components components.emplace_back(stackToKill); if(!isCustom) { - addReplacements(iwText, text); + addReplacements(iwText, text.toString()); } } break; @@ -286,7 +286,7 @@ void CQuest::getVisitText(MetaString &iwText, std::vector &components case MISSION_PLAYER: components.emplace_back(Component::EComponentType::FLAG, m13489val, 0, 0); if(!isCustom) - iwText.replaceRawString(VLC->generaltexth->colors[m13489val]); + iwText.replaceLocalString(EMetaText::COLOR, m13489val); break; } } @@ -380,7 +380,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const void CQuest::getCompletionText(MetaString &iwText) const { - iwText.appendRawString(completedText); + iwText.appendRawString(completedText.toString()); switch(missionType) { case CQuest::MISSION_LEVEL: @@ -388,22 +388,22 @@ void CQuest::getCompletionText(MetaString &iwText) const iwText.replaceNumber(m13489val); break; case CQuest::MISSION_PRIMARY_STAT: - if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace + { + MetaString loot; + assert(m2stats.size() <= 4); + for (int i = 0; i < m2stats.size(); ++i) { - MetaString loot; - for (int i = 0; i < 4; ++i) + if (m2stats[i]) { - if (m2stats[i]) - { - loot.appendRawString("%d %s"); - loot.replaceNumber(m2stats[i]); - loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); - } + loot.appendRawString("%d %s"); + loot.replaceNumber(m2stats[i]); + loot.replaceRawString(VLC->generaltexth->primarySkillNames[i]); } - if (!isCustomComplete) - iwText.replaceRawString(loot.buildList()); } + if (!isCustomComplete) + iwText.replaceRawString(loot.buildList()); break; + } case CQuest::MISSION_ART: { MetaString loot; @@ -447,7 +447,7 @@ void CQuest::getCompletionText(MetaString &iwText) const case MISSION_KILL_HERO: case MISSION_KILL_CREATURE: if (!isCustomComplete) - addReplacements(iwText, completedText); + addReplacements(iwText, completedText.toString()); break; case MISSION_HERO: if (!isCustomComplete) @@ -470,9 +470,9 @@ void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fi { auto q = handler.enterStruct(fieldName); - handler.serializeString("firstVisitText", firstVisitText); - handler.serializeString("nextVisitText", nextVisitText); - handler.serializeString("completedText", completedText); + handler.serializeStruct("firstVisitText", firstVisitText); + handler.serializeStruct("nextVisitText", nextVisitText); + handler.serializeStruct("completedText", completedText); if(!handler.saving) { @@ -585,16 +585,16 @@ void CGSeerHut::initObj(CRandomGenerator & rand) std::string questName = quest->missionName(quest->missionType); if(!quest->isCustomFirst) - quest->firstVisitText = VLC->generaltexth->translate("core.seerhut.quest." + questName + "." + quest->missionState(0), quest->textOption); + quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(0), quest->textOption).get()); if(!quest->isCustomNext) - quest->nextVisitText = VLC->generaltexth->translate("core.seerhut.quest." + questName + "." + quest->missionState(1), quest->textOption); + quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(1), quest->textOption).get()); if(!quest->isCustomComplete) - quest->completedText = VLC->generaltexth->translate("core.seerhut.quest." + questName + "." + quest->missionState(2), quest->textOption); + quest->firstVisitText.appendTextID(TextIdentifier("core", "seerhut", "quest", questName, quest->missionState(2), quest->textOption).get()); } else { quest->progress = CQuest::COMPLETE; - quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->completedOption]; + quest->firstVisitText.appendTextID(TextIdentifier("core", "seehut", "empty", quest->completedOption).get()); } quest->getCompletionText(configuration.onSelect); @@ -639,7 +639,7 @@ void CQuest::addReplacements(MetaString &out, const std::string &base) const } break; case MISSION_KILL_HERO: - out.replaceRawString(heroName); + out.replaceTextID(heroName); break; } } diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 3885229dd..3f4d6b793 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -11,6 +11,7 @@ #include "CRewardableObject.h" #include "../ResourceSet.h" +#include "../MetaString.h" VCMI_LIB_NAMESPACE_BEGIN @@ -70,7 +71,7 @@ public: std::string heroName; //backup of hero name si32 heroPortrait; - std::string firstVisitText, nextVisitText, completedText; + MetaString firstVisitText, nextVisitText, completedText; bool isCustomFirst; bool isCustomNext; bool isCustomComplete; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 828bfb3e9..ba6fcaba2 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2072,9 +2072,9 @@ void CMapLoaderH3M::readQuest(IQuestObject * guard, const int3 & position) } guard->quest->lastDay = reader->readInt32(); - guard->quest->firstVisitText = readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "firstVisit")); - guard->quest->nextVisitText = readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "nextVisit")); - guard->quest->completedText = readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "completed")); + guard->quest->firstVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "firstVisit"))); + guard->quest->nextVisitText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "nextVisit"))); + guard->quest->completedText.appendTextID(readLocalizedString(TextIdentifier("quest", position.x, position.y, position.z, "completed"))); guard->quest->isCustomFirst = !guard->quest->firstVisitText.empty(); guard->quest->isCustomNext = !guard->quest->nextVisitText.empty(); guard->quest->isCustomComplete = !guard->quest->completedText.empty(); From 0c94a4d8910dbf36c2a5e1c7e47ec308bd99d24b Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 27 Sep 2023 23:57:05 +0200 Subject: [PATCH 19/59] Town name switched to id --- lib/gameState/CGameState.cpp | 2 +- lib/mapObjects/CGTownInstance.cpp | 20 ++++++++++---------- lib/mapObjects/CGTownInstance.h | 8 ++++---- lib/mapping/MapFormatH3M.cpp | 2 +- mapeditor/inspector/inspector.cpp | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index f5657821c..dd3c72a5e 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -1020,7 +1020,7 @@ void CGameState::initTowns() if(vti->getNameTranslated().empty()) { size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1); - vti->setNameTranslated(vti->getTown()->getRandomNameTranslated(nameID)); + vti->setNameTextId(vti->getTown()->getRandomNameTextID(nameID)); } static const BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 }; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index b82fe8d68..bf8b25124 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -327,7 +327,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const } else { - logGlobal->error("%s visits allied town of %s from different pos?", h->getNameTranslated(), name); + logGlobal->error("%s visits allied town of %s from different pos?", h->getNameTranslated(), nameTextId); } } @@ -337,15 +337,15 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const if(visitingHero == h) { cb->stopHeroVisitCastle(this, h); - logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), name); + logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), nameTextId); } else - logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), name); + logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), nameTextId); } std::string CGTownInstance::getObjectName() const { - return name + ", " + town->faction->getNameTranslated(); + return nameTextId + ", " + town->faction->getNameTranslated(); } bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const @@ -767,7 +767,7 @@ void CGTownInstance::updateAppearance() std::string CGTownInstance::nodeName() const { - return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + name; + return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + nameTextId; } void CGTownInstance::deserializationFix() @@ -915,12 +915,12 @@ CBonusSystemNode & CGTownInstance::whatShouldBeAttached() std::string CGTownInstance::getNameTranslated() const { - return name; + return VLC->generaltexth->translate(nameTextId); } -void CGTownInstance::setNameTranslated( const std::string & newName ) +void CGTownInstance::setNameTextId( const std::string & newName ) { - name = newName; + nameTextId = newName; } const CArmedInstance * CGTownInstance::getUpperArmy() const @@ -980,7 +980,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const return town->buildings.at(buildingID)->resources; else { - logGlobal->error("Town %s at %s has no possible building %d!", name, pos.toString(), buildingID.toEnum()); + logGlobal->error("Town %s at %s has no possible building %d!", nameTextId, pos.toString(), buildingID.toEnum()); return TResources(); } @@ -1097,7 +1097,7 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) if(!handler.saving) handler.serializeEnum("tightFormation", formation, NArmyFormation::names); //for old format CArmedInstance::serializeJsonOptions(handler); - handler.serializeString("name", name); + handler.serializeString("name", nameTextId); { auto decodeBuilding = [this](const std::string & identifier) -> si32 diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 6f47cf525..07da57644 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -44,7 +44,7 @@ struct DLL_LINKAGE GrowthInfo class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader { - std::string name; // name of town + std::string nameTextId; // name of town public: using CGDwelling::getPosition; @@ -73,7 +73,7 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & name; + h & nameTextId; h & builded; h & destroyed; h & identifier; @@ -102,7 +102,7 @@ public: { if(!town->buildings.count(building) || !town->buildings.at(building)) { - logGlobal->error("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s", name, pos.toString(), building); + logGlobal->error("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s", nameTextId, pos.toString(), building); return true; } return false; @@ -126,7 +126,7 @@ public: const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself std::string getNameTranslated() const; - void setNameTranslated(const std::string & newName); + void setNameTextId(const std::string & newName); ////////////////////////////////////////////////////////////////////////// diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index ba6fcaba2..90aeba11e 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2094,7 +2094,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt bool hasName = reader->readBool(); if(hasName) - object->setNameTranslated(readLocalizedString(TextIdentifier("town", position.x, position.y, position.z, "name"))); + object->setNameTextId(readLocalizedString(TextIdentifier("town", position.x, position.y, position.z, "name"))); bool hasGarrison = reader->readBool(); if(hasGarrison) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 9955d207b..b32b9092e 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -553,7 +553,7 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari if(!o) return; if(key == "Town name") - o->setNameTranslated(value.toString().toStdString()); + o->setNameTextId(value.toString().toStdString()); } void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value) From 486091a91580f788712a1c0ff207a8737d5b259a Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 00:00:32 +0200 Subject: [PATCH 20/59] Heroes switched to text id --- lib/mapObjects/CGHeroInstance.cpp | 12 ++++++------ lib/mapObjects/CGHeroInstance.h | 8 ++++---- lib/mapping/MapFormatH3M.cpp | 8 ++++---- lib/mapping/MapFormatJson.cpp | 2 +- mapeditor/inspector/inspector.cpp | 6 +++--- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 9b2361c72..f7735e053 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1035,8 +1035,8 @@ std::string CGHeroInstance::getNameTranslated() const std::string CGHeroInstance::getNameTextID() const { - if (!nameCustom.empty()) - return nameCustom; + if (!nameCustomTextId.empty()) + return nameCustomTextId; if (type) return type->getNameTextID(); @@ -1052,8 +1052,8 @@ std::string CGHeroInstance::getBiographyTranslated() const std::string CGHeroInstance::getBiographyTextID() const { - if (!biographyCustom.empty()) - return biographyCustom; + if (!biographyCustomTextId.empty()) + return biographyCustomTextId; if (type) return type->getBiographyTextID(); @@ -1515,7 +1515,7 @@ void CGHeroInstance::updateFrom(const JsonNode & data) void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) { - handler.serializeString("biography", biographyCustom); + handler.serializeString("biography", biographyCustomTextId); handler.serializeInt("experience", exp, 0); if(!handler.saving && exp != UNINITIALIZED_EXPERIENCE) //do not gain levels if experience is not initialized @@ -1526,7 +1526,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) } } - handler.serializeString("name", nameCustom); + handler.serializeString("name", nameCustomTextId); handler.serializeInt("gender", gender, 0); { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index c8aaad4fa..43a600e15 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -74,8 +74,8 @@ public: std::vector > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities EHeroGender gender; - std::string nameCustom; - std::string biographyCustom; + std::string nameCustomTextId; + std::string biographyCustomTextId; bool inTownGarrison; // if hero is in town garrison ConstTransitivePtr visitedTown; //set if hero is visiting town or in the town garrison @@ -319,8 +319,8 @@ public: h & static_cast(*this); h & exp; h & level; - h & nameCustom; - h & biographyCustom; + h & nameCustomTextId; + h & biographyCustomTextId; h & portrait; h & mana; h & secSkills; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 90aeba11e..3cbbad65d 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -860,7 +860,7 @@ void CMapLoaderH3M::readPredefinedHeroes() bool hasCustomBio = reader->readBool(); if(hasCustomBio) - hero->biographyCustom = readLocalizedString(TextIdentifier("heroes", heroID, "biography")); + hero->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", heroID, "biography")); // 0xFF is default, 00 male, 01 female hero->gender = static_cast(reader->readUInt8()); @@ -1685,7 +1685,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec { if(elem.heroId.getNum() == object->subID) { - object->nameCustom = elem.name; + object->nameCustomTextId = elem.name; object->portrait = elem.portrait; break; } @@ -1693,7 +1693,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec bool hasName = reader->readBool(); if(hasName) - object->nameCustom = readLocalizedString(TextIdentifier("heroes", object->subID, "name")); + object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->subID, "name")); if(features.levelSOD) { @@ -1748,7 +1748,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec { bool hasCustomBiography = reader->readBool(); if(hasCustomBiography) - object->biographyCustom = readLocalizedString(TextIdentifier("heroes", object->subID, "biography")); + object->biographyCustomTextId = readLocalizedString(TextIdentifier("heroes", object->subID, "biography")); object->gender = static_cast(reader->readUInt8()); assert(object->gender == EHeroGender::MALE || object->gender == EHeroGender::FEMALE || object->gender == EHeroGender::DEFAULT); diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index ba3c044ff..52e981733 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -529,7 +529,7 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) if(hero) { auto heroData = handler.enterStruct(hero->instanceName); - heroData->serializeString("name", hero->nameCustom); + heroData->serializeString("name", hero->nameCustomTextId); if(hero->ID == Obj::HERO) { diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index b32b9092e..8acaa91ae 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -277,8 +277,8 @@ void Inspector::updateProperties(CGHeroInstance * o) delegate->options = {{"MALE", QVariant::fromValue(int(EHeroGender::MALE))}, {"FEMALE", QVariant::fromValue(int(EHeroGender::FEMALE))}}; addProperty("Gender", (o->gender == EHeroGender::FEMALE ? "FEMALE" : "MALE"), delegate , false); } - addProperty("Name", o->nameCustom, false); - addProperty("Biography", o->biographyCustom, new MessageDelegate, false); + addProperty("Name", o->nameCustomTextId, false); + addProperty("Biography", o->biographyCustomTextId, new MessageDelegate, false); addProperty("Portrait", o->portrait, false); auto * delegate = new HeroSkillsDelegate(*o); @@ -606,7 +606,7 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->gender = EHeroGender(value.toInt()); if(key == "Name") - o->nameCustom = value.toString().toStdString(); + o->nameCustomTextId = value.toString().toStdString(); if(key == "Experience") o->exp = value.toString().toInt(); From 5b97c323d3c833309ea053f58cbaa8f89db1a129 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 00:04:05 +0200 Subject: [PATCH 21/59] Rename hero strings to text id --- client/lobby/OptionsTab.cpp | 8 ++++---- lib/StartInfo.h | 4 ++-- lib/mapping/CMapHeader.cpp | 2 +- lib/mapping/CMapHeader.h | 4 ++-- lib/mapping/MapFormatH3M.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 2 +- server/CVCMIServer.cpp | 2 +- test/game/CGameStateTest.cpp | 2 +- test/map/MapComparer.cpp | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index cfa94763c..16541525c 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -405,8 +405,8 @@ std::string OptionsTab::CPlayerSettingsHelper::getName() return CGI->generaltexth->allTexts[522]; default: { - if(!playerSettings.heroName.empty()) - return playerSettings.heroName; + if(!playerSettings.heroNameTextId.empty()) + return playerSettings.heroNameTextId; auto index = playerSettings.hero.getNum() >= CGI->heroh->size() ? 0 : playerSettings.hero.getNum(); return (*CGI->heroh)[index]->getNameTranslated(); } @@ -927,7 +927,7 @@ void OptionsTab::SelectionWindow::setElement(int elem, bool doApply) if(!doApply) { CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO); - if(settings["general"]["enableUiEnhancements"].Bool() && helper.playerSettings.hero.getNum() > PlayerSettings::RANDOM && helper.playerSettings.heroName.empty()) + if(settings["general"]["enableUiEnhancements"].Bool() && helper.playerSettings.hero.getNum() > PlayerSettings::RANDOM && helper.playerSettings.heroNameTextId.empty()) GH.windows().createAndPushWindow(helper.playerSettings.hero); else GH.windows().createAndPushWindow(helper); @@ -1013,7 +1013,7 @@ void OptionsTab::SelectedBox::showPopupWindow(const Point & cursorPosition) if(playerSettings.hero.getNum() == PlayerSettings::NONE && !SEL->getPlayerInfo(playerSettings.color.getNum()).hasCustomMainHero() && CPlayerSettingsHelper::type == HERO) return; - if(settings["general"]["enableUiEnhancements"].Bool() && CPlayerSettingsHelper::type == HERO && playerSettings.hero.getNum() > PlayerSettings::RANDOM && playerSettings.heroName.empty()) + if(settings["general"]["enableUiEnhancements"].Bool() && CPlayerSettingsHelper::type == HERO && playerSettings.hero.getNum() > PlayerSettings::RANDOM && playerSettings.heroNameTextId.empty()) GH.windows().createAndPushWindow(playerSettings.hero); else GH.windows().createAndPushWindow(*this); diff --git a/lib/StartInfo.h b/lib/StartInfo.h index a558c4d32..5c4797acc 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -59,7 +59,7 @@ struct DLL_LINKAGE PlayerSettings HeroTypeID hero; HeroTypeID heroPortrait; //-1 if default, else ID - std::string heroName; + std::string heroNameTextId; PlayerColor color; //from 0 - enum EHandicap {NO_HANDICAP, MILD, SEVERE}; EHandicap handicap;//0-no, 1-mild, 2-severe @@ -73,7 +73,7 @@ struct DLL_LINKAGE PlayerSettings h & castle; h & hero; h & heroPortrait; - h & heroName; + h & heroNameTextId; h & bonus; h & color; h & handicap; diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 98a8afdb6..9d7cd718a 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -62,7 +62,7 @@ bool PlayerInfo::canAnyonePlay() const bool PlayerInfo::hasCustomMainHero() const { - return !mainCustomHeroName.empty() && mainCustomHeroPortrait != -1; + return !mainCustomHeroNameTextId.empty() && mainCustomHeroPortrait != -1; } EventCondition::EventCondition(EWinLoseType condition): diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 3c43b03e4..9866e095d 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -63,7 +63,7 @@ struct DLL_LINKAGE PlayerInfo bool hasRandomHero; /// The default value is -1. si32 mainCustomHeroPortrait; - std::string mainCustomHeroName; + std::string mainCustomHeroNameTextId; /// ID of custom hero (only if portrait and hero name are set, otherwise unpredicted value), -1 if none (not always -1) si32 mainCustomHeroId; @@ -84,7 +84,7 @@ struct DLL_LINKAGE PlayerInfo h & allowedFactions; h & isFactionRandom; h & mainCustomHeroPortrait; - h & mainCustomHeroName; + h & mainCustomHeroNameTextId; h & heroesNames; h & hasMainTown; h & generateHeroAtMainTown; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 3cbbad65d..a9e42b9db 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -253,7 +253,7 @@ void CMapLoaderH3M::readPlayerInfo() if(playerInfo.mainCustomHeroId != -1) { playerInfo.mainCustomHeroPortrait = reader->readHeroPortrait(); - playerInfo.mainCustomHeroName = readLocalizedString(TextIdentifier("header", "player", i, "mainHeroName")); + playerInfo.mainCustomHeroNameTextId = readLocalizedString(TextIdentifier("header", "player", i, "mainHeroName")); } if(features.levelAB) diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 52e981733..b6da8d13b 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -571,7 +571,7 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) if(instanceName == info.mainHeroInstance) { //this is main hero - info.mainCustomHeroName = hname.heroName; + info.mainCustomHeroNameTextId = hname.heroName; info.hasRandomHero = (hname.heroId == -1); info.mainCustomHeroId = hname.heroId; info.mainCustomHeroPortrait = -1; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 5b3eecdef..11e934ee5 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -730,7 +730,7 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr mapInfo, if(pset.hero.getNum() != PlayerSettings::RANDOM && pinfo.hasCustomMainHero()) { pset.hero = pinfo.mainCustomHeroId; - pset.heroName = pinfo.mainCustomHeroName; + pset.heroNameTextId = pinfo.mainCustomHeroNameTextId; pset.heroPortrait = pinfo.mainCustomHeroPortrait; } diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index 2b6498943..e571d2d25 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -170,7 +170,7 @@ public: if(pset.hero.getNum() != PlayerSettings::RANDOM && pinfo.hasCustomMainHero()) { pset.hero = pinfo.mainCustomHeroId; - pset.heroName = pinfo.mainCustomHeroName; + pset.heroNameTextId = pinfo.mainCustomHeroNameTextId; pset.heroPortrait = pinfo.mainCustomHeroPortrait; } diff --git a/test/map/MapComparer.cpp b/test/map/MapComparer.cpp index cfc908ead..62ffcabfb 100644 --- a/test/map/MapComparer.cpp +++ b/test/map/MapComparer.cpp @@ -76,7 +76,7 @@ void checkEqual(const PlayerInfo & actual, const PlayerInfo & expected) VCMI_CHECK_FIELD_EQUAL(isFactionRandom); VCMI_CHECK_FIELD_EQUAL(mainCustomHeroPortrait); - VCMI_CHECK_FIELD_EQUAL(mainCustomHeroName); + VCMI_CHECK_FIELD_EQUAL(mainCustomHeroNameTextId); VCMI_CHECK_FIELD_EQUAL(mainCustomHeroId); From 6da605ff831fe109f88ea04ec58a71528bc75cd7 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 00:10:28 +0200 Subject: [PATCH 22/59] Campaign meta strings --- client/lobby/CBonusSelection.cpp | 6 +++--- client/mainmenu/CPrologEpilogVideo.cpp | 2 +- lib/campaign/CampaignHandler.cpp | 8 ++++---- lib/campaign/CampaignScenarioPrologEpilog.h | 3 ++- lib/campaign/CampaignState.h | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/client/lobby/CBonusSelection.cpp b/client/lobby/CBonusSelection.cpp index e63167a0c..4237cdb90 100644 --- a/client/lobby/CBonusSelection.cpp +++ b/client/lobby/CBonusSelection.cpp @@ -509,9 +509,9 @@ void CBonusSelection::CRegion::clickReleased(const Point & cursorPosition) void CBonusSelection::CRegion::showPopupWindow(const Point & cursorPosition) { // FIXME: For some reason "down" is only ever contain indeterminate_value - auto text = CSH->si->campState->scenario(idOfMapAndRegion).regionText; - if(!graphicsNotSelected->getSurface()->isTransparent(cursorPosition - pos.topLeft()) && text.size()) + auto & text = CSH->si->campState->scenario(idOfMapAndRegion).regionText; + if(!graphicsNotSelected->getSurface()->isTransparent(cursorPosition - pos.topLeft()) && !text.empty()) { - CRClickPopup::createAndPush(text); + CRClickPopup::createAndPush(text.toString()); } } diff --git a/client/mainmenu/CPrologEpilogVideo.cpp b/client/mainmenu/CPrologEpilogVideo.cpp index b37383c03..33231d4da 100644 --- a/client/mainmenu/CPrologEpilogVideo.cpp +++ b/client/mainmenu/CPrologEpilogVideo.cpp @@ -36,7 +36,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f }; CCS->soundh->setCallback(voiceSoundHandle, onVoiceStop); - text = std::make_shared(Rect(100, 500, 600, 100), EFonts::FONT_BIG, ETextAlignment::CENTER, Colors::METALLIC_GOLD, spe.prologText); + text = std::make_shared(Rect(100, 500, 600, 100), EFonts::FONT_BIG, ETextAlignment::CENTER, Colors::METALLIC_GOLD, spe.prologText.toString()); text->scrollTextTo(-100); } diff --git a/lib/campaign/CampaignHandler.cpp b/lib/campaign/CampaignHandler.cpp index ad1e9146f..2efc2fac2 100644 --- a/lib/campaign/CampaignHandler.cpp +++ b/lib/campaign/CampaignHandler.cpp @@ -169,7 +169,7 @@ CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader) ret.prologVideo = VideoPath::fromJson(identifier["video"]); ret.prologMusic = AudioPath::fromJson(identifier["music"]); ret.prologVoice = AudioPath::fromJson(identifier["voice"]); - ret.prologText = identifier["text"].String(); + ret.prologText.jsonDeserialize(identifier["text"]); } return ret; }; @@ -181,7 +181,7 @@ CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader) ret.regionColor = reader["color"].Integer(); ret.difficulty = reader["difficulty"].Integer(); - ret.regionText = reader["regionText"].String(); + ret.regionText.jsonDeserialize(reader["regionText"]); ret.prolog = prologEpilogReader(reader["prolog"]); ret.epilog = prologEpilogReader(reader["epilog"]); @@ -410,7 +410,7 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader ret.prologVideo = CampaignHandler::prologVideoName(index); ret.prologMusic = CampaignHandler::prologMusicName(reader.readUInt8()); ret.prologVoice = isOriginalCampaign ? CampaignHandler::prologVoiceName(index) : AudioPath(); - ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier); + ret.prologText.appendTextID(readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier)); } return ret; }; @@ -428,7 +428,7 @@ CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader } ret.regionColor = reader.readUInt8(); ret.difficulty = reader.readUInt8(); - ret.regionText = readLocalizedString(reader, header.filename, header.modName, header.encoding, ret.mapName + ".region"); + ret.regionText.appendTextID(readLocalizedString(reader, header.filename, header.modName, header.encoding, ret.mapName + ".region")); ret.prolog = prologEpilogReader(ret.mapName + ".prolog"); ret.epilog = prologEpilogReader(ret.mapName + ".epilog"); diff --git a/lib/campaign/CampaignScenarioPrologEpilog.h b/lib/campaign/CampaignScenarioPrologEpilog.h index ab67cc584..64611be70 100644 --- a/lib/campaign/CampaignScenarioPrologEpilog.h +++ b/lib/campaign/CampaignScenarioPrologEpilog.h @@ -10,6 +10,7 @@ #pragma once #include "../filesystem/ResourcePath.h" +#include "../MetaString.h" VCMI_LIB_NAMESPACE_BEGIN @@ -19,7 +20,7 @@ struct DLL_LINKAGE CampaignScenarioPrologEpilog VideoPath prologVideo; AudioPath prologMusic; // from CmpMusic.txt AudioPath prologVoice; - std::string prologText; + MetaString prologText; template void serialize(Handler &h, const int formatVersion) { diff --git a/lib/campaign/CampaignState.h b/lib/campaign/CampaignState.h index fc5f1cbdf..cbe133467 100644 --- a/lib/campaign/CampaignState.h +++ b/lib/campaign/CampaignState.h @@ -182,7 +182,7 @@ struct DLL_LINKAGE CampaignScenario ui8 regionColor = 0; ui8 difficulty = 0; - std::string regionText; + MetaString regionText; CampaignScenarioPrologEpilog prolog; CampaignScenarioPrologEpilog epilog; From 3c2549d905460264af856a93cdcf9f26bf854c2f Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 02:51:44 +0200 Subject: [PATCH 23/59] Fix town name --- lib/mapObjects/CGTownInstance.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index bf8b25124..7c9d97a7b 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -327,7 +327,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const } else { - logGlobal->error("%s visits allied town of %s from different pos?", h->getNameTranslated(), nameTextId); + logGlobal->error("%s visits allied town of %s from different pos?", h->getNameTranslated(), getNameTranslated()); } } @@ -337,15 +337,15 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const if(visitingHero == h) { cb->stopHeroVisitCastle(this, h); - logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), nameTextId); + logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), getNameTranslated()); } else - logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), nameTextId); + logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), getNameTranslated()); } std::string CGTownInstance::getObjectName() const { - return nameTextId + ", " + town->faction->getNameTranslated(); + return getNameTranslated() + ", " + town->faction->getNameTranslated(); } bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const @@ -767,7 +767,7 @@ void CGTownInstance::updateAppearance() std::string CGTownInstance::nodeName() const { - return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + nameTextId; + return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + getNameTranslated(); } void CGTownInstance::deserializationFix() @@ -980,7 +980,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const return town->buildings.at(buildingID)->resources; else { - logGlobal->error("Town %s at %s has no possible building %d!", nameTextId, pos.toString(), buildingID.toEnum()); + logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), pos.toString(), buildingID.toEnum()); return TResources(); } From 40af83a55cde1877f8648cf3985c6592b8aa8c06 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 02:51:58 +0200 Subject: [PATCH 24/59] Fix quest --- lib/mapObjects/CQuest.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index bf0b3f833..bb0f69186 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -632,10 +632,13 @@ void CQuest::addReplacements(MetaString &out, const std::string &base) const switch(missionType) { case MISSION_KILL_CREATURE: - out.replaceCreatureName(stackToKill); - if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster + if(stackToKill.type) { - out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]); + out.replaceCreatureName(stackToKill); + if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster + { + out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]); + } } break; case MISSION_KILL_HERO: From 909812668478452a4d6654f1b08e64863ef19f92 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 02:52:49 +0200 Subject: [PATCH 25/59] Separate map text identifiers from global --- lib/CGeneralTextHandler.cpp | 115 ++++++++++++++++++++--------------- lib/CGeneralTextHandler.h | 81 ++++++++++++++++++------ lib/mapping/CMapHeader.cpp | 6 ++ lib/mapping/CMapHeader.h | 6 +- lib/mapping/MapFormatH3M.cpp | 2 +- 5 files changed, 141 insertions(+), 69 deletions(-) diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 3e54d7cf7..6188a7941 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -247,22 +247,38 @@ bool CLegacyConfigParser::endLine() return curr < end; } -void CGeneralTextHandler::readToVector(const std::string & sourceID, const std::string & sourceName) +void TextLocalizationContainer::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) { - CLegacyConfigParser parser(TextPath::builtin(sourceName)); - size_t index = 0; - do - { - registerString( "core", {sourceID, index}, parser.readString()); - index += 1; - } - while (parser.endLine()); + assert(!modContext.empty()); + assert(!language.empty()); + + // NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment + auto & entry = stringsLocalizations[UID.get()]; + + entry.overrideLanguage = language; + entry.overrideValue = localized; + if (entry.modContext.empty()) + entry.modContext = modContext; } -const std::string & CGeneralTextHandler::deserialize(const TextIdentifier & identifier) const +void TextLocalizationContainer::addSubContainer(const TextLocalizationContainer & container) +{ + subContainers.insert(&container); +} + +void TextLocalizationContainer::removeSubContainer(const TextLocalizationContainer & container) +{ + subContainers.erase(&container); +} + +const std::string & TextLocalizationContainer::deserialize(const TextIdentifier & identifier) const { if(stringsLocalizations.count(identifier.get()) == 0) { + for(const auto * container : subContainers) + if(container->identifierExists(identifier)) + return container->deserialize(identifier); + logGlobal->error("Unable to find localization for string '%s'", identifier.get()); return identifier.get(); } @@ -274,7 +290,7 @@ const std::string & CGeneralTextHandler::deserialize(const TextIdentifier & iden return entry.baseValue; } -void CGeneralTextHandler::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) +void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) { assert(!modContext.empty()); assert(!getModLanguage(modContext).empty()); @@ -307,21 +323,7 @@ void CGeneralTextHandler::registerString(const std::string & modContext, const T } } -void CGeneralTextHandler::registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized) -{ - assert(!modContext.empty()); - assert(!language.empty()); - - // NOTE: implicitly creates entry, intended - strings added by maps, campaigns, vcmi and potentially - UI mods are not registered anywhere at the moment - auto & entry = stringsLocalizations[UID.get()]; - - entry.overrideLanguage = language; - entry.overrideValue = localized; - if (entry.modContext.empty()) - entry.modContext = modContext; -} - -bool CGeneralTextHandler::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const +bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const { bool allPresent = true; @@ -372,12 +374,50 @@ bool CGeneralTextHandler::validateTranslation(const std::string & language, cons return allPresent && allFound; } -void CGeneralTextHandler::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config) +void TextLocalizationContainer::loadTranslationOverrides(const std::string & language, const std::string & modContext, const JsonNode & config) { for(const auto & node : config.Struct()) registerStringOverride(modContext, language, node.first, node.second.String()); } +bool TextLocalizationContainer::identifierExists(const TextIdentifier & UID) const +{ + return stringsLocalizations.count(UID.get()); +} + +void TextLocalizationContainer::dumpAllTexts() +{ + logGlobal->info("BEGIN TEXT EXPORT"); + for(const auto & entry : stringsLocalizations) + { + if (!entry.second.overrideValue.empty()) + logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.overrideValue)); + else + logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.baseValue)); + } + + logGlobal->info("END TEXT EXPORT"); +} + +std::string TextLocalizationContainer::getModLanguage(const std::string & modContext) +{ + if (modContext == "core") + return CGeneralTextHandler::getInstalledLanguage(); + return VLC->modh->getModLanguage(modContext); +} + +void CGeneralTextHandler::readToVector(const std::string & sourceID, const std::string & sourceName) +{ + CLegacyConfigParser parser(TextPath::builtin(sourceName)); + size_t index = 0; + do + { + registerString( "core", {sourceID, index}, parser.readString()); + index += 1; + } + while (parser.endLine()); +} + CGeneralTextHandler::CGeneralTextHandler(): victoryConditions(*this, "core.vcdesc" ), lossCondtions (*this, "core.lcdesc" ), @@ -591,20 +631,6 @@ int32_t CGeneralTextHandler::pluralText(const int32_t textIndex, const int32_t c return textIndex + 1; } -void CGeneralTextHandler::dumpAllTexts() -{ - logGlobal->info("BEGIN TEXT EXPORT"); - for(const auto & entry : stringsLocalizations) - { - if (!entry.second.overrideValue.empty()) - logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.overrideValue)); - else - logGlobal->info(R"("%s" : "%s",)", entry.first, TextOperations::escapeString(entry.second.baseValue)); - } - - logGlobal->info("END TEXT EXPORT"); -} - size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const { assert(campaignID < scenariosCountPerCampaign.size()); @@ -614,13 +640,6 @@ size_t CGeneralTextHandler::getCampaignLength(size_t campaignID) const return 0; } -std::string CGeneralTextHandler::getModLanguage(const std::string & modContext) -{ - if (modContext == "core") - return getInstalledLanguage(); - return VLC->modh->getModLanguage(modContext); -} - std::string CGeneralTextHandler::getPreferredLanguage() { assert(!settings["general"]["language"].String().empty()); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 6bbbc98c1..82b68e66b 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -113,9 +113,9 @@ public: {} }; -/// Handles all text-related data in game -class DLL_LINKAGE CGeneralTextHandler +class DLL_LINKAGE TextLocalizationContainer { +protected: struct StringState { /// Human-readable string that was added on registration @@ -132,21 +132,26 @@ class DLL_LINKAGE CGeneralTextHandler /// ID of mod that created this string std::string modContext; + + template + void serialize(Handler & h, const int Version) + { + h & baseValue; + h & baseLanguage; + h & modContext; + } }; - + /// map identifier -> localization std::unordered_map stringsLocalizations; - - void readToVector(const std::string & sourceID, const std::string & sourceName); - - /// number of scenarios in specific campaign. TODO: move to a better location - std::vector scenariosCountPerCampaign; - - std::string getModLanguage(const std::string & modContext); - + + std::set subContainers; + /// add selected string to internal storage as high-priority strings void registerStringOverride(const std::string & modContext, const std::string & language, const TextIdentifier & UID, const std::string & localized); - + + std::string getModLanguage(const std::string & modContext); + public: /// validates translation of specified language for specified mod /// returns true if localization is valid and complete @@ -157,13 +162,12 @@ public: /// Any entries loaded by this will have priority over texts registered normally void loadTranslationOverrides(const std::string & language, const std::string & modContext, JsonNode const & file); + // returns true if identifier with such name was registered, even if not translated to current language + bool identifierExists(const TextIdentifier & UID) const; + /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); - - // returns true if identifier with such name was registered, even if not translated to current language - // not required right now, can be added if necessary - // bool identifierExists( const std::string identifier) const; - + /// returns translated version of a string that can be displayed to user template std::string translate(std::string arg1, Args ... args) const @@ -174,10 +178,51 @@ public: /// converts identifier into user-readable string const std::string & deserialize(const TextIdentifier & identifier) const; - + /// Debug method, dumps all currently known texts into console using Json-like format void dumpAllTexts(); + + /// Add or override subcontainer which can store identifiers + void addSubContainer(const TextLocalizationContainer & container); + + /// Remove subcontainer with give name + void removeSubContainer(const TextLocalizationContainer & container); + + template + void serialize(Handler & h, const int Version) + { + std::string key; + auto sz = stringsLocalizations.size(); + h & sz; + if(h.saving) + { + for(auto s : stringsLocalizations) + { + key = s.first; + h & key; + h & s.second; + } + } + else + { + for(size_t i = 0; i < sz; ++i) + { + h & key; + h & stringsLocalizations[key]; + } + } + } +}; +/// Handles all text-related data in game +class DLL_LINKAGE CGeneralTextHandler: public TextLocalizationContainer +{ + void readToVector(const std::string & sourceID, const std::string & sourceName); + + /// number of scenarios in specific campaign. TODO: move to a better location + std::vector scenariosCountPerCampaign; + +public: LegacyTextContainer allTexts; LegacyTextContainer arraytxt; diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 9d7cd718a..345974bc2 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -127,6 +127,12 @@ CMapHeader::CMapHeader() : version(EMapFormat::VCMI), height(72), width(72), setupEvents(); allowedHeroes = VLC->heroh->getDefaultAllowed(); players.resize(PlayerColor::PLAYER_LIMIT_I); + VLC->generaltexth->addSubContainer(*this); +} + +CMapHeader::~CMapHeader() +{ + VLC->generaltexth->removeSubContainer(*this); } ui8 CMapHeader::levels() const diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 9866e095d..5250d575b 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -14,6 +14,7 @@ #include "../LogicalExpression.h" #include "../int3.h" #include "../MetaString.h" +#include "../CGeneralTextHandler.h" VCMI_LIB_NAMESPACE_BEGIN @@ -199,7 +200,7 @@ struct DLL_LINKAGE TriggeredEvent }; /// The map header holds information about loss/victory condition,map format, version, players, height, width,... -class DLL_LINKAGE CMapHeader +class DLL_LINKAGE CMapHeader: public TextLocalizationContainer { void setupEvents(); public: @@ -213,7 +214,7 @@ public: static const int MAP_SIZE_GIANT = 252; CMapHeader(); - virtual ~CMapHeader() = default; + virtual ~CMapHeader(); ui8 levels() const; @@ -248,6 +249,7 @@ public: template void serialize(Handler & h, const int Version) { + h & static_cast(*this); h & version; h & mods; h & name; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index a9e42b9db..5b44974ef 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2274,7 +2274,7 @@ std::string CMapLoaderH3M::readLocalizedString(const TextIdentifier & stringIden if(mapString.empty()) return ""; - VLC->generaltexth->registerString(modName, fullIdentifier, mapString); + mapHeader->registerString(modName, fullIdentifier, mapString); return fullIdentifier.get(); } From 65f696b01896e40108ea50c5b4ecf4fd8a5a3bb5 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 03:02:46 +0200 Subject: [PATCH 26/59] Cosmetic changes for json map reader --- lib/mapping/MapFormatJson.cpp | 13 +++++++------ lib/mapping/MapFormatJson.h | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index b6da8d13b..6f739eb1c 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -346,6 +346,7 @@ const int CMapFormatJson::VERSION_MINOR = 3; const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json"; +const std::string CMapFormatJson::TERRAIN_FILE_NAMES[2] = {"surface_terrain.json", "underground_terrain.json"}; CMapFormatJson::CMapFormatJson(): fileVersionMajor(0), fileVersionMinor(0), @@ -424,10 +425,10 @@ void CMapFormatJson::serializeHeader(JsonSerializeFormat & handler) handler.serializeLIC("allowedHeroes", &HeroTypeID::decode, &HeroTypeID::encode, VLC->heroh->getDefaultAllowed(), mapHeader->allowedHeroes); -// handler.serializeString("victoryString", mapHeader->victoryMessage); + handler.serializeStruct("victoryMessage", mapHeader->victoryMessage); handler.serializeInt("victoryIconIndex", mapHeader->victoryIconIndex); -// handler.serializeString("defeatString", mapHeader->defeatMessage); + handler.serializeStruct("defeatMessage", mapHeader->defeatMessage); handler.serializeInt("defeatIconIndex", mapHeader->defeatIconIndex); } @@ -1122,12 +1123,12 @@ void CMapLoaderJson::readTerrainLevel(const JsonNode & src, const int index) void CMapLoaderJson::readTerrain() { { - const JsonNode surface = getFromArchive("surface_terrain.json"); + const JsonNode surface = getFromArchive(TERRAIN_FILE_NAMES[0]); readTerrainLevel(surface, 0); } if(map->twoLevel) { - const JsonNode underground = getFromArchive("underground_terrain.json"); + const JsonNode underground = getFromArchive(TERRAIN_FILE_NAMES[1]); readTerrainLevel(underground, 1); } @@ -1388,12 +1389,12 @@ void CMapSaverJson::writeTerrain() //todo: multilevel map save support JsonNode surface = writeTerrainLevel(0); - addToArchive(surface, "surface_terrain.json"); + addToArchive(surface, TERRAIN_FILE_NAMES[0]); if(map->twoLevel) { JsonNode underground = writeTerrainLevel(1); - addToArchive(underground, "underground_terrain.json"); + addToArchive(underground, TERRAIN_FILE_NAMES[1]); } } diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index d23d06be0..af89f89eb 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -42,6 +42,7 @@ public: static const std::string HEADER_FILE_NAME; static const std::string OBJECTS_FILE_NAME; + static const std::string TERRAIN_FILE_NAMES[2]; int fileVersionMajor; int fileVersionMinor; From ba1dbbbb1d6c6c1d411b0f179f47c90895bff90d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 04:23:14 +0200 Subject: [PATCH 27/59] New version for map format --- lib/CGeneralTextHandler.cpp | 25 +++++++++++--- lib/CGeneralTextHandler.h | 4 +++ lib/mapping/CMapHeader.h | 3 ++ lib/mapping/MapFormatJson.cpp | 65 +++++++++++++++++++++++++++++++++-- lib/mapping/MapFormatJson.h | 11 ++++++ 5 files changed, 101 insertions(+), 7 deletions(-) diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 6188a7941..360b69157 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -11,6 +11,7 @@ #include "CGeneralTextHandler.h" #include "filesystem/Filesystem.h" +#include "serializer/JsonSerializeFormat.h" #include "CConfigHandler.h" #include "GameSettings.h" #include "mapObjects/CQuest.h" @@ -290,10 +291,10 @@ const std::string & TextLocalizationContainer::deserialize(const TextIdentifier return entry.baseValue; } -void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) +void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language) { assert(!modContext.empty()); - assert(!getModLanguage(modContext).empty()); + assert(!Languages::getLanguageOptions(language).identifier.empty()); assert(UID.get().find("..") == std::string::npos); // invalid identifier - there is section that was evaluated to empty string //assert(stringsLocalizations.count(UID.get()) == 0); // registering already registered string? @@ -303,7 +304,7 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c if(value.baseLanguage.empty()) { - value.baseLanguage = getModLanguage(modContext); + value.baseLanguage = language; value.baseValue = localized; } else @@ -315,7 +316,7 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c else { StringState result; - result.baseLanguage = getModLanguage(modContext); + result.baseLanguage = language; result.baseValue = localized; result.modContext = modContext; @@ -323,6 +324,12 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c } } +void TextLocalizationContainer::registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized) +{ + assert(!getModLanguage(modContext).empty()); + registerString(modContext, UID, localized, getModLanguage(modContext)); +} + bool TextLocalizationContainer::validateTranslation(const std::string & language, const std::string & modContext, const JsonNode & config) const { bool allPresent = true; @@ -406,6 +413,16 @@ std::string TextLocalizationContainer::getModLanguage(const std::string & modCon return VLC->modh->getModLanguage(modContext); } +void TextLocalizationContainer::jsonSerialize(JsonNode & dest) const +{ + for(auto & s : stringsLocalizations) + { + dest.Struct()[s.first].String() = s.second.baseValue; + if(!s.second.overrideValue.empty()) + dest.Struct()[s.first].String() = s.second.overrideValue; + } +} + void CGeneralTextHandler::readToVector(const std::string & sourceID, const std::string & sourceName) { CLegacyConfigParser parser(TextPath::builtin(sourceName)); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 82b68e66b..3807ddc7a 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -15,6 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CInputStream; class JsonNode; +class JsonSerializeFormat; /// Parser for any text files from H3 class DLL_LINKAGE CLegacyConfigParser @@ -167,6 +168,7 @@ public: /// add selected string to internal storage void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized); + void registerString(const std::string & modContext, const TextIdentifier & UID, const std::string & localized, const std::string & language); /// returns translated version of a string that can be displayed to user template @@ -188,6 +190,8 @@ public: /// Remove subcontainer with give name void removeSubContainer(const TextLocalizationContainer & container); + void jsonSerialize(JsonNode & dest) const; + template void serialize(Handler & h, const int Version) { diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 5250d575b..737eb98e0 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -270,6 +270,9 @@ public: h & defeatMessage; h & defeatIconIndex; } + + /// do not serialize, used only in map editor to write translations properly + JsonNode mapEditorTranslations; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 6f739eb1c..c0dafdeec 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -35,6 +35,7 @@ #include "../constants/StringConstants.h" #include "../serializer/JsonDeserializer.h" #include "../serializer/JsonSerializer.h" +#include "../Languages.h" VCMI_LIB_NAMESPACE_BEGIN @@ -341,8 +342,8 @@ namespace TerrainDetail } ///CMapFormatJson -const int CMapFormatJson::VERSION_MAJOR = 1; -const int CMapFormatJson::VERSION_MINOR = 3; +const int CMapFormatJson::VERSION_MAJOR = 2; +const int CMapFormatJson::VERSION_MINOR = 0; const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json"; @@ -906,6 +907,11 @@ std::unique_ptr CMapLoaderJson::loadMapHeader() return result; } +bool CMapLoaderJson::isExistArchive(const std::string & archiveFilename) +{ + return loader.existsResource(JsonPath::builtin(archiveFilename)); +} + JsonNode CMapLoaderJson::getFromArchive(const std::string & archiveFilename) { JsonPath resource = JsonPath::builtin(archiveFilename); @@ -938,7 +944,7 @@ void CMapLoaderJson::readHeader(const bool complete) fileVersionMajor = static_cast(header["versionMajor"].Integer()); - if(fileVersionMajor != VERSION_MAJOR) + if(fileVersionMajor > VERSION_MAJOR) { logGlobal->error("Unsupported map format version: %d", fileVersionMajor); throw std::runtime_error("Unsupported map format version"); @@ -998,6 +1004,8 @@ void CMapLoaderJson::readHeader(const bool complete) if(complete) readOptions(handler); + + readTranslations(); } void CMapLoaderJson::readTerrainTile(const std::string & src, TerrainTile & tile) @@ -1259,6 +1267,36 @@ void CMapLoaderJson::readObjects() }); } +void CMapLoaderJson::readTranslations() +{ + auto language = CGeneralTextHandler::getPreferredLanguage(); + JsonNode data; + + if(!isExistArchive(language + ".json")) + { + //english is preferrable + language = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier; + std::list options{Languages::getLanguageList().begin(), Languages::getLanguageList().end()}; + while(!isExistArchive(language + ".json") && !options.empty()) + { + language = options.front().identifier; + options.pop_front(); + } + + if(!isExistArchive(language + ".json")) + { + logGlobal->info("Map doesn't have any translation"); + return; + } + } + + data = getFromArchive(language + ".json"); + + for(auto & s : data.Struct()) + mapHeader->registerString("map", TextIdentifier(s.first), s.second.String(), language); +} + + ///CMapSaverJson CMapSaverJson::CMapSaverJson(CInputOutputStream * stream) : buffer(stream) @@ -1340,6 +1378,12 @@ void CMapSaverJson::writeHeader() writeTeams(handler); writeOptions(handler); + + for(auto & s : mapHeader->mapEditorTranslations.Struct()) + { + mapHeader->loadTranslationOverrides(s.first, "map", s.second); + writeTranslations(s.first); + } addToArchive(header, HEADER_FILE_NAME); } @@ -1440,5 +1484,20 @@ void CMapSaverJson::writeObjects() addToArchive(data, OBJECTS_FILE_NAME); } +void CMapSaverJson::writeTranslations(const std::string & language) +{ + if(Languages::getLanguageOptions(language).identifier.empty()) + { + logGlobal->error("Serializing of unsupported language %s is not permitted", language); + return; + } + + logGlobal->trace("Saving translations, language: %s", language); + JsonNode data(JsonNode::JsonType::DATA_STRUCT); + + mapHeader->jsonSerialize(data); + + addToArchive(data, language + ".json"); +} VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index af89f89eb..8f0a6a39f 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -202,6 +202,11 @@ public: * Reads complete map. */ void readMap(); + + /** + * Reads texts and translations + */ + void readTranslations(); static void readTerrainTile(const std::string & src, TerrainTile & tile); @@ -214,6 +219,7 @@ public: */ void readObjects(); + bool isExistArchive(const std::string & archiveFilename); JsonNode getFromArchive(const std::string & archiveFilename); private: @@ -249,6 +255,11 @@ public: * Saves header to zip archive */ void writeHeader(); + + /** + * Saves texts and translations to zip archive + */ + void writeTranslations(const std::string & language); /** * Encodes one tile into string From de1bb1be19c82c1bc853e612149fb3b27dcc2e0d Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 04:33:33 +0200 Subject: [PATCH 28/59] Disable map editor --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d711ceec9..c82b84508 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) if(NOT ANDROID) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) - option(ENABLE_EDITOR "Enable compilation of map editor" ON) +# option(ENABLE_EDITOR "Enable compilation of map editor" ON) disable map editor until it is ready endif() option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON) From 67e120044810b17e434249fa518b4620a4af0d35 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 12:46:28 +0200 Subject: [PATCH 29/59] Remove forward declaration --- lib/IGameCallback.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 1c5c7b4b9..cdbb40209 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -20,7 +20,6 @@ struct SetMovePoints; struct GiveBonus; struct BlockingDialog; struct TeleportDialog; -class MetaString; struct StackLocation; struct ArtifactLocation; class CCreatureSet; From a710c88b07e97508a1be8d8415a739b4e8570204 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 13:09:01 +0200 Subject: [PATCH 30/59] Proper map translations loading --- lib/mapping/CMapHeader.cpp | 28 ++++++++++++++++++ lib/mapping/CMapHeader.h | 11 +++++-- lib/mapping/MapFormatJson.cpp | 56 ++++++++++------------------------- lib/mapping/MapFormatJson.h | 2 +- 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 345974bc2..cd3193f41 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -16,6 +16,7 @@ #include "../CTownHandler.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" +#include "../Languages.h" VCMI_LIB_NAMESPACE_BEGIN @@ -140,4 +141,31 @@ ui8 CMapHeader::levels() const return (twoLevel ? 2 : 1); } +void CMapHeader::registerMapStrings() +{ + auto language = CGeneralTextHandler::getPreferredLanguage(); + JsonNode data; + + if(translations[language].isNull()) + { + //english is preferrable + language = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier; + std::list languages{Languages::getLanguageList().begin(), Languages::getLanguageList().end()}; + while(translations[language].isNull() && !languages.empty()) + { + language = languages.front().identifier; + languages.pop_front(); + } + + if(!translations[language].isNull()) + { + logGlobal->info("Map %s doesn't have any translation", name.toString()); + return; + } + } + + for(auto & s : translations[language].Struct()) + registerString("map", TextIdentifier(s.first), s.second.String(), language); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 737eb98e0..9f08747f2 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -245,6 +245,11 @@ public: /// "main quests" of the map that describe victory and loss conditions std::vector triggeredEvents; + + /// translations for map to be transferred over network + JsonNode translations; + + void registerMapStrings(); template void serialize(Handler & h, const int Version) @@ -269,10 +274,10 @@ public: h & victoryIconIndex; h & defeatMessage; h & defeatIconIndex; + h & translations; + if(!h.saving) + registerMapStrings(); } - - /// do not serialize, used only in map editor to write translations properly - JsonNode mapEditorTranslations; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index c0dafdeec..5ab9f6297 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1269,31 +1269,12 @@ void CMapLoaderJson::readObjects() void CMapLoaderJson::readTranslations() { - auto language = CGeneralTextHandler::getPreferredLanguage(); - JsonNode data; - - if(!isExistArchive(language + ".json")) + std::list languages{Languages::getLanguageList().begin(), Languages::getLanguageList().end()}; + for(auto & language : Languages::getLanguageList()) { - //english is preferrable - language = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier; - std::list options{Languages::getLanguageList().begin(), Languages::getLanguageList().end()}; - while(!isExistArchive(language + ".json") && !options.empty()) - { - language = options.front().identifier; - options.pop_front(); - } - - if(!isExistArchive(language + ".json")) - { - logGlobal->info("Map doesn't have any translation"); - return; - } + if(isExistArchive(language.identifier + ".json")) + mapHeader->translations.Struct()[language.identifier] = getFromArchive(language.identifier + ".json"); } - - data = getFromArchive(language + ".json"); - - for(auto & s : data.Struct()) - mapHeader->registerString("map", TextIdentifier(s.first), s.second.String(), language); } @@ -1378,12 +1359,8 @@ void CMapSaverJson::writeHeader() writeTeams(handler); writeOptions(handler); - - for(auto & s : mapHeader->mapEditorTranslations.Struct()) - { - mapHeader->loadTranslationOverrides(s.first, "map", s.second); - writeTranslations(s.first); - } + + writeTranslations(); addToArchive(header, HEADER_FILE_NAME); } @@ -1484,20 +1461,19 @@ void CMapSaverJson::writeObjects() addToArchive(data, OBJECTS_FILE_NAME); } -void CMapSaverJson::writeTranslations(const std::string & language) +void CMapSaverJson::writeTranslations() { - if(Languages::getLanguageOptions(language).identifier.empty()) + for(auto & s : mapHeader->translations.Struct()) { - logGlobal->error("Serializing of unsupported language %s is not permitted", language); - return; + auto & language = s.first; + if(Languages::getLanguageOptions(language).identifier.empty()) + { + logGlobal->error("Serializing of unsupported language %s is not permitted", language); + continue;; + } + logGlobal->trace("Saving translations, language: %s", language); + addToArchive(s.second, language + ".json"); } - - logGlobal->trace("Saving translations, language: %s", language); - JsonNode data(JsonNode::JsonType::DATA_STRUCT); - - mapHeader->jsonSerialize(data); - - addToArchive(data, language + ".json"); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index 8f0a6a39f..9f5ee3e36 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -259,7 +259,7 @@ public: /** * Saves texts and translations to zip archive */ - void writeTranslations(const std::string & language); + void writeTranslations(); /** * Encodes one tile into string From d6616008587f520e39be07dfc7722fe28d5efc9c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 13:14:23 +0200 Subject: [PATCH 31/59] Add header --- lib/pathfinder/CGPathNode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pathfinder/CGPathNode.cpp b/lib/pathfinder/CGPathNode.cpp index 057161295..701c2f5bb 100644 --- a/lib/pathfinder/CGPathNode.cpp +++ b/lib/pathfinder/CGPathNode.cpp @@ -15,6 +15,7 @@ #include "../gameState/CGameState.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapping/CMapDefines.h" +#include "../MetaString.h" VCMI_LIB_NAMESPACE_BEGIN From 1dbecc2e186f8b7900eaaba5e90c9aadb36685e0 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 13:28:04 +0200 Subject: [PATCH 32/59] Revert "Add header" This reverts commit d6616008587f520e39be07dfc7722fe28d5efc9c. --- lib/pathfinder/CGPathNode.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pathfinder/CGPathNode.cpp b/lib/pathfinder/CGPathNode.cpp index 701c2f5bb..057161295 100644 --- a/lib/pathfinder/CGPathNode.cpp +++ b/lib/pathfinder/CGPathNode.cpp @@ -15,7 +15,6 @@ #include "../gameState/CGameState.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapping/CMapDefines.h" -#include "../MetaString.h" VCMI_LIB_NAMESPACE_BEGIN From 9df85192c92c8482b45e0852c97c25a67973bc75 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 13:28:15 +0200 Subject: [PATCH 33/59] Revert "Disable map editor" This reverts commit de1bb1be19c82c1bc853e612149fb3b27dcc2e0d. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c82b84508..d711ceec9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF) if(NOT ANDROID) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) -# option(ENABLE_EDITOR "Enable compilation of map editor" ON) disable map editor until it is ready + option(ENABLE_EDITOR "Enable compilation of map editor" ON) endif() option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON) option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON) From 56eefab2551004e54b526ec761d110ef4b8e8ed5 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 13:28:24 +0200 Subject: [PATCH 34/59] Fix headers --- lib/mapping/CMapDefines.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index 250e2eab4..f36a0889e 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -10,11 +10,11 @@ #pragma once -VCMI_LIB_NAMESPACE_BEGIN - #include "../ResourceSet.h" #include "../MetaString.h" +VCMI_LIB_NAMESPACE_BEGIN + class TerrainType; class RiverType; class RoadType; From 232b3e8cf63e7a62180f4ba102c686c7eba5568b Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 13:28:37 +0200 Subject: [PATCH 35/59] Compile map editor --- mapeditor/inspector/inspector.cpp | 19 ++++++++++++------- mapeditor/inspector/inspector.h | 2 ++ mapeditor/mapsettings/eventsettings.cpp | 4 ++-- mapeditor/mapsettings/generalsettings.cpp | 8 ++++---- mapeditor/mapsettings/rumorsettings.cpp | 4 ++-- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 8acaa91ae..35d2d6407 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -531,7 +531,7 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message = value.toString().toStdString(); + o->message.appendRawString(value.toString().toStdString()); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) @@ -561,7 +561,7 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message = value.toString().toStdString(); + o->message.appendRawString(value.toString().toStdString()); } void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value) @@ -577,7 +577,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message = value.toString().toStdString(); + o->message.appendRawString(value.toString().toStdString()); if(o->storedArtifact && key == "Spell") { @@ -643,7 +643,7 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message = value.toString().toStdString(); + o->message.appendRawString(value.toString().toStdString()); if(key == "Character") o->character = CGCreature::Character(value.toInt()); if(key == "Never flees") @@ -661,11 +661,11 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant & if(key == "Mission type") o->quest->missionType = CQuest::Emission(value.toInt()); if(key == "First visit text") - o->quest->firstVisitText = value.toString().toStdString(); + o->quest->firstVisitText.appendRawString(value.toString().toStdString()); if(key == "Next visit text") - o->quest->nextVisitText = value.toString().toStdString(); + o->quest->nextVisitText.appendRawString(value.toString().toStdString()); if(key == "Completed text") - o->quest->completedText = value.toString().toStdString(); + o->quest->completedText.appendRawString(value.toString().toStdString()); } @@ -713,6 +713,11 @@ QTableWidgetItem * Inspector::addProperty(const std::string & value) return addProperty(QString::fromStdString(value)); } +QTableWidgetItem * Inspector::addProperty(const MetaString & value) +{ + return addProperty(value.toString()); +} + QTableWidgetItem * Inspector::addProperty(const QString & value) { auto * item = new QTableWidgetItem(value); diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 1e44964ca..e6409d190 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -19,6 +19,7 @@ #include "../lib/mapObjects/MapObjects.h" #include "../lib/mapObjects/CRewardableObject.h" #include "../lib/ResourceSet.h" +#include "../lib/MetaString.h" #define DECLARE_OBJ_TYPE(x) void initialize(x*); #define DECLARE_OBJ_PROPERTY_METHODS(x) \ @@ -83,6 +84,7 @@ protected: //===============DECLARE PROPERTY VALUE TYPE============================== QTableWidgetItem * addProperty(unsigned int value); QTableWidgetItem * addProperty(int value); + QTableWidgetItem * addProperty(const MetaString & value); QTableWidgetItem * addProperty(const std::string & value); QTableWidgetItem * addProperty(const QString & value); QTableWidgetItem * addProperty(const int3 & value); diff --git a/mapeditor/mapsettings/eventsettings.cpp b/mapeditor/mapsettings/eventsettings.cpp index 6892d2903..047c9e1ae 100644 --- a/mapeditor/mapsettings/eventsettings.cpp +++ b/mapeditor/mapsettings/eventsettings.cpp @@ -37,7 +37,7 @@ QVariant toVariant(const CMapEvent & event) { QVariantMap result; result["name"] = QString::fromStdString(event.name); - result["message"] = QString::fromStdString(event.message); + result["message"] = QString::fromStdString(event.message.toString()); result["players"] = QVariant::fromValue(event.players); result["humanAffected"] = QVariant::fromValue(event.humanAffected); result["computerAffected"] = QVariant::fromValue(event.computerAffected); @@ -52,7 +52,7 @@ CMapEvent eventFromVariant(const QVariant & variant) CMapEvent result; auto v = variant.toMap(); result.name = v.value("name").toString().toStdString(); - result.message = v.value("message").toString().toStdString(); + result.message.appendRawString(v.value("message").toString().toStdString()); result.players = v.value("players").toInt(); result.humanAffected = v.value("humanAffected").toInt(); result.computerAffected = v.value("computerAffected").toInt(); diff --git a/mapeditor/mapsettings/generalsettings.cpp b/mapeditor/mapsettings/generalsettings.cpp index 09ad221b3..12322f73b 100644 --- a/mapeditor/mapsettings/generalsettings.cpp +++ b/mapeditor/mapsettings/generalsettings.cpp @@ -27,8 +27,8 @@ GeneralSettings::~GeneralSettings() void GeneralSettings::initialize(MapController & c) { AbstractSettings::initialize(c); - ui->mapNameEdit->setText(tr(controller->map()->name.c_str())); - ui->mapDescriptionEdit->setPlainText(tr(controller->map()->description.c_str())); + ui->mapNameEdit->setText(QString::fromStdString(controller->map()->name.toString())); + ui->mapDescriptionEdit->setPlainText(QString::fromStdString(controller->map()->description.toString())); ui->heroLevelLimit->setValue(controller->map()->levelLimit); ui->heroLevelLimitCheck->setChecked(controller->map()->levelLimit); @@ -59,8 +59,8 @@ void GeneralSettings::initialize(MapController & c) void GeneralSettings::update() { - controller->map()->name = ui->mapNameEdit->text().toStdString(); - controller->map()->description = ui->mapDescriptionEdit->toPlainText().toStdString(); + controller->map()->name.appendRawString(ui->mapNameEdit->text().toStdString()); + controller->map()->description.appendRawString(ui->mapDescriptionEdit->toPlainText().toStdString()); if(ui->heroLevelLimitCheck->isChecked()) controller->map()->levelLimit = ui->heroLevelLimit->value(); else diff --git a/mapeditor/mapsettings/rumorsettings.cpp b/mapeditor/mapsettings/rumorsettings.cpp index a91691539..da122be93 100644 --- a/mapeditor/mapsettings/rumorsettings.cpp +++ b/mapeditor/mapsettings/rumorsettings.cpp @@ -30,7 +30,7 @@ void RumorSettings::initialize(MapController & c) for(auto & rumor : controller->map()->rumors) { auto * item = new QListWidgetItem(QString::fromStdString(rumor.name)); - item->setData(Qt::UserRole, QVariant(QString::fromStdString(rumor.text))); + item->setData(Qt::UserRole, QVariant(QString::fromStdString(rumor.text.toString()))); item->setFlags(item->flags() | Qt::ItemIsEditable); ui->rumors->addItem(item); } @@ -43,7 +43,7 @@ void RumorSettings::update() { Rumor rumor; rumor.name = ui->rumors->item(i)->text().toStdString(); - rumor.text = ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString(); + rumor.text.appendRawString(ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString()); controller->map()->rumors.push_back(rumor); } } From 60df49236f49e2bb4908fd497b6ef977bc0891fa Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 14:03:58 +0200 Subject: [PATCH 36/59] Inspector widgets set string ID --- lib/CGeneralTextHandler.cpp | 23 ++++++-------------- mapeditor/inspector/inspector.cpp | 36 +++++++++++++++++++++---------- mapeditor/inspector/inspector.h | 4 ++++ 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index 360b69157..09d046ef7 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -301,26 +301,17 @@ void TextLocalizationContainer::registerString(const std::string & modContext, c if(stringsLocalizations.count(UID.get()) > 0) { auto & value = stringsLocalizations[UID.get()]; - - if(value.baseLanguage.empty()) - { - value.baseLanguage = language; - value.baseValue = localized; - } - else - { - if(value.baseValue != localized) - logMod->warn("Duplicate registered string '%s' found! Old value: '%s', new value: '%s'", UID.get(), value.baseValue, localized); - } + value.baseLanguage = language; + value.baseValue = localized; } else { - StringState result; - result.baseLanguage = language; - result.baseValue = localized; - result.modContext = modContext; + StringState value; + value.baseLanguage = language; + value.baseValue = localized; + value.modContext = modContext; - stringsLocalizations[UID.get()] = result; + stringsLocalizations[UID.get()] = value; } } diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 35d2d6407..c577770c0 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -277,8 +277,8 @@ void Inspector::updateProperties(CGHeroInstance * o) delegate->options = {{"MALE", QVariant::fromValue(int(EHeroGender::MALE))}, {"FEMALE", QVariant::fromValue(int(EHeroGender::FEMALE))}}; addProperty("Gender", (o->gender == EHeroGender::FEMALE ? "FEMALE" : "MALE"), delegate , false); } - addProperty("Name", o->nameCustomTextId, false); - addProperty("Biography", o->biographyCustomTextId, new MessageDelegate, false); + addProperty("Name", o->getNameTranslated(), false); + addProperty("Biography", o->getBiographyTranslated(), new MessageDelegate, false); addProperty("Portrait", o->portrait, false); auto * delegate = new HeroSkillsDelegate(*o); @@ -531,7 +531,7 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendRawString(value.toString().toStdString()); + o->message.appendTextID(mapWriteStringId(TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) @@ -553,7 +553,7 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari if(!o) return; if(key == "Town name") - o->setNameTextId(value.toString().toStdString()); + o->setNameTextId(mapWriteStringId(TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); } void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value) @@ -561,7 +561,7 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendRawString(value.toString().toStdString()); + o->message.appendTextID(mapWriteStringId(TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value) @@ -577,7 +577,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendRawString(value.toString().toStdString()); + o->message.appendTextID(mapWriteStringId(TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); if(o->storedArtifact && key == "Spell") { @@ -606,7 +606,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->gender = EHeroGender(value.toInt()); if(key == "Name") - o->nameCustomTextId = value.toString().toStdString(); + o->nameCustomTextId = mapWriteStringId(TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); + + if(key == "Biography") + o->biographyCustomTextId = mapWriteStringId(TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); if(key == "Experience") o->exp = value.toString().toInt(); @@ -643,7 +646,7 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendRawString(value.toString().toStdString()); + o->message.appendTextID(mapWriteStringId(TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); if(key == "Character") o->character = CGCreature::Character(value.toInt()); if(key == "Never flees") @@ -661,11 +664,11 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant & if(key == "Mission type") o->quest->missionType = CQuest::Emission(value.toInt()); if(key == "First visit text") - o->quest->firstVisitText.appendRawString(value.toString().toStdString()); + o->quest->firstVisitText.appendTextID(mapWriteStringId(TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); if(key == "Next visit text") - o->quest->nextVisitText.appendRawString(value.toString().toStdString()); + o->quest->nextVisitText.appendTextID(mapWriteStringId(TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); if(key == "Completed text") - o->quest->completedText.appendRawString(value.toString().toStdString()); + o->quest->completedText.appendTextID(mapWriteStringId(TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); } @@ -713,6 +716,11 @@ QTableWidgetItem * Inspector::addProperty(const std::string & value) return addProperty(QString::fromStdString(value)); } +QTableWidgetItem * Inspector::addProperty(const TextIdentifier & value) +{ + return addProperty(VLC->generaltexth->translate(value.get())); +} + QTableWidgetItem * Inspector::addProperty(const MetaString & value) { return addProperty(value.toString()); @@ -797,6 +805,12 @@ Inspector::Inspector(CMap * m, CGObjectInstance * o, QTableWidget * t): obj(o), { } +std::string Inspector::mapWriteStringId(const TextIdentifier & stringIdentifier, const std::string & localized) +{ + map->registerString("map", stringIdentifier, localized); + return stringIdentifier.get(); +} + /* * Delegates */ diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index e6409d190..3c097b5d1 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -18,6 +18,7 @@ #include "../lib/mapObjects/CGCreature.h" #include "../lib/mapObjects/MapObjects.h" #include "../lib/mapObjects/CRewardableObject.h" +#include "../lib/CGeneralTextHandler.h" #include "../lib/ResourceSet.h" #include "../lib/MetaString.h" @@ -85,6 +86,7 @@ protected: QTableWidgetItem * addProperty(unsigned int value); QTableWidgetItem * addProperty(int value); QTableWidgetItem * addProperty(const MetaString & value); + QTableWidgetItem * addProperty(const TextIdentifier & value); QTableWidgetItem * addProperty(const std::string & value); QTableWidgetItem * addProperty(const QString & value); QTableWidgetItem * addProperty(const int3 & value); @@ -146,6 +148,8 @@ protected: { addProperty(key, value, nullptr, restricted); } + + std::string mapWriteStringId(const TextIdentifier & stringIdentifier, const std::string & localized); protected: int row = 0; From 98fde9ab1ddc6b8ed37613f95296211d93ca0300 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 14:38:31 +0200 Subject: [PATCH 37/59] Add string IDs in map editor --- lib/mapping/CMapHeader.cpp | 12 ++++++++++++ lib/mapping/CMapHeader.h | 4 ++++ lib/modding/CModHandler.cpp | 4 +++- mapeditor/inspector/inspector.cpp | 26 ++++++++++---------------- mapeditor/inspector/inspector.h | 2 -- mapeditor/inspector/rewardswidget.cpp | 8 ++++---- mapeditor/inspector/rewardswidget.h | 8 ++++---- 7 files changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index cd3193f41..fe0583731 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -168,4 +168,16 @@ void CMapHeader::registerMapStrings() registerString("map", TextIdentifier(s.first), s.second.String(), language); } +std::string mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized) +{ + return mapRegisterLocalizedString(mapHeader, UID, localized, VLC->generaltexth->getPreferredLanguage()); +} + +std::string mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language) +{ + mapHeader.registerString("map", UID, localized, language); + mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized; + return UID.get(); +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index 9f08747f2..fc915c09c 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -280,4 +280,8 @@ public: } }; +/// wrapper functions to register string into the map and stores its translation +std::string DLL_LINKAGE mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized); +std::string DLL_LINKAGE mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language); + VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 12187990a..1414817f5 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -319,8 +319,10 @@ TModID CModHandler::findResourceOrigin(const ResourcePath & name) std::string CModHandler::getModLanguage(const TModID& modId) const { - if ( modId == "core") + if(modId == "core") return VLC->generaltexth->getInstalledLanguage(); + if(modId == "map") + return VLC->generaltexth->getPreferredLanguage(); return allMods.at(modId).baseLanguage; } diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index c577770c0..5f0e6ef54 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -531,7 +531,7 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendTextID(mapWriteStringId(TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) @@ -553,7 +553,7 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari if(!o) return; if(key == "Town name") - o->setNameTextId(mapWriteStringId(TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); + o->setNameTextId(mapRegisterLocalizedString(*map, TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); } void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value) @@ -561,7 +561,7 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendTextID(mapWriteStringId(TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value) @@ -577,7 +577,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendTextID(mapWriteStringId(TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); if(o->storedArtifact && key == "Spell") { @@ -606,10 +606,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->gender = EHeroGender(value.toInt()); if(key == "Name") - o->nameCustomTextId = mapWriteStringId(TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); + o->nameCustomTextId = mapRegisterLocalizedString(*map, TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); if(key == "Biography") - o->biographyCustomTextId = mapWriteStringId(TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); + o->biographyCustomTextId = mapRegisterLocalizedString(*map, TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); if(key == "Experience") o->exp = value.toString().toInt(); @@ -646,7 +646,7 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendTextID(mapWriteStringId(TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); if(key == "Character") o->character = CGCreature::Character(value.toInt()); if(key == "Never flees") @@ -664,11 +664,11 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant & if(key == "Mission type") o->quest->missionType = CQuest::Emission(value.toInt()); if(key == "First visit text") - o->quest->firstVisitText.appendTextID(mapWriteStringId(TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); + o->quest->firstVisitText.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); if(key == "Next visit text") - o->quest->nextVisitText.appendTextID(mapWriteStringId(TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); + o->quest->nextVisitText.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); if(key == "Completed text") - o->quest->completedText.appendTextID(mapWriteStringId(TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); + o->quest->completedText.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); } @@ -805,12 +805,6 @@ Inspector::Inspector(CMap * m, CGObjectInstance * o, QTableWidget * t): obj(o), { } -std::string Inspector::mapWriteStringId(const TextIdentifier & stringIdentifier, const std::string & localized) -{ - map->registerString("map", stringIdentifier, localized); - return stringIdentifier.get(); -} - /* * Delegates */ diff --git a/mapeditor/inspector/inspector.h b/mapeditor/inspector/inspector.h index 3c097b5d1..c31f41818 100644 --- a/mapeditor/inspector/inspector.h +++ b/mapeditor/inspector/inspector.h @@ -149,8 +149,6 @@ protected: addProperty(key, value, nullptr, restricted); } - std::string mapWriteStringId(const TextIdentifier & stringIdentifier, const std::string & localized); - protected: int row = 0; QTableWidget * table; diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index ffecec0e1..b8990b135 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -23,7 +23,7 @@ #include "../lib/mapObjects/CGPandoraBox.h" #include "../lib/mapObjects/CQuest.h" -RewardsWidget::RewardsWidget(const CMap & m, CRewardableObject & p, QWidget *parent) : +RewardsWidget::RewardsWidget(CMap & m, CRewardableObject & p, QWidget *parent) : QDialog(parent), map(m), object(p), @@ -211,7 +211,7 @@ bool RewardsWidget::commitChanges() if(ui->onSelectText->text().isEmpty()) object.configuration.onSelect.clear(); else - object.configuration.onSelect = MetaString::createFromRawString(ui->onSelectText->text().toStdString()); + object.configuration.onSelect = MetaString::createFromTextID(mapRegisterLocalizedString(map, TextIdentifier("reward", object.instanceName, "onSelect"), ui->onSelectText->text().toStdString())); object.configuration.canRefuse = ui->canRefuse->isChecked(); //reset parameters @@ -232,7 +232,7 @@ void RewardsWidget::saveCurrentVisitInfo(int index) if(ui->rewardMessage->text().isEmpty()) vinfo.message.clear(); else - vinfo.message = MetaString::createFromRawString(ui->rewardMessage->text().toStdString()); + vinfo.message = MetaString::createFromTextID(mapRegisterLocalizedString(map, TextIdentifier("reward", object.instanceName, "info", index, "message"), ui->rewardMessage->text().toStdString())); vinfo.reward.heroLevel = ui->rHeroLevel->value(); vinfo.reward.heroExperience = ui->rHeroExperience->value(); @@ -649,7 +649,7 @@ void RewardsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, c } } -RewardsDelegate::RewardsDelegate(const CMap & m, CRewardableObject & t): map(m), object(t) +RewardsDelegate::RewardsDelegate(CMap & m, CRewardableObject & t): map(m), object(t) { } diff --git a/mapeditor/inspector/rewardswidget.h b/mapeditor/inspector/rewardswidget.h index 2a5958d9e..aeffe17e2 100644 --- a/mapeditor/inspector/rewardswidget.h +++ b/mapeditor/inspector/rewardswidget.h @@ -22,7 +22,7 @@ class RewardsWidget : public QDialog public: - explicit RewardsWidget(const CMap &, CRewardableObject &, QWidget *parent = nullptr); + explicit RewardsWidget(CMap &, CRewardableObject &, QWidget *parent = nullptr); ~RewardsWidget(); void obtainData(); @@ -64,14 +64,14 @@ private: Ui::RewardsWidget *ui; CRewardableObject & object; - const CMap & map; + CMap & map; }; class RewardsDelegate : public QStyledItemDelegate { Q_OBJECT public: - RewardsDelegate(const CMap &, CRewardableObject &); + RewardsDelegate(CMap &, CRewardableObject &); using QStyledItemDelegate::QStyledItemDelegate; @@ -82,5 +82,5 @@ public: private: CRewardableObject & object; - const CMap & map; + CMap & map; }; From dbf3cad796042bd50e8b0b2edd55a8b2fb25dad7 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 14:49:47 +0200 Subject: [PATCH 38/59] Register all text IDs in map editor --- mapeditor/mapsettings/eventsettings.cpp | 6 +++--- mapeditor/mapsettings/generalsettings.cpp | 4 ++-- mapeditor/mapsettings/loseconditions.cpp | 4 ++-- mapeditor/mapsettings/rumorsettings.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mapeditor/mapsettings/eventsettings.cpp b/mapeditor/mapsettings/eventsettings.cpp index 047c9e1ae..60cd97982 100644 --- a/mapeditor/mapsettings/eventsettings.cpp +++ b/mapeditor/mapsettings/eventsettings.cpp @@ -47,12 +47,12 @@ QVariant toVariant(const CMapEvent & event) return QVariant(result); } -CMapEvent eventFromVariant(const QVariant & variant) +CMapEvent eventFromVariant(CMapHeader & mapHeader, const QVariant & variant) { CMapEvent result; auto v = variant.toMap(); result.name = v.value("name").toString().toStdString(); - result.message.appendRawString(v.value("message").toString().toStdString()); + result.message.appendTextID(mapRegisterLocalizedString(mapHeader, TextIdentifier("header", "event", result.name, "message"), v.value("message").toString().toStdString())); result.players = v.value("players").toInt(); result.humanAffected = v.value("humanAffected").toInt(); result.computerAffected = v.value("computerAffected").toInt(); @@ -91,7 +91,7 @@ void EventSettings::update() for(int i = 0; i < ui->eventsList->count(); ++i) { const auto * item = ui->eventsList->item(i); - controller->map()->events.push_back(eventFromVariant(item->data(Qt::UserRole))); + controller->map()->events.push_back(eventFromVariant(*controller->map(), item->data(Qt::UserRole))); } } diff --git a/mapeditor/mapsettings/generalsettings.cpp b/mapeditor/mapsettings/generalsettings.cpp index 12322f73b..8f74867dc 100644 --- a/mapeditor/mapsettings/generalsettings.cpp +++ b/mapeditor/mapsettings/generalsettings.cpp @@ -59,8 +59,8 @@ void GeneralSettings::initialize(MapController & c) void GeneralSettings::update() { - controller->map()->name.appendRawString(ui->mapNameEdit->text().toStdString()); - controller->map()->description.appendRawString(ui->mapDescriptionEdit->toPlainText().toStdString()); + controller->map()->name.appendTextID(mapRegisterLocalizedString(*controller->map(), TextIdentifier("header", "name"), ui->mapNameEdit->text().toStdString())); + controller->map()->description.appendTextID(mapRegisterLocalizedString(*controller->map(), TextIdentifier("header", "description"), ui->mapDescriptionEdit->toPlainText().toStdString())); if(ui->heroLevelLimitCheck->isChecked()) controller->map()->levelLimit = ui->heroLevelLimit->value(); else diff --git a/mapeditor/mapsettings/loseconditions.cpp b/mapeditor/mapsettings/loseconditions.cpp index 017ad9e78..c66c0f936 100644 --- a/mapeditor/mapsettings/loseconditions.cpp +++ b/mapeditor/mapsettings/loseconditions.cpp @@ -243,7 +243,7 @@ void LoseConditions::on_loseComboBox_currentIndexChanged(int index) loseTypeWidget = new QComboBox; ui->loseParamsLayout->addWidget(loseTypeWidget); for(int i : getObjectIndexes(*controller->map())) - loseTypeWidget->addItem(tr(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i)); + loseTypeWidget->addItem(QString::fromStdString(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &LoseConditions::onObjectSelect); ui->loseParamsLayout->addWidget(pickObjectButton); @@ -254,7 +254,7 @@ void LoseConditions::on_loseComboBox_currentIndexChanged(int index) loseTypeWidget = new QComboBox; ui->loseParamsLayout->addWidget(loseTypeWidget); for(int i : getObjectIndexes(*controller->map())) - loseTypeWidget->addItem(tr(getHeroName(*controller->map(), i).c_str()), QVariant::fromValue(i)); + loseTypeWidget->addItem(QString::fromStdString(getHeroName(*controller->map(), i).c_str()), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &LoseConditions::onObjectSelect); ui->loseParamsLayout->addWidget(pickObjectButton); diff --git a/mapeditor/mapsettings/rumorsettings.cpp b/mapeditor/mapsettings/rumorsettings.cpp index da122be93..6527c67f1 100644 --- a/mapeditor/mapsettings/rumorsettings.cpp +++ b/mapeditor/mapsettings/rumorsettings.cpp @@ -43,7 +43,7 @@ void RumorSettings::update() { Rumor rumor; rumor.name = ui->rumors->item(i)->text().toStdString(); - rumor.text.appendRawString(ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString()); + rumor.text.appendTextID(mapRegisterLocalizedString(*controller->map(), TextIdentifier("header", "rumor", i, "text"), ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString())); controller->map()->rumors.push_back(rumor); } } From db93b8eaa1e69d8f96b9ae4110de965d675eea8b Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 15:29:43 +0200 Subject: [PATCH 39/59] UI draft for map translations --- mapeditor/CMakeLists.txt | 3 + mapeditor/icons/translations.png | Bin 0 -> 1864 bytes mapeditor/mainwindow.cpp | 9 +++ mapeditor/mainwindow.h | 2 + mapeditor/mainwindow.ui | 25 ++++++-- mapeditor/mapsettings/translations.cpp | 43 +++++++++++++ mapeditor/mapsettings/translations.h | 38 +++++++++++ mapeditor/mapsettings/translations.ui | 84 +++++++++++++++++++++++++ 8 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 mapeditor/icons/translations.png create mode 100644 mapeditor/mapsettings/translations.cpp create mode 100644 mapeditor/mapsettings/translations.h create mode 100644 mapeditor/mapsettings/translations.ui diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index 4041bcd1e..3a2c6191b 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -21,6 +21,7 @@ set(editor_SRCS mapsettings/loseconditions.cpp mapsettings/eventsettings.cpp mapsettings/rumorsettings.cpp + mapsettings/translations.cpp playersettings.cpp playerparams.cpp scenelayer.cpp @@ -58,6 +59,7 @@ set(editor_HEADERS mapsettings/loseconditions.h mapsettings/eventsettings.h mapsettings/rumorsettings.h + mapsettings/translations.h playersettings.h playerparams.h scenelayer.h @@ -85,6 +87,7 @@ set(editor_FORMS mapsettings/loseconditions.ui mapsettings/eventsettings.ui mapsettings/rumorsettings.ui + mapsettings/translations.ui playersettings.ui playerparams.ui validator.ui diff --git a/mapeditor/icons/translations.png b/mapeditor/icons/translations.png new file mode 100644 index 0000000000000000000000000000000000000000..0b031738683f700600f137a7ad8eea4deddda41d GIT binary patch literal 1864 zcmV-O2eBE1ZQLxAY_vg;$?V;oX(lt{{G@3qJ`_O^!KmPq;Qw2}Cn0L9?Tam?khm;cmfj@-EE=vY<#k5o0f^m&*sM6pBI0EL{uRZtU4R5Z z`^X8`V)zNfJ8YaF;u6EP09P2E5Ad?jnUOMxUqvh^s*L``+lU9}BlcPZ2XpY-jyyG} zttleQMaed+noc9ry@UIIwng1*#M^^UkFzj+6yF25_LnMhmf?pGpH<}CbcnAmX|u%; z|7E#p3GpKUUAw%6atiT2#A8)^yd3Cssj4^8z$>TJnP(8+gLrkpJj%xIMEtceUz`ec zeo~wa<_{hK;NmJRuzL3+9o0iVC08s$)vACv7|KExH6jmbd4gn8^0FkllzkRT6fd|P0n zrwSVAXrbt(XrcJk;OyVRDf#{Yw^VBW$m{6QFhZvPBk<6w!tZERBfJcLd1$M3&)TehHS09YRtkN(Z(GDi^F2;vKBX;HY+Q*UEnTqUvgh@A9J5aYSek}G;8j! zqp=F+X_rylpF&#LEj%b>g+0Q~5^H*5N-?MwSLAe5T{?GXqX;9WfX;*4pXw6&V*io?m|<6;W0GAb4f<-njQ>$>Q>9MPr@ zwU=n+0I+*9IKpp9{{0u*d8K7@p9VPmCp-6AwmA>rtOL;dXv;Q$XXCx^0IWWyc=l3w zC&Xgk0yx!!!@*Zi8qcdA>-AfBZYACT*nGWS|NH%Vee(m<-vGFv{{#Oecnig2*2@3@ z061k>NoGw=04e|g00;m9hiL!=000010000Q0000000N)_00aO40096103e_P00aO4 z0096103ZMW0056pK*<0A0un=0Jww8?A#Gota^B9I<)cA#c07pSP@Cm-B zu# zkqYSqO{`*6ge$ceCCr8o#%F9i1{JvjqI7g(R}7l3iB&|j^8e63^E33SDmbcAXl3ug zccJJG(6u^N&C2yq^7EFjb2 z9QcVZ>gj+5{d=&Vw#d~UGQ^2QrY@t-HEIzJF$1Ff4fvAaEEoY@XyJDOw{_C0b)hRZ z#RY3CcGzNtjXF0X?S{T1%3o$3^y^VuJh#}GDS8KD-*NH%mgWuStC!9C?Q0y}AD#P0 z*kHtYmJ;iNyU>s{sb4C$SsiclP<^uS?@jJ%G?6Av{;^2=Q_(^03(#%Xdq7`M*8+3; zvBRLYY$0h?y5TBHBvm1xoU6$Pq5afy{5=sKHRi^xjfLn!zp=sf5lT}p4>ZOdCFETw zactionMapSettings->setEnabled(true); ui->actionPlayers_settings->setEnabled(true); + ui->actionTranslations->setEnabled(true); //set minimal players count if(isNew) @@ -1239,3 +1241,10 @@ void MainWindow::on_actionExport_triggered() } } + +void MainWindow::on_actionTranslations_triggered() +{ + auto translationsDialog = new Translations(*controller.map(), this); + translationsDialog->show(); +} + diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index f509b0882..3954ca173 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -117,6 +117,8 @@ private slots: void on_actionExport_triggered(); + void on_actionTranslations_triggered(); + public slots: void treeViewSelected(const QModelIndex &selected, const QModelIndex &deselected); diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 159a1ec98..6f7b0da5f 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -51,7 +51,7 @@ 0 0 1024 - 22 + 37 @@ -70,6 +70,7 @@ + @@ -140,6 +141,7 @@ + @@ -754,7 +756,7 @@ 0 0 128 - 251 + 236 @@ -797,7 +799,7 @@ 0 0 128 - 251 + 236 @@ -833,7 +835,7 @@ 0 0 128 - 251 + 236 @@ -1237,6 +1239,21 @@ Export as... + + + false + + + + icons:translations.pngicons:translations.png + + + Translations + + + Ctrl+T + + diff --git a/mapeditor/mapsettings/translations.cpp b/mapeditor/mapsettings/translations.cpp new file mode 100644 index 000000000..e8f181551 --- /dev/null +++ b/mapeditor/mapsettings/translations.cpp @@ -0,0 +1,43 @@ +/* + * translations.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 "translations.h" +#include "ui_translations.h" + +Translations::Translations(CMapHeader & mh, QWidget *parent) : + QDialog(parent), + ui(new Ui::Translations), + mapHeader(mh) +{ + ui->setupUi(this); +} + +Translations::~Translations() +{ + delete ui; +} + +void Translations::on_languageSelect_currentIndexChanged(int index) +{ + +} + + +void Translations::on_supportedCheck_toggled(bool checked) +{ + +} + + +void Translations::on_translationsTable_itemChanged(QTableWidgetItem *item) +{ + +} + diff --git a/mapeditor/mapsettings/translations.h b/mapeditor/mapsettings/translations.h new file mode 100644 index 000000000..1444e6f25 --- /dev/null +++ b/mapeditor/mapsettings/translations.h @@ -0,0 +1,38 @@ +/* + * translations.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 +#include "../lib/mapping/CMapHeader.h" + +namespace Ui { +class Translations; +} + +class Translations : public QDialog +{ + Q_OBJECT + +public: + explicit Translations(CMapHeader & mapHeader, QWidget *parent = nullptr); + ~Translations(); + +private slots: + void on_languageSelect_currentIndexChanged(int index); + + void on_supportedCheck_toggled(bool checked); + + void on_translationsTable_itemChanged(QTableWidgetItem *item); + +private: + Ui::Translations *ui; + CMapHeader & mapHeader; +}; diff --git a/mapeditor/mapsettings/translations.ui b/mapeditor/mapsettings/translations.ui new file mode 100644 index 000000000..41b6b8578 --- /dev/null +++ b/mapeditor/mapsettings/translations.ui @@ -0,0 +1,84 @@ + + + Translations + + + + 0 + 0 + 989 + 641 + + + + Map translations + + + true + + + + + + + + + 0 + 0 + + + + Language + + + + + + + + 0 + 0 + + + + + + + + Suppported + + + + + + + + + 240 + + + true + + + false + + + 24 + + + + String ID + + + + + Text + + + + + + + + + From ab85e724eb0ba7c2d93537d2d694248a9a2aea85 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 16:20:21 +0200 Subject: [PATCH 40/59] Read-only UI logic for translations --- mapeditor/mapsettings/translations.cpp | 93 +++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/mapeditor/mapsettings/translations.cpp b/mapeditor/mapsettings/translations.cpp index e8f181551..6d4f6fc43 100644 --- a/mapeditor/mapsettings/translations.cpp +++ b/mapeditor/mapsettings/translations.cpp @@ -8,8 +8,12 @@ * */ +#include "StdInc.h" #include "translations.h" #include "ui_translations.h" +#include "../../lib/Languages.h" +#include "../../lib/CGeneralTextHandler.h" +#include "../../lib/VCMI_Lib.h" Translations::Translations(CMapHeader & mh, QWidget *parent) : QDialog(parent), @@ -17,6 +21,15 @@ Translations::Translations(CMapHeader & mh, QWidget *parent) : mapHeader(mh) { ui->setupUi(this); + + //fill languages list + for(auto & language : Languages::getLanguageList()) + { + ui->languageSelect->addItem(QString("%1 (%2)").arg(QString::fromStdString(language.nameEnglish), QString::fromStdString(language.nameNative))); + ui->languageSelect->setItemData(ui->languageSelect->count() - 1, QVariant(QString::fromStdString(language.identifier))); + if(language.identifier == VLC->generaltexth->getPreferredLanguage()) + ui->languageSelect->setCurrentIndex(ui->languageSelect->count() - 1); + } } Translations::~Translations() @@ -26,13 +39,89 @@ Translations::~Translations() void Translations::on_languageSelect_currentIndexChanged(int index) { - + auto language = ui->languageSelect->currentData().toString().toStdString(); + auto & translation = mapHeader.translations[language]; + bool hasLanguage = !translation.isNull(); + ui->supportedCheck->blockSignals(true); + ui->supportedCheck->setChecked(hasLanguage); + ui->supportedCheck->blockSignals(false); + ui->translationsTable->setEnabled(hasLanguage); + if(hasLanguage) + { + ui->translationsTable->blockSignals(true); + ui->translationsTable->setRowCount(translation.Struct().size()); + int i = 0; + for(auto & s : translation.Struct()) + { + auto * wId = new QTableWidgetItem(QString::fromStdString(s.first)); + auto * wText = new QTableWidgetItem(QString::fromStdString(s.second.String())); + wId->setFlags(wId->flags() & ~Qt::ItemIsEditable); + wText->setFlags(wId->flags() | Qt::ItemIsEditable); + ui->translationsTable->setItem(i, 0, wId); + ui->translationsTable->setItem(i++, 0, wText); + } + ui->translationsTable->blockSignals(false); + } } void Translations::on_supportedCheck_toggled(bool checked) { - + auto language = ui->languageSelect->currentData().toString().toStdString(); + auto & translation = mapHeader.translations[language]; + bool hasLanguage = !translation.isNull(); + bool hasRecord = !translation.Struct().empty(); + + if(checked) + { + if(!hasLanguage) + translation = JsonNode(JsonNode::JsonType::DATA_STRUCT); + + //copy from default language + translation = mapHeader.translations[VLC->generaltexth->getPreferredLanguage()]; + hasLanguage = !translation.isNull(); + + ui->translationsTable->blockSignals(true); + ui->translationsTable->setRowCount(translation.Struct().size()); + int i = 0; + for(auto & s : translation.Struct()) + { + auto * wId = new QTableWidgetItem(QString::fromStdString(s.first)); + auto * wText = new QTableWidgetItem(QString::fromStdString(s.second.String())); + wId->setFlags(wId->flags() & ~Qt::ItemIsEditable); + wText->setFlags(wId->flags() | Qt::ItemIsEditable); + ui->translationsTable->setItem(i, 0, wId); + ui->translationsTable->setItem(i++, 0, wText); + } + ui->translationsTable->blockSignals(false); + ui->translationsTable->setEnabled(hasLanguage); + } + else + { + bool canRemove = language != VLC->generaltexth->getPreferredLanguage(); + if(!canRemove) + { + QMessageBox::information(this, tr("Remove translation"), tr("Default language cannot be removed")); + } + else if(hasRecord) + { + auto sure = QMessageBox::question(this, tr("Remove translation"), tr("This language has text records which will be removed. Continue?")); + canRemove = sure != QMessageBox::No; + } + + if(!canRemove) + { + ui->supportedCheck->blockSignals(true); + ui->supportedCheck->setChecked(true); + ui->supportedCheck->blockSignals(false); + return; + } + ui->translationsTable->blockSignals(true); + ui->translationsTable->clear(); + translation = JsonNode(); + ui->translationsTable->blockSignals(false); + ui->translationsTable->setEnabled(false); + } } From 38d12bbe8c2d3c3e62ae40f5610a67b7e72c9dd7 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 22:48:52 +0200 Subject: [PATCH 41/59] Final changes for map editor translations --- mapeditor/icons/translations.png | Bin 1864 -> 2113 bytes mapeditor/mapsettings/translations.cpp | 84 +++++++++++++------------ mapeditor/mapsettings/translations.h | 2 + 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/mapeditor/icons/translations.png b/mapeditor/icons/translations.png index 0b031738683f700600f137a7ad8eea4deddda41d..1935db72fc3bfee1f6fafcbb6da3b6d95694351e 100644 GIT binary patch delta 975 zcmV;=12Fu^4#5zxfdqd8s!2paR9FecS4&8gQ52prp;b^&lz})1K~SJE5h%8;D0>)I zAoQeKw2F2`)J9rGMD&0%7lErbl}6OA3_)XPh=L-VN3}4Th+Ytt{e9o{oSEbJ|Ns1> zkq18Sx!*nKp7Wpk8e_3pmUwymd_I4toMXSCp<#<3|4%syz8HU#5}Scbz;mEJC^3x9 zNNVXFb@DK~X;(-lU5PxN0p9>m@CWTMa0;jgJ_El3uq`Y(_ymgE1G!wTKaog0g4H`< zy;wJ+B~*SwIRgwSf%cy0ok(E6;hh?#DsBnl@wkTx*3t`3Ph=t+T&UPnC=axQmeo>O z2j98U=|MZ9RSADhM2uF!;iljPT7t`=To-g!U@KtdH#$TI=RqaMWIkO+wk_m@x4uqy z6@bRcKgg(ZKMY5`qF@=9(1^hbg~EblGRdlXs6Z?ER4O$n9J@|xSsu*}`W~=Z8JmOl zDv;(l#)OX6AW$1DTa5KkI-NG%3DtcqlaP`5Q!M@$Ayt3qMm$|Vu5AHmma&R^z~(AG zIu|#&?+xpGWRUB`eQ;gCdYuD40z45_B>;Q9;6j7fFgTXrDC}_!hUY06P@zEo910!z$2Am{t z>@&nh=!<_$f68mhF$sPc>RW&@3ghGB!EB=nV8r%d_JwWpWVx+lJ#&4Uz*ga1HGtKp!i< zvi>3obYhe^ODl51bM4$!OI{>_HZeLndRGatOlZ?Zge{d#WTuK~OU|V5)92?E`ki=suoIp3I=^ums8sKi{J-`P{rB0NKXm`N3j&p=2zyKOZ zTK=!l1N3q-a%@c^``Cxv$0KweZ^_6+xAYgbFVW-X+*x8HY7)?vmrLV*^C4hAbl&|O z-=)bxFN$y{K+tB;&!%V`q|iwr6S;&WfhI?AO?-5M{Il6CpJ&>wfX`Uf$B^YVB@c=d xnfHB8qk3xzJ-uHd)dz(9y`PZ*= zp7WmPdCv23-}jbEld9c;d4$9^7=S0R5L%+PmW0jg)cA#c07pSP@Cm-B zu# zkqYSqO{`*6ge!lw7$wYx55{L~JO&lH1EO?vVpj~BuZdMewDSMZKl3y6swz0DQfOuG z!FQqP4$!qaR?W)wQ1bJZuX8#GXVgGz;3nLFjnIsA7c{wF&YCg&2p03?3LJx12By>l zdKIr#qe-PsuJOEpBltsbQo$e{!&Vp!u_EqXk`KY=v`&A11ZPQj4fW3IOX$1>e{N`J zt!_Sf=RtGmF13TC*LhXG1J%GpJH7+A8k~4Hd2u_l7O;<8AAb)1cAMjp*)fwfK+JQ$ z8>bxji7)EufCc?~u%EWb)gCg$iA1I@qs}#I5e+c|qWlf`lHe>D0bOX}cLBF`(yDc# zD>lUiYb$?t*kXl^IyWNihQ1@pUuGTj>rq=gx7e5|dIw_Paq<0@<_+enm(BX^YaHDl zo%=`FV8nTr66=Dy(2z8#Un;j*9dGkceX{WHP3~$mktR+4u}J$<(LwGD&~4XyKwnVT z0(1JY!=ScoA!${*;VMcbRUx39tH}qU{nT>&JrPVEHRi^xjfLn!zp=sf5lT}p4>ZOd zCFETwlanguageSelect->blockSignals(true); ui->languageSelect->addItem(QString("%1 (%2)").arg(QString::fromStdString(language.nameEnglish), QString::fromStdString(language.nameNative))); ui->languageSelect->setItemData(ui->languageSelect->count() - 1, QVariant(QString::fromStdString(language.identifier))); + ui->languageSelect->blockSignals(false); if(language.identifier == VLC->generaltexth->getPreferredLanguage()) ui->languageSelect->setCurrentIndex(ui->languageSelect->count() - 1); } @@ -37,31 +39,38 @@ Translations::~Translations() delete ui; } +void Translations::fillTranslationsTable(const std::string & language) +{ + auto & translation = mapHeader.translations[language]; + ui->translationsTable->blockSignals(true); + ui->translationsTable->setRowCount(0); + ui->translationsTable->setRowCount(translation.Struct().size()); + int i = 0; + for(auto & s : translation.Struct()) + { + auto * wId = new QTableWidgetItem(QString::fromStdString(s.first)); + auto * wText = new QTableWidgetItem(QString::fromStdString(s.second.String())); + wId->setFlags(wId->flags() & ~Qt::ItemIsEditable); + wText->setFlags(wId->flags() | Qt::ItemIsEditable); + ui->translationsTable->setItem(i, 0, wId); + ui->translationsTable->setItem(i++, 1, wText); + } + ui->translationsTable->resizeColumnToContents(0); + ui->translationsTable->blockSignals(false); +} + void Translations::on_languageSelect_currentIndexChanged(int index) { auto language = ui->languageSelect->currentData().toString().toStdString(); - auto & translation = mapHeader.translations[language]; - bool hasLanguage = !translation.isNull(); + bool hasLanguage = !mapHeader.translations[language].isNull(); ui->supportedCheck->blockSignals(true); ui->supportedCheck->setChecked(hasLanguage); ui->supportedCheck->blockSignals(false); ui->translationsTable->setEnabled(hasLanguage); if(hasLanguage) - { - ui->translationsTable->blockSignals(true); - ui->translationsTable->setRowCount(translation.Struct().size()); - int i = 0; - for(auto & s : translation.Struct()) - { - auto * wId = new QTableWidgetItem(QString::fromStdString(s.first)); - auto * wText = new QTableWidgetItem(QString::fromStdString(s.second.String())); - wId->setFlags(wId->flags() & ~Qt::ItemIsEditable); - wText->setFlags(wId->flags() | Qt::ItemIsEditable); - ui->translationsTable->setItem(i, 0, wId); - ui->translationsTable->setItem(i++, 0, wText); - } - ui->translationsTable->blockSignals(false); - } + fillTranslationsTable(language); + else + ui->translationsTable->setRowCount(0); } @@ -69,32 +78,15 @@ void Translations::on_supportedCheck_toggled(bool checked) { auto language = ui->languageSelect->currentData().toString().toStdString(); auto & translation = mapHeader.translations[language]; - bool hasLanguage = !translation.isNull(); bool hasRecord = !translation.Struct().empty(); if(checked) { - if(!hasLanguage) - translation = JsonNode(JsonNode::JsonType::DATA_STRUCT); - //copy from default language translation = mapHeader.translations[VLC->generaltexth->getPreferredLanguage()]; - hasLanguage = !translation.isNull(); - ui->translationsTable->blockSignals(true); - ui->translationsTable->setRowCount(translation.Struct().size()); - int i = 0; - for(auto & s : translation.Struct()) - { - auto * wId = new QTableWidgetItem(QString::fromStdString(s.first)); - auto * wText = new QTableWidgetItem(QString::fromStdString(s.second.String())); - wId->setFlags(wId->flags() & ~Qt::ItemIsEditable); - wText->setFlags(wId->flags() | Qt::ItemIsEditable); - ui->translationsTable->setItem(i, 0, wId); - ui->translationsTable->setItem(i++, 0, wText); - } - ui->translationsTable->blockSignals(false); - ui->translationsTable->setEnabled(hasLanguage); + fillTranslationsTable(language); + ui->translationsTable->setEnabled(true); } else { @@ -117,16 +109,28 @@ void Translations::on_supportedCheck_toggled(bool checked) return; } ui->translationsTable->blockSignals(true); - ui->translationsTable->clear(); - translation = JsonNode(); + ui->translationsTable->setRowCount(0); + translation = JsonNode(JsonNode::JsonType::DATA_NULL); ui->translationsTable->blockSignals(false); ui->translationsTable->setEnabled(false); } } -void Translations::on_translationsTable_itemChanged(QTableWidgetItem *item) +void Translations::on_translationsTable_itemChanged(QTableWidgetItem * item) { - + assert(item->column() == 1); + + auto language = ui->languageSelect->currentData().toString().toStdString(); + auto & translation = mapHeader.translations[language]; + + assert(!translation.isNull()); + + auto textId = ui->translationsTable->item(item->row(), 0)->text().toStdString(); + assert(!textId.empty()); + if(textId.empty()) + return; + + translation[textId].String() = item->text().toStdString(); } diff --git a/mapeditor/mapsettings/translations.h b/mapeditor/mapsettings/translations.h index 1444e6f25..27b9c8218 100644 --- a/mapeditor/mapsettings/translations.h +++ b/mapeditor/mapsettings/translations.h @@ -20,6 +20,8 @@ class Translations; class Translations : public QDialog { Q_OBJECT + + void fillTranslationsTable(const std::string & language); public: explicit Translations(CMapHeader & mapHeader, QWidget *parent = nullptr); From ae073ee35d6b04ad0268b84174646343e198b5a6 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Thu, 28 Sep 2023 23:15:36 +0200 Subject: [PATCH 42/59] Remove unused identifiers --- lib/mapping/MapFormatJson.cpp | 1 + mapeditor/mainwindow.cpp | 2 ++ mapeditor/mapsettings/translations.cpp | 39 ++++++++++++++++++++++++++ mapeditor/mapsettings/translations.h | 6 +++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 5ab9f6297..beb31cf7a 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1275,6 +1275,7 @@ void CMapLoaderJson::readTranslations() if(isExistArchive(language.identifier + ".json")) mapHeader->translations.Struct()[language.identifier] = getFromArchive(language.identifier + ".json"); } + mapHeader->registerMapStrings(); } diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 5a18d1fc3..f4048c8e3 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -400,6 +400,8 @@ void MainWindow::saveMap() else QMessageBox::information(this, "Map validation", "Map has some errors. Open Validator from the Map menu to see issues found"); } + + Translations::cleanupRemovedItems(*controller.map()); CMapService mapService; try diff --git a/mapeditor/mapsettings/translations.cpp b/mapeditor/mapsettings/translations.cpp index 0b77b4cfc..fd2ae9130 100644 --- a/mapeditor/mapsettings/translations.cpp +++ b/mapeditor/mapsettings/translations.cpp @@ -15,6 +15,44 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/VCMI_Lib.h" +void Translations::cleanupRemovedItems(CMap & map) +{ + std::set existingObjects; + for(auto object : map.objects) + existingObjects.insert(object->instanceName); + + for(auto & translations : map.translations.Struct()) + { + auto updateTranslations = JsonNode(JsonNode::JsonType::DATA_STRUCT); + for(auto & s : translations.second.Struct()) + { + for(auto part : QString::fromStdString(s.first).split('.')) + { + if(existingObjects.count(part.toStdString())) + { + updateTranslations.Struct()[s.first] = s.second; + break; + } + } + } + translations.second = updateTranslations; + } +} + +void Translations::cleanupRemovedItems(CMap & map, const std::string & pattern) +{ + for(auto & translations : map.translations.Struct()) + { + auto updateTranslations = JsonNode(JsonNode::JsonType::DATA_STRUCT); + for(auto & s : translations.second.Struct()) + { + if(s.first.find(pattern) == std::string::npos) + updateTranslations.Struct()[s.first] = s.second; + } + translations.second = updateTranslations; + } +} + Translations::Translations(CMapHeader & mh, QWidget *parent) : QDialog(parent), ui(new Ui::Translations), @@ -41,6 +79,7 @@ Translations::~Translations() void Translations::fillTranslationsTable(const std::string & language) { + Translations::cleanupRemovedItems(dynamic_cast(mapHeader)); auto & translation = mapHeader.translations[language]; ui->translationsTable->blockSignals(true); ui->translationsTable->setRowCount(0); diff --git a/mapeditor/mapsettings/translations.h b/mapeditor/mapsettings/translations.h index 27b9c8218..af63600c8 100644 --- a/mapeditor/mapsettings/translations.h +++ b/mapeditor/mapsettings/translations.h @@ -11,7 +11,7 @@ #pragma once #include -#include "../lib/mapping/CMapHeader.h" +#include "../lib/mapping/CMap.h" namespace Ui { class Translations; @@ -26,6 +26,10 @@ class Translations : public QDialog public: explicit Translations(CMapHeader & mapHeader, QWidget *parent = nullptr); ~Translations(); + + //removes unused string IDs from map translations + static void cleanupRemovedItems(CMap & map); + static void cleanupRemovedItems(CMap & map, const std::string & pattern); private slots: void on_languageSelect_currentIndexChanged(int index); From 70796d232bdcc4db104e6e3a45afafd55b0311f7 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 29 Sep 2023 00:24:45 +0200 Subject: [PATCH 43/59] Full support of maps translations --- lib/mapping/CMapHeader.cpp | 9 ++++--- lib/mapping/CMapHeader.h | 4 +-- lib/mapping/MapFormatH3M.cpp | 3 +-- mapeditor/inspector/inspector.cpp | 20 +++++++------- mapeditor/inspector/rewardswidget.cpp | 4 +-- mapeditor/mapsettings/eventsettings.cpp | 2 +- mapeditor/mapsettings/generalsettings.cpp | 4 +-- mapeditor/mapsettings/rumorsettings.cpp | 2 +- mapeditor/mapsettings/translations.cpp | 32 +++++++++++++++++------ mapeditor/mapsettings/translations.h | 1 + 10 files changed, 49 insertions(+), 32 deletions(-) diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index fe0583731..3f39bac7f 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -15,6 +15,7 @@ #include "../VCMI_Lib.h" #include "../CTownHandler.h" #include "../CGeneralTextHandler.h" +#include "../modding/CModHandler.h" #include "../CHeroHandler.h" #include "../Languages.h" @@ -168,14 +169,14 @@ void CMapHeader::registerMapStrings() registerString("map", TextIdentifier(s.first), s.second.String(), language); } -std::string mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized) +std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized) { - return mapRegisterLocalizedString(mapHeader, UID, localized, VLC->generaltexth->getPreferredLanguage()); + return mapRegisterLocalizedString(modContext, mapHeader, UID, localized, VLC->modh->getModLanguage(modContext)); } -std::string mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language) +std::string mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language) { - mapHeader.registerString("map", UID, localized, language); + mapHeader.registerString(modContext, UID, localized, language); mapHeader.translations.Struct()[language].Struct()[UID.get()].String() = localized; return UID.get(); } diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index fc915c09c..8977a71d2 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -281,7 +281,7 @@ public: }; /// wrapper functions to register string into the map and stores its translation -std::string DLL_LINKAGE mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized); -std::string DLL_LINKAGE mapRegisterLocalizedString(CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language); +std::string DLL_LINKAGE mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized); +std::string DLL_LINKAGE mapRegisterLocalizedString(const std::string & modContext, CMapHeader & mapHeader, const TextIdentifier & UID, const std::string & localized, const std::string & language); VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 5b44974ef..c6dcf2df0 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2274,8 +2274,7 @@ std::string CMapLoaderH3M::readLocalizedString(const TextIdentifier & stringIden if(mapString.empty()) return ""; - mapHeader->registerString(modName, fullIdentifier, mapString); - return fullIdentifier.get(); + return mapRegisterLocalizedString(modName, *mapHeader, fullIdentifier, mapString); } void CMapLoaderH3M::afterRead() diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 5f0e6ef54..8f2580308 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -531,7 +531,7 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) @@ -553,7 +553,7 @@ void Inspector::setProperty(CGTownInstance * o, const QString & key, const QVari if(!o) return; if(key == "Town name") - o->setNameTextId(mapRegisterLocalizedString(*map, TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); + o->setNameTextId(mapRegisterLocalizedString("map", *map, TextIdentifier("town", o->instanceName, "name"), value.toString().toStdString())); } void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVariant & value) @@ -561,7 +561,7 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value) @@ -577,7 +577,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); if(o->storedArtifact && key == "Spell") { @@ -606,10 +606,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari o->gender = EHeroGender(value.toInt()); if(key == "Name") - o->nameCustomTextId = mapRegisterLocalizedString(*map, TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); + o->nameCustomTextId = mapRegisterLocalizedString("map", *map, TextIdentifier("hero", o->instanceName, "name"), value.toString().toStdString()); if(key == "Biography") - o->biographyCustomTextId = mapRegisterLocalizedString(*map, TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); + o->biographyCustomTextId = mapRegisterLocalizedString("map", *map, TextIdentifier("hero", o->instanceName, "biography"), value.toString().toStdString()); if(key == "Experience") o->exp = value.toString().toInt(); @@ -646,7 +646,7 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); + o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); if(key == "Character") o->character = CGCreature::Character(value.toInt()); if(key == "Never flees") @@ -664,11 +664,11 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant & if(key == "Mission type") o->quest->missionType = CQuest::Emission(value.toInt()); if(key == "First visit text") - o->quest->firstVisitText.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); + o->quest->firstVisitText.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); if(key == "Next visit text") - o->quest->nextVisitText.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); + o->quest->nextVisitText.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); if(key == "Completed text") - o->quest->completedText.appendTextID(mapRegisterLocalizedString(*map, TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); + o->quest->completedText.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); } diff --git a/mapeditor/inspector/rewardswidget.cpp b/mapeditor/inspector/rewardswidget.cpp index b8990b135..cfd900326 100644 --- a/mapeditor/inspector/rewardswidget.cpp +++ b/mapeditor/inspector/rewardswidget.cpp @@ -211,7 +211,7 @@ bool RewardsWidget::commitChanges() if(ui->onSelectText->text().isEmpty()) object.configuration.onSelect.clear(); else - object.configuration.onSelect = MetaString::createFromTextID(mapRegisterLocalizedString(map, TextIdentifier("reward", object.instanceName, "onSelect"), ui->onSelectText->text().toStdString())); + object.configuration.onSelect = MetaString::createFromTextID(mapRegisterLocalizedString("map", map, TextIdentifier("reward", object.instanceName, "onSelect"), ui->onSelectText->text().toStdString())); object.configuration.canRefuse = ui->canRefuse->isChecked(); //reset parameters @@ -232,7 +232,7 @@ void RewardsWidget::saveCurrentVisitInfo(int index) if(ui->rewardMessage->text().isEmpty()) vinfo.message.clear(); else - vinfo.message = MetaString::createFromTextID(mapRegisterLocalizedString(map, TextIdentifier("reward", object.instanceName, "info", index, "message"), ui->rewardMessage->text().toStdString())); + vinfo.message = MetaString::createFromTextID(mapRegisterLocalizedString("map", map, TextIdentifier("reward", object.instanceName, "info", index, "message"), ui->rewardMessage->text().toStdString())); vinfo.reward.heroLevel = ui->rHeroLevel->value(); vinfo.reward.heroExperience = ui->rHeroExperience->value(); diff --git a/mapeditor/mapsettings/eventsettings.cpp b/mapeditor/mapsettings/eventsettings.cpp index 60cd97982..a128aa6ef 100644 --- a/mapeditor/mapsettings/eventsettings.cpp +++ b/mapeditor/mapsettings/eventsettings.cpp @@ -52,7 +52,7 @@ CMapEvent eventFromVariant(CMapHeader & mapHeader, const QVariant & variant) CMapEvent result; auto v = variant.toMap(); result.name = v.value("name").toString().toStdString(); - result.message.appendTextID(mapRegisterLocalizedString(mapHeader, TextIdentifier("header", "event", result.name, "message"), v.value("message").toString().toStdString())); + result.message.appendTextID(mapRegisterLocalizedString("map", mapHeader, TextIdentifier("header", "event", result.name, "message"), v.value("message").toString().toStdString())); result.players = v.value("players").toInt(); result.humanAffected = v.value("humanAffected").toInt(); result.computerAffected = v.value("computerAffected").toInt(); diff --git a/mapeditor/mapsettings/generalsettings.cpp b/mapeditor/mapsettings/generalsettings.cpp index 8f74867dc..dd5cff493 100644 --- a/mapeditor/mapsettings/generalsettings.cpp +++ b/mapeditor/mapsettings/generalsettings.cpp @@ -59,8 +59,8 @@ void GeneralSettings::initialize(MapController & c) void GeneralSettings::update() { - controller->map()->name.appendTextID(mapRegisterLocalizedString(*controller->map(), TextIdentifier("header", "name"), ui->mapNameEdit->text().toStdString())); - controller->map()->description.appendTextID(mapRegisterLocalizedString(*controller->map(), TextIdentifier("header", "description"), ui->mapDescriptionEdit->toPlainText().toStdString())); + controller->map()->name.appendTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "name"), ui->mapNameEdit->text().toStdString())); + controller->map()->description.appendTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "description"), ui->mapDescriptionEdit->toPlainText().toStdString())); if(ui->heroLevelLimitCheck->isChecked()) controller->map()->levelLimit = ui->heroLevelLimit->value(); else diff --git a/mapeditor/mapsettings/rumorsettings.cpp b/mapeditor/mapsettings/rumorsettings.cpp index 6527c67f1..7d47cc6c9 100644 --- a/mapeditor/mapsettings/rumorsettings.cpp +++ b/mapeditor/mapsettings/rumorsettings.cpp @@ -43,7 +43,7 @@ void RumorSettings::update() { Rumor rumor; rumor.name = ui->rumors->item(i)->text().toStdString(); - rumor.text.appendTextID(mapRegisterLocalizedString(*controller->map(), TextIdentifier("header", "rumor", i, "text"), ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString())); + rumor.text.appendTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "rumor", i, "text"), ui->rumors->item(i)->data(Qt::UserRole).toString().toStdString())); controller->map()->rumors.push_back(rumor); } } diff --git a/mapeditor/mapsettings/translations.cpp b/mapeditor/mapsettings/translations.cpp index fd2ae9130..b314c90c8 100644 --- a/mapeditor/mapsettings/translations.cpp +++ b/mapeditor/mapsettings/translations.cpp @@ -28,7 +28,7 @@ void Translations::cleanupRemovedItems(CMap & map) { for(auto part : QString::fromStdString(s.first).split('.')) { - if(existingObjects.count(part.toStdString())) + if(part == "map" || existingObjects.count(part.toStdString())) { updateTranslations.Struct()[s.first] = s.second; break; @@ -61,15 +61,31 @@ Translations::Translations(CMapHeader & mh, QWidget *parent) : ui->setupUi(this); //fill languages list + std::set indexFoundLang; + int foundLang = -1; + ui->languageSelect->blockSignals(true); for(auto & language : Languages::getLanguageList()) { - ui->languageSelect->blockSignals(true); ui->languageSelect->addItem(QString("%1 (%2)").arg(QString::fromStdString(language.nameEnglish), QString::fromStdString(language.nameNative))); ui->languageSelect->setItemData(ui->languageSelect->count() - 1, QVariant(QString::fromStdString(language.identifier))); - ui->languageSelect->blockSignals(false); + if(mapHeader.translations.Struct().count(language.identifier) && !mapHeader.translations[language.identifier].Struct().empty()) + indexFoundLang.insert(ui->languageSelect->count() - 1); if(language.identifier == VLC->generaltexth->getPreferredLanguage()) - ui->languageSelect->setCurrentIndex(ui->languageSelect->count() - 1); + foundLang = ui->languageSelect->count() - 1; } + ui->languageSelect->blockSignals(false); + + if(foundLang >= 0 && !indexFoundLang.empty() && !indexFoundLang.count(foundLang)) + { + foundLang = *indexFoundLang.begin(); + mapPreferredLanguage = ui->languageSelect->itemData(foundLang).toString().toStdString(); + } + + if(foundLang >= 0) + ui->languageSelect->setCurrentIndex(foundLang); + + if(mapPreferredLanguage.empty()) + mapPreferredLanguage = VLC->generaltexth->getPreferredLanguage(); } Translations::~Translations() @@ -101,7 +117,7 @@ void Translations::fillTranslationsTable(const std::string & language) void Translations::on_languageSelect_currentIndexChanged(int index) { auto language = ui->languageSelect->currentData().toString().toStdString(); - bool hasLanguage = !mapHeader.translations[language].isNull(); + bool hasLanguage = mapHeader.translations.Struct().count(language); ui->supportedCheck->blockSignals(true); ui->supportedCheck->setChecked(hasLanguage); ui->supportedCheck->blockSignals(false); @@ -122,21 +138,21 @@ void Translations::on_supportedCheck_toggled(bool checked) if(checked) { //copy from default language - translation = mapHeader.translations[VLC->generaltexth->getPreferredLanguage()]; + translation = mapHeader.translations[mapPreferredLanguage]; fillTranslationsTable(language); ui->translationsTable->setEnabled(true); } else { - bool canRemove = language != VLC->generaltexth->getPreferredLanguage(); + bool canRemove = language != mapPreferredLanguage; if(!canRemove) { QMessageBox::information(this, tr("Remove translation"), tr("Default language cannot be removed")); } else if(hasRecord) { - auto sure = QMessageBox::question(this, tr("Remove translation"), tr("This language has text records which will be removed. Continue?")); + auto sure = QMessageBox::question(this, tr("Remove translation"), tr("All existing text records for this language will be removed. Continue?")); canRemove = sure != QMessageBox::No; } diff --git a/mapeditor/mapsettings/translations.h b/mapeditor/mapsettings/translations.h index af63600c8..85e70ba1b 100644 --- a/mapeditor/mapsettings/translations.h +++ b/mapeditor/mapsettings/translations.h @@ -41,4 +41,5 @@ private slots: private: Ui::Translations *ui; CMapHeader & mapHeader; + std::string mapPreferredLanguage; }; From 9631176e5828a82bd5b30396ce0691c57377bb44 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 29 Sep 2023 00:28:09 +0200 Subject: [PATCH 44/59] Fix tests --- test/map/CMapFormatTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/map/CMapFormatTest.cpp b/test/map/CMapFormatTest.cpp index 26082c8c9..647a76907 100644 --- a/test/map/CMapFormatTest.cpp +++ b/test/map/CMapFormatTest.cpp @@ -65,7 +65,7 @@ TEST(MapFormat, Random) CMapGenerator gen(opt, TEST_RANDOM_SEED); std::unique_ptr initialMap = gen.generate(); - initialMap->name = "Test"; + initialMap->name.appendRawString("Test"); SCOPED_TRACE("MapFormat_Random generated"); CMemoryBuffer serializeBuffer; From f3fa0f8652a140f938263685c21a379a96dbbcae Mon Sep 17 00:00:00 2001 From: nordsoft Date: Fri, 29 Sep 2023 19:49:18 +0200 Subject: [PATCH 45/59] Allow to reconnect to proxy server --- client/CServerHandler.cpp | 3 +++ server/CVCMIServer.cpp | 16 ++++++++-------- server/CVCMIServer.h | 3 ++- server/NetPacksLobbyServer.cpp | 6 ++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 952b6feb1..356262976 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -417,6 +417,9 @@ void CServerHandler::sendClientDisconnecting() logNetwork->info("Sent leaving signal to the server"); } sendLobbyPack(lcd); + + c->close(); + c.reset(); } void CServerHandler::setCampaignState(std::shared_ptr newCampaign) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 5b3eecdef..45dac8b32 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -211,21 +211,20 @@ void CVCMIServer::establishRemoteConnections() uuid = cmdLineOptions["lobby-uuid"].as(); int numOfConnections = cmdLineOptions["connections"].as(); - auto address = cmdLineOptions["lobby"].as(); - int port = cmdLineOptions["lobby-port"].as(); - logGlobal->info("Server is connecting to remote at %s:%d with uuid %s %d times", address, port, uuid, numOfConnections); - for(int i = 0; i < numOfConnections; ++i) - connectToRemote(address, port); + connectToRemote(); } -void CVCMIServer::connectToRemote(const std::string & addr, int port) +void CVCMIServer::connectToRemote() { std::shared_ptr c; try { - logNetwork->info("Establishing connection..."); - c = std::make_shared(addr, port, SERVER_NAME, uuid); + auto address = cmdLineOptions["lobby"].as(); + int port = cmdLineOptions["lobby-port"].as(); + + logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid); + c = std::make_shared(address, port, SERVER_NAME, uuid); } catch(...) { @@ -235,6 +234,7 @@ void CVCMIServer::connectToRemote(const std::string & addr, int port) if(c) { connections.insert(c); + remoteConnections.insert(c); c->handler = std::make_shared(&CVCMIServer::threadHandleClient, this, c); } } diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 450fe3732..4e3295aa0 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -64,6 +64,7 @@ public: boost::program_options::variables_map cmdLineOptions; std::set> connections; + std::set> remoteConnections; std::set> hangingConnections; //keep connections of players disconnected during the game std::atomic currentClientId; @@ -78,7 +79,7 @@ public: void startGameImmidiately(); void establishRemoteConnections(); - void connectToRemote(const std::string & addr, int port); + void connectToRemote(); void startAsyncAccept(); void connectionAccepted(const boost::system::error_code & ec); void threadHandleClient(std::shared_ptr c); diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index dbb68d617..804874f94 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -189,6 +189,12 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(Lobb srv.addToAnnounceQueue(std::move(ph)); } srv.updateAndPropagateLobbyState(); + + if(srv.getState() != EServerState::SHUTDOWN && srv.remoteConnections.count(pack.c)) + { + srv.remoteConnections -= pack.c; + srv.connectToRemote(); + } } void ClientPermissionsCheckerNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage & pack) From ce62ab3e66c91a1ef1ff4689fa834a459e04d2f1 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 30 Sep 2023 01:19:18 +0200 Subject: [PATCH 46/59] Select random object template instead of first --- lib/rmg/RmgObject.cpp | 21 +++++++++++---------- lib/rmg/RmgObject.h | 11 ++++++----- lib/rmg/modificators/ConnectionsPlacer.cpp | 4 ++-- lib/rmg/modificators/ObjectManager.cpp | 10 +++++----- lib/rmg/modificators/RiverPlacer.cpp | 2 +- lib/rmg/modificators/TownPlacer.cpp | 2 +- lib/rmg/modificators/WaterProxy.cpp | 4 ++-- 7 files changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/rmg/RmgObject.cpp b/lib/rmg/RmgObject.cpp index 31e287e06..9934602a5 100644 --- a/lib/rmg/RmgObject.cpp +++ b/lib/rmg/RmgObject.cpp @@ -111,18 +111,18 @@ void Object::Instance::setPositionRaw(const int3 & position) dObject.pos = dPosition + dParent.getPosition(); } -void Object::Instance::setAnyTemplate() +void Object::Instance::setAnyTemplate(CRandomGenerator & rng) { auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(); if(templates.empty()) throw rmgException(boost::str(boost::format("Did not find any graphics for object (%d,%d)") % dObject.ID % dObject.subID)); - dObject.appearance = templates.front(); + dObject.appearance = *RandomGeneratorUtil::nextItem(templates, rng); dAccessibleAreaCache.clear(); setPosition(getPosition(false)); } -void Object::Instance::setTemplate(TerrainId terrain) +void Object::Instance::setTemplate(TerrainId terrain, CRandomGenerator & rng) { auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain); if (templates.empty()) @@ -130,7 +130,8 @@ void Object::Instance::setTemplate(TerrainId terrain) auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getNameTranslated(); throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName)); } - dObject.appearance = templates.front(); + + dObject.appearance = *RandomGeneratorUtil::nextItem(templates, rng); dAccessibleAreaCache.clear(); setPosition(getPosition(false)); } @@ -280,10 +281,10 @@ void Object::setPosition(const int3 & position) i.setPositionRaw(i.getPosition()); } -void Object::setTemplate(const TerrainId & terrain) +void Object::setTemplate(const TerrainId & terrain, CRandomGenerator & rng) { for(auto& i : dInstances) - i.setTemplate(terrain); + i.setTemplate(terrain, rng); } const Area & Object::getArea() const @@ -325,7 +326,7 @@ void rmg::Object::setGuardedIfMonster(const Instance& object) } } -void Object::Instance::finalize(RmgMap & map) +void Object::Instance::finalize(RmgMap & map, CRandomGenerator & rng) { if(!map.isOnMap(getPosition(true))) throw rmgException(boost::str(boost::format("Position of object %d at %s is outside the map") % dObject.id % getPosition(true).toString())); @@ -341,7 +342,7 @@ void Object::Instance::finalize(RmgMap & map) } else { - setTemplate(terrainType->getId()); + setTemplate(terrainType->getId(), rng); } } @@ -362,14 +363,14 @@ void Object::Instance::finalize(RmgMap & map) map.getMapProxy()->insertObject(&dObject); } -void Object::finalize(RmgMap & map) +void Object::finalize(RmgMap & map, CRandomGenerator & rng) { if(dInstances.empty()) throw rmgException("Cannot finalize object without instances"); for(auto & dInstance : dInstances) { - dInstance.finalize(map); + dInstance.finalize(map, rng); } } diff --git a/lib/rmg/RmgObject.h b/lib/rmg/RmgObject.h index d444a90b4..2ba78a29a 100644 --- a/lib/rmg/RmgObject.h +++ b/lib/rmg/RmgObject.h @@ -17,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGObjectInstance; +class CRandomGenerator; class RmgMap; namespace rmg { @@ -35,8 +36,8 @@ public: int3 getVisitablePosition() const; bool isVisitableFrom(const int3 & tile) const; const Area & getAccessibleArea() const; - void setTemplate(TerrainId terrain); //cache invalidation - void setAnyTemplate(); //cache invalidation + void setTemplate(TerrainId terrain, CRandomGenerator &); //cache invalidation + void setAnyTemplate(CRandomGenerator &); //cache invalidation int3 getTopTile() const; int3 getPosition(bool isAbsolute = false) const; @@ -45,7 +46,7 @@ public: const CGObjectInstance & object() const; CGObjectInstance & object(); - void finalize(RmgMap & map); //cache invalidation + void finalize(RmgMap & map, CRandomGenerator &); //cache invalidation void clear(); private: @@ -73,7 +74,7 @@ public: const int3 & getPosition() const; void setPosition(const int3 & position); - void setTemplate(const TerrainId & terrain); + void setTemplate(const TerrainId & terrain, CRandomGenerator &); const Area & getArea() const; //lazy cache invalidation const int3 getVisibleTop() const; @@ -81,7 +82,7 @@ public: bool isGuarded() const; void setGuardedIfMonster(const Instance & object); - void finalize(RmgMap & map); + void finalize(RmgMap & map, CRandomGenerator &); void clear(); private: diff --git a/lib/rmg/modificators/ConnectionsPlacer.cpp b/lib/rmg/modificators/ConnectionsPlacer.cpp index 5526d2731..44b1e11d2 100644 --- a/lib/rmg/modificators/ConnectionsPlacer.cpp +++ b/lib/rmg/modificators/ConnectionsPlacer.cpp @@ -316,8 +316,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c auto * gate2 = factory->create(); rmg::Object rmgGate1(*gate1); rmg::Object rmgGate2(*gate2); - rmgGate1.setTemplate(zone.getTerrainType()); - rmgGate2.setTemplate(otherZone->getTerrainType()); + rmgGate1.setTemplate(zone.getTerrainType(), zone.getRand()); + rmgGate2.setTemplate(otherZone->getTerrainType(), zone.getRand()); bool guarded1 = manager.addGuard(rmgGate1, connection.getGuardStrength(), true); bool guarded2 = managerOther.addGuard(rmgGate2, connection.getGuardStrength(), true); int minDist = 3; diff --git a/lib/rmg/modificators/ObjectManager.cpp b/lib/rmg/modificators/ObjectManager.cpp index 02267aaa8..9d34b3833 100644 --- a/lib/rmg/modificators/ObjectManager.cpp +++ b/lib/rmg/modificators/ObjectManager.cpp @@ -354,7 +354,7 @@ bool ObjectManager::createRequiredObjects() for(const auto & objInfo : requiredObjects) { rmg::Object rmgObject(*objInfo.obj); - rmgObject.setTemplate(zone.getTerrainType()); + rmgObject.setTemplate(zone.getTerrainType(), zone.getRand()); bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY)); Zone::Lock lock(zone.areaMutex); @@ -394,7 +394,7 @@ bool ObjectManager::createRequiredObjects() auto possibleArea = zone.areaPossible(); rmg::Object rmgObject(*objInfo.obj); - rmgObject.setTemplate(zone.getTerrainType()); + rmgObject.setTemplate(zone.getTerrainType(), zone.getRand()); bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY)); auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, [this, &rmgObject](const int3 & tile) @@ -480,7 +480,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD if (!monster->object().appearance) { //Needed to determine visitable offset - monster->setAnyTemplate(); + monster->setAnyTemplate(zone.getRand()); } object.getPosition(); auto visitableOffset = monster->object().getVisitableOffset(); @@ -492,7 +492,7 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD int3 parentOffset = monster->getPosition(true) - monster->getPosition(false); monster->setPosition(fixedPos - parentOffset); } - object.finalize(map); + object.finalize(map, zone.getRand()); Zone::Lock lock(zone.areaMutex); zone.areaPossible().subtract(object.getArea()); @@ -689,7 +689,7 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard }); auto & instance = object.addInstance(*guard); - instance.setAnyTemplate(); //terrain is irrelevant for monsters, but monsters need some template now + instance.setAnyTemplate(zone.getRand()); //terrain is irrelevant for monsters, but monsters need some template now //Fix HoTA monsters with offset template auto visitableOffset = instance.object().getVisitableOffset(); diff --git a/lib/rmg/modificators/RiverPlacer.cpp b/lib/rmg/modificators/RiverPlacer.cpp index 0738bdc72..d9c7af3e1 100644 --- a/lib/rmg/modificators/RiverPlacer.cpp +++ b/lib/rmg/modificators/RiverPlacer.cpp @@ -397,7 +397,7 @@ void RiverPlacer::connectRiver(const int3 & tile) { auto * obj = handler->create(templ); rmg::Object deltaObj(*obj, deltaPositions[pos]); - deltaObj.finalize(map); + deltaObj.finalize(map, zone.getRand()); } } } diff --git a/lib/rmg/modificators/TownPlacer.cpp b/lib/rmg/modificators/TownPlacer.cpp index 1f046588e..76bf3bbec 100644 --- a/lib/rmg/modificators/TownPlacer.cpp +++ b/lib/rmg/modificators/TownPlacer.cpp @@ -140,7 +140,7 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town) { //towns are big objects and should be centered around visitable position rmg::Object rmgObject(town); - rmgObject.setTemplate(zone.getTerrainType()); + rmgObject.setTemplate(zone.getTerrainType(), zone.getRand()); int3 position(-1, -1, -1); { diff --git a/lib/rmg/modificators/WaterProxy.cpp b/lib/rmg/modificators/WaterProxy.cpp index 023a13af1..839511d7c 100644 --- a/lib/rmg/modificators/WaterProxy.cpp +++ b/lib/rmg/modificators/WaterProxy.cpp @@ -254,7 +254,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, bool createRoad, Rout auto * boat = dynamic_cast(VLC->objtypeh->getHandlerFor(Obj::BOAT, *RandomGeneratorUtil::nextItem(sailingBoatTypes, zone.getRand()))->create()); rmg::Object rmgObject(*boat); - rmgObject.setTemplate(zone.getTerrainType()); + rmgObject.setTemplate(zone.getTerrainType(), zone.getRand()); auto waterAvailable = zone.areaPossible() + zone.freePaths(); rmg::Area coast = lake.neighbourZones.at(land.getId()); //having land tiles @@ -319,7 +319,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool shipyard->tempOwner = PlayerColor::NEUTRAL; rmg::Object rmgObject(*shipyard); - rmgObject.setTemplate(land.getTerrainType()); + rmgObject.setTemplate(land.getTerrainType(), zone.getRand()); bool guarded = manager->addGuard(rmgObject, guard); auto waterAvailable = zone.areaPossible() + zone.freePaths(); From 71ea057ad1b651851648820f186098509417023a Mon Sep 17 00:00:00 2001 From: Alexander Wilms Date: Sat, 30 Sep 2023 01:27:30 +0200 Subject: [PATCH 47/59] CI: Install all packages at once on Linux --- CI/linux-qt6/before_install.sh | 11 +++++------ CI/linux/before_install.sh | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CI/linux-qt6/before_install.sh b/CI/linux-qt6/before_install.sh index 756b42eb3..689101138 100644 --- a/CI/linux-qt6/before_install.sh +++ b/CI/linux-qt6/before_install.sh @@ -3,9 +3,8 @@ sudo apt-get update # Dependencies -sudo apt-get install libboost-all-dev -sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev -sudo apt-get install qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools -sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev -# Optional dependencies -sudo apt-get install libminizip-dev libfuzzylite-dev +sudo apt-get install libboost-all-dev \ +libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ +qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools \ +ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ +libminizip-dev libfuzzylite-dev # Optional dependencies diff --git a/CI/linux/before_install.sh b/CI/linux/before_install.sh index 8b0c75d59..e08075d7d 100644 --- a/CI/linux/before_install.sh +++ b/CI/linux/before_install.sh @@ -3,9 +3,8 @@ sudo apt-get update # Dependencies -sudo apt-get install libboost-all-dev -sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev -sudo apt-get install qtbase5-dev -sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev -# Optional dependencies -sudo apt-get install libminizip-dev libfuzzylite-dev qttools5-dev +sudo apt-get install libboost-all-dev \ +libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \ +qtbase5-dev \ +ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev \ +libminizip-dev libfuzzylite-dev qttools5-dev # Optional dependencies From 1502ea22506e5fe1176f10e2eb1e458a86b99fe2 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 30 Sep 2023 01:33:12 +0200 Subject: [PATCH 48/59] Fix pandora mana problem --- lib/mapObjects/CGPandoraBox.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index de2a00448..9d1a9874c 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -101,7 +101,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b } } - if(vi.reward.manaDiff || vi.reward.manaPercentage) + if(vi.reward.manaDiff || vi.reward.manaPercentage >= 0) txt = setText(temp.manaDiff > 0, 177, 176, h); for(auto b : vi.reward.bonuses) @@ -155,7 +155,7 @@ void CGPandoraBox::grantRewardWithMessage(const CGHeroInstance * h, int index, b temp.resources.amin(0); temp.resources.amax(0); temp.manaDiff = 0; - temp.manaPercentage = 0; + temp.manaPercentage = -1; temp.spells.clear(); temp.creatures.clear(); temp.bonuses.clear(); From 53dadc6dc37544fcd5c9cc86f9d5f1e43b586f00 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 30 Sep 2023 03:42:29 +0200 Subject: [PATCH 49/59] Add map converter --- mapeditor/mainwindow.cpp | 82 ++++++++++++++++++++++++++-------------- mapeditor/mainwindow.h | 6 ++- mapeditor/mainwindow.ui | 6 +++ 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index f4048c8e3..90bad7b69 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -313,7 +313,7 @@ void MainWindow::initializeMap(bool isNew) onPlayersChanged(); } -bool MainWindow::openMap(const QString & filenameSelect) +std::unique_ptr MainWindow::openMapInternal(const QString & filenameSelect) { QFileInfo fi(filenameSelect); std::string fname = fi.fileName().toStdString(); @@ -327,26 +327,30 @@ bool MainWindow::openMap(const QString & filenameSelect) CResourceHandler::addFilesystem("local", "mapEditor", mapEditorFilesystem); if(!CResourceHandler::get("mapEditor")->existsResource(resId)) - { - QMessageBox::warning(this, tr("Failed to open map"), tr("Cannot open map from this folder")); - return false; - } + throw std::runtime_error("Cannot open map from this folder"); CMapService mapService; + if(auto header = mapService.loadMapHeader(resId)) + { + auto missingMods = CMapService::verifyMapHeaderMods(*header); + ModIncompatibility::ModListWithVersion modList; + for(const auto & m : missingMods) + modList.push_back({m.second.name, m.second.version.toString()}); + + if(!modList.empty()) + throw ModIncompatibility(modList); + + return mapService.loadMap(resId); + } + else + throw std::runtime_error("Corrupted map"); +} + +bool MainWindow::openMap(const QString & filenameSelect) +{ try { - if(auto header = mapService.loadMapHeader(resId)) - { - auto missingMods = CMapService::verifyMapHeaderMods(*header); - ModIncompatibility::ModListWithVersion modList; - for(const auto & m : missingMods) - modList.push_back({m.second.name, m.second.version.toString()}); - - if(!modList.empty()) - throw ModIncompatibility(modList); - - controller.setMap(mapService.loadMap(resId)); - } + controller.setMap(openMapInternal(filenameSelect)); } catch(const ModIncompatibility & e) { @@ -356,7 +360,7 @@ bool MainWindow::openMap(const QString & filenameSelect) } catch(const std::exception & e) { - QMessageBox::critical(this, "Failed to open map", e.what()); + QMessageBox::critical(this, "Failed to open map", tr(e.what())); return false; } @@ -423,7 +427,7 @@ void MainWindow::on_actionSave_as_triggered() if(!controller.map()) return; - auto filenameSelect = QFileDialog::getSaveFileName(this, tr("Save map"), "", tr("VCMI maps (*.vmap)")); + auto filenameSelect = QFileDialog::getSaveFileName(this, tr("Save map"), lastSavingDir, tr("VCMI maps (*.vmap)")); if(filenameSelect.isNull()) return; @@ -432,6 +436,7 @@ void MainWindow::on_actionSave_as_triggered() return; filename = filenameSelect; + lastSavingDir = filenameSelect.remove(QUrl(filenameSelect).fileName()); saveMap(); } @@ -449,16 +454,9 @@ void MainWindow::on_actionSave_triggered() return; if(filename.isNull()) - { - auto filenameSelect = QFileDialog::getSaveFileName(this, tr("Save map"), "", tr("VCMI maps (*.vmap)")); - - if(filenameSelect.isNull()) - return; - - filename = filenameSelect; - } - - saveMap(); + on_actionSave_as_triggered(); + else + saveMap(); } void MainWindow::terrainButtonClicked(TerrainId terrain) @@ -1250,3 +1248,29 @@ void MainWindow::on_actionTranslations_triggered() translationsDialog->show(); } +void MainWindow::on_actionh3m_coverter_triggered() +{ + auto mapFiles = QFileDialog::getOpenFileNames(this, tr("Select maps to covert"), + QString::fromStdString(VCMIDirs::get().userCachePath().make_preferred().string()), + tr("HoMM3 maps(*.h3m)")); + if(mapFiles.empty()) + return; + + auto saveDirectory = QFileDialog::getExistingDirectory(this, tr("Choose directory to save coverted maps"), QCoreApplication::applicationDirPath()); + if(saveDirectory.isEmpty()) + return; + + try + { + for(auto & m : mapFiles) + { + CMapService mapService; + mapService.saveMap(openMapInternal(m), (saveDirectory + QFileInfo(m).fileName()).toStdString()); + } + } + catch(const std::exception & e) + { + QMessageBox::critical(this, tr("Failed to convert the map. Abort operation"), tr(e.what())); + } +} + diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index 3954ca173..74b051d80 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -31,6 +31,8 @@ class MainWindow : public QMainWindow #ifdef ENABLE_QT_TRANSLATIONS QTranslator translator; #endif + + std::unique_ptr openMapInternal(const QString &); public: explicit MainWindow(QWidget *parent = nullptr); @@ -118,6 +120,8 @@ private slots: void on_actionExport_triggered(); void on_actionTranslations_triggered(); + + void on_actionh3m_coverter_triggered(); public slots: @@ -155,7 +159,7 @@ private: ObjectBrowserProxyModel * objectBrowser = nullptr; QGraphicsScene * scenePreview; - QString filename; + QString filename, lastSavingDir; bool unsaved = false; QStandardItemModel objectsModel; diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 6f7b0da5f..6017a6a9e 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -63,6 +63,7 @@ + @@ -1254,6 +1255,11 @@ Ctrl+T + + + h3m coverter + + From 30181789562855179e653ff39d7f8caf82b107ed Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 30 Sep 2023 03:44:13 +0200 Subject: [PATCH 50/59] Remember folder for images as well --- mapeditor/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 90bad7b69..0dcf519d2 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -1231,7 +1231,7 @@ void MainWindow::on_actionPaste_triggered() void MainWindow::on_actionExport_triggered() { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save to image"), QCoreApplication::applicationDirPath(), "BMP (*.bmp);;JPEG (*.jpeg);;PNG (*.png)"); + QString fileName = QFileDialog::getSaveFileName(this, tr("Save to image"), lastSavingDir, "BMP (*.bmp);;JPEG (*.jpeg);;PNG (*.png)"); if(!fileName.isNull()) { QImage image(ui->mapView->scene()->sceneRect().size().toSize(), QImage::Format_RGB888); From cb02e221f3bbdbb89265da428417f8db45a2b657 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 30 Sep 2023 04:33:41 +0200 Subject: [PATCH 51/59] Couple fixes --- mapeditor/mapcontroller.cpp | 14 ++++++++++---- mapeditor/windownewmap.cpp | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index b1b86212a..0007da3b1 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -105,7 +105,9 @@ void MapController::repairMap() } //fix owners for objects - for(auto obj : _map->objects) + auto allImpactedObjects(_map->objects); + allImpactedObjects.insert(allImpactedObjects.end(), _map->predefinedHeroes.begin(), _map->predefinedHeroes.end()); + for(auto obj : allImpactedObjects) { //setup proper names (hero name will be fixed later if(obj->ID != Obj::HERO && obj->ID != Obj::PRISON && (obj->typeName.empty() || obj->subTypeName.empty())) @@ -154,12 +156,16 @@ void MapController::repairMap() if(nih->spellbookContainsSpell(SpellID::PRESET)) { nih->removeSpellFromSpellbook(SpellID::PRESET); - } - else - { for(auto spellID : type->spells) nih->addSpellToSpellbook(spellID); } + if(nih->spellbookContainsSpell(SpellID::SPELLBOOK_PRESET)) + { + nih->removeSpellFromSpellbook(SpellID::SPELLBOOK_PRESET); + if(!nih->getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) + nih->putArtifact(ArtifactPosition::SPELLBOOK, ArtifactUtils::createNewArtifactInstance(ArtifactID::SPELLBOOK)); + } + //fix portrait if(nih->portrait < 0 || nih->portrait == 255) nih->portrait = type->imageIndex; diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 6fdd404b8..68e3b70a7 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -90,6 +90,11 @@ void WindowNewMap::loadUserSettings() { ui->heightTxt->setText(height.toString()); } + for(auto & sz : mapSizes) + { + if(sz.second.first == width.toInt() && sz.second.second == height.toInt()) + ui->sizeCombo->setCurrentIndex(sz.first); + } auto twoLevel = s.value(newMapTwoLevel); if (twoLevel.isValid()) { From 3ea7988883c2cee6f726da95375fd6958625a4ad Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sat, 30 Sep 2023 23:06:38 +0200 Subject: [PATCH 52/59] Treasure placement uses random templates now --- lib/rmg/modificators/ObjectDistributor.cpp | 19 +--- lib/rmg/modificators/TreasurePlacer.cpp | 125 +++++++++++---------- lib/rmg/modificators/TreasurePlacer.h | 6 +- 3 files changed, 70 insertions(+), 80 deletions(-) diff --git a/lib/rmg/modificators/ObjectDistributor.cpp b/lib/rmg/modificators/ObjectDistributor.cpp index 92570b791..7ed97708e 100644 --- a/lib/rmg/modificators/ObjectDistributor.cpp +++ b/lib/rmg/modificators/ObjectDistributor.cpp @@ -79,25 +79,14 @@ void ObjectDistributor::distributeLimitedObjects() for (auto& zone : matchingZones) { - //We already know there are some templates - auto templates = handler->getTemplates(zone->getTerrainType()); - - //FIXME: Templates empty?! Maybe zone changed terrain type over time? - - //Assume the template with fewest terrains is the most suitable - auto temp = *boost::min_element(templates, [](std::shared_ptr lhs, std::shared_ptr rhs) -> bool + oi.generateObject = [primaryID, secondaryID]() -> CGObjectInstance * { - return lhs->getAllowedTerrains().size() < rhs->getAllowedTerrains().size(); - }); - - oi.generateObject = [temp]() -> CGObjectInstance * - { - return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp); + return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(); }; oi.value = rmgInfo.value; oi.probability = rmgInfo.rarity; - oi.templ = temp; + oi.setTemplates(primaryID, secondaryID, zone->getTerrainType()); //Rounding up will make sure all possible objects are exhausted uint32_t mapLimit = rmgInfo.mapLimit.value(); @@ -109,7 +98,7 @@ void ObjectDistributor::distributeLimitedObjects() rmgInfo.setMapLimit(mapLimit - oi.maxPerZone); //Don't add objects with 0 count remaining - if (oi.maxPerZone) + if(oi.maxPerZone && !oi.templates.empty()) { zone->getModificator()->addObjectToRandomPool(oi); } diff --git a/lib/rmg/modificators/TreasurePlacer.cpp b/lib/rmg/modificators/TreasurePlacer.cpp index c92637aea..eff834612 100644 --- a/lib/rmg/modificators/TreasurePlacer.cpp +++ b/lib/rmg/modificators/TreasurePlacer.cpp @@ -72,26 +72,16 @@ void TreasurePlacer::addAllPossibleObjects() continue; } - auto templates = handler->getTemplates(zone.getTerrainType()); - if (templates.empty()) - continue; - - //TODO: Reuse chooseRandomAppearance (eg. WoG treasure chests) - //Assume the template with fewest terrains is the most suitable - auto temp = *boost::min_element(templates, [](std::shared_ptr lhs, std::shared_ptr rhs) -> bool + oi.generateObject = [primaryID, secondaryID]() -> CGObjectInstance * { - return lhs->getAllowedTerrains().size() < rhs->getAllowedTerrains().size(); - }); - - oi.generateObject = [temp]() -> CGObjectInstance * - { - return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp); + return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(); }; oi.value = rmgInfo.value; oi.probability = rmgInfo.rarity; - oi.templ = temp; + oi.setTemplates(primaryID, secondaryID, zone.getTerrainType()); oi.maxPerZone = rmgInfo.zoneLimit; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } } } @@ -125,18 +115,18 @@ void TreasurePlacer::addAllPossibleObjects() obj->exp = generator.getConfig().prisonExperience[i]; obj->setOwner(PlayerColor::NEUTRAL); generator.banHero(hid); - obj->appearance = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType()).front(); //can't init template with hero subID return obj; }; - oi.setTemplate(Obj::PRISON, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PRISON, 0, zone.getTerrainType()); oi.value = generator.getConfig().prisonValues[i]; oi.probability = 30; //Distribute all allowed prisons, starting from the most valuable oi.maxPerZone = (std::ceil((float)prisonsLeft / (i + 1))); prisonsLeft -= oi.maxPerZone; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } } @@ -183,22 +173,16 @@ void TreasurePlacer::addAllPossibleObjects() auto nativeZonesCount = static_cast(map.getZoneCount(cre->getFaction())); oi.value = static_cast(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2))); oi.probability = 40; - - for(const auto & tmplate : dwellingHandler->getTemplates()) + + oi.generateObject = [secondaryID, dwellingType]() -> CGObjectInstance * { - if(tmplate->canBePlacedAt(zone.getTerrainType())) - { - oi.generateObject = [tmplate, secondaryID, dwellingType]() -> CGObjectInstance * - { - auto * obj = VLC->objtypeh->getHandlerFor(dwellingType, secondaryID)->create(tmplate); - obj->tempOwner = PlayerColor::NEUTRAL; - return obj; - }; - - oi.templ = tmplate; - addObjectToRandomPool(oi); - } - } + auto * obj = VLC->objtypeh->getHandlerFor(dwellingType, secondaryID)->create(); + obj->tempOwner = PlayerColor::NEUTRAL; + return obj; + }; + oi.setTemplates(dwellingType, secondaryID, zone.getTerrainType()); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } } } @@ -222,10 +206,11 @@ void TreasurePlacer::addAllPossibleObjects() obj->storedArtifact = a; return obj; }; - oi.setTemplate(Obj::SPELL_SCROLL, 0, zone.getTerrainType()); + oi.setTemplates(Obj::SPELL_SCROLL, 0, zone.getTerrainType()); oi.value = generator.getConfig().scrollValues[i]; oi.probability = 30; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } //pandora box with gold @@ -243,10 +228,11 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); oi.value = i * generator.getConfig().pandoraMultiplierGold; oi.probability = 5; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } //pandora box with experience @@ -264,10 +250,11 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); oi.value = i * generator.getConfig().pandoraMultiplierExperience; oi.probability = 20; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } //pandora box with creatures @@ -325,10 +312,11 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); oi.value = static_cast((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) / 3); oi.probability = 3; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } //Pandora with 12 spells of certain level @@ -357,10 +345,11 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); oi.value = (i + 1) * generator.getConfig().pandoraMultiplierSpells; //5000 - 15000 oi.probability = 2; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } //Pandora with 15 spells of certain school @@ -389,10 +378,11 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); oi.value = generator.getConfig().pandoraSpellSchool; oi.probability = 2; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); } // Pandora box with 60 random spells @@ -420,10 +410,11 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); + oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType()); oi.value = generator.getConfig().pandoraSpell60; oi.probability = 2; - addObjectToRandomPool(oi); + if(!oi.templates.empty()) + addObjectToRandomPool(oi); //Seer huts with creatures or generic rewards @@ -483,7 +474,7 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; oi.probability = 3; - oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); + oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); oi.value = static_cast(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) - 4000) / 3); if (oi.value > zone.getMaxTreasureValue()) { @@ -491,7 +482,8 @@ void TreasurePlacer::addAllPossibleObjects() } else { - possibleSeerHuts.push_back(oi); + if(!oi.templates.empty()) + possibleSeerHuts.push_back(oi); } } @@ -500,7 +492,7 @@ void TreasurePlacer::addAllPossibleObjects() { int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType()); - oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); + oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType()); oi.value = generator.getConfig().questValues[i]; if (oi.value > zone.getMaxTreasureValue()) { @@ -533,7 +525,8 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - possibleSeerHuts.push_back(oi); + if(!oi.templates.empty()) + possibleSeerHuts.push_back(oi); oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance * { @@ -557,7 +550,8 @@ void TreasurePlacer::addAllPossibleObjects() return obj; }; - possibleSeerHuts.push_back(oi); + if(!oi.templates.empty()) + possibleSeerHuts.push_back(oi); } if (possibleSeerHuts.empty()) @@ -610,7 +604,12 @@ std::vector TreasurePlacer::prepareTreasurePile(const CTreasureInfo if(!oi) //fail break; - if(oi->templ->isVisitableFromTop()) + bool visitableFromTop = true; + for(auto & t : oi->templates) + if(!t->isVisitableFromTop()) + visitableFromTop = false; + + if(visitableFromTop) { objectInfos.push_back(oi); } @@ -641,7 +640,10 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector accessibleArea.add(int3()); auto * object = oi->generateObject(); - object->appearance = oi->templ; + if(oi->templates.empty()) + continue; + + object->appearance = *RandomGeneratorUtil::nextItem(oi->templates, zone.getRand()); auto & instance = rmgObject.addInstance(*object); do @@ -717,7 +719,12 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu if(oi.value > maxVal) break; //this assumes values are sorted in ascending order - if(!oi.templ->isVisitableFromTop() && !allowLargeObjects) + bool visitableFromTop = true; + for(auto & t : oi.templates) + if(!t->isVisitableFromTop()) + visitableFromTop = false; + + if(!visitableFromTop && !allowLargeObjects) continue; if(oi.value >= minValue && oi.maxPerZone > 0) @@ -921,17 +928,13 @@ char TreasurePlacer::dump(const int3 & t) return Modificator::dump(t); } -void ObjectInfo::setTemplate(si32 type, si32 subtype, TerrainId terrainType) +void ObjectInfo::setTemplates(si32 type, si32 subtype, TerrainId terrainType) { auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype); if(!templHandler) return; - auto templates = templHandler->getTemplates(terrainType); - if(templates.empty()) - return; - - templ = templates.front(); + templates = templHandler->getTemplates(terrainType); } VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/modificators/TreasurePlacer.h b/lib/rmg/modificators/TreasurePlacer.h index 822bc793b..ef4881e25 100644 --- a/lib/rmg/modificators/TreasurePlacer.h +++ b/lib/rmg/modificators/TreasurePlacer.h @@ -22,16 +22,14 @@ class CRandomGenerator; struct ObjectInfo { - std::shared_ptr templ; + std::vector> templates; ui32 value = 0; ui16 probability = 0; ui32 maxPerZone = 1; //ui32 maxPerMap; //unused std::function generateObject; - void setTemplate(si32 type, si32 subtype, TerrainId terrain); - - bool operator==(const ObjectInfo& oi) const { return (templ == oi.templ); } + void setTemplates(si32 type, si32 subtype, TerrainId terrain); }; class TreasurePlacer: public Modificator From e9855de10132044fa01b929c0e057bf3da9f0465 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 02:35:02 +0200 Subject: [PATCH 53/59] Layout fixes --- mapeditor/mainwindow.ui | 110 ++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 6017a6a9e..73a73c89f 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -14,20 +14,20 @@ VCMI Map Editor - + - 2 + 0 - 2 + 0 - 2 + 0 - 2 + 0 - + @@ -159,7 +159,7 @@ - 192 + 524287 214 @@ -222,7 +222,7 @@ - + 0 0 @@ -247,7 +247,7 @@ - + 0 0 @@ -274,7 +274,7 @@ - 0 + 1 @@ -432,11 +432,17 @@ 128 - 496 + 192 + + + + + 524287 + 192 - Terrains View + Tools 1 @@ -476,7 +482,7 @@ - + 0 0 @@ -491,6 +497,18 @@ Brush + + 0 + + + 0 + + + 0 + + + 0 + @@ -734,6 +752,36 @@ + + + + + + + 0 + 0 + + + + Painting + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + @@ -757,7 +805,7 @@ 0 0 128 - 236 + 192 @@ -800,7 +848,7 @@ 0 0 128 - 236 + 192 @@ -836,7 +884,7 @@ 0 0 128 - 236 + 192 @@ -868,6 +916,36 @@ + + + + + + + 524287 + 150 + + + + Preview + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + From 4e5580f3d902a783f92d4865864e411de2064c30 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 02:39:03 +0200 Subject: [PATCH 54/59] Fix typo --- mapeditor/mainwindow.cpp | 6 +++--- mapeditor/mainwindow.h | 2 +- mapeditor/mainwindow.ui | 9 ++++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 0dcf519d2..04f54bc8f 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -1248,15 +1248,15 @@ void MainWindow::on_actionTranslations_triggered() translationsDialog->show(); } -void MainWindow::on_actionh3m_coverter_triggered() +void MainWindow::on_actionh3m_converter_triggered() { - auto mapFiles = QFileDialog::getOpenFileNames(this, tr("Select maps to covert"), + auto mapFiles = QFileDialog::getOpenFileNames(this, tr("Select maps to convert"), QString::fromStdString(VCMIDirs::get().userCachePath().make_preferred().string()), tr("HoMM3 maps(*.h3m)")); if(mapFiles.empty()) return; - auto saveDirectory = QFileDialog::getExistingDirectory(this, tr("Choose directory to save coverted maps"), QCoreApplication::applicationDirPath()); + auto saveDirectory = QFileDialog::getExistingDirectory(this, tr("Choose directory to save converted maps"), QCoreApplication::applicationDirPath()); if(saveDirectory.isEmpty()) return; diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index 74b051d80..6ff39d165 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -121,7 +121,7 @@ private slots: void on_actionTranslations_triggered(); - void on_actionh3m_coverter_triggered(); + void on_actionh3m_converter_triggered(); public slots: diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 73a73c89f..01aae4a05 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -63,7 +63,7 @@ - + @@ -1333,9 +1333,12 @@ Ctrl+T - + - h3m coverter + h3m converter + + + h3m converter From 9c5725da6609d72c7644a55a6fe41d7234ade764 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 13:32:35 +0200 Subject: [PATCH 55/59] Convertion finished --- mapeditor/mainwindow.cpp | 5 ++++- mapeditor/mapcontroller.cpp | 32 ++++++++++++++++++++------------ mapeditor/mapcontroller.h | 3 ++- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 04f54bc8f..6e7a72125 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -1265,8 +1265,11 @@ void MainWindow::on_actionh3m_converter_triggered() for(auto & m : mapFiles) { CMapService mapService; - mapService.saveMap(openMapInternal(m), (saveDirectory + QFileInfo(m).fileName()).toStdString()); + auto map = openMapInternal(m); + controller.repairMap(map.get()); + mapService.saveMap(map, (saveDirectory + '/' + QFileInfo(m).completeBaseName() + ".vmap").toStdString()); } + QMessageBox::information(this, tr("Operation completed"), tr("Successfully converted %1 maps").arg(mapFiles.size())); } catch(const std::exception & e) { diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 0007da3b1..4aaeff622 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -86,27 +86,35 @@ MinimapScene * MapController::miniScene(int level) void MapController::repairMap() { + repairMap(map()); +} + +void MapController::repairMap(CMap * map) const +{ + if(!map) + return; + //there might be extra skills, arts and spells not imported from map - if(VLC->skillh->getDefaultAllowed().size() > map()->allowedAbilities.size()) + if(VLC->skillh->getDefaultAllowed().size() > map->allowedAbilities.size()) { - map()->allowedAbilities.resize(VLC->skillh->getDefaultAllowed().size()); + map->allowedAbilities.resize(VLC->skillh->getDefaultAllowed().size()); } - if(VLC->arth->getDefaultAllowed().size() > map()->allowedArtifact.size()) + if(VLC->arth->getDefaultAllowed().size() > map->allowedArtifact.size()) { - map()->allowedArtifact.resize(VLC->arth->getDefaultAllowed().size()); + map->allowedArtifact.resize(VLC->arth->getDefaultAllowed().size()); } - if(VLC->spellh->getDefaultAllowed().size() > map()->allowedSpells.size()) + if(VLC->spellh->getDefaultAllowed().size() > map->allowedSpells.size()) { - map()->allowedSpells.resize(VLC->spellh->getDefaultAllowed().size()); + map->allowedSpells.resize(VLC->spellh->getDefaultAllowed().size()); } - if(VLC->heroh->getDefaultAllowed().size() > map()->allowedHeroes.size()) + if(VLC->heroh->getDefaultAllowed().size() > map->allowedHeroes.size()) { - map()->allowedHeroes.resize(VLC->heroh->getDefaultAllowed().size()); + map->allowedHeroes.resize(VLC->heroh->getDefaultAllowed().size()); } //fix owners for objects - auto allImpactedObjects(_map->objects); - allImpactedObjects.insert(allImpactedObjects.end(), _map->predefinedHeroes.begin(), _map->predefinedHeroes.end()); + auto allImpactedObjects(map->objects); + allImpactedObjects.insert(allImpactedObjects.end(), map->predefinedHeroes.begin(), map->predefinedHeroes.end()); for(auto obj : allImpactedObjects) { //setup proper names (hero name will be fixed later @@ -131,7 +139,7 @@ void MapController::repairMap() //fix hero instance if(auto * nih = dynamic_cast(obj.get())) { - map()->allowedHeroes.at(nih->subID) = true; + map->allowedHeroes.at(nih->subID) = true; auto type = VLC->heroh->objects[nih->subID]; assert(type->heroClass); //TODO: find a way to get proper type name @@ -202,7 +210,7 @@ void MapController::repairMap() art->storedArtifact = a; } else - map()->allowedArtifact.at(art->subID) = true; + map->allowedArtifact.at(art->subID) = true; } } } diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 979c2c6d2..18d660a3a 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -30,8 +30,9 @@ public: ~MapController(); void setMap(std::unique_ptr); - void initObstaclePainters(CMap* map); + void initObstaclePainters(CMap * map); + void repairMap(CMap * map) const; void repairMap(); const std::unique_ptr & getMapUniquePtr() const; //to be used for map saving From 7d56b704a24e215d92f5ca48c3d3e2bdc0619c17 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 13:38:16 +0200 Subject: [PATCH 56/59] Fix events/rumors identifiers --- mapeditor/mapcontroller.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 4aaeff622..2b4eb9a8f 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -112,6 +112,16 @@ void MapController::repairMap(CMap * map) const map->allowedHeroes.resize(VLC->heroh->getDefaultAllowed().size()); } + //make sure events/rumors has name to have proper identifiers + int emptyNameId = 1; + for(auto & e : map->events) + if(e.name.empty()) + e.name = "event_" + std::to_string(emptyNameId++); + emptyNameId = 1; + for(auto & e : map->rumors) + if(e.name.empty()) + e.name = "rumor_" + std::to_string(emptyNameId++); + //fix owners for objects auto allImpactedObjects(map->objects); allImpactedObjects.insert(allImpactedObjects.end(), map->predefinedHeroes.begin(), map->predefinedHeroes.end()); From f6093a84f0ff328030693e0dd4f11d1e761d5f4c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 14:00:08 +0200 Subject: [PATCH 57/59] Add newline symbol --- mapeditor/mapsettings/translations.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mapeditor/mapsettings/translations.cpp b/mapeditor/mapsettings/translations.cpp index b314c90c8..ab8a76bcc 100644 --- a/mapeditor/mapsettings/translations.cpp +++ b/mapeditor/mapsettings/translations.cpp @@ -58,6 +58,7 @@ Translations::Translations(CMapHeader & mh, QWidget *parent) : ui(new Ui::Translations), mapHeader(mh) { + setAttribute(Qt::WA_DeleteOnClose, true); ui->setupUi(this); //fill languages list @@ -90,6 +91,7 @@ Translations::Translations(CMapHeader & mh, QWidget *parent) : Translations::~Translations() { + mapHeader.registerMapStrings(); delete ui; } @@ -103,8 +105,11 @@ void Translations::fillTranslationsTable(const std::string & language) int i = 0; for(auto & s : translation.Struct()) { + auto textLines = QString::fromStdString(s.second.String()); + textLines = textLines.replace('\n', "\\n"); + auto * wId = new QTableWidgetItem(QString::fromStdString(s.first)); - auto * wText = new QTableWidgetItem(QString::fromStdString(s.second.String())); + auto * wText = new QTableWidgetItem(textLines); wId->setFlags(wId->flags() & ~Qt::ItemIsEditable); wText->setFlags(wId->flags() | Qt::ItemIsEditable); ui->translationsTable->setItem(i, 0, wId); @@ -186,6 +191,8 @@ void Translations::on_translationsTable_itemChanged(QTableWidgetItem * item) if(textId.empty()) return; - translation[textId].String() = item->text().toStdString(); + auto textLines = item->text(); + textLines = textLines.replace("\\n", "\n"); + translation[textId].String() = textLines.toStdString(); } From 65e63bdf691e58617b84067d05d12e51fc2d3d6c Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 14:04:46 +0200 Subject: [PATCH 58/59] Fix text duplicating --- mapeditor/inspector/inspector.cpp | 14 +++++++------- mapeditor/mapsettings/generalsettings.cpp | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 8f2580308..eb2925953 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -531,7 +531,7 @@ void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) @@ -561,7 +561,7 @@ void Inspector::setProperty(CGSignBottle * o, const QString & key, const QVarian if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("sign", o->instanceName, "message"), value.toString().toStdString())); } void Inspector::setProperty(CGMine * o, const QString & key, const QVariant & value) @@ -577,7 +577,7 @@ void Inspector::setProperty(CGArtifact * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("guards", o->instanceName, "message"), value.toString().toStdString())); if(o->storedArtifact && key == "Spell") { @@ -646,7 +646,7 @@ void Inspector::setProperty(CGCreature * o, const QString & key, const QVariant if(!o) return; if(key == "Message") - o->message.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); + o->message = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("monster", o->instanceName, "message"), value.toString().toStdString())); if(key == "Character") o->character = CGCreature::Character(value.toInt()); if(key == "Never flees") @@ -664,11 +664,11 @@ void Inspector::setProperty(CGSeerHut * o, const QString & key, const QVariant & if(key == "Mission type") o->quest->missionType = CQuest::Emission(value.toInt()); if(key == "First visit text") - o->quest->firstVisitText.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); + o->quest->firstVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "firstVisit"), value.toString().toStdString())); if(key == "Next visit text") - o->quest->nextVisitText.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); + o->quest->nextVisitText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "nextVisit"), value.toString().toStdString())); if(key == "Completed text") - o->quest->completedText.appendTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); + o->quest->completedText = MetaString::createFromTextID(mapRegisterLocalizedString("map", *map, TextIdentifier("quest", o->instanceName, "completed"), value.toString().toStdString())); } diff --git a/mapeditor/mapsettings/generalsettings.cpp b/mapeditor/mapsettings/generalsettings.cpp index dd5cff493..a15599e84 100644 --- a/mapeditor/mapsettings/generalsettings.cpp +++ b/mapeditor/mapsettings/generalsettings.cpp @@ -59,8 +59,8 @@ void GeneralSettings::initialize(MapController & c) void GeneralSettings::update() { - controller->map()->name.appendTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "name"), ui->mapNameEdit->text().toStdString())); - controller->map()->description.appendTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "description"), ui->mapDescriptionEdit->toPlainText().toStdString())); + controller->map()->name = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "name"), ui->mapNameEdit->text().toStdString())); + controller->map()->description = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "description"), ui->mapDescriptionEdit->toPlainText().toStdString())); if(ui->heroLevelLimitCheck->isChecked()) controller->map()->levelLimit = ui->heroLevelLimit->value(); else From 10eb19758a3334b73796fa0164a1e6d145c6d2ed Mon Sep 17 00:00:00 2001 From: nordsoft Date: Sun, 1 Oct 2023 18:00:36 +0200 Subject: [PATCH 59/59] Code review fixes --- lib/mapping/CMapHeader.cpp | 67 ++++++++++++++++++++++++++++---------- lib/mapping/CMapInfo.h | 2 +- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/lib/mapping/CMapHeader.cpp b/lib/mapping/CMapHeader.cpp index 3f39bac7f..bdd6de2e4 100644 --- a/lib/mapping/CMapHeader.cpp +++ b/lib/mapping/CMapHeader.cpp @@ -144,28 +144,59 @@ ui8 CMapHeader::levels() const void CMapHeader::registerMapStrings() { - auto language = CGeneralTextHandler::getPreferredLanguage(); - JsonNode data; - - if(translations[language].isNull()) + //get supported languages. Assuming that translation containing most strings is the base language + std::set mapLanguages, mapBaseLanguages; + int maxStrings = 0; + for(auto & translation : translations.Struct()) { - //english is preferrable - language = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier; - std::list languages{Languages::getLanguageList().begin(), Languages::getLanguageList().end()}; - while(translations[language].isNull() && !languages.empty()) - { - language = languages.front().identifier; - languages.pop_front(); - } + if(translation.first.empty() || !translation.second.isStruct() || translation.second.Struct().empty()) + continue; - if(!translations[language].isNull()) - { - logGlobal->info("Map %s doesn't have any translation", name.toString()); - return; - } + if(translation.second.Struct().size() > maxStrings) + maxStrings = translation.second.Struct().size(); + mapLanguages.insert(translation.first); } + + if(maxStrings == 0 || mapBaseLanguages.empty()) + { + logGlobal->info("Map %s doesn't have any supported translation", name.toString()); + return; + } + + //identifying base languages + for(auto & translation : translations.Struct()) + { + if(translation.second.isStruct() && translation.second.Struct().size() == maxStrings) + mapBaseLanguages.insert(translation.first); + } + + std::string baseLanguage, language; + //english is preferrable as base language + if(mapBaseLanguages.count(Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier)) + baseLanguage = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).identifier; + else + baseLanguage = *mapBaseLanguages.begin(); - for(auto & s : translations[language].Struct()) + if(mapBaseLanguages.count(CGeneralTextHandler::getPreferredLanguage())) + { + language = CGeneralTextHandler::getPreferredLanguage(); //preferred language is base language - use it + baseLanguage = language; + } + else + { + if(mapLanguages.count(CGeneralTextHandler::getPreferredLanguage())) + language = CGeneralTextHandler::getPreferredLanguage(); + else + language = baseLanguage; //preferred language is not supported, use base language + } + + assert(!language.empty()); + + JsonNode data = translations[baseLanguage]; + if(language != baseLanguage) + JsonUtils::mergeCopy(data, translations[language]); + + for(auto & s : data.Struct()) registerString("map", TextIdentifier(s.first), s.second.String(), language); } diff --git a/lib/mapping/CMapInfo.h b/lib/mapping/CMapInfo.h index 6ac7ccea9..ba09a3c15 100644 --- a/lib/mapping/CMapInfo.h +++ b/lib/mapping/CMapInfo.h @@ -49,7 +49,7 @@ public: void saveInit(const ResourcePath & file); void campaignInit(); void countPlayers(); - // TODO: Those must be on client-side + std::string getNameTranslated() const; std::string getNameForList() const; std::string getDescriptionTranslated() const;