1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-10-08 23:22:25 +02:00

Merge pull request #6150 from Laserlicht/campaignsets

Campaignsets in custom campaign menu
This commit is contained in:
Ivan Savenko
2025-09-28 19:23:07 +03:00
committed by GitHub
10 changed files with 158 additions and 18 deletions

View File

@@ -425,6 +425,7 @@
"vcmi.keyBindings.keyBinding.lobbySelectScenario": "Lobby select scenario",
"vcmi.keyBindings.keyBinding.lobbyToggleChat": "Lobby toggle chat",
"vcmi.keyBindings.keyBinding.lobbyTurnOptions": "Lobby turn options",
"vcmi.keyBindings.keyBinding.lobbyCampaignSets": "Lobby campaign sets",
"vcmi.keyBindings.keyBinding.mainMenuBack": "Main menu back",
"vcmi.keyBindings.keyBinding.mainMenuCampaign": "Main menu campaign",
"vcmi.keyBindings.keyBinding.mainMenuCampaignAb": "Main menu campaign ab",
@@ -835,6 +836,9 @@
"vcmi.optionsTab.simturns.blocked1" : "Simturns: 1 week, contacts blocked",
"vcmi.optionsTab.simturns.blocked2" : "Simturns: 2 weeks, contacts blocked",
"vcmi.optionsTab.simturns.blocked4" : "Simturns: 1 month, contacts blocked",
"vcmi.campaignSet.chronicles" : "Heroes Chronicles",
"vcmi.campaignSet.hota" : "Horn of the Abyss",
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
// Using this information, VCMI will automatically select correct plural form for every possible amount
@@ -848,6 +852,9 @@
"vcmi.optionsTab.simturns.months.1" : " %d month",
"vcmi.optionsTab.simturns.months.2" : " %d months",
"vcmi.selectionTab.campaignSets.hover" : "Campaign sets",
"vcmi.selectionTab.campaignSets.help" : "Multiple campaigns bundeled as set",
"vcmi.optionsTab.extraOptions.hover" : "Extra Options",
"vcmi.optionsTab.extraOptions.help" : "Additional settings for the game",

View File

@@ -434,6 +434,7 @@
"vcmi.keyBindings.keyBinding.lobbySelectScenario": "Lobby Szenario wählen",
"vcmi.keyBindings.keyBinding.lobbyToggleChat": "Lobby Chat umschalten",
"vcmi.keyBindings.keyBinding.lobbyTurnOptions": "Lobby Zugoptionen",
"vcmi.keyBindings.keyBinding.lobbyCampaignSets": "Lobby Kampagnensätze",
"vcmi.keyBindings.keyBinding.mainMenuBack": "Hauptmenü zurück",
"vcmi.keyBindings.keyBinding.mainMenuCampaign": "Hauptmenü Kampagne",
"vcmi.keyBindings.keyBinding.mainMenuCampaignAb": "Hauptmenü Kampagne Ab",
@@ -834,6 +835,9 @@
"vcmi.optionsTab.simturns.blocked1" : "Simzüge: 1 Woche, Kontakte block.",
"vcmi.optionsTab.simturns.blocked2" : "Simzüge: 2 Wochen, Kontakte block.",
"vcmi.optionsTab.simturns.blocked4" : "Simzüge: 1 Monat, Kontakte block.",
"vcmi.campaignSet.chronicles" : "Heroes Chronicles",
"vcmi.campaignSet.hota" : "Horn of the Abyss",
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
// Using this information, VCMI will automatically select correct plural form for every possible amount
@@ -847,6 +851,9 @@
"vcmi.optionsTab.simturns.months.1" : "%d Monat",
"vcmi.optionsTab.simturns.months.2" : "%d Monate",
"vcmi.selectionTab.campaignSets.hover" : "Kampagnensätze",
"vcmi.selectionTab.campaignSets.help" : "Mehrere Kampagnen gebündelt als Satz",
"vcmi.optionsTab.extraOptions.hover" : "Extra Optionen",
"vcmi.optionsTab.extraOptions.help" : "Zusätzliche Einstellungen für das Spiel",

View File

@@ -97,6 +97,7 @@ enum class EShortcut
LOBBY_RANDOM_TOWN,
LOBBY_RANDOM_TOWN_VS,
LOBBY_HANDICAP,
LOBBY_CAMPAIGN_SETS,
MAPS_SIZE_S,
MAPS_SIZE_M,

View File

@@ -304,6 +304,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"lobbyRandomTown", EShortcut::LOBBY_RANDOM_TOWN },
{"lobbyRandomTownVs", EShortcut::LOBBY_RANDOM_TOWN_VS },
{"lobbyHandicap", EShortcut::LOBBY_HANDICAP },
{"lobbyCampaignSets", EShortcut::LOBBY_CAMPAIGN_SETS },
{"mapsSizeS", EShortcut::MAPS_SIZE_S },
{"mapsSizeM", EShortcut::MAPS_SIZE_M },
{"mapsSizeL", EShortcut::MAPS_SIZE_L },

View File

@@ -32,6 +32,7 @@
#include "../render/CAnimation.h"
#include "../render/IImage.h"
#include "../render/IRenderHandler.h"
#include "../mainmenu/CCampaignScreen.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/IGameSettings.h"
@@ -154,15 +155,24 @@ static ESortBy getSortBySelectionScreen(ESelectionScreen Type)
}
SelectionTab::SelectionTab(ESelectionScreen Type)
: CIntObject(LCLICK | SHOW_POPUP | KEYBOARD | DOUBLECLICK), callOnSelect(nullptr), tabType(Type), selectionPos(0), sortModeAscending(true), inputNameRect{32, 539, 350, 20}, curFolder(""), currentMapSizeFilter(0), showRandom(false), deleteMode(false)
: CIntObject(LCLICK | SHOW_POPUP | KEYBOARD | DOUBLECLICK)
, callOnSelect(nullptr)
, tabType(Type)
, selectionPos(0)
, sortModeAscending(true)
, inputNameRect{32, 539, 350, 20}
, curFolder("")
, currentMapSizeFilter(0)
, showRandom(false)
, deleteMode(false)
, enableUiEnhancements(settings["general"]["enableUiEnhancements"].Bool())
, campaignSets(JsonUtils::assembleFromFiles("config/campaignSets.json"))
{
OBJECT_CONSTRUCTION;
generalSortingBy = getSortBySelectionScreen(tabType);
sortingBy = _format;
bool enableUiEnhancements = settings["general"]["enableUiEnhancements"].Bool();
if(tabType != ESelectionScreen::campaignList)
{
background = std::make_shared<CPicture>(ImagePath::builtin("SCSELBCK.bmp"), 0, 6);
@@ -244,6 +254,44 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
if(tabType == ESelectionScreen::newGame)
buttonDeleteMode->setEnabled(false);
}
if(tabType == ESelectionScreen::campaignList)
{
buttonCampaignSet = std::make_shared<CButton>(Point(262, 53), AnimationPath::builtin("GSPBUT2.DEF"), CButton::tooltip("", LIBRARY->generaltexth->translate("vcmi.selectionTab.campaignSets.help")), [this]{
std::vector<std::pair<si64, std::pair<std::string, std::string>>> namesWithIndex;
for (auto const & set : campaignSets.Struct())
{
bool oneCampaignExists = false;
for (auto const & item : set.second["items"].Vector())
if(CResourceHandler::get()->existsResource(ResourcePath(item["file"].String(), EResType::CAMPAIGN)))
oneCampaignExists = true;
if(oneCampaignExists)
namesWithIndex.push_back({set.second["index"].isNull() ? std::numeric_limits<si64>::max() : set.second["index"].Integer(), { set.first, set.second["text"].isNull() ? set.first : LIBRARY->generaltexth->translate(set.second["text"].String()) }});
}
std::sort(namesWithIndex.begin(), namesWithIndex.end(), [](const std::pair<si64, std::pair<std::string, std::string>>& a, const std::pair<si64, std::pair<std::string, std::string>>& b)
{
if (a.first != b.first) return a.first < b.first;
return a.second.second < b.second.second;
});
std::vector<std::string> namesIdentifier;
for (const auto& pair : namesWithIndex)
namesIdentifier.push_back(pair.second.first);
std::vector<std::string> namesTranslated;
for (const auto& pair : namesWithIndex)
namesTranslated.push_back(pair.second.second);
ENGINE->windows().createAndPushWindow<CampaignSetSelector>(namesTranslated, [this, namesIdentifier](int index)
{
GAME->server().sendClientDisconnecting();
(static_cast<CLobbyScreen *>(parent))->close();
ENGINE->windows().createAndPushWindow<CCampaignScreen>(campaignSets, namesIdentifier[index]);
});
}, EShortcut::LOBBY_CAMPAIGN_SETS);
buttonCampaignSet->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.selectionTab.campaignSets.hover"), FONT_SMALL, Colors::WHITE);
}
}
for(int i = 0; i < positionsToShow; i++)
@@ -961,9 +1009,6 @@ void SelectionTab::handleUnsupportedSavegames(const std::vector<ResourcePath> &
void SelectionTab::parseCampaigns(const std::unordered_set<ResourcePath> & files)
{
auto campaignSets = JsonUtils::assembleFromFiles("config/campaignSets.json");
auto mainmenu = JsonNode(JsonPath::builtin("config/mainmenu.json"));
allItems.reserve(files.size());
for(auto & file : files)
{
@@ -977,22 +1022,13 @@ void SelectionTab::parseCampaigns(const std::unordered_set<ResourcePath> & files
if(info->campaign)
{
// skip campaigns organized in sets
std::string foundInSet = "";
bool foundInSet = false;
for (auto const & set : campaignSets.Struct())
for (auto const & item : set.second["items"].Vector())
if(file.getName() == ResourcePath(item["file"].String()).getName())
foundInSet = set.first;
foundInSet = true;
// set has to be used in main menu
bool setInMainmenu = false;
if(!foundInSet.empty())
for (auto const & item : mainmenu["window"]["items"].Vector())
if(item["name"].String() == "campaign")
for (auto const & button : item["buttons"].Vector())
if(boost::algorithm::ends_with(boost::algorithm::to_lower_copy(button["command"].String()), boost::algorithm::to_lower_copy(foundInSet)))
setInMainmenu = true;
if(!setInMainmenu)
if(!foundInSet || !enableUiEnhancements)
allItems.push_back(info);
}
}
@@ -1128,3 +1164,33 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool
labelName->setText(info->name);
labelName->setColor(color);
}
CampaignSetSelector::CampaignSetSelector(const std::vector<std::string> & texts, const std::function<void(int selectedIndex)> & cb)
: CWindowObject(BORDERED), texts(texts), cb(cb)
{
OBJECT_CONSTRUCTION;
pos = center(Rect(0, 0, 200 + 16, std::min(static_cast<int>(texts.size()), LINES) * 40));
filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
filledBackground->setPlayerColor(PlayerColor(1));
slider = std::make_shared<CSlider>(Point(pos.w - 16, 0), pos.h, [this](int to){ update(to); redraw(); }, LINES, texts.size(), 0, Orientation::VERTICAL, CSlider::BLUE);
slider->setPanningStep(40);
slider->setScrollBounds(Rect(-pos.w + slider->pos.w, 0, pos.w, pos.h));
update(0);
}
void CampaignSetSelector::update(int to)
{
OBJECT_CONSTRUCTION;
buttons.clear();
for(int i = to; i < LINES + to; i++)
{
if(i>=texts.size())
continue;
auto button = std::make_shared<CToggleButton>(Point(0, 10 + (i - to) * 40), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this, i](bool on){ close(); cb(i); });
button->setTextOverlay(texts[i], EFonts::FONT_SMALL, Colors::WHITE);
buttons.emplace_back(button);
}
}

View File

@@ -21,6 +21,7 @@ class CLabel;
class CPicture;
class IImage;
class CAnimation;
class CToggleButton;
enum ESortBy
{
@@ -74,6 +75,8 @@ class SelectionTab : public CIntObject
std::shared_ptr<CAnimation> iconsLossCondition;
std::vector<std::shared_ptr<ListItem>> unSupportedSaves;
JsonNode campaignSets;
public:
std::vector<std::shared_ptr<ElementInfo>> allItems;
std::vector<std::shared_ptr<ElementInfo>> curItems;
@@ -125,6 +128,9 @@ private:
std::shared_ptr<CButton> buttonDeleteMode;
bool deleteMode;
bool enableUiEnhancements;
std::shared_ptr<CButton> buttonCampaignSet;
auto checkSubfolder(std::string path);
bool isMapSupported(const CMapInfo & info);
@@ -135,3 +141,19 @@ private:
void handleUnsupportedSavegames(const std::vector<ResourcePath> & files);
};
class CampaignSetSelector : public CWindowObject
{
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
std::vector<std::shared_ptr<CToggleButton>> buttons;
std::shared_ptr<CSlider> slider;
const int LINES = 10;
std::vector<std::string> texts;
std::function<void(int selectedIndex)> cb;
void update(int to);
public:
CampaignSetSelector(const std::vector<std::string> & texts, const std::function<void(int selectedIndex)> & cb);
};

View File

@@ -69,6 +69,8 @@ void AssetGenerator::initialize()
imageFiles[ImagePath::builtin("CampaignHc" + std::to_string(i) + "Image.png")] = [this, i](){ return createChroniclesCampaignImages(i);};
animationFiles[AnimationPath::builtin("SPRITES/adventureLayersButton")] = createAdventureMapButton(ImagePath::builtin("adventureLayers.png"), true);
animationFiles[AnimationPath::builtin("SPRITES/GSPButtonClear")] = createGSPButtonClear();
createPaletteShiftedSprites();
}
@@ -764,3 +766,27 @@ AssetGenerator::CanvasPtr AssetGenerator::createQuestWindow() const
return image;
}
AssetGenerator::AnimationLayoutMap AssetGenerator::createGSPButtonClear()
{
auto baseImg = ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("GSPBUTT"), EImageBlitMode::OPAQUE);
auto overlayImg = ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("GSPBUT2"), EImageBlitMode::OPAQUE);
AnimationLayoutMap layout;
for(int i = 0; i < 4; i++)
{
ImagePath spriteName = ImagePath::builtin("GSPButtonClear" + std::to_string(i) + ".png");
imageFiles[spriteName] = [baseImg, overlayImg, i](){
auto newImg = ENGINE->renderHandler().createImage(baseImg->getImage(i)->dimensions(), CanvasScalingPolicy::IGNORE);
auto canvas = newImg->getCanvas();
canvas.draw(baseImg->getImage(i), Point(0, 0));
canvas.draw(overlayImg->getImage(i), Point(0, 0), Rect(0, 0, 20, 20));
return newImg;
};
layout[0].push_back(ImageLocator(spriteName, EImageBlitMode::SIMPLE));
}
return layout;
}

View File

@@ -62,6 +62,7 @@ private:
enum CreatureInfoPanelElement{ BONUS_EFFECTS, SPELL_EFFECTS, BUTTON_PANEL, COMMANDER_BACKGROUND, COMMANDER_ABILITIES };
CanvasPtr createCreatureInfoPanelElement(CreatureInfoPanelElement element) const;
CanvasPtr createQuestWindow() const;
AnimationLayoutMap createGSPButtonClear();
void createPaletteShiftedSprites();
void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector<PaletteAnimation> & animation);

View File

@@ -1,6 +1,8 @@
{
"roe" :
{
"index" : 0,
"text" : "core.genrltxt.762",
"images" : [ {"x": 0, "y": 0, "name":"CAMPBACK"} ],
"exitbutton" : {"x": 658, "y": 482, "name":"CMPSCAN" },
"items":
@@ -16,6 +18,8 @@
},
"ab" :
{
"index" : 1,
"text" : "core.genrltxt.745",
"images" :
[
{"x": 0, "y": 0, "name":"CampaignBackground6"},
@@ -34,6 +38,8 @@
},
"sod":
{
"index" : 2,
"text" : "core.genrltxt.746",
"images" : [ {"x": 0, "y": 0, "name":"CAMPBKX2"} ],
"exitbutton" : {"x": 658, "y": 482, "name":"CMPSCAN" },
"items":
@@ -49,6 +55,8 @@
},
"chr":
{
"index" : 3,
"text" : "vcmi.campaignSet.chronicles",
"images" : [ {"x": 0, "y": 0, "name":"CampaignBackground8"} ],
"exitbutton" : {"x": 658, "y": 482, "name":"CMPSCAN" },
"items":

View File

@@ -154,6 +154,7 @@
"lobbySelectScenario": "S",
"lobbyToggleChat": "C",
"lobbyTurnOptions": "T",
"lobbyCampaignSets": "G",
"mainMenuBack": [ "B", "Escape" ],
"mainMenuCampaign": "C",
"mainMenuCampaignAb": "A",