diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java index 4f3cc0582..2bbd73ae3 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java @@ -50,7 +50,7 @@ import eu.vcmi.vcmi.util.ServerResponse; public class ActivityMods extends ActivityWithToolbar { private static final boolean ENABLE_REPO_DOWNLOADING = true; - private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.3.json"; + private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.4.json"; private VCMIModsRepo mRepo; private RecyclerView mRecycler; diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index b18ccb36e..3fd04a848 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -95,6 +95,7 @@ set(client_SRCS widgets/CComponent.cpp widgets/CGarrisonInt.cpp widgets/CreatureCostBox.cpp + widgets/ComboBox.cpp widgets/Images.cpp widgets/MiscWidgets.cpp widgets/ObjectLists.cpp @@ -253,6 +254,7 @@ set(client_HEADERS widgets/CComponent.h widgets/CGarrisonInt.h widgets/CreatureCostBox.h + widgets/ComboBox.h widgets/Images.h widgets/MiscWidgets.h widgets/ObjectLists.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 8a4051fed..ddf40406d 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -39,6 +39,7 @@ #include "../lib/CThreadHelper.h" #include "../lib/NetPackVisitor.h" #include "../lib/StartInfo.h" +#include "../lib/TurnTimerInfo.h" #include "../lib/VCMIDirs.h" #include "../lib/campaign/CampaignState.h" #include "../lib/mapping/CMapInfo.h" @@ -475,11 +476,10 @@ void CServerHandler::setDifficulty(int to) const sendLobbyPack(lsd); } -void CServerHandler::setTurnLength(int npos) const +void CServerHandler::setTurnTimerInfo(const TurnTimerInfo & info) const { - vstd::amin(npos, GameConstants::POSSIBLE_TURNTIME.size() - 1); LobbySetTurnTime lstt; - lstt.turnTimerInfo.turnTimer = GameConstants::POSSIBLE_TURNTIME[npos] * 60 * 1000; + lstt.turnTimerInfo = info; sendLobbyPack(lstt); } diff --git a/client/CServerHandler.h b/client/CServerHandler.h index 777cbc29e..edecd3a64 100644 --- a/client/CServerHandler.h +++ b/client/CServerHandler.h @@ -19,6 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CConnection; class PlayerColor; struct StartInfo; +struct TurnTimerInfo; class CMapInfo; class CGameState; @@ -64,7 +65,7 @@ public: virtual void setPlayer(PlayerColor color) const = 0; virtual void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const = 0; virtual void setDifficulty(int to) const = 0; - virtual void setTurnLength(int npos) const = 0; + virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0; virtual void sendMessage(const std::string & txt) const = 0; virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it? virtual void sendStartGame(bool allowOnlyAI = false) const = 0; @@ -146,7 +147,7 @@ public: void setPlayer(PlayerColor color) const override; void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const override; void setDifficulty(int to) const override; - void setTurnLength(int npos) const override; + void setTurnTimerInfo(const TurnTimerInfo &) const override; void sendMessage(const std::string & txt) const override; void sendGuiAction(ui8 action) const override; void sendRestartGame() const override; diff --git a/client/adventureMap/TurnTimerWidget.cpp b/client/adventureMap/TurnTimerWidget.cpp index 76bd90129..68428ad03 100644 --- a/client/adventureMap/TurnTimerWidget.cpp +++ b/client/adventureMap/TurnTimerWidget.cpp @@ -13,6 +13,8 @@ #include "../CGameInfo.h" #include "../CMusicHandler.h" #include "../CPlayerInterface.h" +#include "../battle/BattleInterface.h" +#include "../battle/BattleStacksController.h" #include "../render/EFont.h" #include "../render/Graphics.h" @@ -21,6 +23,7 @@ #include "../widgets/Images.h" #include "../widgets/TextControls.h" #include "../../CCallback.h" +#include "../../lib/CStack.h" #include "../../lib/CPlayerState.h" #include "../../lib/filesystem/ResourceID.h" @@ -38,7 +41,7 @@ void TurnTimerWidget::DrawRect::showAll(Canvas & to) TurnTimerWidget::TurnTimerWidget(): InterfaceObjectConfigurable(TIME), - turnTime(0), lastTurnTime(0), cachedTurnTime(0) + turnTime(0), lastTurnTime(0), cachedTurnTime(0), lastPlayer(PlayerColor::CANNOT_DETERMINE) { REGISTER_BUILDER("drawRect", &TurnTimerWidget::buildDrawRect); @@ -70,8 +73,8 @@ void TurnTimerWidget::show(Canvas & to) void TurnTimerWidget::setTime(PlayerColor player, int time) { int newTime = time / 1000; - if((LOCPLINT->cb->isPlayerMakingTurn(LOCPLINT->playerID)) - && (newTime != turnTime) + if(player == LOCPLINT->playerID + && newTime != turnTime && notifications.count(newTime)) { CCS->soundh->playSound(variables["notificationSound"].String()); @@ -99,15 +102,27 @@ void TurnTimerWidget::tick(uint32_t msPassed) if(!LOCPLINT || !LOCPLINT->cb) return; - for (PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) + for (PlayerColor p(0); p < PlayerColor::PLAYER_LIMIT; ++p) { - if (!LOCPLINT->cb->isPlayerMakingTurn(player)) + auto player = p; + if(LOCPLINT->battleInt) + { + if(auto * stack = LOCPLINT->battleInt->stacksController->getActiveStack()) + player = stack->getOwner(); + } + else if (!LOCPLINT->cb->isPlayerMakingTurn(player)) continue; auto time = LOCPLINT->cb->getPlayerTurnTime(player); cachedTurnTime -= msPassed; if(cachedTurnTime < 0) cachedTurnTime = 0; //do not go below zero + if(lastPlayer != player) + { + lastPlayer = player; + lastTurnTime = 0; + } + auto timeCheckAndUpdate = [&](int time) { if(time / 1000 != lastTurnTime / 1000) @@ -121,7 +136,7 @@ void TurnTimerWidget::tick(uint32_t msPassed) }; auto * playerInfo = LOCPLINT->cb->getPlayer(player); - if(playerInfo && playerInfo->isHuman()) + if(player.isValidPlayer() || (playerInfo && playerInfo->isHuman())) { if(LOCPLINT->battleInt) { diff --git a/client/adventureMap/TurnTimerWidget.h b/client/adventureMap/TurnTimerWidget.h index e5d5614fc..ccc801eb4 100644 --- a/client/adventureMap/TurnTimerWidget.h +++ b/client/adventureMap/TurnTimerWidget.h @@ -18,6 +18,12 @@ class CAnimImage; class CLabel; +VCMI_LIB_NAMESPACE_BEGIN + +class PlayerColor; + +VCMI_LIB_NAMESPACE_END + class TurnTimerWidget : public InterfaceObjectConfigurable { private: @@ -35,6 +41,7 @@ private: int turnTime; int lastTurnTime; int cachedTurnTime; + PlayerColor lastPlayer; std::set notifications; diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 37cb233f3..bab69dd1d 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -20,6 +20,7 @@ #include "../render/Graphics.h" #include "../render/IFont.h" #include "../widgets/CComponent.h" +#include "../widgets/ComboBox.h" #include "../widgets/Buttons.h" #include "../widgets/MiscWidgets.h" #include "../widgets/ObjectLists.h" @@ -52,6 +53,8 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset) REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup); REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider); REGISTER_BUILDER("layout", &InterfaceObjectConfigurable::buildLayout); + REGISTER_BUILDER("comboBox", &InterfaceObjectConfigurable::buildComboBox); + REGISTER_BUILDER("textInput", &InterfaceObjectConfigurable::buildTextInput); } void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f) @@ -61,9 +64,15 @@ void InterfaceObjectConfigurable::registerBuilder(const std::string & type, Buil void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function callback) { - callbacks[callbackName] = callback; + callbacks_int[callbackName] = callback; } +void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function callback) +{ + callbacks_string[callbackName] = callback; +} + + void InterfaceObjectConfigurable::deleteWidget(const std::string & name) { auto iter = widgets.find(name); @@ -338,7 +347,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildToggleGroup(cons if(!config["selected"].isNull()) group->setSelected(config["selected"].Integer()); if(!config["callback"].isNull()) - group->addCallback(callbacks.at(config["callback"].String())); + group->addCallback(callbacks_int.at(config["callback"].String())); return group; } @@ -411,8 +420,8 @@ void InterfaceObjectConfigurable::loadToggleButtonCallback(std::shared_ptr 0) - button->addCallback(callbacks.at(callbackName)); + if (callbacks_int.count(callbackName) > 0) + button->addCallback(callbacks_int.at(callbackName)); else logGlobal->error("Invalid callback '%s' in widget", callbackName ); } @@ -424,8 +433,8 @@ void InterfaceObjectConfigurable::loadButtonCallback(std::shared_ptr bu std::string callbackName = config.String(); - if (callbacks.count(callbackName) > 0) - button->addCallback(std::bind(callbacks.at(callbackName), 0)); + if (callbacks_int.count(callbackName) > 0) + button->addCallback(std::bind(callbacks_int.at(callbackName), 0)); else logGlobal->error("Invalid callback '%s' in widget", callbackName ); } @@ -481,7 +490,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildSlider(const JsonNode auto value = config["selected"].Integer(); bool horizontal = config["orientation"].String() == "horizontal"; const auto & result = - std::make_shared(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal ? Orientation::HORIZONTAL : Orientation::VERTICAL, style); + std::make_shared(position, length, callbacks_int.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal ? Orientation::HORIZONTAL : Orientation::VERTICAL, style); if(!config["scrollBounds"].isNull()) { @@ -513,6 +522,54 @@ std::shared_ptr InterfaceObjectConfigurable::buildTexture(const return std::make_shared(image, rect); } +std::shared_ptr InterfaceObjectConfigurable::buildComboBox(const JsonNode & config) +{ + logGlobal->debug("Building widget ComboBox"); + auto position = readPosition(config["position"]); + auto image = config["image"].String(); + auto help = readHintText(config["help"]); + auto result = std::make_shared(position, image, help, config["dropDown"]); + if(!config["items"].isNull()) + { + for(const auto & item : config["items"].Vector()) + { + result->addOverlay(buildWidget(item)); + } + } + if(!config["imageOrder"].isNull()) + { + auto imgOrder = config["imageOrder"].Vector(); + assert(imgOrder.size() >= 4); + result->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer()); + } + + loadButtonBorderColor(result, config["borderColor"]); + loadButtonHotkey(result, config["hotkey"]); + return result; +} + +std::shared_ptr InterfaceObjectConfigurable::buildTextInput(const JsonNode & config) const +{ + logGlobal->debug("Building widget CTextInput"); + auto rect = readRect(config["rect"]); + auto offset = readPosition(config["backgroundOffset"]); + auto bgName = config["background"].String(); + auto result = std::make_shared(rect, offset, bgName, 0); + if(!config["alignment"].isNull()) + result->alignment = readTextAlignment(config["alignment"]); + if(!config["font"].isNull()) + result->font = readFont(config["font"]); + if(!config["color"].isNull()) + result->setColor(readColor(config["color"])); + if(!config["text"].isNull()) + result->setText(readText(config["text"])); + if(!config["callback"].isNull()) + result->cb += callbacks_string.at(config["callback"].String()); + if(!config["help"].isNull()) + result->setHelpText(readText(config["help"])); + return result; +} + /// Small helper class that provides ownership for shared_ptr's of child elements class InterfaceLayoutWidget : public CIntObject { @@ -597,7 +654,7 @@ std::shared_ptr InterfaceObjectConfigurable::buildAnimation(const if(!config["alpha"].isNull()) anim->setAlpha(config["alpha"].Integer()); if(!config["callback"].isNull()) - anim->callback = std::bind(callbacks.at(config["callback"].String()), 0); + anim->callback = std::bind(callbacks_int.at(config["callback"].String()), 0); if(!config["frames"].isNull()) { auto b = config["frames"]["start"].Integer(); diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h index 86a3381c0..b2558655c 100644 --- a/client/gui/InterfaceObjectConfigurable.h +++ b/client/gui/InterfaceObjectConfigurable.h @@ -27,6 +27,8 @@ class CSlider; class CAnimImage; class CShowableAnim; class CFilledTexture; +class ComboBox; +class CTextInput; #define REGISTER_BUILDER(type, method) registerBuilder(type, std::bind(method, this, std::placeholders::_1)) @@ -58,6 +60,7 @@ protected: void addWidget(const std::string & name, std::shared_ptr widget); void addCallback(const std::string & callbackName, std::function callback); + void addCallback(const std::string & callbackName, std::function callback); JsonNode variables; template @@ -99,6 +102,8 @@ protected: std::shared_ptr buildAnimation(const JsonNode &) const; std::shared_ptr buildTexture(const JsonNode &) const; std::shared_ptr buildLayout(const JsonNode &); + std::shared_ptr buildComboBox(const JsonNode &); + std::shared_ptr buildTextInput(const JsonNode &) const; //composite widgets std::shared_ptr buildWidget(JsonNode config) const; @@ -114,7 +119,8 @@ private: int unnamedObjectId = 0; std::map builders; std::map> widgets; - std::map> callbacks; + std::map> callbacks_int; + std::map> callbacks_string; std::map conditionals; std::map shortcuts; }; diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index bbbd25ca5..59f77054f 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -21,6 +21,7 @@ #include "../render/Graphics.h" #include "../render/IFont.h" #include "../widgets/CComponent.h" +#include "../widgets/ComboBox.h" #include "../widgets/Buttons.h" #include "../widgets/Images.h" #include "../widgets/MiscWidgets.h" @@ -43,20 +44,154 @@ OptionsTab::OptionsTab() : 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("setTurnLength", std::bind(&IServerAPI::setTurnLength, CSH, _1)); + //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(ResourceID("config/widgets/optionsTab.json")); build(config); - if(SEL->screenType == ESelectionScreen::newGame || SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::scenarioInfo) + //set timers combo box callbacks + if(auto w = widget("timerModeSwitch")) { - if(auto w = widget("sliderTurnDuration")) - w->deactivate(); - if(auto w = widget("labelPlayerTurnDuration")) - w->deactivate(); - if(auto w = widget("labelTurnDurationValue")) - w->deactivate(); + 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); } } @@ -73,12 +208,52 @@ void OptionsTab::recreate() entries.insert(std::make_pair(pInfo.first, std::make_shared(pInfo.second, * this))); } - + + const auto & turnTimerRemote = SEL->getStartInfo()->turnTimerInfo; + + //classic timer if(auto turnSlider = widget("sliderTurnDuration")) { - turnSlider->scrollTo(vstd::find_pos(GameConstants::POSSIBLE_TURNTIME, SEL->getStartInfo()->turnTimerInfo.turnTimer / (60 * 1000))); - if(auto w = widget("labelTurnDurationValue")) - w->setText(CGI->generaltexth->turnDurations[turnSlider->getValue()]); + 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/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 88f015e9b..fad8a28eb 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -18,6 +18,7 @@ #include "../gui/MouseButton.h" #include "../gui/WindowHandler.h" #include "../widgets/CComponent.h" +#include "../widgets/ComboBox.h" #include "../widgets/Buttons.h" #include "../widgets/MiscWidgets.h" #include "../widgets/ObjectLists.h" @@ -102,11 +103,6 @@ RandomMapTab::RandomMapTab(): }); //new callbacks available only from mod - addCallback("templateSelection", [&](int) - { - GH.windows().createAndPushWindow(*this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}); - }); - addCallback("teamAlignments", [&](int) { GH.windows().createAndPushWindow(*this); @@ -125,6 +121,35 @@ RandomMapTab::RandomMapTab(): const JsonNode config(ResourceID("config/widgets/randomMapTab.json")); build(config); + //set combo box callbacks + if(auto w = widget("templateList")) + { + w->onConstructItems = [](std::vector & curItems){ + auto templates = VLC->tplh->getTemplates(); + + boost::range::sort(templates, [](const CRmgTemplate * a, const CRmgTemplate * b){ + return a->getName() < b->getName(); + }); + + curItems.push_back(nullptr); //default template + + for(auto & t : templates) + curItems.push_back(t); + }; + + w->onSetItem = [&](const void * item){ + this->setTemplate(reinterpret_cast(item)); + }; + + w->getItemText = [this](int idx, const void * item){ + if(item) + return reinterpret_cast(item)->getName(); + if(idx == 0) + return readText(variables["randomTemplate"]); + return std::string(""); + }; + } + updateMapInfoByHost(); } @@ -360,163 +385,6 @@ std::vector RandomMapTab::getPossibleMapSizes() return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT}; } -TemplatesDropBox::ListItem::ListItem(const JsonNode & config, TemplatesDropBox & _dropBox, Point position) - : InterfaceObjectConfigurable(LCLICK | HOVER, position), - dropBox(_dropBox) -{ - OBJ_CONSTRUCTION; - - build(config); - - if(auto w = widget("hoverImage")) - { - pos.w = w->pos.w; - pos.h = w->pos.h; - } - setRedrawParent(true); -} - -void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item) -{ - if(auto w = widget("labelName")) - { - item = _item; - if(item) - { - w->setText(item->getName()); - } - else - { - if(idx) - w->setText(""); - else - w->setText(readText(dropBox.variables["randomTemplate"])); - } - } -} - -void TemplatesDropBox::ListItem::hover(bool on) -{ - auto h = widget("hoverImage"); - auto w = widget("labelName"); - if(h && w) - { - if(w->getText().empty()) - h->visible = false; - else - h->visible = on; - } - redraw(); -} - -void TemplatesDropBox::ListItem::clickPressed(const Point & cursorPosition) -{ - if(isHovered()) - dropBox.setTemplate(item); -} - -void TemplatesDropBox::ListItem::clickReleased(const Point & cursorPosition) -{ - dropBox.clickPressed(cursorPosition); - dropBox.clickReleased(cursorPosition); -} - -TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size): - InterfaceObjectConfigurable(LCLICK | HOVER), - randomMapTab(randomMapTab) -{ - REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem); - - curItems = VLC->tplh->getTemplates(); - - boost::range::sort(curItems, [](const CRmgTemplate * a, const CRmgTemplate * b){ - return a->getName() < b->getName(); - }); - - curItems.insert(curItems.begin(), nullptr); //default template - - const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json")); - - addCallback("sliderMove", std::bind(&TemplatesDropBox::sliderMove, this, std::placeholders::_1)); - - OBJ_CONSTRUCTION; - pos = randomMapTab.pos; - - build(config); - - if(auto w = widget("slider")) - { - w->setAmount(curItems.size()); - } - - //FIXME: this should be done by InterfaceObjectConfigurable, but might have side-effects - pos = children.front()->pos; - for (auto const & child : children) - pos = pos.include(child->pos); - - updateListItems(); -} - -std::shared_ptr TemplatesDropBox::buildListItem(const JsonNode & config) -{ - auto position = readPosition(config["position"]); - listItems.push_back(std::make_shared(config, *this, position)); - return listItems.back(); -} - -void TemplatesDropBox::sliderMove(int slidPos) -{ - auto w = widget("slider"); - if(!w) - return; // ignore spurious call when slider is being created - updateListItems(); - redraw(); -} - -bool TemplatesDropBox::receiveEvent(const Point & position, int eventType) const -{ - if (eventType == LCLICK) - return true; // we want drop box to close when clicking outside drop box borders - - return CIntObject::receiveEvent(position, eventType); -} - -void TemplatesDropBox::clickPressed(const Point & cursorPosition) -{ - if (!pos.isInside(cursorPosition)) - { - assert(GH.windows().isTopWindow(this)); - GH.windows().popWindows(1); - } -} - -void TemplatesDropBox::updateListItems() -{ - if(auto w = widget("slider")) - { - int elemIdx = w->getValue(); - for(auto item : listItems) - { - if(elemIdx < curItems.size()) - { - item->updateItem(elemIdx, curItems[elemIdx]); - elemIdx++; - } - else - { - item->updateItem(elemIdx); - } - } - } -} - -void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl) -{ - randomMapTab.setTemplate(tmpl); - assert(GH.windows().isTopWindow(this)); - GH.windows().popWindows(1); -} - TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): InterfaceObjectConfigurable() { diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h index 356085ab5..e23657548 100644 --- a/client/lobby/RandomMapTab.h +++ b/client/lobby/RandomMapTab.h @@ -51,43 +51,6 @@ private: std::set playerCountAllowed, playerTeamsAllowed, compCountAllowed, compTeamsAllowed; }; -class TemplatesDropBox : public InterfaceObjectConfigurable -{ - struct ListItem : public InterfaceObjectConfigurable - { - TemplatesDropBox & dropBox; - const CRmgTemplate * item = nullptr; - - ListItem(const JsonNode &, TemplatesDropBox &, Point position); - void updateItem(int index, const CRmgTemplate * item = nullptr); - - void hover(bool on) override; - void clickPressed(const Point & cursorPosition) override; - void clickReleased(const Point & cursorPosition) override; - }; - - friend struct ListItem; - -public: - TemplatesDropBox(RandomMapTab & randomMapTab, int3 size); - - bool receiveEvent(const Point & position, int eventType) const override; - void clickPressed(const Point & cursorPosition) override; - void setTemplate(const CRmgTemplate *); - -private: - std::shared_ptr buildListItem(const JsonNode & config); - - void sliderMove(int slidPos); - void updateListItems(); - - RandomMapTab & randomMapTab; - std::vector> listItems; - - std::vector curItems; - -}; - class TeamAlignmentsWidget: public InterfaceObjectConfigurable { public: diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index 32c4ab3b9..ec387451f 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -35,7 +35,7 @@ public: BLOCKED=2, HIGHLIGHTED=3 }; -private: +protected: std::vector imageNames;//store list of images that can be used by this button size_t currentImage; diff --git a/client/widgets/ComboBox.cpp b/client/widgets/ComboBox.cpp new file mode 100644 index 000000000..29ac42d0c --- /dev/null +++ b/client/widgets/ComboBox.cpp @@ -0,0 +1,181 @@ +/* + * ComboBox.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 "ComboBox.h" + +#include "Slider.h" +#include "Images.h" +#include "TextControls.h" +#include "../gui/CGuiHandler.h" +#include "../gui/WindowHandler.h" + +ComboBox::DropDown::Item::Item(const JsonNode & config, ComboBox::DropDown & _dropDown, Point position) + : InterfaceObjectConfigurable(LCLICK | HOVER, position), + dropDown(_dropDown) +{ + build(config); + + if(auto w = widget("hoverImage")) + { + pos.w = w->pos.w; + pos.h = w->pos.h; + } + setRedrawParent(true); +} + +void ComboBox::DropDown::Item::updateItem(int idx, const void * _item) +{ + if(auto w = widget("labelName")) + { + item = _item; + if(dropDown.comboBox.getItemText) + w->setText(dropDown.comboBox.getItemText(idx, item)); + } +} + +void ComboBox::DropDown::Item::hover(bool on) +{ + auto h = widget("hoverImage"); + auto w = widget("labelName"); + if(h && w) + { + if(w->getText().empty()) + h->visible = false; + else + h->visible = on; + } + redraw(); +} + +void ComboBox::DropDown::Item::clickPressed(const Point & cursorPosition) +{ + if(isHovered()) + dropDown.setItem(item); +} + +void ComboBox::DropDown::Item::clickReleased(const Point & cursorPosition) +{ + dropDown.clickPressed(cursorPosition); + dropDown.clickReleased(cursorPosition); +} + +ComboBox::DropDown::DropDown(const JsonNode & config, ComboBox & _comboBox): + InterfaceObjectConfigurable(LCLICK | HOVER), + comboBox(_comboBox) +{ + REGISTER_BUILDER("item", &ComboBox::DropDown::buildItem); + + if(comboBox.onConstructItems) + comboBox.onConstructItems(curItems); + + addCallback("sliderMove", std::bind(&ComboBox::DropDown::sliderMove, this, std::placeholders::_1)); + + pos = comboBox.pos; + + build(config); + + if(auto w = widget("slider")) + { + w->setAmount(curItems.size()); + } + + //FIXME: this should be done by InterfaceObjectConfigurable, but might have side-effects + pos = children.front()->pos; + for (auto const & child : children) + pos = pos.include(child->pos); + + updateListItems(); +} + +std::shared_ptr ComboBox::DropDown::buildItem(const JsonNode & config) +{ + auto position = readPosition(config["position"]); + items.push_back(std::make_shared(config, *this, position)); + return items.back(); +} + +void ComboBox::DropDown::sliderMove(int slidPos) +{ + auto w = widget("slider"); + if(!w) + return; // ignore spurious call when slider is being created + updateListItems(); + redraw(); +} + +bool ComboBox::DropDown::receiveEvent(const Point & position, int eventType) const +{ + if (eventType == LCLICK) + return true; // we want drop box to close when clicking outside drop box borders + + return CIntObject::receiveEvent(position, eventType); +} + +void ComboBox::DropDown::clickPressed(const Point & cursorPosition) +{ + if (!pos.isInside(cursorPosition)) + { + assert(GH.windows().isTopWindow(this)); + GH.windows().popWindows(1); + } +} + +void ComboBox::DropDown::updateListItems() +{ + if(auto w = widget("slider")) + { + int elemIdx = w->getValue(); + for(auto item : items) + { + if(elemIdx < curItems.size()) + { + item->updateItem(elemIdx, curItems[elemIdx]); + elemIdx++; + } + else + { + item->updateItem(elemIdx); + } + } + } +} + +void ComboBox::DropDown::setItem(const void * item) +{ + comboBox.setItem(item); + + assert(GH.windows().isTopWindow(this)); + GH.windows().popWindows(1); +} + +ComboBox::ComboBox(Point position, const std::string & defName, const std::pair & help, const JsonNode & dropDownDescriptor, EShortcut key, bool playerColoredButton): + CButton(position, defName, help, 0, key, playerColoredButton) +{ + addCallback([&, dropDownDescriptor]() + { + GH.windows().createAndPushWindow(dropDownDescriptor, *this); + }); +} + +void ComboBox::setItem(const void * item) +{ + if(auto w = std::dynamic_pointer_cast(overlay); getItemText) + addTextOverlay(getItemText(0, item), w->font, w->color); + + if(onSetItem) + onSetItem(item); +} + +void ComboBox::setItem(int id) +{ + std::vector tempItems; + onConstructItems(tempItems); + setItem(tempItems.at(id)); +} diff --git a/client/widgets/ComboBox.h b/client/widgets/ComboBox.h new file mode 100644 index 000000000..ced39987d --- /dev/null +++ b/client/widgets/ComboBox.h @@ -0,0 +1,69 @@ +/* + * ComboBox.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 "Buttons.h" + +class ComboBox : public CButton +{ + class DropDown : public InterfaceObjectConfigurable + { + struct Item : public InterfaceObjectConfigurable + { + DropDown & dropDown; + const void * item = nullptr; + + Item(const JsonNode &, ComboBox::DropDown &, Point position); + void updateItem(int index, const void * item = nullptr); + + void hover(bool on) override; + void clickPressed(const Point & cursorPosition) override; + void clickReleased(const Point & cursorPosition) override; + }; + + friend struct Item; + + public: + DropDown(const JsonNode &, ComboBox &); + + bool receiveEvent(const Point & position, int eventType) const override; + void clickPressed(const Point & cursorPosition) override; + void setItem(const void *); + + private: + std::shared_ptr buildItem(const JsonNode & config); + + void sliderMove(int slidPos); + void updateListItems(); + + ComboBox & comboBox; + std::vector> items; + std::vector curItems; + }; + + friend class DropDown; + + void setItem(const void *); + +public: + ComboBox(Point position, const std::string & defName, const std::pair & help, const JsonNode & dropDownDescriptor, EShortcut key = {}, bool playerColoredButton = false); + + //define this callback to fill input vector with data for the combo box + std::function &)> onConstructItems; + + //callback is called when item is selected and its value can be used + std::function onSetItem; + + //return text value from item data + std::function getItemText; + + void setItem(int id); +}; diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 5401f0f64..be496a84e 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -17,6 +17,7 @@ #include "../gui/CGuiHandler.h" #include "../gui/Shortcut.h" #include "../windows/CMessage.h" +#include "../windows/InfoWindows.h" #include "../adventureMap/CInGameConsole.h" #include "../renderSDL/SDL_Extensions.h" #include "../render/Canvas.h" @@ -495,7 +496,7 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList(bgName, bgOffset.x, bgOffset.y); - addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT); + addUsedEvents(LCLICK | SHOW_POPUP | KEYBOARD | TEXTINPUT); #if !defined(VCMI_MOBILE) giveFocus(); @@ -604,6 +605,13 @@ void CTextInput::keyPressed(EShortcut key) } } +void CTextInput::showPopupWindow(const Point & cursorPosition) +{ + if(!helpBox.empty()) //there is no point to show window with nothing inside... + CRClickPopup::createAndPush(helpBox); +} + + void CTextInput::setText(const std::string & nText) { setText(nText, false); @@ -616,6 +624,11 @@ void CTextInput::setText(const std::string & nText, bool callCb) cb(text); } +void CTextInput::setHelpText(const std::string & text) +{ + helpBox = text; +} + void CTextInput::textInputed(const std::string & enteredText) { if(!focus) diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index bc67e8abf..ec7fe6bfa 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -209,14 +209,18 @@ public: class CTextInput : public CLabel, public CFocusable { std::string newText; + std::string helpBox; //for right-click help + protected: std::string visibleText() override; public: + CFunctionList cb; CFunctionList filters; void setText(const std::string & nText) override; void setText(const std::string & nText, bool callCb); + void setHelpText(const std::string &); CTextInput(const Rect & Pos, EFonts font, const CFunctionList & CB); CTextInput(const Rect & Pos, const Point & bgOffset, const std::string & bgName, const CFunctionList & CB); @@ -224,6 +228,7 @@ public: void clickPressed(const Point & cursorPosition) override; void keyPressed(EShortcut key) override; + void showPopupWindow(const Point & cursorPosition) override; //bool captureThisKey(EShortcut key) override; diff --git a/config/schemas/settings.json b/config/schemas/settings.json index dd3877cfe..139b13cb2 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -478,7 +478,7 @@ }, "defaultRepositoryURL" : { "type" : "string", - "default" : "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.3.json", + "default" : "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/vcmi-1.4.json", }, "extraRepositoryEnabled" : { "type" : "boolean", diff --git a/config/widgets/optionsTab.json b/config/widgets/optionsTab.json index 8e9072d78..ca3c936f2 100644 --- a/config/widgets/optionsTab.json +++ b/config/widgets/optionsTab.json @@ -75,7 +75,6 @@ // timer { - "name": "labelPlayerTurnDuration", "type": "label", "font": "small", "alignment": "center", @@ -100,7 +99,7 @@ "orientation": "horizontal", "position": {"x": 55, "y": 557}, "size": 194, - "callback": "setTurnLength", + "callback": "setTimerPreset", "itemsVisible": 1, "itemsTotal": 11, "selected": 11, @@ -108,5 +107,23 @@ "scrollBounds": {"x": -3, "y": -25, "w": 337, "h": 43}, "panningStep": 20 }, - ] + ], + + "variables": + { + "timerPresets" : + [ + [0, 60, 0, 0], + [0, 120, 0, 0], + [0, 240, 0, 0], + [0, 360, 0, 0], + [0, 480, 0, 0], + [0, 600, 0, 0], + [0, 900, 0, 0], + [0, 1200, 0, 0], + [0, 1500, 0, 0], + [0, 1800, 0, 0], + [0, 0, 0, 0], + ] + } } diff --git a/lib/constants/NumericConstants.h b/lib/constants/NumericConstants.h index e291f4c3b..fdc2a67bd 100644 --- a/lib/constants/NumericConstants.h +++ b/lib/constants/NumericConstants.h @@ -52,8 +52,6 @@ namespace GameConstants constexpr ui32 BASE_MOVEMENT_COST = 100; //default cost for non-diagonal movement constexpr int HERO_PORTRAIT_SHIFT = 9;// 2 special frames + 7 extra portraits - - constexpr std::array POSSIBLE_TURNTIME = {1, 2, 4, 6, 8, 10, 15, 20, 25, 30, 0}; } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index adebe608a..4e8d1b62e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -604,7 +604,7 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa void CGameHandler::onPlayerTurnStarted(PlayerColor which) { events::PlayerGotTurn::defaultExecute(serverEventBus.get(), which); - turnTimerHandler.onPlayerGetTurn(gs->players[which]); + turnTimerHandler.onPlayerGetTurn(which); } void CGameHandler::onPlayerTurnEnded(PlayerColor which) @@ -991,7 +991,7 @@ void CGameHandler::run(bool resume) onNewTurn(); events::TurnStarted::defaultExecute(serverEventBus.get()); for(auto & player : gs->players) - turnTimerHandler.onGameplayStart(player.second); + turnTimerHandler.onGameplayStart(player.first); } else events::GameResumed::defaultExecute(serverEventBus.get()); @@ -1003,9 +1003,9 @@ void CGameHandler::run(bool resume) { const int waitTime = 100; //ms - for(auto & player : gs->players) - if (gs->isPlayerMakingTurn(player.first)) - turnTimerHandler.onPlayerMakingTurn(player.second, waitTime); + for(PlayerColor player(0); player < PlayerColor::PLAYER_LIMIT; ++player) + if(gs->isPlayerMakingTurn(player)) + turnTimerHandler.onPlayerMakingTurn(player, waitTime); if(gs->curB) turnTimerHandler.onBattleLoop(waitTime); @@ -1183,7 +1183,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo { moveQuery = std::dynamic_pointer_cast(topQuery); if(moveQuery - && (!transit || result == TryMoveHero::FAILED || moveQuery->tmh.stopMovement())) + && (!transit || result != TryMoveHero::SUCCESS)) queries->popIfTop(moveQuery); else break; diff --git a/server/TurnTimerHandler.cpp b/server/TurnTimerHandler.cpp index a3b0c81b5..d3aca09d9 100644 --- a/server/TurnTimerHandler.cpp +++ b/server/TurnTimerHandler.cpp @@ -26,63 +26,65 @@ TurnTimerHandler::TurnTimerHandler(CGameHandler & gh): } -void TurnTimerHandler::onGameplayStart(PlayerState & state) +void TurnTimerHandler::onGameplayStart(PlayerColor player) { if(const auto * si = gameHandler.getStartInfo()) { if(si->turnTimerInfo.isEnabled()) { - state.turnTimer = si->turnTimerInfo; - state.turnTimer.turnTimer = 0; + timers[player] = si->turnTimerInfo; + timers[player].turnTimer = 0; } } } -void TurnTimerHandler::onPlayerGetTurn(PlayerState & state) +void TurnTimerHandler::onPlayerGetTurn(PlayerColor player) { if(const auto * si = gameHandler.getStartInfo()) { if(si->turnTimerInfo.isEnabled()) { - state.turnTimer.baseTimer += state.turnTimer.turnTimer; - state.turnTimer.turnTimer = si->turnTimerInfo.turnTimer; + timers[player].baseTimer += timers[player].turnTimer; + timers[player].turnTimer = si->turnTimerInfo.turnTimer; TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = state.turnTimer; + ttu.player = player; + ttu.turnTimer = timers[player]; gameHandler.sendAndApply(&ttu); } } } -void TurnTimerHandler::onPlayerMakingTurn(PlayerState & state, int waitTime) +void TurnTimerHandler::onPlayerMakingTurn(PlayerColor player, int waitTime) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); if(!si || !gs) return; + auto & state = gs->players.at(player); + if(state.human && si->turnTimerInfo.isEnabled() && !gs->curB) { - if(state.turnTimer.turnTimer > 0) + if(timers[player].turnTimer > 0) { - state.turnTimer.turnTimer -= waitTime; - int frequency = (state.turnTimer.creatureTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit); + timers[player].turnTimer -= waitTime; + int frequency = (timers[player].turnTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit); if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && state.turnTimer.turnTimer % frequency == 0) + && timers[player].turnTimer % frequency == 0) { TurnTimeUpdate ttu; ttu.player = state.color; - ttu.turnTimer = state.turnTimer; + ttu.turnTimer = timers[player]; gameHandler.sendAndApply(&ttu); } } - else if(state.turnTimer.baseTimer > 0) + else if(timers[player].baseTimer > 0) { - state.turnTimer.turnTimer = state.turnTimer.baseTimer; - state.turnTimer.baseTimer = 0; - onPlayerMakingTurn(state, waitTime); + timers[player].turnTimer = timers[player].baseTimer; + timers[player].baseTimer = 0; + onPlayerMakingTurn(player, 0); } else if(!gameHandler.queries->topQuery(state.color)) //wait for replies to avoid pending queries gameHandler.turnOrder->onPlayerEndsTurn(state.color); @@ -103,12 +105,12 @@ void TurnTimerHandler::onBattleStart() { if(i.isValidPlayer()) { - const auto & state = gs->players.at(i); + timers[i].battleTimer = si->turnTimerInfo.battleTimer; + timers[i].creatureTimer = si->turnTimerInfo.creatureTimer; + TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = state.turnTimer; - ttu.turnTimer.battleTimer = si->turnTimerInfo.battleTimer; - ttu.turnTimer.creatureTimer = si->turnTimerInfo.creatureTimer; + ttu.player = i; + ttu.turnTimer = timers[i]; gameHandler.sendAndApply(&ttu); } } @@ -118,24 +120,22 @@ void TurnTimerHandler::onBattleNextStack(const CStack & stack) { const auto * gs = gameHandler.gameState(); const auto * si = gameHandler.getStartInfo(); - if(!si || !gs || !gs->curB) + if(!si || !gs || !gs->curB || !si->turnTimerInfo.isBattleEnabled()) return; - if(!stack.getOwner().isValidPlayer()) + auto player = stack.getOwner(); + + if(!player.isValidPlayer()) return; - - const auto & state = gs->players.at(stack.getOwner()); - - if(si->turnTimerInfo.isBattleEnabled()) - { - TurnTimeUpdate ttu; - ttu.player = state.color; - ttu.turnTimer = state.turnTimer; - if(state.turnTimer.battleTimer < si->turnTimerInfo.battleTimer) - ttu.turnTimer.battleTimer = ttu.turnTimer.creatureTimer; - ttu.turnTimer.creatureTimer = si->turnTimerInfo.creatureTimer; - gameHandler.sendAndApply(&ttu); - } + + if(timers[player].battleTimer < si->turnTimerInfo.battleTimer) + timers[player].battleTimer = timers[player].creatureTimer; + timers[player].creatureTimer = si->turnTimerInfo.creatureTimer; + + TurnTimeUpdate ttu; + ttu.player = player; + ttu.turnTimer = timers[player]; + gameHandler.sendAndApply(&ttu); } void TurnTimerHandler::onBattleLoop(int waitTime) @@ -151,20 +151,21 @@ void TurnTimerHandler::onBattleLoop(int waitTime) auto & state = gs->players.at(gs->curB->getSidePlayer(stack->unitSide())); - auto turnTimerUpdateApplier = [&](const TurnTimerInfo & tTimer) + auto turnTimerUpdateApplier = [&](TurnTimerInfo & tTimer, int waitTime) { - TurnTimerInfo turnTimerUpdate = tTimer; if(tTimer.creatureTimer > 0) { - turnTimerUpdate.creatureTimer -= waitTime; - int frequency = (turnTimerUpdate.creatureTimer > turnTimePropagateThreshold ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit); + tTimer.creatureTimer -= waitTime; + int frequency = (tTimer.creatureTimer > turnTimePropagateThreshold + && si->turnTimerInfo.creatureTimer - tTimer.creatureTimer > turnTimePropagateThreshold) + ? turnTimePropagateFrequency : turnTimePropagateFrequencyCrit; if(state.status == EPlayerStatus::INGAME //do not send message if player is not active already - && turnTimerUpdate.creatureTimer % frequency == 0) + && tTimer.creatureTimer % frequency == 0) { TurnTimeUpdate ttu; ttu.player = state.color; - ttu.turnTimer = turnTimerUpdate; + ttu.turnTimer = tTimer; gameHandler.sendAndApply(&ttu); } return true; @@ -174,14 +175,13 @@ void TurnTimerHandler::onBattleLoop(int waitTime) if(state.human && si->turnTimerInfo.isBattleEnabled()) { - TurnTimerInfo turnTimer = state.turnTimer; - if(!turnTimerUpdateApplier(turnTimer)) + if(!turnTimerUpdateApplier(timers[state.color], waitTime)) { - if(turnTimer.battleTimer > 0) + if(timers[state.color].battleTimer > 0) { - turnTimer.creatureTimer = turnTimer.battleTimer; - turnTimer.battleTimer = 0; - turnTimerUpdateApplier(turnTimer); + timers[state.color].creatureTimer = timers[state.color].battleTimer; + timers[state.color].battleTimer = 0; + turnTimerUpdateApplier(timers[state.color], 0); } else { diff --git a/server/TurnTimerHandler.h b/server/TurnTimerHandler.h index 602ecfde0..d805905a4 100644 --- a/server/TurnTimerHandler.h +++ b/server/TurnTimerHandler.h @@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CStack; class PlayerColor; -struct PlayerState; +struct TurnTimerInfo; VCMI_LIB_NAMESPACE_END @@ -26,13 +26,14 @@ class TurnTimerHandler const int turnTimePropagateFrequency = 5000; const int turnTimePropagateFrequencyCrit = 1000; const int turnTimePropagateThreshold = 3000; + std::map timers; public: TurnTimerHandler(CGameHandler &); - void onGameplayStart(PlayerState & state); - void onPlayerGetTurn(PlayerState & state); - void onPlayerMakingTurn(PlayerState & state, int waitTime); + void onGameplayStart(PlayerColor player); + void onPlayerGetTurn(PlayerColor player); + void onPlayerMakingTurn(PlayerColor player, int waitTime); void onBattleStart(); void onBattleNextStack(const CStack & stack); void onBattleLoop(int waitTime);