diff --git a/.github/workflows/github.yml b/.github/workflows/github.yml index 026e7b67e..010179540 100644 --- a/.github/workflows/github.yml +++ b/.github/workflows/github.yml @@ -244,7 +244,7 @@ jobs: - name: Trigger Android uses: peter-evans/repository-dispatch@v1 - if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }} + if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/master') && matrix.platform == 'mxe' }} with: token: ${{ secrets.VCMI_ANDROID_ACCESS_TOKEN }} repository: vcmi/vcmi-android diff --git a/README.md b/README.md index a6c760796..d3ea7de69 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ [![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/github/vcmi/vcmi?branch=develop&svg=true)](https://ci.appveyor.com/project/vcmi/vcmi) [![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi) -[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.0.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.0.0) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.1.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.1.0) +[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases) # VCMI Project VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities. diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 5fb2924dd..6bcd92424 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -26,6 +26,7 @@ set(client_SRCS gui/Geometries.cpp gui/SDL_Extensions.cpp gui/NotificationHandler.cpp + gui/InterfaceObjectConfigurable.cpp widgets/AdventureMapClasses.cpp widgets/Buttons.cpp @@ -113,6 +114,7 @@ set(client_HEADERS gui/SDL_Extensions.h gui/SDL_Pixels.h gui/NotificationHandler.h + gui/InterfaceObjectConfigurable.h widgets/AdventureMapClasses.h widgets/Buttons.h diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 86526d5d6..0c4df679d 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -165,6 +165,7 @@ struct SSetCaptureState }; #define OBJ_CONSTRUCTION SObjectConstruction obj__i(this) +#define OBJ_CONSTRUCTION_TARGETED(obj) SObjectConstruction obj__i(obj) #define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this) #define OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(actions) SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this) diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp new file mode 100644 index 000000000..99639b0f1 --- /dev/null +++ b/client/gui/InterfaceObjectConfigurable.cpp @@ -0,0 +1,389 @@ +/* +* InterfaceBuilder.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 "InterfaceObjectConfigurable.h" + +#include "../CGameInfo.h" +#include "../gui/CAnimation.h" +#include "../gui/CGuiHandler.h" +#include "../widgets/CComponent.h" +#include "../widgets/Buttons.h" +#include "../widgets/MiscWidgets.h" +#include "../widgets/ObjectLists.h" +#include "../widgets/TextControls.h" +#include "../windows/GUIClasses.h" +#include "../windows/InfoWindows.h" + +#include "../../lib/CGeneralTextHandler.h" + + +InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset): + InterfaceObjectConfigurable(used, offset) +{ + init(config); +} + +InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset): + CIntObject(used, offset) +{ + REGISTER_BUILDER("picture", &InterfaceObjectConfigurable::buildPicture); + REGISTER_BUILDER("image", &InterfaceObjectConfigurable::buildImage); + REGISTER_BUILDER("texture", &InterfaceObjectConfigurable::buildTexture); + REGISTER_BUILDER("animation", &InterfaceObjectConfigurable::buildAnimation); + REGISTER_BUILDER("label", &InterfaceObjectConfigurable::buildLabel); + REGISTER_BUILDER("toggleGroup", &InterfaceObjectConfigurable::buildToggleGroup); + REGISTER_BUILDER("toggleButton", &InterfaceObjectConfigurable::buildToggleButton); + REGISTER_BUILDER("button", &InterfaceObjectConfigurable::buildButton); + REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup); + REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider); +} + +void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f) +{ + builders[type] = f; +} + +void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function callback) +{ + callbacks[callbackName] = callback; +} + +void InterfaceObjectConfigurable::init(const JsonNode &config) +{ + OBJ_CONSTRUCTION; + logGlobal->debug("Building configurable interface object"); + for(auto & item : config["variables"].Struct()) + { + logGlobal->debug("Read variable named %s", item.first); + variables[item.first] = item.second; + } + + int unnamedObjectId = 0; + const std::string unnamedObjectPrefix = "__widget_"; + for(const auto & item : config["items"].Vector()) + { + std::string name = item["name"].isNull() + ? unnamedObjectPrefix + std::to_string(unnamedObjectId++) + : item["name"].String(); + logGlobal->debug("Building widget with name %s", name); + widgets[name] = buildWidget(item); + } +} + +std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const +{ + if(config.isNull()) + return ""; + + if(config.isNumber()) + { + logGlobal->debug("Reading text from generaltext handler id:%d", config.Integer()); + return CGI->generaltexth->allTexts[config.Integer()]; + } + + const std::string delimiter = "/"; + std::string s = config.String(); + logGlobal->debug("Reading text from translations by key: %s", s); + JsonNode translated = CGI->generaltexth->localizedTexts; + for(size_t p = s.find(delimiter); p != std::string::npos; p = s.find(delimiter)) + { + translated = translated[s.substr(0, p)]; + s.erase(0, p + delimiter.length()); + } + if(s == config.String()) + { + logGlobal->warn("Reading non-translated text: %s", s); + return s; + } + return translated[s].String(); +} + +Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const +{ + Point p; + logGlobal->debug("Reading point"); + p.x = config["x"].Integer(); + p.y = config["y"].Integer(); + return p; +} + +Rect InterfaceObjectConfigurable::readRect(const JsonNode & config) const +{ + Rect p; + logGlobal->debug("Reading rect"); + p.x = config["x"].Integer(); + p.y = config["y"].Integer(); + p.w = config["w"].Integer(); + p.h = config["h"].Integer(); + return p; +} + +ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const +{ + logGlobal->debug("Reading text alignment"); + if(!config.isNull()) + { + if(config.String() == "center") + return ETextAlignment::CENTER; + if(config.String() == "left") + return ETextAlignment::TOPLEFT; + if(config.String() == "right") + return ETextAlignment::BOTTOMRIGHT; + } + logGlobal->debug("Uknown text alignment attribute"); + return ETextAlignment::CENTER; +} + +SDL_Color InterfaceObjectConfigurable::readColor(const JsonNode & config) const +{ + logGlobal->debug("Reading color"); + if(!config.isNull()) + { + if(config.String() == "yellow") + return Colors::YELLOW; + if(config.String() == "white") + return Colors::WHITE; + if(config.String() == "gold") + return Colors::METALLIC_GOLD; + if(config.String() == "green") + return Colors::GREEN; + if(config.String() == "orange") + return Colors::ORANGE; + if(config.String() == "bright-yellow") + return Colors::BRIGHT_YELLOW; + } + logGlobal->debug("Uknown color attribute"); + return Colors::DEFAULT_KEY_COLOR; + +} +EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const +{ + logGlobal->debug("Reading font"); + if(!config.isNull()) + { + if(config.String() == "big") + return EFonts::FONT_BIG; + if(config.String() == "medium") + return EFonts::FONT_MEDIUM; + if(config.String() == "small") + return EFonts::FONT_SMALL; + if(config.String() == "tiny") + return EFonts::FONT_TINY; + } + logGlobal->debug("Uknown font attribute"); + return EFonts::FONT_TIMES; +} + +std::pair InterfaceObjectConfigurable::readHintText(const JsonNode & config) const +{ + logGlobal->debug("Reading hint text"); + std::pair result; + if(!config.isNull()) + { + if(config.isNumber()) + { + logGlobal->debug("Reading hint text (zelp) from generaltext handler id:%d", config.Integer()); + return CGI->generaltexth->zelp[config.Integer()]; + } + + if(config.getType() == JsonNode::JsonType::DATA_STRUCT) + { + result.first = readText(config["hover"]); + result.second = readText(config["help"]); + return result; + } + if(config.getType() == JsonNode::JsonType::DATA_STRING) + { + logGlobal->debug("Reading non-translated hint: %s", config.String()); + result.first = result.second = config.String(); + } + } + return result; +} + +std::shared_ptr InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const +{ + logGlobal->debug("Building widget CPicture"); + auto image = config["image"].String(); + auto position = readPosition(config["position"]); + auto pic = std::make_shared(image, position.x, position.y); + if(!config["visible"].isNull()) + pic->visible = config["visible"].Bool(); + return pic; +} + +std::shared_ptr InterfaceObjectConfigurable::buildLabel(const JsonNode & config) const +{ + logGlobal->debug("Building widget CLabel"); + auto font = readFont(config["font"]); + auto alignment = readTextAlignment(config["alignment"]); + auto color = readColor(config["color"]); + auto text = readText(config["text"]); + auto position = readPosition(config["position"]); + return std::make_shared(position.x, position.y, font, alignment, color, text); +} + +std::shared_ptr InterfaceObjectConfigurable::buildToggleGroup(const JsonNode & config) const +{ + logGlobal->debug("Building widget CToggleGroup"); + auto position = readPosition(config["position"]); + auto group = std::make_shared(0); + group->pos += position; + if(!config["items"].isNull()) + { + OBJ_CONSTRUCTION_TARGETED(group.get()); + int itemIdx = -1; + for(const auto & item : config["items"].Vector()) + { + itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer(); + group->addToggle(itemIdx, std::dynamic_pointer_cast(buildWidget(item))); + } + } + if(!config["selected"].isNull()) + group->setSelected(config["selected"].Integer()); + if(!config["callback"].isNull()) + group->addCallback(callbacks.at(config["callback"].String())); + return group; +} + +std::shared_ptr InterfaceObjectConfigurable::buildToggleButton(const JsonNode & config) const +{ + logGlobal->debug("Building widget CToggleButton"); + auto position = readPosition(config["position"]); + auto image = config["image"].String(); + auto zelp = readHintText(config["zelp"]); + auto button = std::make_shared(position, image, zelp); + if(!config["selected"].isNull()) + button->setSelected(config["selected"].Bool()); + if(!config["imageOrder"].isNull()) + { + auto imgOrder = config["imageOrder"].Vector(); + assert(imgOrder.size() >= 4); + button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer()); + } + if(!config["callback"].isNull()) + button->addCallback(callbacks.at(config["callback"].String())); + return button; +} + +std::shared_ptr InterfaceObjectConfigurable::buildButton(const JsonNode & config) const +{ + logGlobal->debug("Building widget CButton"); + auto position = readPosition(config["position"]); + auto image = config["image"].String(); + auto zelp = readHintText(config["zelp"]); + auto button = std::make_shared(position, image, zelp); + if(!config["items"].isNull()) + { + for(const auto & item : config["items"].Vector()) + { + button->addOverlay(buildWidget(item)); + } + } + if(!config["callback"].isNull()) + button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0)); + return button; +} + +std::shared_ptr InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const +{ + logGlobal->debug("Building widget CLabelGroup"); + auto font = readFont(config["font"]); + auto alignment = readTextAlignment(config["alignment"]); + auto color = readColor(config["color"]); + auto group = std::make_shared(font, alignment, color); + if(!config["items"].isNull()) + { + for(const auto & item : config["items"].Vector()) + { + auto position = readPosition(item["position"]); + auto text = readText(item["text"]); + group->add(position.x, position.y, text); + } + } + return group; +} + +std::shared_ptr InterfaceObjectConfigurable::buildSlider(const JsonNode & config) const +{ + logGlobal->debug("Building widget CSlider"); + 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"; + return std::make_shared(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style); +} + +std::shared_ptr InterfaceObjectConfigurable::buildImage(const JsonNode & config) const +{ + logGlobal->debug("Building widget CAnimImage"); + auto position = readPosition(config["position"]); + auto image = config["image"].String(); + int group = config["group"].isNull() ? 0 : config["group"].Integer(); + int frame = config["frame"].isNull() ? 0 : config["frame"].Integer(); + return std::make_shared(image, frame, group, position.x, position.y); +} + +std::shared_ptr InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const +{ + logGlobal->debug("Building widget CFilledTexture"); + auto image = config["image"].String(); + auto rect = readRect(config["rect"]); + return std::make_shared(image, rect); +} + +std::shared_ptr InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const +{ + logGlobal->debug("Building widget CShowableAnim"); + auto position = readPosition(config["position"]); + auto image = config["image"].String(); + ui8 flags = 0; + if(!config["repeat"].Bool()) + flags |= CShowableAnim::EFlags::PLAY_ONCE; + + int group = config["group"].isNull() ? 0 : config["group"].Integer(); + auto anim = std::make_shared(position.x, position.y, image, flags, 4, group); + if(!config["alpha"].isNull()) + anim->setAlpha(config["alpha"].Integer()); + if(!config["callback"].isNull()) + anim->callback = std::bind(callbacks.at(config["callback"].String()), 0); + if(!config["frames"].isNull()) + { + auto b = config["frames"]["start"].Integer(); + auto e = config["frames"]["end"].Integer(); + anim->set(group, b, e); + } + return anim; +} + +std::shared_ptr InterfaceObjectConfigurable::buildWidget(JsonNode config) const +{ + assert(!config.isNull()); + logGlobal->debug("Building widget from config"); + //overrides from variables + for(auto & item : config["overrides"].Struct()) + { + logGlobal->debug("Config attribute %s was overriden by variable %s", item.first, item.second.String()); + config[item.first] = variables[item.second.String()]; + } + + auto type = config["type"].String(); + auto buildIterator = builders.find(type); + if(buildIterator != builders.end()) + return (buildIterator->second)(config); + + logGlobal->error("Builder with type %s is not registered", type); + return nullptr; +} diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h new file mode 100644 index 000000000..3525a8d09 --- /dev/null +++ b/client/gui/InterfaceObjectConfigurable.h @@ -0,0 +1,85 @@ +/* +* InterfaceBuilder.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 "CIntObject.h" + +#include "../../lib/JsonNode.h" + +class CPicture; +class CLabel; +class CToggleGroup; +class CToggleButton; +class CButton; +class CLabelGroup; +class CSlider; +class CAnimImage; +class CShowableAnim; +class CFilledTexture; + +#define REGISTER_BUILDER(type, method) registerBuilder(type, std::bind(method, this, std::placeholders::_1)) + +class InterfaceObjectConfigurable: public CIntObject +{ +public: + InterfaceObjectConfigurable(int used=0, Point offset=Point()); + InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point()); + +protected: + + using BuilderFunction = std::function(const JsonNode &)>; + void registerBuilder(const std::string &, BuilderFunction); + + //must be called after adding callbacks + void init(const JsonNode & config); + + void addCallback(const std::string & callbackName, std::function callback); + JsonNode variables; + + template + const std::shared_ptr widget(const std::string & name) const + { + auto iter = widgets.find(name); + if(iter == widgets.end()) + return nullptr; + return std::dynamic_pointer_cast(iter->second); + } + + //basic serializers + Point readPosition(const JsonNode &) const; + Rect readRect(const JsonNode &) const; + ETextAlignment readTextAlignment(const JsonNode &) const; + SDL_Color readColor(const JsonNode &) const; + EFonts readFont(const JsonNode &) const; + std::string readText(const JsonNode &) const; + std::pair readHintText(const JsonNode &) const; + + //basic widgets + std::shared_ptr buildPicture(const JsonNode &) const; + std::shared_ptr buildLabel(const JsonNode &) const; + std::shared_ptr buildToggleGroup(const JsonNode &) const; + std::shared_ptr buildToggleButton(const JsonNode &) const; + std::shared_ptr buildButton(const JsonNode &) const; + std::shared_ptr buildLabelGroup(const JsonNode &) const; + std::shared_ptr buildSlider(const JsonNode &) const; + std::shared_ptr buildImage(const JsonNode &) const; + std::shared_ptr buildAnimation(const JsonNode &) const; + std::shared_ptr buildTexture(const JsonNode &) const; + + //composite widgets + std::shared_ptr buildWidget(JsonNode config) const; + +private: + + std::map builders; + std::map> widgets; + std::map> callbacks; +}; diff --git a/client/gui/NotificationHandler.h b/client/gui/NotificationHandler.h index a1ac262a7..536e0978d 100644 --- a/client/gui/NotificationHandler.h +++ b/client/gui/NotificationHandler.h @@ -1,5 +1,5 @@ /* -* NotificationHandler.cpp, part of VCMI engine +* NotificationHandler.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 41a82ab43..8f89ffaa1 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -27,127 +27,68 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/mapping/CMapInfo.h" #include "../../lib/rmg/CMapGenOptions.h" +#include "../../lib/CModHandler.h" +#include "../../lib/rmg/CRmgTemplateStorage.h" -RandomMapTab::RandomMapTab() +RandomMapTab::RandomMapTab(): + InterfaceObjectConfigurable() { recActions = 0; mapGenOptions = std::make_shared(); - OBJ_CONSTRUCTION; - background = std::make_shared("RANMAPBK", 0, 6); - - labelHeadlineBig = std::make_shared(222, 36, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[738]); - labelHeadlineSmall = std::make_shared(222, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[739]); - - labelMapSize = std::make_shared(104, 97, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[752]); - groupMapSize = std::make_shared(0); - groupMapSize->pos.y += 81; - groupMapSize->pos.x += 158; - const std::vector mapSizeBtns = {"RANSIZS", "RANSIZM", "RANSIZL", "RANSIZX"}; - addButtonsToGroup(groupMapSize.get(), mapSizeBtns, 0, 3, 47, 198); - groupMapSize->setSelected(1); - groupMapSize->addCallback([&](int btnId) + + const JsonNode config(ResourceID("config/widgets/randomMapTab.json")); + addCallback("toggleMapSize", [&](int btnId) { auto mapSizeVal = getPossibleMapSizes(); mapGenOptions->setWidth(mapSizeVal[btnId]); mapGenOptions->setHeight(mapSizeVal[btnId]); + if(mapGenOptions->getMapTemplate()) + if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()})) + setTemplate(nullptr); updateMapInfoByHost(); }); - - buttonTwoLevels = std::make_shared(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]); - buttonTwoLevels->setSelected(true); - buttonTwoLevels->addCallback([&](bool on) + addCallback("toggleTwoLevels", [&](bool on) { mapGenOptions->setHasTwoLevels(on); + if(mapGenOptions->getMapTemplate()) + if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()})) + setTemplate(nullptr); updateMapInfoByHost(); }); - - labelGroupForOptions = std::make_shared(FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE); - // Create number defs list - std::vector numberDefs; - for(int i = 0; i <= 8; ++i) - { - numberDefs.push_back("RANNUM" + boost::lexical_cast(i)); - } - - const int NUMBERS_WIDTH = 32; - const int BTNS_GROUP_LEFT_MARGIN = 67; - labelGroupForOptions->add(68, 133, CGI->generaltexth->allTexts[753]); - groupMaxPlayers = std::make_shared(0); - groupMaxPlayers->pos.y += 153; - groupMaxPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN; - addButtonsWithRandToGroup(groupMaxPlayers.get(), numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212); - groupMaxPlayers->addCallback([&](int btnId) + + addCallback("setPlayersCount", [&](int btnId) { mapGenOptions->setPlayerCount(btnId); - deactivateButtonsFrom(groupMaxTeams.get(), btnId); - - // deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I - deactivateButtonsFrom(groupCompOnlyPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1); - - validatePlayersCnt(btnId); + setMapGenOptions(mapGenOptions); updateMapInfoByHost(); }); - - labelGroupForOptions->add(68, 199, CGI->generaltexth->allTexts[754]); - groupMaxTeams = std::make_shared(0); - groupMaxTeams->pos.y += 219; - groupMaxTeams->pos.x += BTNS_GROUP_LEFT_MARGIN; - addButtonsWithRandToGroup(groupMaxTeams.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222); - groupMaxTeams->addCallback([&](int btnId) + + addCallback("setTeamsCount", [&](int btnId) { mapGenOptions->setTeamCount(btnId); updateMapInfoByHost(); }); - - labelGroupForOptions->add(68, 265, CGI->generaltexth->allTexts[755]); - groupCompOnlyPlayers = std::make_shared(0); - groupCompOnlyPlayers->pos.y += 285; - groupCompOnlyPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN; - addButtonsWithRandToGroup(groupCompOnlyPlayers.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232); - groupCompOnlyPlayers->addCallback([&](int btnId) + + addCallback("setCompOnlyPlayers", [&](int btnId) { mapGenOptions->setCompOnlyPlayerCount(btnId); - - // deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I - deactivateButtonsFrom(groupMaxPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1); - - deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId)); - validateCompOnlyPlayersCnt(btnId); + setMapGenOptions(mapGenOptions); updateMapInfoByHost(); }); - - labelGroupForOptions->add(68, 331, CGI->generaltexth->allTexts[756]); - groupCompOnlyTeams = std::make_shared(0); - groupCompOnlyTeams->pos.y += 351; - groupCompOnlyTeams->pos.x += BTNS_GROUP_LEFT_MARGIN; - addButtonsWithRandToGroup(groupCompOnlyTeams.get(), numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241); - deactivateButtonsFrom(groupCompOnlyTeams.get(), 1); - groupCompOnlyTeams->addCallback([&](int btnId) + + addCallback("setCompOnlyTeams", [&](int btnId) { mapGenOptions->setCompOnlyTeamCount(btnId); updateMapInfoByHost(); }); - - labelGroupForOptions->add(68, 398, CGI->generaltexth->allTexts[757]); - const int WIDE_BTN_WIDTH = 85; - groupWaterContent = std::make_shared(0); - groupWaterContent->pos.y += 419; - groupWaterContent->pos.x += BTNS_GROUP_LEFT_MARGIN; - const std::vector waterContentBtns = {"RANNONE", "RANNORM", "RANISLD"}; - addButtonsWithRandToGroup(groupWaterContent.get(), waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246); - groupWaterContent->addCallback([&](int btnId) + + addCallback("setWaterContent", [&](int btnId) { mapGenOptions->setWaterContent(static_cast(btnId)); updateMapInfoByHost(); }); - - labelGroupForOptions->add(68, 465, CGI->generaltexth->allTexts[758]); - groupMonsterStrength = std::make_shared(0); - groupMonsterStrength->pos.y += 485; - groupMonsterStrength->pos.x += BTNS_GROUP_LEFT_MARGIN; - const std::vector monsterStrengthBtns = {"RANWEAK", "RANNORM", "RANSTRG"}; - addButtonsWithRandToGroup(groupMonsterStrength.get(), monsterStrengthBtns, 2, 4, WIDE_BTN_WIDTH, 248, 251, EMonsterStrength::RANDOM, false); - groupMonsterStrength->addCallback([&](int btnId) + + addCallback("setMonsterStrength", [&](int btnId) { if(btnId < 0) mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM); @@ -155,9 +96,31 @@ RandomMapTab::RandomMapTab() mapGenOptions->setMonsterStrength(static_cast(btnId)); //value 2 to 4 updateMapInfoByHost(); }); - - buttonShowRandomMaps = std::make_shared(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]); - + + //new callbacks available only from mod + addCallback("templateSelection", [&](int) + { + GH.pushIntT(*this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}); + }); + + addCallback("teamAlignments", [&](int) + { + GH.pushIntT(*this); + }); + + for(auto road : VLC->terrainTypeHandler->roads()) + { + std::string cbRoadType = "selectRoad_" + road.name; + addCallback(cbRoadType, [&, road](bool on) + { + mapGenOptions->setRoadEnabled(road.name, on); + updateMapInfoByHost(); + }); + } + + + init(config); + updateMapInfoByHost(); } @@ -192,6 +155,7 @@ void RandomMapTab::updateMapInfoByHost() mapInfo->mapHeader->howManyTeams = playersToGen; + std::set occupiedTeams; for(int i = 0; i < playersToGen; ++i) { PlayerInfo player; @@ -205,60 +169,153 @@ void RandomMapTab::updateMapInfoByHost() { player.canHumanPlay = true; } - player.team = TeamID(i); + auto team = mapGenOptions->getPlayersSettings().at(PlayerColor(i)).getTeam(); + player.team = team; + occupiedTeams.insert(team); player.hasMainTown = true; player.generateHeroAtMainTown = true; mapInfo->mapHeader->players.push_back(player); } + for(auto & player : mapInfo->mapHeader->players) + { + for(int i = 0; player.team == TeamID::NO_TEAM; ++i) + { + TeamID team(i); + if(!occupiedTeams.count(team)) + { + player.team = team; + occupiedTeams.insert(team); + } + } + } mapInfoChanged(mapInfo, mapGenOptions); } void RandomMapTab::setMapGenOptions(std::shared_ptr opts) { - groupMapSize->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth())); - buttonTwoLevels->setSelected(opts->getHasTwoLevels()); - groupMaxPlayers->setSelected(opts->getPlayerCount()); - groupMaxTeams->setSelected(opts->getTeamCount()); - groupCompOnlyPlayers->setSelected(opts->getCompOnlyPlayerCount()); - groupCompOnlyTeams->setSelected(opts->getCompOnlyTeamCount()); - groupWaterContent->setSelected(opts->getWaterContent()); - groupMonsterStrength->setSelected(opts->getMonsterStrength()); -} - -void RandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex, int randIndex, bool animIdfromBtnId) const -{ - addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex, animIdfromBtnId); - - // Buttons are relative to button group, TODO better solution? - SObjectConstruction obj__i(group); - const std::string RANDOM_DEF = "RANRAND"; - group->addToggle(randIndex, std::make_shared(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex])); - group->setSelected(randIndex); -} - -void RandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, bool animIdfromBtnId) const -{ - // Buttons are relative to button group, TODO better solution? - SObjectConstruction obj__i(group); - int cnt = nEnd - nStart + 1; - for(int i = 0; i < cnt; ++i) + mapGenOptions = opts; + + //prepare allowed options + for(int i = 0; i <= PlayerColor::PLAYER_LIMIT_I; ++i) { - auto button = std::make_shared(Point(i * btnWidth, 0), animIdfromBtnId ? defs[i + nStart] : defs[i], CGI->generaltexth->zelp[helpStartIndex + i]); - // For blocked state we should use pressed image actually - button->setImageOrder(0, 1, 1, 3); - group->addToggle(i + nStart, button); + playerCountAllowed.insert(i); + compCountAllowed.insert(i); + playerTeamsAllowed.insert(i); + compTeamsAllowed.insert(i); + } + auto * tmpl = mapGenOptions->getMapTemplate(); + if(tmpl) + { + playerCountAllowed = tmpl->getPlayers().getNumbers(); + compCountAllowed = tmpl->getCpuPlayers().getNumbers(); + } + if(mapGenOptions->getPlayerCount() != CMapGenOptions::RANDOM_SIZE) + { + vstd::erase_if(compCountAllowed, + [opts](int el){ + return PlayerColor::PLAYER_LIMIT_I - opts->getPlayerCount() < el; + }); + vstd::erase_if(playerTeamsAllowed, + [opts](int el){ + return opts->getPlayerCount() <= el; + }); + + if(!playerTeamsAllowed.count(opts->getTeamCount())) + opts->setTeamCount(CMapGenOptions::RANDOM_SIZE); + } + if(mapGenOptions->getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE) + { + vstd::erase_if(playerCountAllowed, + [opts](int el){ + return PlayerColor::PLAYER_LIMIT_I - opts->getCompOnlyPlayerCount() < el; + }); + vstd::erase_if(compTeamsAllowed, + [opts](int el){ + return opts->getCompOnlyPlayerCount() <= el; + }); + + if(!compTeamsAllowed.count(opts->getCompOnlyTeamCount())) + opts->setCompOnlyTeamCount(CMapGenOptions::RANDOM_SIZE); + } + + if(auto w = widget("groupMapSize")) + w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth())); + if(auto w = widget("buttonTwoLevels")) + w->setSelected(opts->getHasTwoLevels()); + if(auto w = widget("groupMaxPlayers")) + { + w->setSelected(opts->getPlayerCount()); + deactivateButtonsFrom(*w, playerCountAllowed); + } + if(auto w = widget("groupMaxTeams")) + { + w->setSelected(opts->getTeamCount()); + deactivateButtonsFrom(*w, playerTeamsAllowed); + } + if(auto w = widget("groupCompOnlyPlayers")) + { + w->setSelected(opts->getCompOnlyPlayerCount()); + deactivateButtonsFrom(*w, compCountAllowed); + } + if(auto w = widget("groupCompOnlyTeams")) + { + w->setSelected(opts->getCompOnlyTeamCount()); + deactivateButtonsFrom(*w, compTeamsAllowed); + } + if(auto w = widget("groupWaterContent")) + { + w->setSelected(opts->getWaterContent()); + if(opts->getMapTemplate()) + { + std::set allowedWater(opts->getMapTemplate()->getWaterContentAllowed().begin(), opts->getMapTemplate()->getWaterContentAllowed().end()); + deactivateButtonsFrom(*w, allowedWater); + } + else + deactivateButtonsFrom(*w, {-1}); + } + if(auto w = widget("groupMonsterStrength")) + w->setSelected(opts->getMonsterStrength()); + if(auto w = widget("templateButton")) + { + if(tmpl) + w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL); + else + w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL); + } + for(auto r : VLC->terrainTypeHandler->roads()) + { + if(auto w = widget(r.name)) + { + w->setSelected(opts->isRoadEnabled(r.name)); + } } } -void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId) +void RandomMapTab::setTemplate(const CRmgTemplate * tmpl) { - logGlobal->debug("Blocking buttons from %d", startId); - for(auto toggle : group->buttons) + mapGenOptions->setMapTemplate(tmpl); + setMapGenOptions(mapGenOptions); + if(auto w = widget("templateButton")) + { + if(tmpl) + w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL); + else + w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL); + } + updateMapInfoByHost(); +} + +void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set & allowed) +{ + logGlobal->debug("Blocking buttons"); + for(auto toggle : group.buttons) { if(auto button = std::dynamic_pointer_cast(toggle.second)) { - if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId) + if(allowed.count(CMapGenOptions::RANDOM_SIZE) + || allowed.count(toggle.first) + || toggle.first == CMapGenOptions::RANDOM_SIZE) { button->block(false); } @@ -270,45 +327,243 @@ void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId) } } -void RandomMapTab::validatePlayersCnt(int playersCnt) -{ - if(playersCnt == CMapGenOptions::RANDOM_SIZE) - { - return; - } - - if(mapGenOptions->getTeamCount() >= playersCnt) - { - mapGenOptions->setTeamCount(playersCnt - 1); - groupMaxTeams->setSelected(mapGenOptions->getTeamCount()); - } - // total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3) - if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I) - { - mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt); - groupCompOnlyPlayers->setSelected(mapGenOptions->getCompOnlyPlayerCount()); - } - - validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount()); -} - -void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt) -{ - if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE) - { - return; - } - - if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt) - { - int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1; - mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount); - updateMapInfoByHost(); - groupCompOnlyTeams->setSelected(compOnlyTeamCount); - } -} - std::vector RandomMapTab::getPossibleMapSizes() { - return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE}; + 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; + + init(config); + + if(auto w = widget("hoverImage")) + { + pos.w = w->pos.w; + pos.h = w->pos.h; + } + type |= REDRAW_PARENT; +} + +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["defaultTemplate"])); + } + } +} + +void TemplatesDropBox::ListItem::hover(bool on) +{ + auto h = widget("hoverImage"); + auto w = widget("labelName"); + if(h && w) + { + if(w->getText().empty()) + { + hovered = false; + h->visible = false; + } + else + { + h->visible = on; + } + } + redraw(); +} + +void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState) +{ + if(down && hovered) + { + dropBox.setTemplate(item); + } +} + + +TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size): + InterfaceObjectConfigurable(LCLICK | HOVER), + randomMapTab(randomMapTab) +{ + REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem); + + curItems = VLC->tplh->getTemplates(); + vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);}); + 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.topLeft(); + pos.w = randomMapTab.pos.w; + pos.h = randomMapTab.pos.h; + + init(config); + + if(auto w = widget("slider")) + { + w->setAmount(curItems.size()); + } + + 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(); +} + +void TemplatesDropBox::hover(bool on) +{ + hovered = on; +} + +void TemplatesDropBox::clickLeft(tribool down, bool previousState) +{ + if(down && !hovered) + { + assert(GH.topInt().get() == this); + GH.popInt(GH.topInt()); + } +} + +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.topInt().get() == this); + GH.popInt(GH.topInt()); +} + +TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab): + InterfaceObjectConfigurable(), + randomMapTab(randomMapTab) +{ + const JsonNode config(ResourceID("config/widgets/randomMapTeamsWidget.json")); + variables = config["variables"]; + + int humanPlayers = randomMapTab.obtainMapGenOptions().getPlayerCount(); + int cpuPlayers = randomMapTab.obtainMapGenOptions().getCompOnlyPlayerCount(); + int totalPlayers = humanPlayers == CMapGenOptions::RANDOM_SIZE || cpuPlayers == CMapGenOptions::RANDOM_SIZE + ? PlayerColor::PLAYER_LIMIT_I : humanPlayers + cpuPlayers; + assert(totalPlayers <= PlayerColor::PLAYER_LIMIT_I); + auto settings = randomMapTab.obtainMapGenOptions().getPlayersSettings(); + variables["totalPlayers"].Integer() = totalPlayers; + + pos.w = variables["windowSize"]["x"].Integer() + totalPlayers * variables["cellMargin"]["x"].Integer(); + pos.h = variables["windowSize"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer(); + variables["backgroundRect"]["x"].Integer() = pos.x; + variables["backgroundRect"]["y"].Integer() = pos.y; + variables["backgroundRect"]["w"].Integer() = pos.w; + variables["backgroundRect"]["h"].Integer() = pos.h; + variables["okButtonPosition"]["x"].Integer() = variables["buttonsOffset"]["ok"]["x"].Integer(); + variables["okButtonPosition"]["y"].Integer() = variables["buttonsOffset"]["ok"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer(); + variables["cancelButtonPosition"]["x"].Integer() = variables["buttonsOffset"]["cancel"]["x"].Integer(); + variables["cancelButtonPosition"]["y"].Integer() = variables["buttonsOffset"]["cancel"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer(); + + addCallback("ok", [&](int) + { + for(int plId = 0; plId < players.size(); ++plId) + { + randomMapTab.obtainMapGenOptions().setPlayerTeam(PlayerColor(plId), TeamID(players[plId]->getSelected())); + } + randomMapTab.updateMapInfoByHost(); + assert(GH.topInt().get() == this); + GH.popInt(GH.topInt()); + }); + + addCallback("cancel", [&](int) + { + assert(GH.topInt().get() == this); + GH.popInt(GH.topInt()); + }); + + init(config); + + center(pos); + + OBJ_CONSTRUCTION; + + for(int plId = 0; plId < totalPlayers; ++plId) + { + players.push_back(std::make_shared([&, totalPlayers, plId](int sel) + { + variables["player_id"].Integer() = plId; + OBJ_CONSTRUCTION_TARGETED(players[plId].get()); + for(int teamId = 0; teamId < totalPlayers; ++teamId) + { + auto button = std::dynamic_pointer_cast(players[plId]->buttons[teamId]); + assert(button); + if(sel == teamId) + { + button->addOverlay(buildWidget(variables["flagsAnimation"])); + } + else + { + button->addOverlay(nullptr); + } + } + })); + + OBJ_CONSTRUCTION_TARGETED(players.back().get()); + for(int teamId = 0; teamId < totalPlayers; ++teamId) + { + variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer(); + variables["point"]["y"].Integer() = variables["cellOffset"]["y"].Integer() + teamId * variables["cellMargin"]["y"].Integer(); + auto button = buildWidget(variables["button"]); + players.back()->addToggle(teamId, std::dynamic_pointer_cast(button)); + } + + auto team = settings.at(PlayerColor(plId)).getTeam(); + if(team == TeamID::NO_TEAM) + players.back()->setSelected(plId); + else + players.back()->setSelected(team.getNum()); + } } diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h index f7e399a17..454f8fcb2 100644 --- a/client/lobby/RandomMapTab.h +++ b/client/lobby/RandomMapTab.h @@ -13,6 +13,8 @@ #include "../../lib/FunctionList.h" #include "../../lib/GameConstants.h" +#include "../../lib/rmg/CRmgTemplate.h" +#include "../gui/InterfaceObjectConfigurable.h" VCMI_LIB_NAMESPACE_BEGIN @@ -23,42 +25,80 @@ VCMI_LIB_NAMESPACE_END class CToggleButton; class CLabel; class CLabelGroup; +class CSlider; +class CPicture; -class RandomMapTab : public CIntObject +class RandomMapTab : public InterfaceObjectConfigurable { public: RandomMapTab(); void updateMapInfoByHost(); void setMapGenOptions(std::shared_ptr opts); + void setTemplate(const CRmgTemplate *); + CMapGenOptions & obtainMapGenOptions() {return *mapGenOptions;} CFunctionList, std::shared_ptr)> mapInfoChanged; private: - void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex, int randIndex = -1, bool animIdfromBtnId = true) const; - void addButtonsToGroup(CToggleGroup * group, const std::vector & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, bool animIdfromBtnId = true) const; - void deactivateButtonsFrom(CToggleGroup * group, int startId); - void validatePlayersCnt(int playersCnt); - void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt); + void deactivateButtonsFrom(CToggleGroup & group, const std::set & allowed); std::vector getPossibleMapSizes(); - - std::shared_ptr background; - std::shared_ptr labelHeadlineBig; - std::shared_ptr labelHeadlineSmall; - - std::shared_ptr labelMapSize; - std::shared_ptr groupMapSize; - std::shared_ptr buttonTwoLevels; - - std::shared_ptr labelGroupForOptions; - std::shared_ptr groupMaxPlayers; - std::shared_ptr groupMaxTeams; - std::shared_ptr groupCompOnlyPlayers; - std::shared_ptr groupCompOnlyTeams; - std::shared_ptr groupWaterContent; - std::shared_ptr groupMonsterStrength; - std::shared_ptr buttonShowRandomMaps; std::shared_ptr mapGenOptions; std::shared_ptr mapInfo; + + //options allowed - need to store as impact each other + 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 clickLeft(tribool down, bool previousState) override; + }; + + friend struct ListItem; + +public: + TemplatesDropBox(RandomMapTab & randomMapTab, int3 size); + + void hover(bool on) override; + void clickLeft(tribool down, bool previousState) 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: + TeamAlignmentsWidget(RandomMapTab & randomMapTab); + +private: + + RandomMapTab & randomMapTab; + + std::shared_ptr background; + std::shared_ptr labels; + std::shared_ptr buttonOk, buttonCancel; + std::vector> players; + std::vector> placeholders; }; diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index e180181ae..98bb638d7 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -84,8 +84,11 @@ void CButton::addTextOverlay(const std::string & Text, EFonts font, SDL_Color co void CButton::addOverlay(std::shared_ptr newOverlay) { overlay = newOverlay; - addChild(newOverlay.get()); - overlay->moveTo(overlay->pos.centerIn(pos).topLeft()); + if(overlay) + { + addChild(newOverlay.get()); + overlay->moveTo(overlay->pos.centerIn(pos).topLeft()); + } update(); } @@ -449,6 +452,11 @@ void CToggleGroup::selectionChanged(int to) parent->redraw(); } +int CToggleGroup::getSelected() const +{ + return selectedID; +} + CVolumeSlider::CVolumeSlider(const Point & position, const std::string & defName, const int value, const std::pair * const help) : CIntObject(LCLICK | RCLICK | WHEEL), value(value), @@ -566,16 +574,21 @@ void CSlider::setScrollStep(int to) scrollStep = to; } -int CSlider::getAmount() +int CSlider::getAmount() const { return amount; } -int CSlider::getValue() +int CSlider::getValue() const { return value; } +int CSlider::getCapacity() const +{ + return capacity; +} + void CSlider::moveLeft() { moveTo(value-1); diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index d370b515e..872cc448e 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -185,6 +185,7 @@ public: /// in some cases, e.g. LoadGame difficulty selection, after refreshing the UI, the ToggleGroup should /// reset all of it's child buttons to BLOCK state, then make selection again void setSelectedOnly(int id); + int getSelected() const; }; /// A typical slider for volume with an animated indicator @@ -256,8 +257,9 @@ public: void setAmount(int to); /// Accessors - int getAmount(); - int getValue(); + int getAmount() const; + int getValue() const; + int getCapacity() const; void addCallback(std::function callback); diff --git a/client/widgets/Images.h b/client/widgets/Images.h index 8f2fab4a0..cf0b64855 100644 --- a/client/widgets/Images.h +++ b/client/widgets/Images.h @@ -57,7 +57,7 @@ public: }; /// area filled with specific texture -class CFilledTexture : CIntObject +class CFilledTexture : public CIntObject { SDL_Surface * texture; diff --git a/config/randomMap.json b/config/randomMap.json index 926214ea2..fa54f121f 100644 --- a/config/randomMap.json +++ b/config/randomMap.json @@ -16,8 +16,8 @@ "extraResourcesLimit" : 3 }, "minGuardStrength" : 2000, - "defaultRoadType" : "pc", //pd - dirt, pg - gravel, pc - cobblestone - "secondaryRoadType": "pd", + "defaultRoadType" : "cobblestoneRoad", + "secondaryRoadType": "dirtRoad", "treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit "prisons" : { diff --git a/config/translate.json b/config/translate.json index 3352fedd3..35d36f18d 100644 --- a/config/translate.json +++ b/config/translate.json @@ -113,5 +113,15 @@ "label" : "Hide complete quests", "help" : "Hide all quests that already completed" } + }, + "randomMapTab": + { + "widgets": + { + "defaultTemplate": "default", + "templateLabel": "Template", + "teamAlignmentsButton": "Setup...", + "teamAlignmentsLabel": "Team alignments" + } } } diff --git a/config/widgets/randomMapTab.json b/config/widgets/randomMapTab.json new file mode 100644 index 000000000..a1741c121 --- /dev/null +++ b/config/widgets/randomMapTab.json @@ -0,0 +1,510 @@ +{ + "items": + [ + { + "name": "background", + "type": "picture", + "image": "RANMAPBK", + "position": {"x": 0, "y": 6} + }, + + { + "name": "labelHeadlineBig", + "type": "label", + "font": "big", + "alignment": "center", + "color": "yellow", + "text": 738, + "position": {"x": 222, "y": 36} + }, + + { + "name": "labelHeadlineSmall", + "type": "label", + "font": "small", + "alignment": "center", + "color": "white", + "text": 739, + "position": {"x": 222, "y": 56} + }, + + { + "name": "labelMapSize", + "type": "label", + "font": "small", + "alignment": "center", + "color": "white", + "text": 752, + "position": {"x": 104, "y": 97} + }, + + { + "name": "groupMapSize", + "type": "toggleGroup", + "position": {"x": 158, "y": 81}, + "items": + [ + { + "index": 0, + "type": "toggleButton", + "image": "RANSIZS", + "zelp": 198, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0}, + }, + + { + "type": "toggleButton", + "image": "RANSIZM", + "zelp": 199, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 47, "y": 0}, + }, + + { + "type": "toggleButton", + "image": "RANSIZL", + "zelp": 200, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 94, "y": 0}, + }, + + { + "type": "toggleButton", + "image": "RANSIZX", + "zelp": 201, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 141, "y": 0} + } + ], + "selected": 1, + "callback": "toggleMapSize" + }, + + { + "name": "buttonTwoLevels", + "type": "toggleButton", + "image": "RANUNDR", + "position": {"x": 346, "y": 81}, + "selected": true, + "callback": "toggleTwoLevels" + }, + + { + "name": "groupMaxPlayers", + "type": "toggleGroup", + "position": {"x": 67, "y": 153}, + "items": + [ + { + "index": 1, + "type": "toggleButton", + "image": "RANNUM1", + "zelp": 204, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM2", + "zelp": 205, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 32, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM3", + "zelp": 206, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 64, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM4", + "zelp": 207, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 96, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM5", + "zelp": 208, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 128, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM6", + "zelp": 209, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 160, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM7", + "zelp": 210, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 192, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM8", + "zelp": 211, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 224, "y": 0} + }, + { + "index": -1, + "type": "toggleButton", + "image": "RANRAND", + "zelp": 212, + "position": {"x": 256, "y": 0}, + } + ], + "selected": 7, + "callback": "setPlayersCount" + }, + + { + "name": "groupMaxTeams", + "type": "toggleGroup", + "position": {"x": 67, "y": 219}, + "items": + [ + { + "index": 0, + "type": "toggleButton", + "image": "RANNUM0", + "zelp": 214, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM1", + "zelp": 215, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 32, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM2", + "zelp": 216, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 64, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM3", + "zelp": 217, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 96, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM4", + "zelp": 218, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 128, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM5", + "zelp": 219, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 160, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM6", + "zelp": 220, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 192, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM7", + "zelp": 221, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 224, "y": 0} + }, + { + "index": -1, + "type": "toggleButton", + "image": "RANRAND", + "zelp": 222, + "position": {"x": 256, "y": 0}, + } + ], + "selected": 7, + "callback": "setTeamsCount" + }, + + { + "name": "groupCompOnlyPlayers", + "type": "toggleGroup", + "position": {"x": 67, "y": 285}, + "items": + [ + { + "index": 0, + "type": "toggleButton", + "image": "RANNUM0", + "zelp": 224, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM1", + "zelp": 225, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 32, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM2", + "zelp": 226, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 64, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM3", + "zelp": 227, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 96, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM4", + "zelp": 228, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 128, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM5", + "zelp": 229, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 160, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM6", + "zelp": 230, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 192, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM7", + "zelp": 231, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 224, "y": 0} + }, + { + "index": -1, + "type": "toggleButton", + "image": "RANRAND", + "zelp": 232, + "position": {"x": 256, "y": 0}, + } + ], + "selected": 7, + "callback": "setCompOnlyPlayers" + }, + + { + "name": "groupCompOnlyTeams", + "type": "toggleGroup", + "position": {"x": 67, "y": 351}, + "items": + [ + { + "index": 0, + "type": "toggleButton", + "image": "RANNUM0", + "zelp": 234, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM1", + "zelp": 235, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 32, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM2", + "zelp": 236, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 64, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM3", + "zelp": 237, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 96, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM4", + "zelp": 238, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 128, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM5", + "zelp": 239, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 160, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNUM6", + "zelp": 240, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 192, "y": 0} + }, + { + "index": -1, + "type": "toggleButton", + "image": "RANRAND", + "zelp": 241, + "position": {"x": 256, "y": 0}, + } + ], + "selected": 7, + "callback": "setCompOnlyTeams" + }, + + { + "name": "groupWaterContent", + "type": "toggleGroup", + "position": {"x": 67, "y": 419}, + "items": + [ + { + "index": 0, + "type": "toggleButton", + "image": "RANNONE", + "zelp": 243, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANNORM", + "zelp": 244, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 85, "y": 0} + }, + { + "type": "toggleButton", + "image": "RANISLD", + "zelp": 245, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 170, "y": 0} + }, + { + "index": -1, + "type": "toggleButton", + "image": "RANRAND", + "zelp": 246, + "position": {"x": 256, "y": 0}, + } + ], + "selected": 3, + "callback": "setWaterContent" + }, + + { + "name": "groupMonsterStrength", + "type": "toggleGroup", + "position": {"x": 67, "y": 485}, + "items": + [ + { + "index": 2, + "type": "toggleButton", + "image": "RANWEAK", + "zelp": 248, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 0, "y": 0} + }, + { + "index": 3, + "type": "toggleButton", + "image": "RANNORM", + "zelp": 249, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 85, "y": 0} + }, + { + "index": 4, + "type": "toggleButton", + "image": "RANSTRG", + "zelp": 250, + "imageOrder": [0, 1, 1, 3], + "position": {"x": 170, "y": 0} + }, + { + "index": -2, + "type": "toggleButton", + "image": "RANRAND", + "zelp": 251, + "position": {"x": 256, "y": 0}, + } + ], + "selected": 3, + "callback": "setMonsterStrength" + }, + + { + "name": "buttonShowRandomMaps", + "type": "button", + "position": {"x": 54, "y": 535}, + "image": "RANSHOW", + "zelp": 252 + }, + + { + "type": "labelGroup", + "font": "small", + "alignment": "left", + "color": "white", + "items": + [ + { + "position": {"x": 68, "y": 133}, + "text": 753 + }, + { + "position": {"x": 68, "y": 199}, + "text": 754 + }, + { + "position": {"x": 68, "y": 265}, + "text": 755 + }, + { + "position": {"x": 68, "y": 331}, + "text": 756 + }, + { + "position": {"x": 68, "y": 398}, + "text": 757 + }, + { + "position": {"x": 68, "y": 465}, + "text": 758 + } + ] + } + ] +} diff --git a/debian/changelog b/debian/changelog index 148a70e2f..6f1c5dda7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vcmi (1.2.0) jammy; urgency=medium + + * New upstream release + + -- Ivan Savenko Fri, 23 Dec 2022 16:00:00 +0200 + vcmi (1.1.0) jammy; urgency=medium * New upstream release diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index 227abec6c..c8e843228 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -38,6 +38,7 @@ https://github.com/vcmi/vcmi/issues https://vcmi.eu/faq/ + diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp index 599ab40ea..2280b37dc 100644 --- a/lib/Terrain.cpp +++ b/lib/Terrain.cpp @@ -220,6 +220,7 @@ void TerrainTypeHandler::initRivers(const std::vector & allConfigs) { RiverType info; + info.name = river.first; info.fileName = river.second["animation"].String(); info.code = river.second["code"].String(); info.deltaName = river.second["delta"].String(); @@ -255,6 +256,7 @@ void TerrainTypeHandler::initRoads(const std::vector & allConfigs) { RoadType info; + info.name = road.first; info.fileName = road.second["animation"].String(); info.code = road.second["code"].String(); info.movementCost = static_cast(road.second["moveCost"].Float()); @@ -295,7 +297,7 @@ void TerrainTypeHandler::recreateRiverMaps() { const auto * riverInfo = &riverTypes[i]; - riverInfoByName[riverInfo->fileName] = riverInfo; + riverInfoByName[riverInfo->name] = riverInfo; riverInfoByCode[riverInfo->code] = riverInfo; riverInfoById[riverInfo->id] = riverInfo; } @@ -307,7 +309,7 @@ void TerrainTypeHandler::recreateRoadMaps() { const auto * roadInfo = &roadTypes[i]; - roadInfoByName[roadInfo->fileName] = roadInfo; + roadInfoByName[roadInfo->name] = roadInfo; roadInfoByCode[roadInfo->code] = roadInfo; roadInfoById[roadInfo->id] = roadInfo; } diff --git a/lib/Terrain.h b/lib/Terrain.h index 6e23a9ca2..5d6fca1e7 100644 --- a/lib/Terrain.h +++ b/lib/Terrain.h @@ -89,7 +89,7 @@ public: class DLL_LINKAGE RiverType { public: - + std::string name; std::string fileName; std::string code; std::string deltaName; @@ -99,6 +99,10 @@ public: template void serialize(Handler& h, const int version) { + if(version >= 806) + { + h & name; + } h & fileName; h & code; h & deltaName; @@ -109,6 +113,7 @@ public: class DLL_LINKAGE RoadType { public: + std::string name; std::string fileName; std::string code; RoadId id; @@ -118,6 +123,10 @@ public: template void serialize(Handler& h, const int version) { + if(version >= 806) + { + h & name; + } h & fileName; h & code; h & id; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index b8047624b..25c1a464b 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -284,6 +284,9 @@ public: static const int MAP_SIZE_MIDDLE = 72; static const int MAP_SIZE_LARGE = 108; static const int MAP_SIZE_XLARGE = 144; + static const int MAP_SIZE_HUGE = 180; + static const int MAP_SIZE_XHUGE = 216; + static const int MAP_SIZE_GIANT = 252; CMapHeader(); virtual ~CMapHeader(); diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index 3756ae580..f33f6a02e 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -134,6 +134,12 @@ int CMapInfo::getMapSizeIconId() const return 2; case CMapHeader::MAP_SIZE_XLARGE: return 3; + case CMapHeader::MAP_SIZE_HUGE: + return 4; + case CMapHeader::MAP_SIZE_XHUGE: + return 5; + case CMapHeader::MAP_SIZE_GIANT: + return 6; default: return 4; } @@ -180,6 +186,12 @@ std::string CMapInfo::getMapSizeName() const return "L"; case CMapHeader::MAP_SIZE_XLARGE: return "XL"; + case CMapHeader::MAP_SIZE_HUGE: + return "H"; + case CMapHeader::MAP_SIZE_XHUGE: + return "XH"; + case CMapHeader::MAP_SIZE_GIANT: + return "G"; default: return "C"; } diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp index 141e61782..098511bfe 100644 --- a/lib/rmg/CMapGenOptions.cpp +++ b/lib/rmg/CMapGenOptions.cpp @@ -136,12 +136,14 @@ void CMapGenOptions::resetPlayersMap() { std::map rememberTownTypes; + std::map rememberTeam; for (auto p : players) { auto town = p.second.getStartingTown(); if (town != RANDOM_SIZE) rememberTownTypes[p.first] = town; + rememberTeam[p.first] = p.second.getTeam(); } @@ -169,6 +171,7 @@ void CMapGenOptions::resetPlayersMap() playerType = EPlayerType::COMP_ONLY; } player.setPlayerType(playerType); + player.setTeam(rememberTeam[pc]); players[pc] = player; if (vstd::contains(rememberTownTypes, pc)) @@ -204,8 +207,50 @@ const CRmgTemplate * CMapGenOptions::getMapTemplate() const void CMapGenOptions::setMapTemplate(const CRmgTemplate * value) { mapTemplate = value; - //TODO validate & adapt options according to template - //assert(0); + //validate & adapt options according to template + if(mapTemplate) + { + if(!mapTemplate->matchesSize(int3(getWidth(), getHeight(), 1 + getHasTwoLevels()))) + { + auto sizes = mapTemplate->getMapSizes(); + setWidth(sizes.first.x); + setHeight(sizes.first.y); + setHasTwoLevels(sizes.first.z - 1); + } + + if(!mapTemplate->getPlayers().isInRange(getPlayerCount())) + setPlayerCount(RANDOM_SIZE); + if(!mapTemplate->getCpuPlayers().isInRange(getCompOnlyPlayerCount())) + setCompOnlyPlayerCount(RANDOM_SIZE); + if(!mapTemplate->getWaterContentAllowed().count(getWaterContent())) + setWaterContent(EWaterContent::RANDOM); + } +} + +void CMapGenOptions::setMapTemplate(const std::string & name) +{ + if(!name.empty()) + setMapTemplate(VLC->tplh->getTemplate(name)); +} + +void CMapGenOptions::setRoadEnabled(const std::string & roadName, bool enable) +{ + if(enable) + disabledRoads.erase(roadName); + else + disabledRoads.insert(roadName); +} + +bool CMapGenOptions::isRoadEnabled(const std::string & roadName) const +{ + return !disabledRoads.count(roadName); +} + +void CMapGenOptions::setPlayerTeam(PlayerColor color, TeamID team) +{ + auto it = players.find(color); + if(it == players.end()) assert(0); + it->second.setTeam(team); } void CMapGenOptions::finalize(CRandomGenerator & rand) @@ -391,7 +436,6 @@ PlayerColor CMapGenOptions::getNextPlayerColor() const bool CMapGenOptions::checkOptions() const { - assert(countHumanPlayers() > 0); if(mapTemplate) { return true; @@ -452,7 +496,7 @@ const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand return *RandomGeneratorUtil::nextItem(templates, rand); } -CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI) +CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI), team(TeamID::NO_TEAM) { } @@ -494,4 +538,14 @@ void CMapGenOptions::CPlayerSettings::setPlayerType(EPlayerType::EPlayerType val playerType = value; } +TeamID CMapGenOptions::CPlayerSettings::getTeam() const +{ + return team; +} + +void CMapGenOptions::CPlayerSettings::setTeam(TeamID value) +{ + team = value; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h index 9017576b1..39d947d05 100644 --- a/lib/rmg/CMapGenOptions.h +++ b/lib/rmg/CMapGenOptions.h @@ -11,37 +11,12 @@ #pragma once #include "../GameConstants.h" +#include "CRmgTemplate.h" VCMI_LIB_NAMESPACE_BEGIN -class CRmgTemplate; class CRandomGenerator; -namespace EWaterContent -{ - enum EWaterContent - { - RANDOM = -1, - NONE, - NORMAL, - ISLANDS - }; -} - -namespace EMonsterStrength -{ - enum EMonsterStrength - { - RANDOM = -2, - ZONE_WEAK = -1, - ZONE_NORMAL = 0, - ZONE_STRONG = 1, - GLOBAL_WEAK = 2, - GLOBAL_NORMAL = 3, - GLOBAL_STRONG = 4 - }; -} - namespace EPlayerType { enum EPlayerType @@ -76,6 +51,10 @@ public: /// The default value is EPlayerType::AI. EPlayerType::EPlayerType getPlayerType() const; void setPlayerType(EPlayerType::EPlayerType value); + + /// Team id for this player. TeamID::NO_TEAM by default - team will be randomly assigned + TeamID getTeam() const; + void setTeam(TeamID value); /// Constant for a random town selection. static const si32 RANDOM_TOWN = -1; @@ -84,6 +63,7 @@ public: PlayerColor color; si32 startingTown; EPlayerType::EPlayerType playerType; + TeamID team; public: template @@ -92,6 +72,8 @@ public: h & color; h & startingTown; h & playerType; + if(version >= 806) + h & team; } }; @@ -130,6 +112,9 @@ public: EMonsterStrength::EMonsterStrength getMonsterStrength() const; void setMonsterStrength(EMonsterStrength::EMonsterStrength value); + + bool isRoadEnabled(const std::string & roadName) const; + void setRoadEnabled(const std::string & roadName, bool enable); /// The first player colors belong to standard players and the last player colors belong to comp only players. /// All standard players are by default of type EPlayerType::AI. @@ -138,11 +123,14 @@ public: /// Sets a player type for a standard player. A standard player is the opposite of a computer only player. The /// values which can be chosen for the player type are EPlayerType::AI or EPlayerType::HUMAN. void setPlayerTypeForStandardPlayer(PlayerColor color, EPlayerType::EPlayerType playerType); + + void setPlayerTeam(PlayerColor color, TeamID team = TeamID::NO_TEAM); /// The random map template to generate the map with or empty/not set if the template should be chosen randomly. /// Default: Not set/random. const CRmgTemplate * getMapTemplate() const; void setMapTemplate(const CRmgTemplate * value); + void setMapTemplate(const std::string & name); std::vector getPossibleTemplates() const; @@ -171,6 +159,8 @@ private: EWaterContent::EWaterContent waterContent; EMonsterStrength::EMonsterStrength monsterStrength; std::map players; + std::set disabledRoads; + const CRmgTemplate * mapTemplate; public: @@ -187,7 +177,21 @@ public: h & waterContent; h & monsterStrength; h & players; - //TODO add name of template to class, enables selection of a template by a user + std::string templateName; + if(mapTemplate && h.saving) + { + templateName = mapTemplate->getId(); + } + if(version >= 806) + { + h & templateName; + if(!h.saving) + { + setMapTemplate(templateName); + } + + h & disabledRoads; + } } }; diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index ffe2a691f..9a4b1f916 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -76,6 +76,11 @@ void CMapGenerator::loadConfig() config.pandoraMultiplierSpells = randomMapJson["pandoras"]["valueMultiplierSpells"].Integer(); config.pandoraSpellSchool = randomMapJson["pandoras"]["valueSpellSchool"].Integer(); config.pandoraSpell60 = randomMapJson["pandoras"]["valueSpell60"].Integer(); + //override config with game options + if(!mapGenOptions.isRoadEnabled(config.secondaryRoadType)) + config.secondaryRoadType = ""; + if(!mapGenOptions.isRoadEnabled(config.defaultRoadType)) + config.defaultRoadType = config.secondaryRoadType; } const CMapGenerator::Config & CMapGenerator::getConfig() const @@ -183,6 +188,7 @@ void CMapGenerator::addPlayerInfo() enum ETeams {CPHUMAN = 0, CPUONLY = 1, AFTER_LAST = 2}; std::array, 2> teamNumbers; + std::set teamsTotal; int teamOffset = 0; int playerCount = 0; @@ -238,19 +244,26 @@ void CMapGenerator::addPlayerInfo() player.canHumanPlay = true; } - if (teamNumbers[j].empty()) + if(pSettings.getTeam() != TeamID::NO_TEAM) { - logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human")); - assert (teamNumbers[j].size()); + player.team = pSettings.getTeam(); } - auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand); - player.team = TeamID(*itTeam); - teamNumbers[j].erase(itTeam); + else + { + if (teamNumbers[j].empty()) + { + logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human")); + assert (teamNumbers[j].size()); + } + auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand); + player.team = TeamID(*itTeam); + teamNumbers[j].erase(itTeam); + } + teamsTotal.insert(player.team.getNum()); map->map().players[pSettings.getColor().getNum()] = player; } - map->map().howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount()) - + (mapGenOptions.getCompOnlyTeamCount() == 0 ? mapGenOptions.getCompOnlyPlayerCount() : mapGenOptions.getCompOnlyTeamCount()); + map->map().howManyTeams = teamsTotal.size(); } void CMapGenerator::genZones() diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp index 7e1051a5f..4e54e413a 100644 --- a/lib/rmg/CRmgTemplate.cpp +++ b/lib/rmg/CRmgTemplate.cpp @@ -501,9 +501,19 @@ void CRmgTemplate::setId(const std::string & value) id = value; } +void CRmgTemplate::setName(const std::string & value) +{ + name = value; +} + const std::string & CRmgTemplate::getName() const { - return name.empty() ? id : name; + return name; +} + +const std::string & CRmgTemplate::getId() const +{ + return id; } const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getPlayers() const @@ -531,6 +541,11 @@ void CRmgTemplate::validate() const //TODO add some validation checks, throw on failure } +std::pair CRmgTemplate::getMapSizes() const +{ + return {minSize, maxSize}; +} + void CRmgTemplate::CPlayerCountRange::addRange(int lower, int upper) { range.push_back(std::make_pair(lower, upper)); diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h index 2b8ba45e3..50ce3227f 100644 --- a/lib/rmg/CRmgTemplate.h +++ b/lib/rmg/CRmgTemplate.h @@ -14,7 +14,6 @@ #include "../GameConstants.h" #include "../ResourceSet.h" #include "../Terrain.h" -#include "CMapGenOptions.h" VCMI_LIB_NAMESPACE_BEGIN @@ -32,6 +31,31 @@ namespace ETemplateZoneType }; } +namespace EWaterContent +{ + enum EWaterContent + { + RANDOM = -1, + NONE, + NORMAL, + ISLANDS + }; +} + +namespace EMonsterStrength +{ + enum EMonsterStrength + { + RANDOM = -2, + ZONE_WEAK = -1, + ZONE_NORMAL = 0, + ZONE_STRONG = 1, + GLOBAL_WEAK = 2, + GLOBAL_NORMAL = 3, + GLOBAL_STRONG = 4 + }; +} + class DLL_LINKAGE CTreasureInfo { public: @@ -194,10 +218,13 @@ public: const std::set & getWaterContentAllowed() const; void setId(const std::string & value); + void setName(const std::string & value); + const std::string & getId() const; const std::string & getName() const; const CPlayerCountRange & getPlayers() const; const CPlayerCountRange & getCpuPlayers() const; + std::pair getMapSizes() const; const Zones & getZones() const; const std::vector & getConnections() const; diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp index 699652830..813fb785e 100644 --- a/lib/rmg/CRmgTemplateStorage.cpp +++ b/lib/rmg/CRmgTemplateStorage.cpp @@ -32,8 +32,9 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const { JsonDeserializer handler(nullptr, data); auto fullKey = normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name); //actually it's not used - templates[fullKey].setId(name); + templates[fullKey].setId(fullKey); templates[fullKey].serializeJson(handler); + templates[fullKey].setName(name); templates[fullKey].validate(); } catch(const std::exception & e) diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 33c9639dc..03616a48f 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -13,6 +13,7 @@ #include "CZonePlacer.h" #include "../mapping/CMap.h" #include "../mapping/CMapEditManager.h" +#include "CMapGenOptions.h" #include "RmgMap.h" #include "Zone.h" #include "Functions.h" diff --git a/lib/rmg/Functions.h b/lib/rmg/Functions.h index 9e60506cf..326ad8891 100644 --- a/lib/rmg/Functions.h +++ b/lib/rmg/Functions.h @@ -19,6 +19,7 @@ class RmgMap; class ObjectManager; class ObjectTemplate; class CMapGenerator; +class CRandomGenerator; class rmgException : public std::exception { diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp index a435395af..b8a037135 100644 --- a/lib/rmg/RoadPlacer.cpp +++ b/lib/rmg/RoadPlacer.cpp @@ -22,6 +22,9 @@ VCMI_LIB_NAMESPACE_BEGIN void RoadPlacer::process() { + if(generator.getConfig().defaultRoadType.empty() && generator.getConfig().secondaryRoadType.empty()) + return; //do not generate roads at all + connectRoads(); } @@ -68,11 +71,15 @@ bool RoadPlacer::createRoad(const int3 & dst) void RoadPlacer::drawRoads(bool secondary) { + if((secondary && generator.getConfig().secondaryRoadType.empty()) + || (!secondary && generator.getConfig().defaultRoadType.empty())) + return; + zone.areaPossible().subtract(roads); zone.freePaths().unite(roads); map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector()); - std::string roadCode = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType); - RoadId roadType = VLC->terrainTypeHandler->getRoadByCode(roadCode)->id; + std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType); + RoadId roadType = VLC->terrainTypeHandler->getRoadByName(roadName)->id; map.getEditManager()->drawRoad(roadType, &generator.rand); } diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 172a8c681..19cabdd22 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -1118,14 +1118,11 @@ int main(int argc, char * argv[]) } #ifdef VCMI_ANDROID -void CVCMIServer::create(const std::vector & args) +void CVCMIServer::create() { const char * foo = "android-server"; std::vector argv = {foo}; - for(auto & a : args) - argv.push_back(a.c_str()); - - main(argv.size(), const_cast(foo)); + main(argv.size(), reinterpret_cast(const_cast(&*argv.begin()))); } #elif defined(SINGLE_PROCESS_APP) void CVCMIServer::create(boost::condition_variable * cond, const std::vector & args) diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 315d803de..fa84e044c 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -114,7 +114,7 @@ public: ui8 getIdOfFirstUnallocatedPlayer() const; #ifdef VCMI_ANDROID - static void create(const std::vector & args); + static void create(); #elif defined(SINGLE_PROCESS_APP) static void create(boost::condition_variable * cond, const std::vector & args); #endif