1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00
vcmi/client/lobby/RandomMapTab.cpp
Ivan Savenko fe2f765322
Merge pull request #4981 from mikeiit/random_map_player_count
Random map gen com only player count fix
2024-11-27 16:41:18 +02:00

617 lines
18 KiB
C++

/*
* RandomMapTab.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 "RandomMapTab.h"
#include "CSelectionBase.h"
#include "CLobbyScreen.h"
#include "SelectionTab.h"
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/MouseButton.h"
#include "../gui/WindowHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/ComboBox.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/Slider.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../lib/texts/CGeneralTextHandler.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/mapping/CMapHeader.h"
#include "../../lib/mapping/MapFormat.h"
#include "../../lib/rmg/CMapGenOptions.h"
#include "../../lib/rmg/CRmgTemplateStorage.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/RoadHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/serializer/JsonSerializer.h"
#include "../../lib/serializer/JsonDeserializer.h"
RandomMapTab::RandomMapTab():
InterfaceObjectConfigurable()
{
recActions = 0;
mapGenOptions = std::make_shared<CMapGenOptions>();
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();
});
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();
});
addCallback("setPlayersCount", [&](int btnId)
{
mapGenOptions->setHumanOrCpuPlayerCount(btnId);
setMapGenOptions(mapGenOptions);
updateMapInfoByHost();
});
addCallback("setTeamsCount", [&](int btnId)
{
mapGenOptions->setTeamCount(btnId);
updateMapInfoByHost();
});
addCallback("setCompOnlyPlayers", [&](int btnId)
{
mapGenOptions->setCompOnlyPlayerCount(btnId);
setMapGenOptions(mapGenOptions);
updateMapInfoByHost();
});
addCallback("setCompOnlyTeams", [&](int btnId)
{
mapGenOptions->setCompOnlyTeamCount(btnId);
updateMapInfoByHost();
});
addCallback("setWaterContent", [&](int btnId)
{
mapGenOptions->setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
updateMapInfoByHost();
});
addCallback("setMonsterStrength", [&](int btnId)
{
if(btnId < 0)
mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
else
mapGenOptions->setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId)); //value 2 to 4
updateMapInfoByHost();
});
//new callbacks available only from mod
addCallback("teamAlignments", [&](int)
{
GH.windows().createAndPushWindow<TeamAlignments>(*this);
});
for(const auto & road : VLC->roadTypeHandler->objects)
{
std::string cbRoadType = "selectRoad_" + road->getJsonKey();
addCallback(cbRoadType, [&, roadID = road->getId()](bool on)
{
mapGenOptions->setRoadEnabled(roadID, on);
updateMapInfoByHost();
});
}
const JsonNode config(JsonPath::builtin("config/widgets/randomMapTab.json"));
build(config);
if(auto w = widget<CButton>("buttonShowRandomMaps"))
{
w->addCallback([&]()
{
(static_cast<CLobbyScreen *>(parent))->toggleTab((static_cast<CLobbyScreen *>(parent))->tabSel);
(static_cast<CLobbyScreen *>(parent))->tabSel->showRandom = true;
(static_cast<CLobbyScreen *>(parent))->tabSel->filter(0, true);
});
}
//set combo box callbacks
if(auto w = widget<ComboBox>("templateList"))
{
w->onConstructItems = [](std::vector<const void *> & curItems){
auto templates = VLC->tplh->getTemplates();
boost::range::sort(templates, [](const CRmgTemplate * a, const CRmgTemplate * b){
return a->getName() < b->getName();
});
curItems.push_back(nullptr); //default template
for(auto & t : templates)
curItems.push_back(t);
};
w->onSetItem = [&](const void * item){
this->setTemplate(reinterpret_cast<const CRmgTemplate *>(item));
};
w->getItemText = [this](int idx, const void * item){
if(item)
return reinterpret_cast<const CRmgTemplate *>(item)->getName();
if(idx == 0)
return readText(variables["randomTemplate"]);
return std::string("");
};
}
loadOptions();
}
void RandomMapTab::updateMapInfoByHost()
{
if(CSH->isGuest())
return;
// Generate header info
mapInfo = std::make_shared<CMapInfo>();
mapInfo->isRandomMap = true;
mapInfo->mapHeader = std::make_unique<CMapHeader>();
mapInfo->mapHeader->version = EMapFormat::VCMI;
mapInfo->mapHeader->name.appendLocalString(EMetaText::GENERAL_TXT, 740);
mapInfo->mapHeader->description.appendLocalString(EMetaText::GENERAL_TXT, 741);
const auto * temp = mapGenOptions->getMapTemplate();
if (temp)
{
auto randomTemplateDescription = temp->getDescription();
if (!randomTemplateDescription.empty())
{
auto description = std::string("\n\n") + randomTemplateDescription;
mapInfo->mapHeader->description.appendRawString(description);
}
}
mapInfo->mapHeader->difficulty = EMapDifficulty::NORMAL;
mapInfo->mapHeader->height = mapGenOptions->getHeight();
mapInfo->mapHeader->width = mapGenOptions->getWidth();
mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels();
// Generate player information
int playersToGen = mapGenOptions->getMaxPlayersCount();
mapInfo->mapHeader->howManyTeams = playersToGen;
//TODO: Assign all human-controlled colors in first place
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
mapInfo->mapHeader->players[i].canComputerPlay = false;
mapInfo->mapHeader->players[i].canHumanPlay = false;
}
std::vector<PlayerColor> availableColors;
for (ui8 color = 0; color < PlayerColor::PLAYER_LIMIT_I; color++)
{
availableColors.push_back(PlayerColor(color));
}
//First restore known players
for (auto& player : mapGenOptions->getPlayersSettings())
{
PlayerInfo playerInfo;
playerInfo.isFactionRandom = (player.second.getStartingTown() == FactionID::RANDOM);
playerInfo.canComputerPlay = (player.second.getPlayerType() != EPlayerType::HUMAN);
playerInfo.canHumanPlay = (player.second.getPlayerType() != EPlayerType::COMP_ONLY);
auto team = player.second.getTeam();
playerInfo.team = team;
playerInfo.hasMainTown = true;
playerInfo.generateHeroAtMainTown = true;
mapInfo->mapHeader->players[player.first] = playerInfo;
vstd::erase(availableColors, player.first);
}
mapInfoChanged(mapInfo, mapGenOptions);
}
void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
{
mapGenOptions = opts;
//Prepare allowed options - add all, then erase the ones above the limit
for(int i = 0; i <= PlayerColor::PLAYER_LIMIT_I; ++i)
{
playerCountAllowed.insert(i);
compCountAllowed.insert(i);
if (i >= 2)
{
playerTeamsAllowed.insert(i);
}
if (i >= 1)
{
compTeamsAllowed.insert(i);
}
}
std::set<int> humanCountAllowed;
auto * tmpl = mapGenOptions->getMapTemplate();
if(tmpl)
{
playerCountAllowed = tmpl->getPlayers().getNumbers();
humanCountAllowed = tmpl->getHumanPlayers().getNumbers(); // Unused now?
}
si8 playerLimit = opts->getPlayerLimit();
si8 humanOrCpuPlayerCount = opts->getHumanOrCpuPlayerCount();
si8 compOnlyPlayersCount = opts->getCompOnlyPlayerCount();
if(humanOrCpuPlayerCount != CMapGenOptions::RANDOM_SIZE)
{
vstd::erase_if(compCountAllowed, [playerLimit, humanOrCpuPlayerCount](int el)
{
return (playerLimit - humanOrCpuPlayerCount) < el;
});
vstd::erase_if(playerTeamsAllowed, [humanOrCpuPlayerCount](int el)
{
return humanOrCpuPlayerCount <= el;
});
}
else // Random
{
vstd::erase_if(compCountAllowed, [playerLimit](int el)
{
return (playerLimit - 1) < el; // Must leave at least 1 human player
});
vstd::erase_if(playerTeamsAllowed, [playerLimit](int el)
{
return playerLimit <= el;
});
}
if(!playerTeamsAllowed.count(opts->getTeamCount()))
{
opts->setTeamCount(CMapGenOptions::RANDOM_SIZE);
}
if(compOnlyPlayersCount != CMapGenOptions::RANDOM_SIZE)
{
// This setting doesn't impact total number of players
vstd::erase_if(compTeamsAllowed, [compOnlyPlayersCount](int el)
{
return compOnlyPlayersCount<= el;
});
if(!compTeamsAllowed.count(opts->getCompOnlyTeamCount()))
{
opts->setCompOnlyTeamCount(CMapGenOptions::RANDOM_SIZE);
}
}
if(auto w = widget<CToggleGroup>("groupMapSize"))
{
for(auto toggle : w->buttons)
{
if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
{
const auto & mapSizes = getPossibleMapSizes();
int3 size( mapSizes[toggle.first], mapSizes[toggle.first], 1 + mapGenOptions->getHasTwoLevels());
bool sizeAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size);
button->block(!sizeAllowed);
}
}
w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
}
if(auto w = widget<CToggleButton>("buttonTwoLevels"))
{
int3 size( opts->getWidth(), opts->getWidth(), 2);
bool undergoundAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size);
w->setSelected(opts->getHasTwoLevels());
w->block(!undergoundAllowed);
}
if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
{
w->setSelected(opts->getHumanOrCpuPlayerCount());
deactivateButtonsFrom(*w, playerCountAllowed);
}
if(auto w = widget<CToggleGroup>("groupMaxTeams"))
{
w->setSelected(opts->getTeamCount());
deactivateButtonsFrom(*w, playerTeamsAllowed);
}
if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
{
w->setSelected(opts->getCompOnlyPlayerCount());
deactivateButtonsFrom(*w, compCountAllowed);
}
if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
{
w->setSelected(opts->getCompOnlyTeamCount());
deactivateButtonsFrom(*w, compTeamsAllowed);
}
if(auto w = widget<CToggleGroup>("groupWaterContent"))
{
w->setSelected(opts->getWaterContent());
if(opts->getMapTemplate())
{
std::set<int> allowedWater(opts->getMapTemplate()->getWaterContentAllowed().begin(), opts->getMapTemplate()->getWaterContentAllowed().end());
deactivateButtonsFrom(*w, allowedWater);
}
else
deactivateButtonsFrom(*w, {-1});
}
if(auto w = widget<CToggleGroup>("groupMonsterStrength"))
w->setSelected(opts->getMonsterStrength());
if(auto w = widget<CButton>("templateButton"))
{
if(tmpl)
w->setTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE);
else
w->setTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE);
}
for(const auto & r : VLC->roadTypeHandler->objects)
{
// Workaround for vcmi-extras bug
std::string jsonKey = r->getJsonKey();
std::string identifier = jsonKey.substr(jsonKey.find(':')+1);
if(auto w = widget<CToggleButton>(identifier))
{
w->setSelected(opts->isRoadEnabled(r->getId()));
}
}
}
void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
{
mapGenOptions->setMapTemplate(tmpl);
setMapGenOptions(mapGenOptions);
if(auto w = widget<CButton>("templateButton"))
{
if(tmpl)
w->setTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE);
else
w->setTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE);
}
updateMapInfoByHost();
}
void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed)
{
logGlobal->debug("Blocking buttons");
for(auto toggle : group.buttons)
{
if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
{
if(allowed.count(CMapGenOptions::RANDOM_SIZE)
|| allowed.count(toggle.first)
|| toggle.first == CMapGenOptions::RANDOM_SIZE)
{
button->block(false);
}
else
{
button->block(true);
}
}
}
}
std::vector<int> RandomMapTab::getPossibleMapSizes()
{
return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
}
void TeamAlignmentsWidget::checkTeamCount()
{
//Do not allow to select one team only
std::set<TeamID> teams;
for (int plId = 0; plId < players.size(); ++plId)
{
teams.insert(TeamID(players[plId]->getSelected()));
}
if (teams.size() < 2)
{
//Do not let player close the window
buttonOk->block(true);
}
else
{
buttonOk->block(false);
}
}
TeamAlignments::TeamAlignments(RandomMapTab & randomMapTab)
: CWindowObject(BORDERED)
{
OBJECT_CONSTRUCTION;
widget = std::make_shared<TeamAlignmentsWidget>(randomMapTab);
pos = widget->pos;
updateShadow();
center();
}
TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
InterfaceObjectConfigurable()
{
const JsonNode config(JsonPath::builtin("config/widgets/randomMapTeamsWidget.json"));
variables = config["variables"];
//int totalPlayers = randomMapTab.obtainMapGenOptions().getPlayerLimit();
int totalPlayers = randomMapTab.obtainMapGenOptions().getMaxPlayersCount();
assert(totalPlayers <= PlayerColor::PLAYER_LIMIT_I);
auto playerSettings = randomMapTab.obtainMapGenOptions().getPlayersSettings();
variables["totalPlayers"].Integer() = totalPlayers;
pos.w = variables["windowSize"]["x"].Integer() + totalPlayers * variables["cellMargin"]["x"].Integer();
auto widthExtend = std::max(pos.w, 220) - pos.w; // too small for buttons
pos.w += widthExtend;
pos.h = variables["windowSize"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
variables["backgroundRect"]["x"].Integer() = 0;
variables["backgroundRect"]["y"].Integer() = 0;
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();
for(auto & window : GH.windows().findWindows<TeamAlignments>())
GH.windows().popWindow(window);
});
addCallback("cancel", [&](int)
{
for(auto & window : GH.windows().findWindows<TeamAlignments>())
GH.windows().popWindow(window);
});
build(config);
center(pos);
OBJECT_CONSTRUCTION;
// Window should have X * X columns, where X is max players allowed for current settings
// For random player count, X is 8
if (totalPlayers > playerSettings.size())
{
auto savedPlayers = randomMapTab.obtainMapGenOptions().getSavedPlayersMap();
for (const auto & player : savedPlayers)
{
if (!vstd::contains(playerSettings, player.first))
{
playerSettings[player.first] = player.second;
}
}
}
std::vector<CMapGenOptions::CPlayerSettings> settingsVec;
for (const auto & player : playerSettings)
{
settingsVec.push_back(player.second);
}
for(int plId = 0; plId < totalPlayers; ++plId)
{
players.push_back(std::make_shared<CToggleGroup>([&, totalPlayers, plId](int sel)
{
variables["player_id"].Integer() = plId;
OBJECT_CONSTRUCTION_TARGETED(players[plId].get());
for(int teamId = 0; teamId < totalPlayers; ++teamId)
{
auto button = std::dynamic_pointer_cast<CToggleButton>(players[plId]->buttons[teamId]);
assert(button);
if(sel == teamId)
{
button->setOverlay(buildWidget(variables["flagsAnimation"]));
}
else
{
button->setOverlay(nullptr);
}
button->addCallback([this](bool)
{
checkTeamCount();
});
}
}));
OBJECT_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() + (widthExtend / 2);
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<CToggleBase>(button));
}
// plId is not necessarily player color, just an index
auto team = settingsVec.at(plId).getTeam();
if(team == TeamID::NO_TEAM)
{
logGlobal->warn("Player %d (id %d) has uninitialized team", settingsVec.at(plId).getColor(), plId);
players.back()->setSelected(plId);
}
else
players.back()->setSelected(team.getNum());
}
buttonOk = widget<CButton>("buttonOK");
buttonCancel = widget<CButton>("buttonCancel");
}
void RandomMapTab::saveOptions(const CMapGenOptions & options)
{
JsonNode data;
JsonSerializer ser(nullptr, data);
ser.serializeStruct("lastSettings", const_cast<CMapGenOptions & >(options));
// FIXME: Do not nest fields
Settings rmgSettings = persistentStorage.write["rmg"];
rmgSettings["rmg"] = data;
}
void RandomMapTab::loadOptions()
{
auto rmgSettings = persistentStorage["rmg"]["rmg"];
if (!rmgSettings.Struct().empty())
{
mapGenOptions.reset(new CMapGenOptions());
JsonDeserializer handler(nullptr, rmgSettings);
handler.serializeStruct("lastSettings", *mapGenOptions);
// Will check template and set other options as well
setTemplate(mapGenOptions->getMapTemplate());
if(auto w = widget<ComboBox>("templateList"))
{
w->setItem(mapGenOptions->getMapTemplate());
}
} else
{
// Default settings
mapGenOptions->setRoadEnabled(RoadId(Road::DIRT_ROAD), true);
mapGenOptions->setRoadEnabled(RoadId(Road::GRAVEL_ROAD), true);
mapGenOptions->setRoadEnabled(RoadId(Road::COBBLESTONE_ROAD), true);
}
updateMapInfoByHost();
// TODO: Save & load difficulty?
}