From 3268ebe27c7b463354261de76d347e7c47d39527 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 9 Nov 2023 13:45:46 +0200 Subject: [PATCH 01/11] Moved some code from OptionTab to separate class, OptionTabBase --- client/CMakeLists.txt | 2 + client/lobby/OptionsTab.cpp | 219 +--------------------------- client/lobby/OptionsTab.h | 4 +- client/lobby/OptionsTabBase.cpp | 244 ++++++++++++++++++++++++++++++++ client/lobby/OptionsTabBase.h | 22 +++ 5 files changed, 274 insertions(+), 217 deletions(-) create mode 100644 client/lobby/OptionsTabBase.cpp create mode 100644 client/lobby/OptionsTabBase.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 6257722a2..0edca41da 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -52,6 +52,7 @@ set(client_SRCS lobby/CScenarioInfoScreen.cpp lobby/CSelectionBase.cpp lobby/OptionsTab.cpp + lobby/OptionsTabBase.cpp lobby/RandomMapTab.cpp lobby/SelectionTab.cpp @@ -212,6 +213,7 @@ set(client_HEADERS lobby/CScenarioInfoScreen.h lobby/CSelectionBase.h lobby/OptionsTab.h + lobby/OptionsTabBase.h lobby/RandomMapTab.h lobby/SelectionTab.h diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index a2f5027b0..afa3dd12b 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -42,164 +42,10 @@ #include "../../lib/mapping/CMapInfo.h" #include "../../lib/mapping/CMapHeader.h" -OptionsTab::OptionsTab() : humanPlayers(0) +OptionsTab::OptionsTab() + : OptionsTabBase(JsonPath::builtin("config/widgets/optionsTab.json")) + , humanPlayers(0) { - recActions = 0; - - addCallback("setTimerPreset", [&](int index){ - if(!variables["timerPresets"].isNull()) - { - auto tpreset = variables["timerPresets"].Vector().at(index).Vector(); - TurnTimerInfo tinfo; - tinfo.baseTimer = tpreset.at(0).Integer() * 1000; - tinfo.turnTimer = tpreset.at(1).Integer() * 1000; - tinfo.battleTimer = tpreset.at(2).Integer() * 1000; - tinfo.creatureTimer = tpreset.at(3).Integer() * 1000; - CSH->setTurnTimerInfo(tinfo); - } - }); - - addCallback("setSimturnDuration", [&](int index){ - SimturnsInfo info; - info.optionalTurns = index; - CSH->setSimturnsInfo(info); - }); - - //helper function to parse string containing time to integer reflecting time in seconds - //assumed that input string can be modified by user, function shall support user's intention - // normal: 2:00, 12:30 - // adding symbol: 2:005 -> 2:05, 2:305 -> 23:05, - // adding symbol (>60 seconds): 12:095 -> 129:05 - // removing symbol: 129:0 -> 12:09, 2:0 -> 0:20, 0:2 -> 0:02 - auto parseTimerString = [](const std::string & str) -> int - { - auto sc = str.find(":"); - if(sc == std::string::npos) - return str.empty() ? 0 : std::stoi(str); - - auto l = str.substr(0, sc); - auto r = str.substr(sc + 1, std::string::npos); - if(r.length() == 3) //symbol added - { - l.push_back(r.front()); - r.erase(r.begin()); - } - else if(r.length() == 1) //symbol removed - { - r.insert(r.begin(), l.back()); - l.pop_back(); - } - else if(r.empty()) - r = "0"; - - int sec = std::stoi(r); - if(sec >= 60) - { - if(l.empty()) //9:00 -> 0:09 - return sec / 10; - - l.push_back(r.front()); //0:090 -> 9:00 - r.erase(r.begin()); - } - else if(l.empty()) - return sec; - - return std::stoi(l) * 60 + std::stoi(r); - }; - - addCallback("parseAndSetTimer_base", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.baseTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - addCallback("parseAndSetTimer_turn", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.turnTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - addCallback("parseAndSetTimer_battle", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.battleTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - addCallback("parseAndSetTimer_creature", [parseTimerString](const std::string & str){ - int time = parseTimerString(str) * 1000; - if(time >= 0) - { - TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; - tinfo.creatureTimer = time; - CSH->setTurnTimerInfo(tinfo); - } - }); - - const JsonNode config(JsonPath::builtin("config/widgets/optionsTab.json")); - build(config); - - //set timers combo box callbacks - if(auto w = widget("timerModeSwitch")) - { - w->onConstructItems = [&](std::vector & curItems){ - if(variables["timers"].isNull()) - return; - - for(auto & p : variables["timers"].Vector()) - { - curItems.push_back(&p); - } - }; - - w->onSetItem = [&](const void * item){ - if(item) - { - if(auto * tObj = reinterpret_cast(item)) - { - for(auto wname : (*tObj)["hideWidgets"].Vector()) - { - if(auto w = widget(wname.String())) - w->setEnabled(false); - } - for(auto wname : (*tObj)["showWidgets"].Vector()) - { - if(auto w = widget(wname.String())) - w->setEnabled(true); - } - if((*tObj)["default"].isVector()) - { - TurnTimerInfo tinfo; - tinfo.baseTimer = (*tObj)["default"].Vector().at(0).Integer() * 1000; - tinfo.turnTimer = (*tObj)["default"].Vector().at(1).Integer() * 1000; - tinfo.battleTimer = (*tObj)["default"].Vector().at(2).Integer() * 1000; - tinfo.creatureTimer = (*tObj)["default"].Vector().at(3).Integer() * 1000; - CSH->setTurnTimerInfo(tinfo); - } - } - redraw(); - } - }; - - w->getItemText = [this](int idx, const void * item){ - if(item) - { - if(auto * tObj = reinterpret_cast(item)) - return readText((*tObj)["text"]); - } - return std::string(""); - }; - - w->setItem(0); - } } void OptionsTab::recreate() @@ -221,64 +67,7 @@ void OptionsTab::recreate() entries.insert(std::make_pair(pInfo.first, std::make_shared(pInfo.second, * this))); } - //Simultaneous turns - if(auto turnSlider = widget("labelSimturnsDurationValue")) - turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); - - if(auto w = widget("labelSimturnsDurationValue")) - { - MetaString message; - message.appendRawString("Simturns: up to %d days"); - message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns); - w->setText(message.toString()); - } - - const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; - - //classic timer - if(auto turnSlider = widget("sliderTurnDuration")) - { - if(!variables["timerPresets"].isNull() && !turnTimerRemote.battleTimer && !turnTimerRemote.creatureTimer && !turnTimerRemote.baseTimer) - { - for(int idx = 0; idx < variables["timerPresets"].Vector().size(); ++idx) - { - auto & tpreset = variables["timerPresets"].Vector()[idx]; - if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000) - { - turnSlider->scrollTo(idx); - if(auto w = widget("labelTurnDurationValue")) - w->setText(CGI->generaltexth->turnDurations[idx]); - } - } - } - } - - //chess timer - auto timeToString = [](int time) -> std::string - { - std::stringstream ss; - ss << time / 1000 / 60 << ":" << std::setw(2) << std::setfill('0') << time / 1000 % 60; - return ss.str(); - }; - - if(auto ww = widget("chessFieldBase")) - ww->setText(timeToString(turnTimerRemote.baseTimer), false); - if(auto ww = widget("chessFieldTurn")) - ww->setText(timeToString(turnTimerRemote.turnTimer), false); - if(auto ww = widget("chessFieldBattle")) - ww->setText(timeToString(turnTimerRemote.battleTimer), false); - if(auto ww = widget("chessFieldCreature")) - ww->setText(timeToString(turnTimerRemote.creatureTimer), false); - - if(auto w = widget("timerModeSwitch")) - { - if(turnTimerRemote.battleTimer || turnTimerRemote.creatureTimer || turnTimerRemote.baseTimer) - { - if(auto turnSlider = widget("sliderTurnDuration")) - if(turnSlider->isActive()) - w->setItem(1); - } - } + OptionsTabBase::recreate(); } size_t OptionsTab::CPlayerSettingsHelper::getImageIndex(bool big) diff --git a/client/lobby/OptionsTab.h b/client/lobby/OptionsTab.h index 23eff9c96..691a162bb 100644 --- a/client/lobby/OptionsTab.h +++ b/client/lobby/OptionsTab.h @@ -9,9 +9,9 @@ */ #pragma once +#include "OptionsTabBase.h" #include "../windows/CWindowObject.h" #include "../widgets/Scrollable.h" -#include "../gui/InterfaceObjectConfigurable.h" VCMI_LIB_NAMESPACE_BEGIN struct PlayerSettings; @@ -30,7 +30,7 @@ class CButton; class FilledTexturePlayerColored; /// The options tab which is shown at the map selection phase. -class OptionsTab : public InterfaceObjectConfigurable +class OptionsTab : public OptionsTabBase { struct PlayerOptionsEntry; diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp new file mode 100644 index 000000000..8c9fdd3d1 --- /dev/null +++ b/client/lobby/OptionsTabBase.cpp @@ -0,0 +1,244 @@ +/* + * OptionsTabBase.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "OptionsTabBase.h" +#include "CSelectionBase.h" + +#include "../widgets/ComboBox.h" +#include "../widgets/Slider.h" +#include "../widgets/TextControls.h" +#include "../CServerHandler.h" +#include "../CGameInfo.h" + +#include "../../lib/StartInfo.h" +#include "../../lib/MetaString.h" +#include "../../lib/CGeneralTextHandler.h" + +OptionsTabBase::OptionsTabBase(const JsonPath & configPath) +{ + recActions = 0; + + addCallback("setTimerPreset", [&](int index){ + if(!variables["timerPresets"].isNull()) + { + auto tpreset = variables["timerPresets"].Vector().at(index).Vector(); + TurnTimerInfo tinfo; + tinfo.baseTimer = tpreset.at(0).Integer() * 1000; + tinfo.turnTimer = tpreset.at(1).Integer() * 1000; + tinfo.battleTimer = tpreset.at(2).Integer() * 1000; + tinfo.creatureTimer = tpreset.at(3).Integer() * 1000; + CSH->setTurnTimerInfo(tinfo); + } + }); + + addCallback("setSimturnDuration", [&](int index){ + SimturnsInfo info; + info.optionalTurns = index; + CSH->setSimturnsInfo(info); + }); + + //helper function to parse string containing time to integer reflecting time in seconds + //assumed that input string can be modified by user, function shall support user's intention + // normal: 2:00, 12:30 + // adding symbol: 2:005 -> 2:05, 2:305 -> 23:05, + // adding symbol (>60 seconds): 12:095 -> 129:05 + // removing symbol: 129:0 -> 12:09, 2:0 -> 0:20, 0:2 -> 0:02 + auto parseTimerString = [](const std::string & str) -> int + { + auto sc = str.find(":"); + if(sc == std::string::npos) + return str.empty() ? 0 : std::stoi(str); + + auto l = str.substr(0, sc); + auto r = str.substr(sc + 1, std::string::npos); + if(r.length() == 3) //symbol added + { + l.push_back(r.front()); + r.erase(r.begin()); + } + else if(r.length() == 1) //symbol removed + { + r.insert(r.begin(), l.back()); + l.pop_back(); + } + else if(r.empty()) + r = "0"; + + int sec = std::stoi(r); + if(sec >= 60) + { + if(l.empty()) //9:00 -> 0:09 + return sec / 10; + + l.push_back(r.front()); //0:090 -> 9:00 + r.erase(r.begin()); + } + else if(l.empty()) + return sec; + + return std::stoi(l) * 60 + std::stoi(r); + }; + + addCallback("parseAndSetTimer_base", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.baseTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + addCallback("parseAndSetTimer_turn", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.turnTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + addCallback("parseAndSetTimer_battle", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.battleTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + addCallback("parseAndSetTimer_creature", [parseTimerString](const std::string & str){ + int time = parseTimerString(str) * 1000; + if(time >= 0) + { + TurnTimerInfo tinfo = SEL->getStartInfo()->turnTimerInfo; + tinfo.creatureTimer = time; + CSH->setTurnTimerInfo(tinfo); + } + }); + + const JsonNode config(configPath); + build(config); + + //set timers combo box callbacks + if(auto w = widget("timerModeSwitch")) + { + w->onConstructItems = [&](std::vector & curItems){ + if(variables["timers"].isNull()) + return; + + for(auto & p : variables["timers"].Vector()) + { + curItems.push_back(&p); + } + }; + + w->onSetItem = [&](const void * item){ + if(item) + { + if(auto * tObj = reinterpret_cast(item)) + { + for(auto wname : (*tObj)["hideWidgets"].Vector()) + { + if(auto w = widget(wname.String())) + w->setEnabled(false); + } + for(auto wname : (*tObj)["showWidgets"].Vector()) + { + if(auto w = widget(wname.String())) + w->setEnabled(true); + } + if((*tObj)["default"].isVector()) + { + TurnTimerInfo tinfo; + tinfo.baseTimer = (*tObj)["default"].Vector().at(0).Integer() * 1000; + tinfo.turnTimer = (*tObj)["default"].Vector().at(1).Integer() * 1000; + tinfo.battleTimer = (*tObj)["default"].Vector().at(2).Integer() * 1000; + tinfo.creatureTimer = (*tObj)["default"].Vector().at(3).Integer() * 1000; + CSH->setTurnTimerInfo(tinfo); + } + } + redraw(); + } + }; + + w->getItemText = [this](int idx, const void * item){ + if(item) + { + if(auto * tObj = reinterpret_cast(item)) + return readText((*tObj)["text"]); + } + return std::string(""); + }; + + w->setItem(0); + } +} + +void OptionsTabBase::recreate() +{ + //Simultaneous turns + if(auto turnSlider = widget("labelSimturnsDurationValue")) + turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); + + if(auto w = widget("labelSimturnsDurationValue")) + { + MetaString message; + message.appendRawString("Simturns: up to %d days"); + message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns); + w->setText(message.toString()); + } + + const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; + + //classic timer + if(auto turnSlider = widget("sliderTurnDuration")) + { + if(!variables["timerPresets"].isNull() && !turnTimerRemote.battleTimer && !turnTimerRemote.creatureTimer && !turnTimerRemote.baseTimer) + { + for(int idx = 0; idx < variables["timerPresets"].Vector().size(); ++idx) + { + auto & tpreset = variables["timerPresets"].Vector()[idx]; + if(tpreset.Vector().at(1).Integer() == turnTimerRemote.turnTimer / 1000) + { + turnSlider->scrollTo(idx); + if(auto w = widget("labelTurnDurationValue")) + w->setText(CGI->generaltexth->turnDurations[idx]); + } + } + } + } + + //chess timer + auto timeToString = [](int time) -> std::string + { + std::stringstream ss; + ss << time / 1000 / 60 << ":" << std::setw(2) << std::setfill('0') << time / 1000 % 60; + return ss.str(); + }; + + if(auto ww = widget("chessFieldBase")) + ww->setText(timeToString(turnTimerRemote.baseTimer), false); + if(auto ww = widget("chessFieldTurn")) + ww->setText(timeToString(turnTimerRemote.turnTimer), false); + if(auto ww = widget("chessFieldBattle")) + ww->setText(timeToString(turnTimerRemote.battleTimer), false); + if(auto ww = widget("chessFieldCreature")) + ww->setText(timeToString(turnTimerRemote.creatureTimer), false); + + if(auto w = widget("timerModeSwitch")) + { + if(turnTimerRemote.battleTimer || turnTimerRemote.creatureTimer || turnTimerRemote.baseTimer) + { + if(auto turnSlider = widget("sliderTurnDuration")) + if(turnSlider->isActive()) + w->setItem(1); + } + } +} diff --git a/client/lobby/OptionsTabBase.h b/client/lobby/OptionsTabBase.h new file mode 100644 index 000000000..4ba7e82c8 --- /dev/null +++ b/client/lobby/OptionsTabBase.h @@ -0,0 +1,22 @@ +/* + * OptionsTabBase.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 "../gui/InterfaceObjectConfigurable.h" +#include "../../lib/filesystem/ResourcePath.h" + +/// The options tab which is shown at the map selection phase. +class OptionsTabBase : public InterfaceObjectConfigurable +{ +public: + OptionsTabBase(const JsonPath & configPath); + + void recreate(); +}; From 32633d5f521b9964e274dd56537048ebcd02235e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 9 Nov 2023 16:41:00 +0200 Subject: [PATCH 02/11] Initial version of Turn Options tab screen --- Mods/vcmi/config/vcmi/english.json | 24 ++- client/CMakeLists.txt | 2 + client/lobby/CLobbyScreen.cpp | 30 ++- client/lobby/CSelectionBase.h | 3 + client/lobby/OptionsTab.cpp | 2 +- client/lobby/OptionsTabBase.cpp | 2 +- client/lobby/TurnOptionsTab.cpp | 18 ++ client/lobby/TurnOptionsTab.h | 18 ++ ...{optionsTab.json => playerOptionsTab.json} | 36 +--- config/widgets/turnOptionsTab.json | 186 ++++++++++++++++++ 10 files changed, 273 insertions(+), 48 deletions(-) create mode 100644 client/lobby/TurnOptionsTab.cpp create mode 100644 client/lobby/TurnOptionsTab.h rename config/widgets/{optionsTab.json => playerOptionsTab.json} (75%) create mode 100644 config/widgets/turnOptionsTab.json diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 1d0432297..5cd4da2a5 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -220,14 +220,24 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Road Types", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Extra timer}\n\nStarts counting down when the {turn timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nStarts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra timer} if it is in use.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when the {stack timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Stack timer}\n\nStarts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", - "vcmi.optionsTab.widgets.labelTimer" : "Timer", - "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Classic timer", - "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Chess timer", + "vcmi.optionsTab.turnOptions.hover" : "Turn Options", + "vcmi.optionsTab.turnOptions.help" : "Select turn timer and simultaneous turns options", + "vcmi.optionsTab.chessFieldBase.hover" : "Base timer", + "vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer", + "vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer", + "vcmi.optionsTab.chessFieldCreature.hover" : "Unit timer", + "vcmi.optionsTab.chessFieldBase.help" : "Starts counting down when the {Turn Timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", + "vcmi.optionsTab.chessFieldTurn.help" : "Starts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra Timer} if it is in use.", + "vcmi.optionsTab.chessFieldBattle.help" : "Counts down during battles when the {Unit Timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", + "vcmi.optionsTab.chessFieldCreature.help" : "Starts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", + + "vcmi.optionsTab.simturnsMin.hover" : "Simultaneous turns (minimum)", + "vcmi.optionsTab.simturnsMax.hover" : "Simultaneous turns (maximum)", + "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", + "vcmi.optionsTab.simturnsMin.help" : "Players will act simultaneously for specified number of days. Contacts between players are blocked", + "vcmi.optionsTab.simturnsMax.help" : "Players will act simultaneously until they meet each other or if specified date has been reached", + "vcmi.optionsTab.simturnsAI.help" : "", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 0edca41da..2f11a87c9 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -51,6 +51,7 @@ set(client_SRCS lobby/CSavingScreen.cpp lobby/CScenarioInfoScreen.cpp lobby/CSelectionBase.cpp + lobby/TurnOptionsTab.cpp lobby/OptionsTab.cpp lobby/OptionsTabBase.cpp lobby/RandomMapTab.cpp @@ -212,6 +213,7 @@ set(client_HEADERS lobby/CSavingScreen.h lobby/CScenarioInfoScreen.h lobby/CSelectionBase.h + lobby/TurnOptionsTab.h lobby/OptionsTab.h lobby/OptionsTabBase.h lobby/RandomMapTab.h diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index fc0333f37..4537ce332 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -8,14 +8,15 @@ * */ #include "StdInc.h" - #include "CLobbyScreen.h" -#include "CBonusSelection.h" -#include "SelectionTab.h" -#include "RandomMapTab.h" -#include "OptionsTab.h" -#include "../CServerHandler.h" +#include "CBonusSelection.h" +#include "TurnOptionsTab.h" +#include "OptionsTab.h" +#include "RandomMapTab.h" +#include "SelectionTab.h" + +#include "../CServerHandler.h" #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "../widgets/Buttons.h" @@ -50,6 +51,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) }); buttonOptions = std::make_shared(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS); + buttonTurnOptions = std::make_shared(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE); }; buttonChat = std::make_shared(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT); @@ -60,6 +62,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) case ESelectionScreen::newGame: { tabOpt = std::make_shared(); + tabTurnOptions = std::make_shared(); tabRand = std::make_shared(); tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2); buttonRMG = std::make_shared(Point(411, 105), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[47], 0, EShortcut::LOBBY_RANDOM_MAP); @@ -78,6 +81,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) case ESelectionScreen::loadGame: { tabOpt = std::make_shared(); + tabTurnOptions = std::make_shared(); buttonStart = std::make_shared(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, true), EShortcut::LOBBY_LOAD_GAME); initLobby(); break; @@ -145,6 +149,7 @@ void CLobbyScreen::toggleMode(bool host) auto buttonColor = host ? Colors::WHITE : Colors::ORANGE; buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor); + buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor); if(buttonRMG) { buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); @@ -152,9 +157,13 @@ void CLobbyScreen::toggleMode(bool host) } buttonSelect->block(!host); buttonOptions->block(!host); + buttonTurnOptions->block(!host); if(CSH->mi) + { tabOpt->recreate(); + tabTurnOptions->recreate(); + } } void CLobbyScreen::toggleChat() @@ -168,8 +177,13 @@ void CLobbyScreen::toggleChat() void CLobbyScreen::updateAfterStateChange() { - if(CSH->mi && tabOpt) - tabOpt->recreate(); + if(CSH->mi) + { + if (tabOpt) + tabOpt->recreate(); + if (tabTurnOptions) + tabTurnOptions->recreate(); + } buttonStart->block(CSH->mi == nullptr || CSH->isGuest()); diff --git a/client/lobby/CSelectionBase.h b/client/lobby/CSelectionBase.h index 92b0c2b4f..e614468cf 100644 --- a/client/lobby/CSelectionBase.h +++ b/client/lobby/CSelectionBase.h @@ -26,6 +26,7 @@ class CAnimImage; class CToggleGroup; class RandomMapTab; class OptionsTab; +class TurnOptionsTab; class SelectionTab; class InfoCard; class CChatBox; @@ -57,12 +58,14 @@ public: std::shared_ptr buttonSelect; std::shared_ptr buttonRMG; std::shared_ptr buttonOptions; + std::shared_ptr buttonTurnOptions; std::shared_ptr buttonStart; std::shared_ptr buttonBack; std::shared_ptr buttonSimturns; std::shared_ptr tabSel; std::shared_ptr tabOpt; + std::shared_ptr tabTurnOptions; std::shared_ptr tabRand; std::shared_ptr curTab; diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index afa3dd12b..0584a1999 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -43,7 +43,7 @@ #include "../../lib/mapping/CMapHeader.h" OptionsTab::OptionsTab() - : OptionsTabBase(JsonPath::builtin("config/widgets/optionsTab.json")) + : OptionsTabBase(JsonPath::builtin("config/widgets/playerOptionsTab.json")) , humanPlayers(0) { } diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 8c9fdd3d1..60996951b 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -190,7 +190,7 @@ void OptionsTabBase::recreate() if(auto w = widget("labelSimturnsDurationValue")) { MetaString message; - message.appendRawString("Simturns: up to %d days"); + message.appendRawString("%d days"); message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns); w->setText(message.toString()); } diff --git a/client/lobby/TurnOptionsTab.cpp b/client/lobby/TurnOptionsTab.cpp new file mode 100644 index 000000000..6bf3523a8 --- /dev/null +++ b/client/lobby/TurnOptionsTab.cpp @@ -0,0 +1,18 @@ +/* + * TurnOptionsTab.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "TurnOptionsTab.h" + +TurnOptionsTab::TurnOptionsTab() + : OptionsTabBase(JsonPath::builtin("config/widgets/turnOptionsTab.json")) +{ + +} diff --git a/client/lobby/TurnOptionsTab.h b/client/lobby/TurnOptionsTab.h new file mode 100644 index 000000000..ad9ad1450 --- /dev/null +++ b/client/lobby/TurnOptionsTab.h @@ -0,0 +1,18 @@ +/* + * TurnOptionsTab.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 "OptionsTabBase.h" + +class TurnOptionsTab : public OptionsTabBase +{ +public: + TurnOptionsTab(); +}; diff --git a/config/widgets/optionsTab.json b/config/widgets/playerOptionsTab.json similarity index 75% rename from config/widgets/optionsTab.json rename to config/widgets/playerOptionsTab.json index bd8c76126..ca3c936f2 100644 --- a/config/widgets/optionsTab.json +++ b/config/widgets/playerOptionsTab.json @@ -73,41 +73,16 @@ "adoptHeight": true }, + // timer { - "name": "simturnsDuration", - "type": "slider", - "orientation": "horizontal", - "position": {"x": 55, "y": 537}, - "size": 194, - "callback": "setSimturnDuration", - "itemsVisible": 1, - "itemsTotal": 28, - "selected": 0, - "style": "blue", - "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, - "panningStep": 20 - }, - - { - "name": "labelSimturnsDurationValue", "type": "label", "font": "small", "alignment": "center", - "color": "white", - "text": "", - "position": {"x": 319, "y": 545} + "color": "yellow", + "text": "core.genrltxt.521", + "position": {"x": 222, "y": 544} }, - // timer - //{ - // "type": "label", - // "font": "small", - // "alignment": "center", - // "color": "yellow", - // "text": "core.genrltxt.521", - // "position": {"x": 222, "y": 544} - //}, - { "name": "labelTurnDurationValue", "type": "label", @@ -129,8 +104,7 @@ "itemsTotal": 11, "selected": 11, "style": "blue", - "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, - //"scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, + "scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, "panningStep": 20 }, ], diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json new file mode 100644 index 000000000..b158c5b08 --- /dev/null +++ b/config/widgets/turnOptionsTab.json @@ -0,0 +1,186 @@ +{ + "customTypes" : { + "verticalLayout66" : { + "type" : "layout", + "vertical" : true, + "dynamic" : false, + "distance" : 66 + }, + "labelTitle" : { + "type": "label", + "font": "small", + "alignment": "left", + "color": "yellow" + }, + "labelDescription" : { + "type": "multiLineLabel", + "font": "small", + "alignment": "center", + "color": "white", + "rect": {"x": 0, "y": 0, "w": 300, "h": 35}, + "adoptHeight": true + }, + "timeInput" : { + "type": "textInput", + "background": "timerField", + "alignment": "center", + "text": "00:00", + "rect": {"x": 0, "y": 0, "w": 84, "h": 25}, + "offset": {"x": 0, "y": 0} + } + }, + + "items": + [ + { + "name": "background", + "type": "picture", + "image": "RANMAPBK", + "position": {"x": 0, "y": 6} + }, + + { + "name": "labelTitle", + "type": "label", + "font": "big", + "alignment": "center", + "color": "yellow", + "text": "vcmi.optionsTab.turnOptions.hover", + "position": {"x": 222, "y": 36} + }, + + { + "name": "labelSubTitle", + "type": "multiLineLabel", + "font": "small", + "alignment": "center", + "color": "white", + "text": "vcmi.optionsTab.turnOptions.help", + "rect": {"x": 60, "y": 48, "w": 320, "h": 0}, + "adoptHeight": true + }, + { + "type" : "verticalLayout66", + "customType" : "labelTitle", + "position": {"x": 70, "y": 134}, + "items": + [ + { + "text": "vcmi.optionsTab.chessFieldBase.hover" + }, + { + "text": "vcmi.optionsTab.chessFieldTurn.hover" + }, + { + "text": "vcmi.optionsTab.chessFieldBattle.hover" + }, + { + "text": "vcmi.optionsTab.chessFieldCreature.hover" + }, + { + "text": "vcmi.optionsTab.simturnsMin.hover" + }, + { + "text": "vcmi.optionsTab.simturnsMax.hover" + } + ] + }, + + { + "type" : "verticalLayout66", + "customType" : "labelDescription", + "position": {"x": 70, "y": 153}, + "items": + [ + { + "text": "vcmi.optionsTab.chessFieldBase.help" + }, + { + "text": "vcmi.optionsTab.chessFieldTurn.help" + }, + { + "text": "vcmi.optionsTab.chessFieldBattle.help" + }, + { + "text": "vcmi.optionsTab.chessFieldCreature.help" + }, + { + "text": "vcmi.optionsTab.simturnsMin.help" + }, + { + "text": "vcmi.optionsTab.simturnsMax.help" + } + ] + }, + + { + "type" : "verticalLayout66", + "customType" : "timeInput", + "position": {"x": 294, "y": 129}, + "items": + [ + { + "name": "chessFieldBase", + "callback": "parseAndSetTimer_base", + "help": "vcmi.optionsTab.chessFieldBase.help" + }, + { + "name": "chessFieldTurn", + "callback": "parseAndSetTimer_turn", + "help": "vcmi.optionsTab.chessFieldTurn.help" + }, + { + "name": "chessFieldBattle", + "callback": "parseAndSetTimer_battle", + "help": "vcmi.optionsTab.chessFieldBattle.help" + }, + { + "name": "chessFieldCreature", + "callback": "parseAndSetTimer_creature", + "help": "vcmi.optionsTab.chessFieldCreature.help" + }, + ] + }, + + // simturns + { + "name": "simturnsDuration", + "type": "slider", + "orientation": "horizontal", + "position": {"x": 258, "y": 398}, + "size": 120, + "callback": "setSimturnDuration", + "itemsVisible": 1, + "itemsTotal": 28, + "selected": 0, + "style": "blue", + "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, + "panningStep": 20 + }, + + { + "name": "labelSimturnsDurationValue", + "type": "label", + "font": "small", + "alignment": "center", + "color": "white", + "text": "", + "position": {"x": 320, "y": 406} + }, + + { + "position": {"x": 70, "y": 535}, + "type": "toggleButton", + "image": "sysopchk.def" + }, + { + "name": "labelSimturnsAI", + "type": "label", + "font": "small", + "alignment": "left", + "color": "yellow", + "text": "vcmi.optionsTab.simturnsAI.hover", + "position": {"x": 110, "y": 540} + } + ] +} From 1f045ab128d89e0a4ac1fe12fbaa459a3da45b1b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 9 Nov 2023 20:37:41 +0200 Subject: [PATCH 03/11] Layout improvements for turn options tab --- Mods/vcmi/config/vcmi/english.json | 17 ++--- config/widgets/turnOptionsTab.json | 107 ++++++++++++++++++++++++----- 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 5cd4da2a5..0f9e38383 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -227,16 +227,17 @@ "vcmi.optionsTab.chessFieldTurn.hover" : "Turn timer", "vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer", "vcmi.optionsTab.chessFieldCreature.hover" : "Unit timer", - "vcmi.optionsTab.chessFieldBase.help" : "Starts counting down when the {Turn Timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", - "vcmi.optionsTab.chessFieldTurn.help" : "Starts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra Timer} if it is in use.", - "vcmi.optionsTab.chessFieldBattle.help" : "Counts down during battles when the {Unit Timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", - "vcmi.optionsTab.chessFieldCreature.help" : "Starts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", + "vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.", + "vcmi.optionsTab.chessFieldTurn.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to Base Timer at turn's end.", + "vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.", + "vcmi.optionsTab.chessFieldCreature.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn.", - "vcmi.optionsTab.simturnsMin.hover" : "Simultaneous turns (minimum)", - "vcmi.optionsTab.simturnsMax.hover" : "Simultaneous turns (maximum)", + "vcmi.optionsTab.simturns" : "Simultaneous turns", + "vcmi.optionsTab.simturnsMin.hover" : "At least for", + "vcmi.optionsTab.simturnsMax.hover" : "At most for", "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", - "vcmi.optionsTab.simturnsMin.help" : "Players will act simultaneously for specified number of days. Contacts between players are blocked", - "vcmi.optionsTab.simturnsMax.help" : "Players will act simultaneously until they meet each other or if specified date has been reached", + "vcmi.optionsTab.simturnsMin.help" : "Play simultaneously at least for specified number of days. Contacts between players during this period are blocked", + "vcmi.optionsTab.simturnsMax.help" : "Play simultaneously at most for specified number of days or until they meet each other.", "vcmi.optionsTab.simturnsAI.help" : "", // Custom victory conditions for H3 campaigns and HotA maps diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index b158c5b08..17c7ad33b 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -14,7 +14,7 @@ }, "labelDescription" : { "type": "multiLineLabel", - "font": "small", + "font": "tiny", "alignment": "center", "color": "white", "rect": {"x": 0, "y": 0, "w": 300, "h": 35}, @@ -22,11 +22,16 @@ }, "timeInput" : { "type": "textInput", - "background": "timerField", "alignment": "center", "text": "00:00", - "rect": {"x": 0, "y": 0, "w": 84, "h": 25}, + "rect": {"x": 0, "y": 0, "w": 86, "h": 23}, "offset": {"x": 0, "y": 0} + }, + "timeInputBackground" : { + "type": "transparentFilledRectangle", + "rect": {"x": 0, "y": 0, "w": 86, "h": 23}, + "color": [0, 0, 0, 128], + "colorLine": [64, 80, 128, 128] } }, @@ -59,6 +64,24 @@ "rect": {"x": 60, "y": 48, "w": 320, "h": 0}, "adoptHeight": true }, + { + "type": "texture", + "image": "DiBoxBck", + "color" : "blue", + "rect": {"x" : 64, "y" : 416, "w": 316, "h": 102} + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 64, "y" : 416, "w": 316, "h": 102}, + "color": [0, 0, 0, 0], + "colorLine": [64, 80, 128, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 64, "y" : 480, "w": 316, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [32, 40, 128, 128] + }, { "type" : "verticalLayout66", "customType" : "labelTitle", @@ -78,10 +101,7 @@ "text": "vcmi.optionsTab.chessFieldCreature.hover" }, { - "text": "vcmi.optionsTab.simturnsMin.hover" - }, - { - "text": "vcmi.optionsTab.simturnsMax.hover" + "text": "vcmi.optionsTab.simturns" } ] }, @@ -89,7 +109,7 @@ { "type" : "verticalLayout66", "customType" : "labelDescription", - "position": {"x": 70, "y": 153}, + "position": {"x": 70, "y": 155}, "items": [ { @@ -105,14 +125,27 @@ "text": "vcmi.optionsTab.chessFieldCreature.help" }, { - "text": "vcmi.optionsTab.simturnsMin.help" + "text": "" }, { - "text": "vcmi.optionsTab.simturnsMax.help" + "text": "vcmi.optionsTab.simturnsMin.help" } ] }, + { + "type" : "verticalLayout66", + "customType" : "timeInputBackground", + "position": {"x": 294, "y": 129}, + "items": + [ + {}, + {}, + {}, + {} + ] + }, + { "type" : "verticalLayout66", "customType" : "timeInput", @@ -138,17 +171,33 @@ "name": "chessFieldCreature", "callback": "parseAndSetTimer_creature", "help": "vcmi.optionsTab.chessFieldCreature.help" - }, + } ] }, - // simturns { - "name": "simturnsDuration", + "type": "label", + "font": "small", + "alignment": "left", + "color": "white", + "text": "vcmi.optionsTab.simturnsMin.hover", + "position": {"x": 70, "y": 430} + }, + { + "type": "label", + "font": "small", + "alignment": "left", + "color": "white", + "text": "vcmi.optionsTab.simturnsMax.hover", + "position": {"x": 70, "y": 460} + }, + + { + "name": "simturnsDurationMin", "type": "slider", "orientation": "horizontal", - "position": {"x": 258, "y": 398}, - "size": 120, + "position": {"x": 178, "y": 430}, + "size": 200, "callback": "setSimturnDuration", "itemsVisible": 1, "itemsTotal": 28, @@ -157,15 +206,37 @@ "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, "panningStep": 20 }, - { - "name": "labelSimturnsDurationValue", + "name": "simturnsDurationMax", + "type": "slider", + "orientation": "horizontal", + "position": {"x": 178, "y": 460}, + "size": 200, + "callback": "setSimturnDuration", + "itemsVisible": 1, + "itemsTotal": 28, + "selected": 0, + "style": "blue", + "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, + "panningStep": 20 + }, + { + "name": "labelSimturnsDurationValueMin", "type": "label", "font": "small", "alignment": "center", "color": "white", "text": "", - "position": {"x": 320, "y": 406} + "position": {"x": 320, "y": 438} + }, + { + "name": "labelSimturnsDurationValueMax", + "type": "label", + "font": "small", + "alignment": "center", + "color": "white", + "text": "", + "position": {"x": 320, "y": 468} }, { From e160b8557b2faf2e21a3fa72ab2afd84b32df09d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 9 Nov 2023 23:18:03 +0200 Subject: [PATCH 04/11] Implement min/max simturns callbacks --- Mods/vcmi/config/vcmi/english.json | 3 +-- client/lobby/OptionsTabBase.cpp | 25 +++++++++++++++--- config/widgets/turnOptionsTab.json | 42 ++++++++++++++++++------------ 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 0f9e38383..db41b2216 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -236,8 +236,7 @@ "vcmi.optionsTab.simturnsMin.hover" : "At least for", "vcmi.optionsTab.simturnsMax.hover" : "At most for", "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", - "vcmi.optionsTab.simturnsMin.help" : "Play simultaneously at least for specified number of days. Contacts between players during this period are blocked", - "vcmi.optionsTab.simturnsMax.help" : "Play simultaneously at most for specified number of days or until they meet each other.", + "vcmi.optionsTab.simturns.help" : "Play simultaneously at least for specified number of days, after which simultaneous turns will stay active until specified day or until players make contact. Contacts between players during this period are blocked", "vcmi.optionsTab.simturnsAI.help" : "", // Custom victory conditions for H3 campaigns and HotA maps diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 60996951b..1fc917809 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -38,8 +38,14 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) } }); - addCallback("setSimturnDuration", [&](int index){ - SimturnsInfo info; + addCallback("setSimturnDurationMin", [&](int index){ + SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; + info.requiredTurns = index; + CSH->setSimturnsInfo(info); + }); + + addCallback("setSimturnDurationMax", [&](int index){ + SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; info.optionalTurns = index; CSH->setSimturnsInfo(info); }); @@ -184,10 +190,21 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) void OptionsTabBase::recreate() { //Simultaneous turns - if(auto turnSlider = widget("labelSimturnsDurationValue")) + if(auto turnSlider = widget("simturnsDurationMin")) + turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.requiredTurns); + + if(auto turnSlider = widget("simturnsDurationMax")) turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); - if(auto w = widget("labelSimturnsDurationValue")) + if(auto w = widget("labelSimturnsDurationValueMin")) + { + MetaString message; + message.appendRawString("%d days"); + message.replaceNumber(SEL->getStartInfo()->simturnsInfo.requiredTurns); + w->setText(message.toString()); + } + + if(auto w = widget("labelSimturnsDurationValueMax")) { MetaString message; message.appendRawString("%d days"); diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index 17c7ad33b..dda539f8e 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -78,7 +78,13 @@ }, { "type": "transparentFilledRectangle", - "rect": {"x" : 64, "y" : 480, "w": 316, "h": 1}, + "rect": {"x" : 64, "y" : 465, "w": 316, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [80, 96, 160, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 64, "y" : 466, "w": 316, "h": 1}, "color": [0, 0, 0, 0], "colorLine": [32, 40, 128, 128] }, @@ -123,16 +129,10 @@ }, { "text": "vcmi.optionsTab.chessFieldCreature.help" - }, - { - "text": "" - }, - { - "text": "vcmi.optionsTab.simturnsMin.help" } ] }, - + { "type" : "verticalLayout66", "customType" : "timeInputBackground", @@ -181,7 +181,7 @@ "alignment": "left", "color": "white", "text": "vcmi.optionsTab.simturnsMin.hover", - "position": {"x": 70, "y": 430} + "position": {"x": 70, "y": 420} }, { "type": "label", @@ -189,16 +189,16 @@ "alignment": "left", "color": "white", "text": "vcmi.optionsTab.simturnsMax.hover", - "position": {"x": 70, "y": 460} + "position": {"x": 70, "y": 445} }, { "name": "simturnsDurationMin", "type": "slider", "orientation": "horizontal", - "position": {"x": 178, "y": 430}, + "position": {"x": 178, "y": 420}, "size": 200, - "callback": "setSimturnDuration", + "callback": "setSimturnDurationMin", "itemsVisible": 1, "itemsTotal": 28, "selected": 0, @@ -210,9 +210,9 @@ "name": "simturnsDurationMax", "type": "slider", "orientation": "horizontal", - "position": {"x": 178, "y": 460}, + "position": {"x": 178, "y": 445}, "size": 200, - "callback": "setSimturnDuration", + "callback": "setSimturnDurationMax", "itemsVisible": 1, "itemsTotal": 28, "selected": 0, @@ -227,7 +227,7 @@ "alignment": "center", "color": "white", "text": "", - "position": {"x": 320, "y": 438} + "position": {"x": 320, "y": 428} }, { "name": "labelSimturnsDurationValueMax", @@ -236,9 +236,17 @@ "alignment": "center", "color": "white", "text": "", - "position": {"x": 320, "y": 468} + "position": {"x": 320, "y": 453} + }, + { + "type" : "label", + "text": "vcmi.optionsTab.simturns.help", + "type": "multiLineLabel", + "font": "tiny", + "alignment": "center", + "color": "white", + "rect": {"x": 70, "y": 470, "w": 300, "h": 40} }, - { "position": {"x": 70, "y": 535}, "type": "toggleButton", From 7391f2a6eee42d17ccf581e628be9f8c6a329d5a Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Nov 2023 16:09:41 +0200 Subject: [PATCH 05/11] Added custom-made blue version of checkbox --- Mods/vcmi/Sprites/lobby/checkbox.json | 8 ++++++++ Mods/vcmi/Sprites/lobby/checkboxBlueOff.png | Bin 0 -> 485 bytes Mods/vcmi/Sprites/lobby/checkboxBlueOn.png | Bin 0 -> 400 bytes Mods/vcmi/Sprites/lobby/checkboxOff.png | Bin 0 -> 522 bytes Mods/vcmi/Sprites/lobby/checkboxOn.png | Bin 0 -> 374 bytes config/widgets/turnOptionsTab.json | 2 +- 6 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Mods/vcmi/Sprites/lobby/checkbox.json create mode 100644 Mods/vcmi/Sprites/lobby/checkboxBlueOff.png create mode 100644 Mods/vcmi/Sprites/lobby/checkboxBlueOn.png create mode 100644 Mods/vcmi/Sprites/lobby/checkboxOff.png create mode 100644 Mods/vcmi/Sprites/lobby/checkboxOn.png diff --git a/Mods/vcmi/Sprites/lobby/checkbox.json b/Mods/vcmi/Sprites/lobby/checkbox.json new file mode 100644 index 000000000..bfb1e6587 --- /dev/null +++ b/Mods/vcmi/Sprites/lobby/checkbox.json @@ -0,0 +1,8 @@ +{ + "basepath" : "lobby/", + "images" : + [ + { "frame" : 0, "file" : "checkboxBlueOff.png"}, + { "frame" : 1, "file" : "checkboxBlueOn.png"} + ] +} diff --git a/Mods/vcmi/Sprites/lobby/checkboxBlueOff.png b/Mods/vcmi/Sprites/lobby/checkboxBlueOff.png new file mode 100644 index 0000000000000000000000000000000000000000..cfb75ae7cd9f1d18a552caafebbc8bb05efe1ba0 GIT binary patch literal 485 zcmVBLbuT~EpJ^b8i{oQKe#9mNJLXLe&iFiT8t5K$qL-W*S z!l_rvuu}~iHz+79lY&U{)MkNmOzh5L2NEbOFF})nN;Wn^0|*uX0R+OTTItMV``c=W zb~g6cXUDEn0002Pt6KZqY2CqGT23Jc5-HHNR$x;k1q~nu5GD>DJY<0fJ^%m!*GWV{ zRCobmAPgAs0zj+ufMqcZKmqi`@7uO*j)T9~h$VJ=9JED}2+FNb zaRg51p$23css41enqFPw=Jw8TS(yr~+&?^44S-bJgP9Ps!QuS$9P(g>^Yv!4o)0hj bk=22=CL&y7`qZ?bHsAZ}wG5!?*LFt&P3I8S;+MCh z+yX?zzC4o(H2vk7Bwr_KHhyKe>6BZ5h}f^!(`&K~m^me(rc-VKX2hy1i9iw$ITqw3 zdOSX!+)?SPq3eh@V(rPa;OU8EP&iym&rNg!nt~@{$+Td{!U%|Cx6Jd_*0li!&ZA=> zD_@>PiCFS2@N#vzeIO3viMCSbBWt1{_ATjmV&;@UGo8#x4)AmaCPbj2?=Pkx2R*lh z5!DL2r4xgI=0ec*J&2&E+yX?p{CGJHX!@_)nH3p&tl*%ha&cXl>hbb)Vqdj|nxX)o uj1^7l13p=6V2igP)zYH4K zYat66#cmkI9Ao}5vf!WajdEw=Hq2p$ERp?v#WlpbBl#VMwCMHftBX@;+m<0Tq)$tL zXM#_OSVxFSe788_#rY|NT`8dAC%x27dW|wxPOo5MX;}p zt7=Yq^m>gkpHO*zZgTg!;y+xQZc-!HK?I%JJLZ}z1RP`N&E;uy{@^&J@Xsm(@2Q`b z=GG@7{qwR5i1~D52Jx$NjVU@Db*5qcTkU^-e)7J;zpFJ%BKVucP}aBQ5OB;{0WyPT zBh>lHnMqAP-k7n~k_h%U5)F&r{Bxy%M(|k{QA>dAvJ#jDSQb&M1hPw-5OXfZ4rWmV zKkuy4g#TPJhFCI~bNDQY$oa9di0{P=?J$jSz6oJsy%LrT=Da=;rD1Dy!~uR+pkIuk z^Q#!^6@QE+Bj#LU8VMv`QhSY<^)h0jUoNEilRG2%Va7Fv-H!E&1eW-DZ??8{MmHpX zAXc$Utqs;InhPC{F#~vpPt+WoU^*~vvr(eC{xP!PpYY+E|5(V%0pDr}khVIXb>VxxhuoCk>40P+71mr@=Bg^cKJc`wLv5Z*M~<3CXN7`-fK1>!=O z6l8JoHL*7eJQ3KAZI0*GU>=`5KVrbWL>M zZ>0aO$u_6N*Pnr5>+5R0>pMe%tBN%9E}ql&=P?+=aO!KEc<#b{r{>du2cWs&FY6a zYDvEn$nuLcO?1cB1S-A1nDP-?y#B!w@9{KG@t>}wW`it+;p=cyp{0=Wcjb# zng1&?^gx!c1zS!p@cdMQF|efj*;j4xUtN*w3sA^_VE}+85T6HPS8#qG>R=QA01M+7 UUG8P(w*UYD07*qoM6N<$g5^Y|lK=n! literal 0 HcmV?d00001 diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index dda539f8e..e18e13b99 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -250,7 +250,7 @@ { "position": {"x": 70, "y": 535}, "type": "toggleButton", - "image": "sysopchk.def" + "image": "lobby/checkbox" }, { "name": "labelSimturnsAI", From 7d54f6a9c0ecb30e9c7ac06c56c5c1bfb4e0082f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Nov 2023 16:10:01 +0200 Subject: [PATCH 06/11] Implemented basic support for plural forms in translations --- Mods/vcmi/config/vcmi/english.json | 12 ++++ client/lobby/OptionsTabBase.cpp | 41 ++++++++---- lib/Languages.h | 100 +++++++++++++++++++++++------ 3 files changed, 121 insertions(+), 32 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index db41b2216..fc102ff64 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -238,6 +238,18 @@ "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", "vcmi.optionsTab.simturns.help" : "Play simultaneously at least for specified number of days, after which simultaneous turns will stay active until specified day or until players make contact. Contacts between players during this period are blocked", "vcmi.optionsTab.simturnsAI.help" : "", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d days", + "vcmi.optionsTab.simturns.days.1" : " %d day", + "vcmi.optionsTab.simturns.days.2" : " %d days", + "vcmi.optionsTab.simturns.weeks.0" : " %d weeks", + "vcmi.optionsTab.simturns.weeks.1" : " %d week", + "vcmi.optionsTab.simturns.weeks.2" : " %d weeks", + "vcmi.optionsTab.simturns.months.0" : " %d months", + "vcmi.optionsTab.simturns.months.1" : " %d month", + "vcmi.optionsTab.simturns.months.2" : " %d months", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "The enemy has managed to survive till this day. Victory is theirs!", diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 1fc917809..39c9e52c7 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -18,6 +18,7 @@ #include "../CGameInfo.h" #include "../../lib/StartInfo.h" +#include "../../lib/Languages.h" #include "../../lib/MetaString.h" #include "../../lib/CGeneralTextHandler.h" @@ -189,6 +190,32 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) void OptionsTabBase::recreate() { + auto const & generateSimturnsDurationText = [](int days) -> std::string + { + bool canUseMonth = days % 28 == 0 && days >= 28*2; + bool canUseWeek = days % 7 == 0 && days >= 7*2; + + MetaString message; + int value = days; + std::string text = "vcmi.optionsTab.simturns.days"; + + if (canUseWeek) + { + value = days / 7; + text = "vcmi.optionsTab.simturns.weeks"; + } + + if (canUseMonth) + { + value = days / 28; + text = "vcmi.optionsTab.simturns.months"; + } + + message.appendTextID(Languages::getPluralFormTextID( CGI->generaltexth->getPreferredLanguage(), value, text)); + message.replaceNumber(value); + return message.toString(); + }; + //Simultaneous turns if(auto turnSlider = widget("simturnsDurationMin")) turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.requiredTurns); @@ -197,20 +224,10 @@ void OptionsTabBase::recreate() turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); if(auto w = widget("labelSimturnsDurationValueMin")) - { - MetaString message; - message.appendRawString("%d days"); - message.replaceNumber(SEL->getStartInfo()->simturnsInfo.requiredTurns); - w->setText(message.toString()); - } + w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns)); if(auto w = widget("labelSimturnsDurationValueMax")) - { - MetaString message; - message.appendRawString("%d days"); - message.replaceNumber(SEL->getStartInfo()->simturnsInfo.optionalTurns); - w->setText(message.toString()); - } + w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.optionalTurns)); const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; diff --git a/lib/Languages.h b/lib/Languages.h index cfd47021d..2f7e41564 100644 --- a/lib/Languages.h +++ b/lib/Languages.h @@ -12,6 +12,17 @@ namespace Languages { +enum class EPluralForms +{ + NONE, + VI_1, // Single plural form, (Vietnamese) + EN_2, // Two forms, singular used for one only (English) + FR_2, // Two forms, singular used for zero and one (French) + UK_3, // Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4] (Ukrainian) + CZ_3, // Three forms, special cases for 1 and 2, 3, 4 (Czech) + PL_3, // Three forms, special case for one and some numbers ending in 2, 3, or 4 (Polish) +}; + enum class ELanguages { CZECH, @@ -57,6 +68,9 @@ struct Options /// primary IETF language tag std::string tagIETF; + /// Ruleset for plural forms in this language + EPluralForms pluralForms = EPluralForms::NONE; + /// VCMI supports translations into this language bool hasTranslation = false; }; @@ -65,27 +79,27 @@ inline const auto & getLanguageList() { static const std::array languages { { - { "czech", "Czech", "Čeština", "CP1250", "cs", true }, - { "chinese", "Chinese", "简体中文", "GBK", "zh", true }, // Note: actually Simplified Chinese - { "english", "English", "English", "CP1252", "en", true }, - { "finnish", "Finnish", "Suomi", "CP1252", "fi", true }, - { "french", "French", "Français", "CP1252", "fr", true }, - { "german", "German", "Deutsch", "CP1252", "de", true }, - { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", true }, - { "italian", "Italian", "Italiano", "CP1250", "it", true }, - { "korean", "Korean", "한국어", "CP949", "ko", true }, - { "polish", "Polish", "Polski", "CP1250", "pl", true }, - { "portuguese", "Portuguese", "Português", "CP1252", "pt", true }, // Note: actually Brazilian Portuguese - { "russian", "Russian", "Русский", "CP1251", "ru", true }, - { "spanish", "Spanish", "Español", "CP1252", "es", true }, - { "swedish", "Swedish", "Svenska", "CP1252", "sv", true }, - { "turkish", "Turkish", "Türkçe", "CP1254", "tr", true }, - { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", true }, - { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", true }, // Fan translation uses special encoding + { "czech", "Czech", "Čeština", "CP1250", "cs", EPluralForms::CZ_3, true }, + { "chinese", "Chinese", "简体中文", "GBK", "zh", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese + { "english", "English", "English", "CP1252", "en", EPluralForms::EN_2, true }, + { "finnish", "Finnish", "Suomi", "CP1252", "fi", EPluralForms::EN_2, true }, + { "french", "French", "Français", "CP1252", "fr", EPluralForms::FR_2, true }, + { "german", "German", "Deutsch", "CP1252", "de", EPluralForms::EN_2, true }, + { "hungarian", "Hungarian", "Magyar", "CP1250", "hu", EPluralForms::EN_2, true }, + { "italian", "Italian", "Italiano", "CP1250", "it", EPluralForms::EN_2, true }, + { "korean", "Korean", "한국어", "CP949", "ko", EPluralForms::VI_1, true }, + { "polish", "Polish", "Polski", "CP1250", "pl", EPluralForms::PL_3, true }, + { "portuguese", "Portuguese", "Português", "CP1252", "pt", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese + { "russian", "Russian", "Русский", "CP1251", "ru", EPluralForms::UK_3, true }, + { "spanish", "Spanish", "Español", "CP1252", "es", EPluralForms::EN_2, true }, + { "swedish", "Swedish", "Svenska", "CP1252", "sv", EPluralForms::EN_2, true }, + { "turkish", "Turkish", "Türkçe", "CP1254", "tr", EPluralForms::EN_2, true }, + { "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", EPluralForms::UK_3, true }, + { "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", EPluralForms::VI_1, true }, // Fan translation uses special encoding - { "other_cp1250", "Other (East European)", "", "CP1250", "", false }, - { "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", false }, - { "other_cp1252", "Other (West European)", "", "CP1252", "", false } + { "other_cp1250", "Other (East European)", "", "CP1250", "", EPluralForms::NONE, false }, + { "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", EPluralForms::NONE, false }, + { "other_cp1252", "Other (West European)", "", "CP1252", "", EPluralForms::NONE, false } } }; static_assert(languages.size() == static_cast(ELanguages::COUNT), "Languages array is missing a value!"); @@ -109,4 +123,50 @@ inline const Options & getLanguageOptions(const std::string & language) return emptyValue; } +template +inline constexpr int getPluralFormIndex(EPluralForms form, Numeric value) +{ + // Based on https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html + switch(form) + { + case EPluralForms::NONE: + case EPluralForms::VI_1: + return 0; + case EPluralForms::EN_2: + if (value == 1) + return 1; + return 2; + case EPluralForms::FR_2: + if (value == 1 || value == 0) + return 1; + return 2; + case EPluralForms::UK_3: + if (value % 10 == 1 && value % 100 != 11) + return 1; + if (value%10>=2 && value%10<=4 && (value%100<10 || value%100>=20)) + return 2; + return 0; + case EPluralForms::CZ_3: + if (value == 1) + return 1; + if (value>=2 && value<=4) + return 2; + return 0; + case EPluralForms::PL_3: + if (value == 1) + return 1; + if (value%10>=2 && value%10<=4 && (value%100<10 || value%100>=20)) + return 2; + return 0; + } + throw std::runtime_error("Invalid plural form enumeration received!"); +} + +template +inline std::string getPluralFormTextID(std::string languageName, Numeric value, std::string textID) +{ + int formIndex = getPluralFormIndex(getLanguageOptions(languageName).pluralForms, value); + return textID + '.' + std::to_string(formIndex); +} + } From c5eeaa6526bb3f24eab27b1080d512341c5da864 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Nov 2023 17:18:49 +0200 Subject: [PATCH 07/11] Added non-linear slider for better simturn duration selection --- Mods/vcmi/config/vcmi/english.json | 3 +- client/gui/InterfaceObjectConfigurable.cpp | 21 ++++++++-- client/lobby/OptionsTabBase.cpp | 16 ++++++-- client/widgets/Slider.cpp | 37 +++++++++++++++++- client/widgets/Slider.h | 18 ++++++++- config/widgets/turnOptionsTab.json | 45 +++++++++++++++------- 6 files changed, 114 insertions(+), 26 deletions(-) diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index fc102ff64..89ea0a1f8 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -236,7 +236,8 @@ "vcmi.optionsTab.simturnsMin.hover" : "At least for", "vcmi.optionsTab.simturnsMax.hover" : "At most for", "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", - "vcmi.optionsTab.simturns.help" : "Play simultaneously at least for specified number of days, after which simultaneous turns will stay active until specified day or until players make contact. Contacts between players during this period are blocked", + "vcmi.optionsTab.simturnsMin.help" : "Play simultaneously for specified number of days. Contacts between players during this period are blocked", + "vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player", "vcmi.optionsTab.simturnsAI.help" : "", // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 935dc88da..fecc485da 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -501,12 +501,25 @@ std::shared_ptr InterfaceObjectConfigurable::buildSlider(const JsonNode auto position = readPosition(config["position"]); int length = config["size"].Integer(); auto style = config["style"].String() == "brown" ? CSlider::BROWN : CSlider::BLUE; - auto itemsVisible = config["itemsVisible"].Integer(); - auto itemsTotal = config["itemsTotal"].Integer(); auto value = config["selected"].Integer(); bool horizontal = config["orientation"].String() == "horizontal"; - const auto & result = - std::make_shared(position, length, callbacks_int.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal ? Orientation::HORIZONTAL : Orientation::VERTICAL, style); + auto orientation = horizontal ? Orientation::HORIZONTAL : Orientation::VERTICAL; + + std::shared_ptr result; + + if (config["items"].isNull()) + { + auto itemsVisible = config["itemsVisible"].Integer(); + auto itemsTotal = config["itemsTotal"].Integer(); + + result = std::make_shared(position, length, callbacks_int.at(config["callback"].String()), itemsVisible, itemsTotal, value, orientation, style); + } + else + { + auto items = config["items"].convertTo>(); + result = std::make_shared(position, length, callbacks_int.at(config["callback"].String()), items, value, orientation, style); + } + if(!config["scrollBounds"].isNull()) { diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 39c9e52c7..2aefb19f3 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -42,12 +42,14 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) addCallback("setSimturnDurationMin", [&](int index){ SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; info.requiredTurns = index; + info.optionalTurns = std::max(info.optionalTurns, index); CSH->setSimturnsInfo(info); }); addCallback("setSimturnDurationMax", [&](int index){ SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; info.optionalTurns = index; + info.requiredTurns = std::min(info.requiredTurns, index); CSH->setSimturnsInfo(info); }); @@ -192,14 +194,19 @@ void OptionsTabBase::recreate() { auto const & generateSimturnsDurationText = [](int days) -> std::string { + if (days == 0) + return CGI->generaltexth->translate("core.genrltxt.523"); + + if (days >= 1000000) // Not "unlimited" but close enough + return CGI->generaltexth->translate("core.turndur.10"); + bool canUseMonth = days % 28 == 0 && days >= 28*2; bool canUseWeek = days % 7 == 0 && days >= 7*2; - MetaString message; int value = days; std::string text = "vcmi.optionsTab.simturns.days"; - if (canUseWeek) + if (canUseWeek && !canUseMonth) { value = days / 7; text = "vcmi.optionsTab.simturns.weeks"; @@ -211,6 +218,7 @@ void OptionsTabBase::recreate() text = "vcmi.optionsTab.simturns.months"; } + MetaString message; message.appendTextID(Languages::getPluralFormTextID( CGI->generaltexth->getPreferredLanguage(), value, text)); message.replaceNumber(value); return message.toString(); @@ -218,10 +226,10 @@ void OptionsTabBase::recreate() //Simultaneous turns if(auto turnSlider = widget("simturnsDurationMin")) - turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.requiredTurns); + turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.requiredTurns); if(auto turnSlider = widget("simturnsDurationMax")) - turnSlider->scrollTo(SEL->getStartInfo()->simturnsInfo.optionalTurns); + turnSlider->setValue(SEL->getStartInfo()->simturnsInfo.optionalTurns); if(auto w = widget("labelSimturnsDurationValueMin")) w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.requiredTurns)); diff --git a/client/widgets/Slider.cpp b/client/widgets/Slider.cpp index 955c01649..b8dfc98bf 100644 --- a/client/widgets/Slider.cpp +++ b/client/widgets/Slider.cpp @@ -69,6 +69,11 @@ int CSlider::getValue() const return value; } +void CSlider::setValue(int to) +{ + scrollTo(value); +} + int CSlider::getCapacity() const { return capacity; @@ -119,7 +124,7 @@ void CSlider::scrollTo(int to) updateSliderPos(); - moved(to); + moved(getValue()); } void CSlider::clickPressed(const Point & cursorPosition) @@ -164,7 +169,7 @@ bool CSlider::receiveEvent(const Point &position, int eventType) const return testTarget.isInside(position); } -CSlider::CSlider(Point position, int totalw, std::function Moved, int Capacity, int Amount, int Value, Orientation orientation, CSlider::EStyle style) +CSlider::CSlider(Point position, int totalw, const std::function & Moved, int Capacity, int Amount, int Value, Orientation orientation, CSlider::EStyle style) : Scrollable(LCLICK | DRAG, position, orientation ), capacity(Capacity), amount(Amount), @@ -297,3 +302,31 @@ void CSlider::scrollToMax() { scrollTo(amount); } + +SliderNonlinear::SliderNonlinear(Point position, int length, const std::function & Moved, const std::vector & values, int Value, Orientation orientation, EStyle style) + : CSlider(position, length, Moved, 1, values.size(), Value, orientation, style) + , scaledValues(values) +{ + +} + +int SliderNonlinear::getValue() const +{ + return scaledValues.at(CSlider::getValue()); +} + +void SliderNonlinear::setValue(int to) +{ + size_t nearest = 0; + + for(size_t i = 0; i < scaledValues.size(); ++i) + { + int nearestDistance = std::abs(to - scaledValues[nearest]); + int currentDistance = std::abs(to - scaledValues[i]); + + if(currentDistance < nearestDistance) + nearest = i; + } + + scrollTo(nearest); +} diff --git a/client/widgets/Slider.h b/client/widgets/Slider.h index f7a8cad10..b6e8be677 100644 --- a/client/widgets/Slider.h +++ b/client/widgets/Slider.h @@ -59,10 +59,11 @@ public: /// Amount modifier void setAmount(int to); + virtual void setValue(int to); /// Accessors int getAmount() const; - int getValue() const; + virtual int getValue() const; int getCapacity() const; void addCallback(std::function callback); @@ -80,7 +81,20 @@ public: /// @param Capacity maximal number of visible at once elements /// @param Amount total amount of elements, including not visible /// @param Value starting position - CSlider(Point position, int length, std::function Moved, int Capacity, int Amount, + CSlider(Point position, int length, const std::function & Moved, int Capacity, int Amount, int Value, Orientation orientation, EStyle style = BROWN); ~CSlider(); }; + +class SliderNonlinear : public CSlider +{ + /// If non-empty then slider has non-linear values, e.g. if slider is at position 5 out of 10 then actual "value" is not 5, but 5th value in this vector + std::vector scaledValues; + + using CSlider::setAmount; // make private +public: + void setValue(int to) override; + int getValue() const override; + + SliderNonlinear(Point position, int length, const std::function & Moved, const std::vector & values, int Value, Orientation orientation, EStyle style); +}; diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index e18e13b99..d39b7ed89 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -68,23 +68,35 @@ "type": "texture", "image": "DiBoxBck", "color" : "blue", - "rect": {"x" : 64, "y" : 416, "w": 316, "h": 102} + "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124} }, { "type": "transparentFilledRectangle", - "rect": {"x" : 64, "y" : 416, "w": 316, "h": 102}, + "rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}, "color": [0, 0, 0, 0], "colorLine": [64, 80, 128, 128] }, { "type": "transparentFilledRectangle", - "rect": {"x" : 64, "y" : 465, "w": 316, "h": 1}, + "rect": {"x" : 65, "y" : 416, "w": 314, "h": 1}, "color": [0, 0, 0, 0], "colorLine": [80, 96, 160, 128] }, { "type": "transparentFilledRectangle", - "rect": {"x" : 64, "y" : 466, "w": 316, "h": 1}, + "rect": {"x" : 65, "y" : 417, "w": 314, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [32, 40, 128, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 65, "y" : 466, "w": 314, "h": 1}, + "color": [0, 0, 0, 0], + "colorLine": [80, 96, 160, 128] + }, + { + "type": "transparentFilledRectangle", + "rect": {"x" : 65, "y" : 467, "w": 314, "h": 1}, "color": [0, 0, 0, 0], "colorLine": [32, 40, 128, 128] }, @@ -189,7 +201,7 @@ "alignment": "left", "color": "white", "text": "vcmi.optionsTab.simturnsMax.hover", - "position": {"x": 70, "y": 445} + "position": {"x": 70, "y": 470} }, { @@ -199,8 +211,7 @@ "position": {"x": 178, "y": 420}, "size": 200, "callback": "setSimturnDurationMin", - "itemsVisible": 1, - "itemsTotal": 28, + "items": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21, 28, 35, 42, 49, 56, 84, 112, 140, 168 ], "selected": 0, "style": "blue", "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, @@ -210,11 +221,10 @@ "name": "simturnsDurationMax", "type": "slider", "orientation": "horizontal", - "position": {"x": 178, "y": 445}, + "position": {"x": 178, "y": 470}, "size": 200, "callback": "setSimturnDurationMax", - "itemsVisible": 1, - "itemsTotal": 28, + "items": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21, 28, 35, 42, 49, 56, 84, 112, 140, 168, 1000000 ], "selected": 0, "style": "blue", "scrollBounds": {"x": 0, "y": 0, "w": 194, "h": 32}, @@ -236,16 +246,25 @@ "alignment": "center", "color": "white", "text": "", - "position": {"x": 320, "y": 453} + "position": {"x": 320, "y": 478} }, { "type" : "label", - "text": "vcmi.optionsTab.simturns.help", + "text": "vcmi.optionsTab.simturnsMin.help", "type": "multiLineLabel", "font": "tiny", "alignment": "center", "color": "white", - "rect": {"x": 70, "y": 470, "w": 300, "h": 40} + "rect": {"x": 70, "y": 430, "w": 300, "h": 40} + }, + { + "type" : "label", + "text": "vcmi.optionsTab.simturnsMax.help", + "type": "multiLineLabel", + "font": "tiny", + "alignment": "center", + "color": "white", + "rect": {"x": 70, "y": 480, "w": 300, "h": 40} }, { "position": {"x": 70, "y": 535}, From d1ae5bbee5331ffa307bf9ebd2acd76308d83484 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 10 Nov 2023 21:21:10 +0200 Subject: [PATCH 08/11] Implement AI simturns checkbox --- client/lobby/OptionsTabBase.cpp | 9 +++++++++ config/widgets/turnOptionsTab.json | 1 + 2 files changed, 10 insertions(+) diff --git a/client/lobby/OptionsTabBase.cpp b/client/lobby/OptionsTabBase.cpp index 2aefb19f3..18953eb97 100644 --- a/client/lobby/OptionsTabBase.cpp +++ b/client/lobby/OptionsTabBase.cpp @@ -53,6 +53,12 @@ OptionsTabBase::OptionsTabBase(const JsonPath & configPath) CSH->setSimturnsInfo(info); }); + addCallback("setSimturnAI", [&](int index){ + SimturnsInfo info = SEL->getStartInfo()->simturnsInfo; + info.allowHumanWithAI = index; + CSH->setSimturnsInfo(info); + }); + //helper function to parse string containing time to integer reflecting time in seconds //assumed that input string can be modified by user, function shall support user's intention // normal: 2:00, 12:30 @@ -237,6 +243,9 @@ void OptionsTabBase::recreate() if(auto w = widget("labelSimturnsDurationValueMax")) w->setText(generateSimturnsDurationText(SEL->getStartInfo()->simturnsInfo.optionalTurns)); + if(auto buttonSimturnsAI = widget("buttonSimturnsAI")) + buttonSimturnsAI->setSelectedSilent(SEL->getStartInfo()->simturnsInfo.allowHumanWithAI); + const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; //classic timer diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index d39b7ed89..feef3ae61 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -267,6 +267,7 @@ "rect": {"x": 70, "y": 480, "w": 300, "h": 40} }, { + "name": "buttonSimturnsAI", "position": {"x": 70, "y": 535}, "type": "toggleButton", "image": "lobby/checkbox" From eb20e29b2af0c26831b743abc3714c39f3c07cba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 12 Nov 2023 00:11:49 +0200 Subject: [PATCH 09/11] Updated translations --- Mods/vcmi/config/vcmi/chinese.json | 16 ++++---- Mods/vcmi/config/vcmi/czech.json | 5 --- Mods/vcmi/config/vcmi/english.json | 4 +- Mods/vcmi/config/vcmi/russian.json | 15 +++---- Mods/vcmi/config/vcmi/ukrainian.json | 59 ++++++++++++++++++++++++++- Mods/vcmi/config/vcmi/vietnamese.json | 15 +++---- 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 70c7b3275..8f1b403eb 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -202,14 +202,14 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "同盟关系", "vcmi.randomMapTab.widgets.roadTypesLabel" : "道路类型", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{额外计时器}\n\n当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{转动计时器}\n\n当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{战斗计时器}\n\n战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{堆栈计时器}\n\n当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。", - "vcmi.optionsTab.widgets.labelTimer" : "计时器", - "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "经典计时器", - "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "国际象棋计时器", - + "vcmi.optionsTab.chessFieldBase.hover" : "额外计时器", + "vcmi.optionsTab.chessFieldTurn.hover" : "转动计时器", + "vcmi.optionsTab.chessFieldBattle.hover" : "战斗计时器", + "vcmi.optionsTab.chessFieldCreature.hover" : "堆栈计时器", + "vcmi.optionsTab.chessFieldBase.help" : "当{转动计时器}达到零时开始倒计时。 它仅在游戏开始时设置一次。 当计时器达到零时,玩家的回合结束。", + "vcmi.optionsTab.chessFieldTurn.help" : "当玩家在冒险地图上开始回合时开始倒计时。 它在每回合开始时重置为其初始值。 任何未使用的回合时间将被添加到{额外计时器}(如果正在使用)中。", + "vcmi.optionsTab.chessFieldBattle.help" : "战斗期间当 {堆栈计时器} 达到0时进行倒计时。 每次战斗开始时都会重置为初始值。 如果计时器达到零,当前活动的堆栈将进行防御。", + "vcmi.optionsTab.chessFieldCreature.help" : "当玩家在战斗中为当前堆栈选择一个动作时开始倒计时。 堆栈操作完成后,它会重置为其初始值。", // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "敌人依然存活至今,你失败了!", diff --git a/Mods/vcmi/config/vcmi/czech.json b/Mods/vcmi/config/vcmi/czech.json index f1c561b97..fbe240eb8 100644 --- a/Mods/vcmi/config/vcmi/czech.json +++ b/Mods/vcmi/config/vcmi/czech.json @@ -192,15 +192,10 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team Alignments", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Druhy cest", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Extra timer}\n\nStarts counting down when the {turn timer} reaches zero. It is set only once at the beginning of the game. When this timer reaches zero, the player's turn ends.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Turn timer}\n\nStarts counting down when the player starts their turn on the adventure map. It is reset to its initial value at the start of each turn. Any unused turn time will be added to the {Extra timer} if it is in use.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Battle timer}\n\nCounts down during battles when the {stack timer} reaches zero. It is reset to its initial value at the start of each battle. If the timer reaches zero, the currently active stack will defend.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Stack timer}\n\nStarts counting down when the player is selecting an action for the current stack during battle. It resets to its initial value after the stack's action is completed.", "vcmi.optionsTab.widgets.labelTimer" : "Časovač", "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Klasický časovač", "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Šachový časovač", - // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Nepřítel zvládl přežít do této chvíle. Vítězství je jeho!", "vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulace! Zvládli jste přežít. Vítězství je vaše!", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 89ea0a1f8..4ede80f5c 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -228,7 +228,7 @@ "vcmi.optionsTab.chessFieldBattle.hover" : "Battle timer", "vcmi.optionsTab.chessFieldCreature.hover" : "Unit timer", "vcmi.optionsTab.chessFieldBase.help" : "Used when {Turn Timer} reaches 0. Set once at game start. On reaching zero, ends current turn. Any ongoing combat with end with a loss.", - "vcmi.optionsTab.chessFieldTurn.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to Base Timer at turn's end.", + "vcmi.optionsTab.chessFieldTurn.help" : "Used out of combat or when {Battle Timer} runs out. Reset each turn. Leftover added to {Base Timer} at turn's end.", "vcmi.optionsTab.chessFieldBattle.help" : "Used in battles with AI or in pvp combat when {Unit Timer} runs out. Reset at start of each combat.", "vcmi.optionsTab.chessFieldCreature.help" : "Used when selecting unit action in pvp combat. Reset at start of each unit's turn.", @@ -238,7 +238,7 @@ "vcmi.optionsTab.simturnsAI.hover" : "(Experimental) Simultaneous AI Turns", "vcmi.optionsTab.simturnsMin.help" : "Play simultaneously for specified number of days. Contacts between players during this period are blocked", "vcmi.optionsTab.simturnsMax.help" : "Play simultaneously for specified number of days or until contact with another player", - "vcmi.optionsTab.simturnsAI.help" : "", + "vcmi.optionsTab.simturnsAI.help" : "{Simultaneous AI Turns}\nExperimental option. Allows AI players to act at the same time as human player when simultaneous turns are enabled.", // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language // Using this information, VCMI will automatically select correct plural form for every possible amount diff --git a/Mods/vcmi/config/vcmi/russian.json b/Mods/vcmi/config/vcmi/russian.json index 24b7d7931..3374b9df4 100644 --- a/Mods/vcmi/config/vcmi/russian.json +++ b/Mods/vcmi/config/vcmi/russian.json @@ -201,13 +201,14 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Распределение команд", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Виды дорог", - "vcmi.optionsTab.widgets.chessFieldBase.help" : "{Время игрока}\n\nОбратный отсчет начинается когда {время на ход} истекает. Устанавливается один раз в начале игры. По истечении времени игрок завершает ход.", - "vcmi.optionsTab.widgets.chessFieldTurn.help" : "{Время на ход}\n\nОбратный отсчет начивается когда игрок начинает свой ход. В начале каждого хода устанавливается в иходное значение. Все неиспользованное время добавляется ко {времени игрока}, если оно используется.", - "vcmi.optionsTab.widgets.chessFieldBattle.help" : "{Время на битву}\n\nОбратный отсчет начинается когда {время на отряд истекает}. В начале каждой битвы устанавливается в исходное значение. По истечении времени текущий отряд получает приказ защищаться.", - "vcmi.optionsTab.widgets.chessFieldCreature.help" : "{Время на отряд}\n\nОбратный отсчет начинается когда игрок получает получает контроль над отрядом во время битвы. Устанавливается в исходное значение всякий раз, когда отряд получает возможность действовать.", - "vcmi.optionsTab.widgets.labelTimer" : "Таймер", - "vcmi.optionsTab.widgets.timerModeSwitch.classic" : "Классические часы", - "vcmi.optionsTab.widgets.timerModeSwitch.chess" : "Шахматные часы", + "vcmi.optionsTab.chessFieldBase.hover" : "Время игрока", + "vcmi.optionsTab.chessFieldTurn.hover" : "Время на ход", + "vcmi.optionsTab.chessFieldBattle.hover" : "Время на битву", + "vcmi.optionsTab.chessFieldCreature.hover" : "Время на отряд", + "vcmi.optionsTab.chessFieldBase.help" : "Обратный отсчет начинается когда {время на ход} истекает. Устанавливается один раз в начале игры. По истечении времени игрок завершает ход.", + "vcmi.optionsTab.chessFieldTurn.help" : "Обратный отсчет начивается когда игрок начинает свой ход. В начале каждого хода устанавливается в иходное значение. Все неиспользованное время добавляется ко {времени игрока}, если оно используется.", + "vcmi.optionsTab.chessFieldBattle.help" : "Обратный отсчет начинается когда {время на отряд истекает}. В начале каждой битвы устанавливается в исходное значение. По истечении времени текущий отряд получает приказ защищаться.", + "vcmi.optionsTab.chessFieldCreature.help" : "Обратный отсчет начинается когда игрок получает получает контроль над отрядом во время битвы. Устанавливается в исходное значение всякий раз, когда отряд получает возможность действовать.", "mapObject.core.creatureBank.cyclopsStockpile.name" : "Хранилище циклопов", "mapObject.core.creatureBank.dragonFlyHive.name" : "Улей летучих змиев", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 81a7244db..6e01eafb4 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -30,6 +30,11 @@ "vcmi.capitalColors.6" : "Сизий", "vcmi.capitalColors.7" : "Рожевий", + "vcmi.heroOverview.startingArmy" : "Початкові загони", + "vcmi.heroOverview.warMachine" : "Бойові машини", + "vcmi.heroOverview.secondarySkills" : "Навички", + "vcmi.heroOverview.spells" : "Закляття", + "vcmi.radialWheel.mergeSameUnit" : "Об'єднати однакових істот", "vcmi.radialWheel.fillSingleUnit" : "Заповнити одиничними істотами", "vcmi.radialWheel.splitSingleUnit" : "Відділити одну істоту", @@ -37,8 +42,21 @@ "vcmi.radialWheel.moveUnit" : "Перемістити істоту до іншої армії", "vcmi.radialWheel.splitUnit" : "Розділити істоту в інший слот", + "vcmi.radialWheel.heroGetArmy" : "Отримати армію іншого героя", + "vcmi.radialWheel.heroSwapArmy" : "Обміняти армії героїв", + "vcmi.radialWheel.heroExchange" : "Відкрити вікно обміну", + "vcmi.radialWheel.heroGetArtifacts" : "Отримати артефакти іншого героя", + "vcmi.radialWheel.heroSwapArtifacts" : "Обміняти артефакти героїв", + "vcmi.radialWheel.heroDismiss" : "Звільнити цього героя", + + "vcmi.radialWheel.moveTop" : "Перемістити на початок", + "vcmi.radialWheel.moveUp" : "Перемістити вгору", + "vcmi.radialWheel.moveDown" : "Перемістити вниз", + "vcmi.radialWheel.moveBottom" : "Перемістити у кінець", + "vcmi.mainMenu.serverConnecting" : "Підключення...", "vcmi.mainMenu.serverAddressEnter" : "Вкажіть адресу:", + "vcmi.mainMenu.serverConnectionFailed" : "Помилка з'єднання", "vcmi.mainMenu.serverClosing" : "Завершення...", "vcmi.mainMenu.hostTCP" : "Створити TCP/IP гру", "vcmi.mainMenu.joinTCP" : "Приєднатися до TCP/IP гри", @@ -46,9 +64,14 @@ "vcmi.lobby.filepath" : "Назва файлу", "vcmi.lobby.creationDate" : "Дата створення", + "vcmi.lobby.scenarioName" : "Scenario name", + "vcmi.lobby.mapPreview" : "Огляд мапи", + "vcmi.lobby.noPreview" : "огляд недоступний", + "vcmi.lobby.noUnderground" : "немає підземелля", "vcmi.server.errors.existingProcess" : "Працює інший процес vcmiserver, будь ласка, спочатку завершіть його", "vcmi.server.errors.modsToEnable" : "{Потрібні модифікації для завантаження гри}", + "vcmi.server.errors.modsToDisable" : "{Модифікації що мають бути вимкнені}", "vcmi.server.confirmReconnect" : "Підключитися до минулої сесії?", "vcmi.settingsMainWindow.generalTab.hover" : "Загальні", @@ -84,6 +107,8 @@ "vcmi.systemOptions.framerateButton.help" : "{Лічильник кадрів}\n\n Перемикає видимість лічильника кадрів на секунду у кутку ігрового вікна", "vcmi.systemOptions.hapticFeedbackButton.hover" : "Тактильний відгук", "vcmi.systemOptions.hapticFeedbackButton.help" : "{Тактильний відгук}\n\nВикористовувати вібрацію при використанні сенсорного екрану", + "vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Розширення інтерфейсу", + "vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Розширення інтерфейсу}\n\nУвімкніть різні розширення інтерфейсу для покращення якості життя. Наприклад, більша книга заклинань, рюкзак тощо. Вимкнути, щоб отримати більш класичний досвід.", "vcmi.adventureOptions.infoBarPick.help" : "{Повідомлення у панелі статусу}\n\nЗа можливості, повідомлення про відвідування об'єктів карти пригод будуть відображені у панелі статусу замість окремого вікна", "vcmi.adventureOptions.infoBarPick.hover" : "Повідомлення у панелі статусу", @@ -167,7 +192,7 @@ "vcmi.townHall.greetingCustomBonus" : "%s дає вам +%d %s%s", "vcmi.townHall.greetingCustomUntil" : " до наступної битви.", "vcmi.townHall.greetingInTownMagicWell" : "%s повністю відновлює ваш запас очків магії.", - + "vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:", "vcmi.logicalExpressions.allOf" : "Все з перерахованого:", "vcmi.logicalExpressions.noneOf" : "Нічого з перерахованого:", @@ -195,6 +220,38 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Розподіл команд", "vcmi.randomMapTab.widgets.roadTypesLabel" : "Види доріг", + "vcmi.optionsTab.turnOptions.hover" : "Параметри ходів", + "vcmi.optionsTab.turnOptions.help" : "Виберіть опції таймера ходів та одночасних ходів", + + "vcmi.optionsTab.chessFieldBase.hover" : "Основний таймер", + "vcmi.optionsTab.chessFieldTurn.hover" : "Таймер ходу", + "vcmi.optionsTab.chessFieldBattle.hover" : "Таймер битви", + "vcmi.optionsTab.chessFieldCreature.hover" : "Таймер загону", + "vcmi.optionsTab.chessFieldBase.help" : "Використовується коли {Таймер ходу} вичерпується. Встановлюється один раз на початку гри. Коли вичерпується, поточний хід буде перервано, поточна битва буде програна.", + "vcmi.optionsTab.chessFieldTurn.help" : "Використовується під час ходу. Встановлюється кожен хід. Залишок додається до {основного таймеру} у кінці ходу", + "vcmi.optionsTab.chessFieldBattle.help" : "Використовується у боях з ШІ чи у боях з гравцями якщо {таймер загону} вичерпується. Встановлюється на початку кожного бою.", + "vcmi.optionsTab.chessFieldCreature.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку кожної дії.", + + "vcmi.optionsTab.simturns" : "Одночасні ходи", + "vcmi.optionsTab.simturnsMin.hover" : "Щонайменше", + "vcmi.optionsTab.simturnsMax.hover" : "Щонайбільше", + "vcmi.optionsTab.simturnsAI.hover" : "(Експериментально) Одночасні ходи ШІ", + "vcmi.optionsTab.simturnsMin.help" : "Грати одночасно обрану кількість днів. Контакти між гравцями у цей період заблоковані", + "vcmi.optionsTab.simturnsMax.help" : "Грати одночасно обрану кількість днів чи до першого контакту з іншим гравцем", + "vcmi.optionsTab.simturnsAI.help" : "{Одночасні ходи ШІ}\nЕкспериментальна опція. Дозволяє гравцям-ШІ діяти одночасно с гравцями-людьми якщо одночасні ходи увімкнені.", + + // Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language + // Using this information, VCMI will automatically select correct plural form for every possible amount + "vcmi.optionsTab.simturns.days.0" : " %d днів", + "vcmi.optionsTab.simturns.days.1" : " %d день", + "vcmi.optionsTab.simturns.days.2" : " %d дні", + "vcmi.optionsTab.simturns.weeks.0" : " %d тижнів", + "vcmi.optionsTab.simturns.weeks.1" : " %d тиждень", + "vcmi.optionsTab.simturns.weeks.2" : " %d тижні", + "vcmi.optionsTab.simturns.months.0" : " %d місяців", + "vcmi.optionsTab.simturns.months.1" : " %d місяць", + "vcmi.optionsTab.simturns.months.2" : " %d місяці", + // Custom victory conditions for H3 campaigns and HotA maps "vcmi.map.victoryCondition.daysPassed.toOthers" : "Ворогу вдалося вижити до сьогоднішнього дня. Він переміг!", "vcmi.map.victoryCondition.daysPassed.toSelf" : "Вітаємо! Вам вдалося залишитися в живих. Перемога за вами!", diff --git a/Mods/vcmi/config/vcmi/vietnamese.json b/Mods/vcmi/config/vcmi/vietnamese.json index 890e699d1..0750201cf 100644 --- a/Mods/vcmi/config/vcmi/vietnamese.json +++ b/Mods/vcmi/config/vcmi/vietnamese.json @@ -198,13 +198,14 @@ "vcmi.randomMapTab.widgets.teamAlignmentsLabel": "Sắp đội", "vcmi.randomMapTab.widgets.roadTypesLabel": "Kiểu đường xá", - "vcmi.optionsTab.widgets.chessFieldBase.help": "{Thời gian thêm}\n\nBắt đầu đếm ngược khi {Thời gian lượt} giảm đến 0. Được đặt 1 lần khi bắt đầu trò chơi. Khi thời gian này giảm đến 0, lượt của người chơi kết thúc.", - "vcmi.optionsTab.widgets.chessFieldTurn.help": "{Thời gian lượt}\n\nBắt đầu đếm ngược khi đến lượt người chơi trên bản đồ phiêu lưu. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi lượt. Thời gian lượt chưa sử dụng sẽ được thêm vào {Thời gian thêm} nếu có.", - "vcmi.optionsTab.widgets.chessFieldBattle.help": "{Thời gian trận đánh}\n\nĐếm ngược trong suốt trận đánh khi {Thời gian lính} giảm đến 0. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi trận đánh. Nếu thời gian giảm đến 0, đội lính hiện tại sẽ phòng thủ.", - "vcmi.optionsTab.widgets.chessFieldCreature.help": "{Thời gian lính}\n\nBắt đầu đếm ngược khi người chơi đang chọn hành động cho đội linh hiện tại trong suốt trận đánh. Nó được đặt lại giá trị ban đầu sau khi hành động của đội lính hoàn tất.", - "vcmi.optionsTab.widgets.labelTimer": "Đồng hồ", - "vcmi.optionsTab.widgets.timerModeSwitch.classic": "Đồng hồ cơ bản", - "vcmi.optionsTab.widgets.timerModeSwitch.chess": "Đồng hồ đánh cờ", + "vcmi.optionsTab.chessFieldBase.hover" : "Thời gian thêm", + "vcmi.optionsTab.chessFieldTurn.hover" : "Thời gian lượt", + "vcmi.optionsTab.chessFieldBattle.hover" : "Thời gian trận đánh", + "vcmi.optionsTab.chessFieldCreature.hover" : "Thời gian lính", + "vcmi.optionsTab.chessFieldBase.help": "Bắt đầu đếm ngược khi {Thời gian lượt} giảm đến 0. Được đặt 1 lần khi bắt đầu trò chơi. Khi thời gian này giảm đến 0, lượt của người chơi kết thúc.", + "vcmi.optionsTab.chessFieldTurn.help": "Bắt đầu đếm ngược khi đến lượt người chơi trên bản đồ phiêu lưu. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi lượt. Thời gian lượt chưa sử dụng sẽ được thêm vào {Thời gian thêm} nếu có.", + "vcmi.optionsTab.chessFieldBattle.help": "Đếm ngược trong suốt trận đánh khi {Thời gian lính} giảm đến 0. Nó được đặt lại giá trị ban đầu khi bắt đầu mỗi trận đánh. Nếu thời gian giảm đến 0, đội lính hiện tại sẽ phòng thủ.", + "vcmi.optionsTab.chessFieldCreature.help": "Bắt đầu đếm ngược khi người chơi đang chọn hành động cho đội linh hiện tại trong suốt trận đánh. Nó được đặt lại giá trị ban đầu sau khi hành động của đội lính hoàn tất.", "vcmi.map.victoryCondition.daysPassed.toOthers": "Đối thủ đã xoay xở để sinh tồn đến ngày này. Họ giành chiến thắng!", "vcmi.map.victoryCondition.daysPassed.toSelf": "Chúc mừng! Bạn đã vượt khó để sinh tồn. Chiến thắng thuộc về bạn!", From f65f4b128567cd0cc4015a9afaba3cea44094364 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 13 Nov 2023 19:49:41 +0200 Subject: [PATCH 10/11] Disable new UI if enhancements are off --- Mods/vcmi/config/vcmi/ukrainian.json | 2 +- client/lobby/CLobbyScreen.cpp | 17 ++++++++++++----- config/widgets/turnOptionsTab.json | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 6e01eafb4..3a50c17f9 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -227,7 +227,7 @@ "vcmi.optionsTab.chessFieldTurn.hover" : "Таймер ходу", "vcmi.optionsTab.chessFieldBattle.hover" : "Таймер битви", "vcmi.optionsTab.chessFieldCreature.hover" : "Таймер загону", - "vcmi.optionsTab.chessFieldBase.help" : "Використовується коли {Таймер ходу} вичерпується. Встановлюється один раз на початку гри. Коли вичерпується, поточний хід буде перервано, поточна битва буде програна.", + "vcmi.optionsTab.chessFieldBase.help" : "Встановлюється один раз на початку гри. Коли вичерпується, поточний хід буде перервано, поточна битва буде програна.", "vcmi.optionsTab.chessFieldTurn.help" : "Використовується під час ходу. Встановлюється кожен хід. Залишок додається до {основного таймеру} у кінці ходу", "vcmi.optionsTab.chessFieldBattle.help" : "Використовується у боях з ШІ чи у боях з гравцями якщо {таймер загону} вичерпується. Встановлюється на початку кожного бою.", "vcmi.optionsTab.chessFieldCreature.help" : "Використовується при обираннія дії загону у боях з гравцями. Встановлюється на початку кожної дії.", diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index 4537ce332..135e0d078 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -25,12 +25,13 @@ #include "../../CCallback.h" -#include "../CGameInfo.h" -#include "../../lib/networkPacks/PacksForLobby.h" +#include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" #include "../../lib/campaign/CampaignHandler.h" #include "../../lib/mapping/CMapInfo.h" +#include "../../lib/networkPacks/PacksForLobby.h" #include "../../lib/rmg/CMapGenOptions.h" +#include "../CGameInfo.h" CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) : CSelectionBase(screenType), bonusSel(nullptr) @@ -51,7 +52,8 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType) }); buttonOptions = std::make_shared(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS); - buttonTurnOptions = std::make_shared(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE); + if(settings["general"]["enableUiEnhancements"].Bool()) + buttonTurnOptions = std::make_shared(Point(619, 510), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabTurnOptions), EShortcut::NONE); }; buttonChat = std::make_shared(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT); @@ -149,7 +151,10 @@ void CLobbyScreen::toggleMode(bool host) auto buttonColor = host ? Colors::WHITE : Colors::ORANGE; buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor); buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor); - buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor); + + if (buttonTurnOptions) + buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor); + if(buttonRMG) { buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor); @@ -157,7 +162,9 @@ void CLobbyScreen::toggleMode(bool host) } buttonSelect->block(!host); buttonOptions->block(!host); - buttonTurnOptions->block(!host); + + if (buttonTurnOptions) + buttonTurnOptions->block(!host); if(CSH->mi) { diff --git a/config/widgets/turnOptionsTab.json b/config/widgets/turnOptionsTab.json index feef3ae61..b84aba8e8 100644 --- a/config/widgets/turnOptionsTab.json +++ b/config/widgets/turnOptionsTab.json @@ -237,7 +237,7 @@ "alignment": "center", "color": "white", "text": "", - "position": {"x": 320, "y": 428} + "position": {"x": 278, "y": 428} }, { "name": "labelSimturnsDurationValueMax", @@ -246,7 +246,7 @@ "alignment": "center", "color": "white", "text": "", - "position": {"x": 320, "y": 478} + "position": {"x": 278, "y": 478} }, { "type" : "label", From 8392125b047be8083750d398db7db008de6295cd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 13 Nov 2023 19:50:53 +0200 Subject: [PATCH 11/11] Disable experimental simturns AI option by default --- lib/StartInfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/StartInfo.h b/lib/StartInfo.h index 925df407c..208ef2706 100644 --- a/lib/StartInfo.h +++ b/lib/StartInfo.h @@ -30,7 +30,7 @@ struct DLL_LINKAGE SimturnsInfo /// Maximum number of turns that might be played simultaneously unless contact is detected int optionalTurns = 0; /// If set to true, human and 1 AI can act at the same time - bool allowHumanWithAI = true; + bool allowHumanWithAI = false; template void serialize(Handler &h, const int version)