diff --git a/Mods/vcmi/Content/config/english.json b/Mods/vcmi/Content/config/english.json index 2dbfb0d30..ec413518b 100644 --- a/Mods/vcmi/Content/config/english.json +++ b/Mods/vcmi/Content/config/english.json @@ -145,6 +145,8 @@ "vcmi.lobby.battleOnlyModeCreatureSelect" : "Select Creature", "vcmi.lobby.battleOnlyModeSelect" : "Select", "vcmi.lobby.battleOnlyModeReset" : "Reset", + "vcmi.lobby.templatesSelect.hover" : "Templates", + "vcmi.lobby.templatesSelect.help" : "Search and select template", "vcmi.broadcast.failedLoadGame" : "Failed to load game", "vcmi.broadcast.command" : "Use '!help' to list available commands", diff --git a/Mods/vcmi/Content/config/german.json b/Mods/vcmi/Content/config/german.json index 7abccad57..c05a712f8 100644 --- a/Mods/vcmi/Content/config/german.json +++ b/Mods/vcmi/Content/config/german.json @@ -145,6 +145,8 @@ "vcmi.lobby.battleOnlyModeCreatureSelect" : "Kreatur auswählen", "vcmi.lobby.battleOnlyModeSelect" : "Wählen", "vcmi.lobby.battleOnlyModeReset" : "Zurücksetzen", + "vcmi.lobby.templatesSelect.hover" : "Templates", + "vcmi.lobby.templatesSelect.help" : "Suche und wähle Template aus", "vcmi.broadcast.failedLoadGame" : "Spiel konnte nicht geladen werden", "vcmi.broadcast.command" : "Benutze '!help' um alle verfügbaren Befehle aufzulisten", diff --git a/client/ClientNetPackVisitors.h b/client/ClientNetPackVisitors.h index fc7714b5f..1a2813a02 100644 --- a/client/ClientNetPackVisitors.h +++ b/client/ClientNetPackVisitors.h @@ -104,6 +104,7 @@ public: void visitSetAvailableArtifacts(SetAvailableArtifacts & pack) override; void visitEntitiesChanged(EntitiesChanged & pack) override; void visitPlayerCheated(PlayerCheated & pack) override; + void visitChangeTownName(ChangeTownName & pack) override; }; class ApplyFirstClientNetPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index a4396ef31..af8243e98 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -1078,3 +1078,16 @@ void ApplyClientNetPackVisitor::visitPlayerCheated(PlayerCheated & pack) if(pack.colorScheme != ColorScheme::KEEP && vstd::contains(cl.playerint, pack.player)) cl.playerint[pack.player]->setColorScheme(pack.colorScheme); } + +void ApplyClientNetPackVisitor::visitChangeTownName(ChangeTownName & pack) +{ + if(!adventureInt) + return; + + const CGTownInstance *town = gs.getTown(pack.tid); + if(town) + { + adventureInt->onTownChanged(town); + ENGINE->windows().totalRedraw(); + } +} diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 5ed1ff996..edf3c7728 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -1035,10 +1035,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con if(s->isControlledByAI() || GAME->server().isGuest()) labelPlayerName = std::make_shared(55, 10, EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, name, 95); else - { - labelPlayerNameEdit = std::make_shared(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, false); - labelPlayerNameEdit->setText(name); - } + labelPlayerNameEdit = std::make_shared(Rect(6, 3, 95, 15), EFonts::FONT_SMALL, ETextAlignment::CENTER, name, false, [this](){ updateName(); }); labelWhoCanPlay = std::make_shared(Rect(6, 21, 45, 26), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, LIBRARY->generaltexth->arraytxt[206 + whoCanPlay]); @@ -1114,28 +1111,6 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con bonus = std::make_shared(Point(271, 2), *s, BONUS); } -bool OptionsTab::PlayerOptionsEntry::captureThisKey(EShortcut key) -{ - return labelPlayerNameEdit && labelPlayerNameEdit->hasFocus() && key == EShortcut::GLOBAL_ACCEPT; -} - -void OptionsTab::PlayerOptionsEntry::keyPressed(EShortcut key) -{ - if(labelPlayerNameEdit && key == EShortcut::GLOBAL_ACCEPT) - updateName(); -} - -bool OptionsTab::PlayerOptionsEntry::receiveEvent(const Point & position, int eventType) const -{ - return eventType == AEventsReceiver::LCLICK; // capture all left clicks (not only within control) -} - -void OptionsTab::PlayerOptionsEntry::clickReleased(const Point & cursorPosition) -{ - if(labelPlayerNameEdit && !labelPlayerNameEdit->pos.isInside(cursorPosition)) - updateName(); -} - void OptionsTab::PlayerOptionsEntry::updateName() { if(labelPlayerNameEdit->getText() != name) { @@ -1146,8 +1121,6 @@ void OptionsTab::PlayerOptionsEntry::updateName() { set->String() = labelPlayerNameEdit->getText(); } } - - labelPlayerNameEdit->removeFocus(); name = labelPlayerNameEdit->getText(); } diff --git a/client/lobby/OptionsTab.h b/client/lobby/OptionsTab.h index e74666e6d..3acaffa22 100644 --- a/client/lobby/OptionsTab.h +++ b/client/lobby/OptionsTab.h @@ -28,6 +28,7 @@ class CTextBox; class CButton; class CSlider; class LRClickableArea; +class CTextInputWithConfirm; class FilledTexturePlayerColored; class TransparentFilledRectangle; @@ -196,7 +197,7 @@ private: std::unique_ptr pi; std::unique_ptr s; std::shared_ptr labelPlayerName; - std::shared_ptr labelPlayerNameEdit; + std::shared_ptr labelPlayerNameEdit; std::shared_ptr labelWhoCanPlay; std::shared_ptr background; std::shared_ptr buttonTownLeft; @@ -215,10 +216,6 @@ private: PlayerOptionsEntry(const PlayerSettings & S, const OptionsTab & parentTab); void hideUnavailableButtons(); - bool captureThisKey(EShortcut key) override; - void keyPressed(EShortcut key) override; - void clickReleased(const Point & cursorPosition) override; - bool receiveEvent(const Point & position, int eventType) const override; private: const OptionsTab & parentTab; diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 4144bfcaa..8e9f2d45c 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -43,7 +43,8 @@ #include "../../lib/serializer/JsonDeserializer.h" RandomMapTab::RandomMapTab(): - InterfaceObjectConfigurable() + InterfaceObjectConfigurable(), + templateIndex(0) { recActions = 0; mapGenOptions = std::make_shared(); @@ -140,16 +141,18 @@ RandomMapTab::RandomMapTab(): //set combo box callbacks if(auto w = widget("templateList")) { - w->onConstructItems = [](std::vector & curItems){ + auto getTemplates = [](){ auto templates = LIBRARY->tplh->getTemplates(); - boost::range::sort(templates, [](const CRmgTemplate * a, const CRmgTemplate * b){ return a->getName() < b->getName(); }); + return templates; + }; + w->onConstructItems = [getTemplates](std::vector & curItems){ curItems.push_back(nullptr); //default template - for(auto & t : templates) + for(auto & t : getTemplates()) curItems.push_back(t); }; @@ -164,6 +167,20 @@ RandomMapTab::RandomMapTab(): return readText(variables["randomTemplate"]); return std::string(""); }; + + w->addCallback([this, getTemplates]() // no real dropdown... - instead open dialog + { + std::vector texts; + texts.push_back(readText(variables["randomTemplate"])); + for(auto & t : getTemplates()) + texts.push_back(t->getName()); + + ENGINE->windows().popWindows(1); + ENGINE->windows().createAndPushWindow(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.templatesSelect.hover"), LIBRARY->generaltexth->translate("vcmi.lobby.templatesSelect.help"), [this](int index){ + widget("templateList")->setItem(index); + templateIndex = index; + }, templateIndex, std::vector>(), true); + }); } loadOptions(); diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h index fa62e151d..4e199e410 100644 --- a/client/lobby/RandomMapTab.h +++ b/client/lobby/RandomMapTab.h @@ -55,6 +55,8 @@ private: std::set playerTeamsAllowed; std::set compCountAllowed; std::set compTeamsAllowed; + + int templateIndex; }; class TeamAlignmentsWidget: public InterfaceObjectConfigurable diff --git a/client/netlag/PackRollbackGeneratorVisitor.h b/client/netlag/PackRollbackGeneratorVisitor.h index affb415f2..a26877591 100644 --- a/client/netlag/PackRollbackGeneratorVisitor.h +++ b/client/netlag/PackRollbackGeneratorVisitor.h @@ -85,6 +85,7 @@ private: //void visitSetCommanderProperty(SetCommanderProperty & pack) override; //void visitAddQuest(AddQuest & pack) override; //void visitChangeFormation(ChangeFormation & pack) override; + //void visitChangeTownName(ChangeTownName & pack) override; //void visitChangeSpells(ChangeSpells & pack) override; //void visitSetAvailableHero(SetAvailableHero & pack) override; //void visitChangeObjectVisitors(ChangeObjectVisitors & pack) override; diff --git a/client/widgets/CTextInput.cpp b/client/widgets/CTextInput.cpp index d3754a3ed..0320e925c 100644 --- a/client/widgets/CTextInput.cpp +++ b/client/widgets/CTextInput.cpp @@ -27,6 +27,85 @@ std::list CFocusable::focusables; CFocusable * CFocusable::inputWithFocus; +CTextInputWithConfirm::CTextInputWithConfirm(const Rect & Pos, EFonts font, ETextAlignment alignment, std::string text, bool limitToRect, std::function confirmCallback) + : CTextInput(Pos, font, alignment, false), confirmCb(confirmCallback), limitToRect(limitToRect), initialText(text) +{ + setText(text); +} + +bool CTextInputWithConfirm::captureThisKey(EShortcut key) +{ + return hasFocus() && (key == EShortcut::GLOBAL_ACCEPT || key == EShortcut::GLOBAL_CANCEL || key == EShortcut::GLOBAL_BACKSPACE); +} + +void CTextInputWithConfirm::keyPressed(EShortcut key) +{ + if(!hasFocus()) + return; + + if(key == EShortcut::GLOBAL_ACCEPT) + confirm(); + else if(key == EShortcut::GLOBAL_CANCEL) + { + setText(initialText); + removeFocus(); + } + + CTextInput::keyPressed(key); +} + +bool CTextInputWithConfirm::receiveEvent(const Point & position, int eventType) const +{ + return eventType == AEventsReceiver::LCLICK; // capture all left clicks (not only within control) +} + +void CTextInputWithConfirm::clickReleased(const Point & cursorPosition) +{ + if(!pos.isInside(cursorPosition)) // clicked outside + confirm(); +} + +void CTextInputWithConfirm::clickPressed(const Point & cursorPosition) +{ + if(pos.isInside(cursorPosition)) // clickPressed should respect control area (receiveEvent also affects this) + CTextInput::clickPressed(cursorPosition); +} + +void CTextInputWithConfirm::onFocusGot() +{ + initialText = getText(); + + CTextInput::onFocusGot(); +} + +void CTextInputWithConfirm::textInputted(const std::string & enteredText) +{ + if(!hasFocus()) + return; + + std::string visibleText = getVisibleText() + enteredText; + const auto & font = ENGINE->renderHandler().loadFont(label->font); + if(!limitToRect || (font->getStringWidth(visibleText) - CLabel::getDelimitersWidth(label->font, visibleText)) < pos.w) + CTextInput::textInputted(enteredText); +} + +void CTextInputWithConfirm::deactivate() +{ + removeUsedEvents(LCLICK); + + CTextInput::deactivate(); +} + +void CTextInputWithConfirm::confirm() +{ + if(getText().empty()) + setText(initialText); + + if(confirmCb && initialText != getText()) + confirmCb(); + removeFocus(); +} + CTextInput::CTextInput(const Rect & Pos) :originalAlignment(ETextAlignment::CENTERLEFT) { @@ -200,7 +279,7 @@ void CTextInput::updateLabel() label->alignment = originalAlignment; const auto & font = ENGINE->renderHandler().loadFont(label->font); - while (font->getStringWidth(visibleText) > pos.w) + while ((font->getStringWidth(visibleText) - CLabel::getDelimitersWidth(label->font, visibleText)) > pos.w) { label->alignment = ETextAlignment::CENTERRIGHT; visibleText = visibleText.substr(TextOperations::getUnicodeCharacterSize(visibleText[0])); diff --git a/client/widgets/CTextInput.h b/client/widgets/CTextInput.h index 68d75ebd7..9e8bca271 100644 --- a/client/widgets/CTextInput.h +++ b/client/widgets/CTextInput.h @@ -45,8 +45,9 @@ public: }; /// Text input box where players can enter text -class CTextInput final : public CFocusable +class CTextInput : public CFocusable { +protected: using TextEditedCallback = std::function; using TextFilterCallback = std::function; @@ -71,12 +72,12 @@ class CTextInput final : public CFocusable void createLabel(bool giveFocusToInput); void updateLabel(); - void clickPressed(const Point & cursorPosition) final; - void textInputted(const std::string & enteredText) final; - void textEdited(const std::string & enteredText) final; - void onFocusGot() final; - void onFocusLost() final; - void showPopupWindow(const Point & cursorPosition) final; + void clickPressed(const Point & cursorPosition) override; + void textInputted(const std::string & enteredText) override; + void textEdited(const std::string & enteredText) override; + void onFocusGot() override; + void onFocusLost() override; + void showPopupWindow(const Point & cursorPosition) override; CTextInput(const Rect & Pos); public: @@ -105,7 +106,27 @@ public: void setAlignment(ETextAlignment alignment); // CIntObject interface impl - void keyPressed(EShortcut key) final; - void activate() final; - void deactivate() final; + void keyPressed(EShortcut key) override; + void activate() override; + void deactivate() override; +}; + +class CTextInputWithConfirm final : public CTextInput +{ + std::string initialText; + std::function confirmCb; + bool limitToRect; + + void confirm(); +public: + CTextInputWithConfirm(const Rect & Pos, EFonts font, ETextAlignment alignment, std::string text, bool limitToRect, std::function confirmCallback); + + bool captureThisKey(EShortcut key) override; + void keyPressed(EShortcut key) override; + void clickReleased(const Point & cursorPosition) override; + void clickPressed(const Point & cursorPosition) override; + bool receiveEvent(const Point & position, int eventType) const override; + void onFocusGot() override; + void textInputted(const std::string & enteredText) override; + void deactivate() override; }; diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index 2cb8a4442..245e02a1c 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -182,29 +182,39 @@ std::vector CMultiLineLabel::getLines() return lines; } -void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what) +const std::string delimiters = "{}"; + +int CTextContainer::getDelimitersWidth(EFonts font, std::string text) { const auto f = ENGINE->renderHandler().loadFont(font); - Point where = destRect.topLeft(); - const std::string delimiters = "{}"; - auto delimitersCount = std::count_if(what.cbegin(), what.cend(), [&delimiters](char c) + auto delimitersWidth = std::count_if(text.cbegin(), text.cend(), [](char c) { return delimiters.find(c) != std::string::npos; }); //We should count delimiters length from string to correct centering later. - delimitersCount *= f->getStringWidth(delimiters)/2; + delimitersWidth *= f->getStringWidth(delimiters)/2; std::smatch match; std::regex expr("\\{(.*?)\\|"); - std::string::const_iterator searchStart( what.cbegin() ); - while(std::regex_search(searchStart, what.cend(), match, expr)) + std::string::const_iterator searchStart( text.cbegin() ); + while(std::regex_search(searchStart, text.cend(), match, expr)) { std::string colorText = match[1].str(); if(auto c = Colors::parseColor(colorText)) - delimitersCount += f->getStringWidth(colorText + "|"); + delimitersWidth += f->getStringWidth(colorText + "|"); searchStart = match.suffix().first; } + return delimitersWidth; +} + +void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what) +{ + const auto f = ENGINE->renderHandler().loadFont(font); + Point where = destRect.topLeft(); + + int delimitersWidth = getDelimitersWidth(font, what); + // input is rect in which given text should be placed // calculate proper position for top-left corner of the text @@ -212,10 +222,10 @@ void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what) where.x += getBorderSize().x; if(alignment == ETextAlignment::CENTER || alignment == ETextAlignment::TOPCENTER || alignment == ETextAlignment::BOTTOMCENTER) - where.x += (destRect.w - (static_cast(f->getStringWidth(what)) - delimitersCount)) / 2; + where.x += (destRect.w - (static_cast(f->getStringWidth(what)) - delimitersWidth)) / 2; if(alignment == ETextAlignment::TOPRIGHT || alignment == ETextAlignment::BOTTOMRIGHT || alignment == ETextAlignment::CENTERRIGHT) - where.x += getBorderSize().x + destRect.w - (static_cast(f->getStringWidth(what)) - delimitersCount); + where.x += getBorderSize().x + destRect.w - (static_cast(f->getStringWidth(what)) - delimitersWidth); if(alignment == ETextAlignment::TOPLEFT || alignment == ETextAlignment::TOPCENTER || alignment == ETextAlignment::TOPRIGHT) where.y += getBorderSize().y; diff --git a/client/widgets/TextControls.h b/client/widgets/TextControls.h index 0c99c193b..b190279c2 100644 --- a/client/widgets/TextControls.h +++ b/client/widgets/TextControls.h @@ -31,6 +31,8 @@ protected: CTextContainer(ETextAlignment alignment, EFonts font, ColorRGBA color); public: + static int getDelimitersWidth(EFonts font, std::string text); + ETextAlignment alignment; EFonts font; ColorRGBA color; // default font color. Can be overridden by placing "{}" into the string diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 10038bf1d..60eae5819 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -30,6 +30,7 @@ #include "../widgets/MiscWidgets.h" #include "../widgets/CComponent.h" #include "../widgets/CGarrisonInt.h" +#include "../widgets/CTextInput.h" #include "../widgets/Buttons.h" #include "../widgets/TextControls.h" #include "../widgets/RadialMenu.h" @@ -1435,7 +1436,15 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst garr->setRedrawParent(true); heroes = std::make_shared(town, Point(241, 387), Point(241, 483), garr, true); - title = std::make_shared(85, 387, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, town->getNameTranslated()); + title = std::make_shared(Rect(83, 386, 140, 20), FONT_MEDIUM, ETextAlignment::TOPLEFT, town->getNameTranslated(), true, [this](){ + std::string name = title->getText(); + std::string originalName = LIBRARY->generaltexth->translate(town->getNameTextID()); + if(name == originalName) + name = ""; // use textID again + GAME->interface()->cb->setTownName(town, name); + }); + if(town->tempOwner != GAME->interface()->playerID) // disable changing for allied towns + title->deactivate(); income = std::make_shared(195, 443, FONT_SMALL, ETextAlignment::CENTER); icon = std::make_shared(AnimationPath::builtin("ITPT"), 0, 0, 15, 387); diff --git a/client/windows/CCastleInterface.h b/client/windows/CCastleInterface.h index 46a546b5e..3ff010091 100644 --- a/client/windows/CCastleInterface.h +++ b/client/windows/CCastleInterface.h @@ -37,6 +37,7 @@ class CGarrisonInt; class CComponent; class CComponentBox; class LRClickableArea; +class CTextInputWithConfirm; /// Building "button" class CBuildingRect : public CShowableAnim @@ -225,7 +226,7 @@ public: /// Class which manages the castle window class CCastleInterface final : public CStatusbarWindow, public IGarrisonHolder, public IArtifactsHolder { - std::shared_ptr title; + std::shared_ptr title; std::shared_ptr income; std::shared_ptr icon; diff --git a/lib/callback/CCallback.cpp b/lib/callback/CCallback.cpp index a826d2950..f94c8cdf1 100644 --- a/lib/callback/CCallback.cpp +++ b/lib/callback/CCallback.cpp @@ -286,6 +286,12 @@ void CCallback::setFormation(const CGHeroInstance * hero, EArmyFormation mode) sendRequest(pack); } +void CCallback::setTownName(const CGTownInstance * town, std::string & name) +{ + SetTownName pack(town->id, name); + sendRequest(pack); +} + void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero) { assert(townOrTavern); diff --git a/lib/callback/CCallback.h b/lib/callback/CCallback.h index cf8484bb9..7eb6a42c0 100644 --- a/lib/callback/CCallback.h +++ b/lib/callback/CCallback.h @@ -76,6 +76,7 @@ public: void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override; void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector & id1, const std::vector & id2, const std::vector & val1, const CGHeroInstance * hero = nullptr) override; void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override; + void setTownName(const CGTownInstance * town, std::string & name) override; void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override; void save(const std::string &fname) override; void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) override; diff --git a/lib/callback/IGameActionCallback.h b/lib/callback/IGameActionCallback.h index 946eee67d..8f5a9bcd9 100644 --- a/lib/callback/IGameActionCallback.h +++ b/lib/callback/IGameActionCallback.h @@ -70,6 +70,7 @@ public: virtual void endTurn()=0; virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith) virtual void setFormation(const CGHeroInstance * hero, EArmyFormation mode)=0; + virtual void setTownName(const CGTownInstance * town, std::string & name)=0; virtual void save(const std::string &fname) = 0; virtual void sendMessage(const std::string &mess, const CGObjectInstance * currentObject = nullptr) = 0; diff --git a/lib/gameState/GameStatePackVisitor.cpp b/lib/gameState/GameStatePackVisitor.cpp index d527d7555..4fc72af4c 100644 --- a/lib/gameState/GameStatePackVisitor.cpp +++ b/lib/gameState/GameStatePackVisitor.cpp @@ -123,6 +123,11 @@ void GameStatePackVisitor::visitChangeFormation(ChangeFormation & pack) gs.getHero(pack.hid)->setFormation(pack.formation); } +void GameStatePackVisitor::visitChangeTownName(ChangeTownName & pack) +{ + gs.getTown(pack.tid)->setCustomName(pack.name); +} + void GameStatePackVisitor::visitHeroVisitCastle(HeroVisitCastle & pack) { CGHeroInstance *h = gs.getHero(pack.hid); diff --git a/lib/gameState/GameStatePackVisitor.h b/lib/gameState/GameStatePackVisitor.h index fa4421e4e..8cb0642a7 100644 --- a/lib/gameState/GameStatePackVisitor.h +++ b/lib/gameState/GameStatePackVisitor.h @@ -87,6 +87,7 @@ public: void visitSetCommanderProperty(SetCommanderProperty & pack) override; void visitAddQuest(AddQuest & pack) override; void visitChangeFormation(ChangeFormation & pack) override; + void visitChangeTownName(ChangeTownName & pack) override; void visitChangeSpells(ChangeSpells & pack) override; void visitSetAvailableHero(SetAvailableHero & pack) override; void visitChangeObjectVisitors(ChangeObjectVisitors & pack) override; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index f07daa261..085674e74 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -860,7 +860,7 @@ CBonusSystemNode & CGTownInstance::whatShouldBeAttached() std::string CGTownInstance::getNameTranslated() const { - return LIBRARY->generaltexth->translate(nameTextId); + return customName.empty() ? LIBRARY->generaltexth->translate(nameTextId) : customName; } std::string CGTownInstance::getNameTextID() const @@ -873,6 +873,11 @@ void CGTownInstance::setNameTextId( const std::string & newName ) nameTextId = newName; } +void CGTownInstance::setCustomName( const std::string & newName ) +{ + customName = newName; +} + const CArmedInstance * CGTownInstance::getUpperArmy() const { if(getGarrisonHero()) diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 132a5f358..4b1c9811b 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -46,6 +46,7 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I { friend class CTownInstanceConstructor; std::string nameTextId; // name of town + std::string customName; std::map convertOldBuildings(std::vector oldVector); std::set builtBuildings; @@ -75,6 +76,8 @@ public: { h & static_cast(*this); h & nameTextId; + if (h.version >= Handler::Version::CUSTOM_NAMES) + h & customName; h & built; h & destroyed; h & identifier; @@ -128,6 +131,7 @@ public: std::string getNameTranslated() const; std::string getNameTextID() const; void setNameTextId(const std::string & newName); + void setCustomName(const std::string & newName); ////////////////////////////////////////////////////////////////////////// diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index 3edd0d290..88cbde6b1 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -58,6 +58,7 @@ public: virtual void visitSetCommanderProperty(SetCommanderProperty & pack) {} virtual void visitAddQuest(AddQuest & pack) {} virtual void visitChangeFormation(ChangeFormation & pack) {} + virtual void visitChangeTownName(ChangeTownName & pack) {} virtual void visitRemoveObject(RemoveObject & pack) {} virtual void visitTryMoveHero(TryMoveHero & pack) {} virtual void visitNewStructures(NewStructures & pack) {} @@ -144,6 +145,7 @@ public: virtual void visitBuyArtifact(BuyArtifact & pack) {} virtual void visitTradeOnMarketplace(TradeOnMarketplace & pack) {} virtual void visitSetFormation(SetFormation & pack) {} + virtual void visitSetTownName(SetTownName & pack) {} virtual void visitHireHero(HireHero & pack) {} virtual void visitBuildBoat(BuildBoat & pack) {} virtual void visitQueryReply(QueryReply & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index 629260eff..43cd343a1 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -213,6 +213,11 @@ void ChangeFormation::visitTyped(ICPackVisitor & visitor) visitor.visitChangeFormation(*this); } +void ChangeTownName::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitChangeTownName(*this); +} + void RemoveObject::visitTyped(ICPackVisitor & visitor) { visitor.visitRemoveObject(*this); @@ -643,6 +648,11 @@ void SetFormation::visitTyped(ICPackVisitor & visitor) visitor.visitSetFormation(*this); } +void SetTownName::visitTyped(ICPackVisitor & visitor) +{ + visitor.visitSetTownName(*this); +} + void HireHero::visitTyped(ICPackVisitor & visitor) { visitor.visitHireHero(*this); diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 0159729ad..9c3fb54d6 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -616,6 +616,20 @@ struct DLL_LINKAGE ChangeFormation : public CPackForClient } }; +struct DLL_LINKAGE ChangeTownName : public CPackForClient +{ + ObjectInstanceID tid; + std::string name; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & tid; + h & name; + } +}; + struct DLL_LINKAGE RemoveObject : public CPackForClient { RemoveObject() = default; diff --git a/lib/networkPacks/PacksForServer.h b/lib/networkPacks/PacksForServer.h index 9924d3b4b..f28d64c5f 100644 --- a/lib/networkPacks/PacksForServer.h +++ b/lib/networkPacks/PacksForServer.h @@ -609,6 +609,28 @@ struct DLL_LINKAGE SetFormation : public CPackForServer } }; +struct DLL_LINKAGE SetTownName : public CPackForServer +{ + SetTownName() = default; + ; + SetTownName(const ObjectInstanceID & TID, std::string Name) + : tid(TID) + , name(Name) + { + } + ObjectInstanceID tid; + std::string name; + + void visitTyped(ICPackVisitor & visitor) override; + + template void serialize(Handler & h) + { + h & static_cast(*this); + h & tid; + h & name; + } +}; + struct DLL_LINKAGE HireHero : public CPackForServer { HireHero() = default; diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index afd163772..a2c534e3e 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -50,6 +50,7 @@ enum class ESerializationVersion : int32_t BONUS_HIDDEN, // hidden bonus MORE_MAP_LAYERS, // more map layers CONFIGURABLE_RESOURCES, // configurable resources + CUSTOM_NAMES, // custom names BATTLE_ONLY, // battle only mode CURRENT = BATTLE_ONLY, diff --git a/lib/serializer/RegisterTypes.h b/lib/serializer/RegisterTypes.h index 7f02132d8..be48a67c1 100644 --- a/lib/serializer/RegisterTypes.h +++ b/lib/serializer/RegisterTypes.h @@ -291,7 +291,9 @@ void registerTypes(Serializer &s) s.template registerType(249); s.template registerType(250); s.template registerType(251); - s.template registerType(252); + s.template registerType(252); + s.template registerType(253); + s.template registerType(254); } VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b94c41676..8ef9dc39a 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3219,6 +3219,23 @@ bool CGameHandler::setFormation(ObjectInstanceID hid, EArmyFormation formation) return true; } +bool CGameHandler::setTownName(ObjectInstanceID tid, std::string & name) +{ + const CGTownInstance *t = gameInfo().getTown(tid); + if (!t) + { + logGlobal->error("Town doesn't exist!"); + return false; + } + + ChangeTownName ctn; + ctn.tid = tid; + ctn.name = name; + sendAndApply(ctn); + + return true; +} + bool CGameHandler::queryReply(QueryID qid, std::optional answer, PlayerColor player) { logGlobal->trace("Player %s attempts answering query %d with answer:", player, qid); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index f1661b294..e0e7c791e 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -212,6 +212,7 @@ public: bool queryReply( QueryID qid, std::optional reply, PlayerColor player ); bool buildBoat( ObjectInstanceID objid, PlayerColor player ); bool setFormation( ObjectInstanceID hid, EArmyFormation formation ); + bool setTownName( ObjectInstanceID tid, std::string & name ); bool tradeResources(const IMarket *market, ui32 amountToSell, PlayerColor player, GameResID toSell, GameResID toBuy); bool sacrificeCreatures(const IMarket * market, const CGHeroInstance * hero, const std::vector & slot, const std::vector & count); bool sendResources(ui32 val, PlayerColor player, GameResID r1, PlayerColor r2); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 47b9c52f0..e7cde39c3 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -362,6 +362,13 @@ void ApplyGhNetPackVisitor::visitSetFormation(SetFormation & pack) result = gh.setFormation(pack.hid, pack.formation); } +void ApplyGhNetPackVisitor::visitSetTownName(SetTownName & pack) +{ + gh.throwIfWrongOwner(connection, &pack, pack.tid); + + result = gh.setTownName(pack.tid, pack.name); +} + void ApplyGhNetPackVisitor::visitHireHero(HireHero & pack) { gh.throwIfWrongPlayer(connection, &pack); diff --git a/server/ServerNetPackVisitors.h b/server/ServerNetPackVisitors.h index a6ffffb63..3b971fa7d 100644 --- a/server/ServerNetPackVisitors.h +++ b/server/ServerNetPackVisitors.h @@ -58,6 +58,7 @@ public: void visitBuyArtifact(BuyArtifact & pack) override; void visitTradeOnMarketplace(TradeOnMarketplace & pack) override; void visitSetFormation(SetFormation & pack) override; + void visitSetTownName(SetTownName & pack) override; void visitHireHero(HireHero & pack) override; void visitBuildBoat(BuildBoat & pack) override; void visitQueryReply(QueryReply & pack) override;