mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
Merge pull request #6301 from Laserlicht/battleonly2
tab support / add artifacts, skills, warmachines & spellbook for battle mode
This commit is contained in:
@@ -52,7 +52,7 @@ set(vcmiclientcommon_SRCS
|
||||
gui/ShortcutHandler.cpp
|
||||
gui/WindowHandler.cpp
|
||||
|
||||
lobby/BattleOnlyMode.cpp
|
||||
lobby/BattleOnlyModeTab.cpp
|
||||
lobby/CBonusSelection.cpp
|
||||
lobby/CCampaignInfoScreen.cpp
|
||||
lobby/CLobbyScreen.cpp
|
||||
@@ -263,7 +263,7 @@ set(vcmiclientcommon_HEADERS
|
||||
gui/TextAlignment.h
|
||||
gui/WindowHandler.h
|
||||
|
||||
lobby/BattleOnlyMode.h
|
||||
lobby/BattleOnlyModeTab.h
|
||||
lobby/CBonusSelection.h
|
||||
lobby/CCampaignInfoScreen.h
|
||||
lobby/CLobbyScreen.h
|
||||
|
||||
@@ -107,6 +107,7 @@ CServerHandler::CServerHandler()
|
||||
, screenType(ESelectionScreen::unknown)
|
||||
, serverMode(EServerMode::NONE)
|
||||
, loadMode(ELoadMode::NONE)
|
||||
, battleMode(false)
|
||||
, client(nullptr)
|
||||
{
|
||||
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
|
||||
|
||||
@@ -140,6 +140,7 @@ public:
|
||||
ESelectionScreen screenType; // To create lobby UI only after server is setup
|
||||
EServerMode serverMode;
|
||||
ELoadMode loadMode; // For saves filtering in SelectionTab
|
||||
bool battleMode;
|
||||
////////////////////
|
||||
|
||||
std::unique_ptr<CStopWatch> th;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "lobby/ExtraOptionsTab.h"
|
||||
#include "lobby/SelectionTab.h"
|
||||
#include "lobby/CBonusSelection.h"
|
||||
#include "lobby/BattleOnlyMode.h"
|
||||
#include "lobby/BattleOnlyModeTab.h"
|
||||
#include "globalLobby/GlobalLobbyWindow.h"
|
||||
#include "globalLobby/GlobalLobbyServerSetup.h"
|
||||
#include "globalLobby/GlobalLobbyClient.h"
|
||||
@@ -114,9 +114,6 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
|
||||
if(!lobby || !handler.isGuest())
|
||||
return;
|
||||
|
||||
if(auto topWindow = ENGINE->windows().topWindow<BattleOnlyModeWindow>())
|
||||
topWindow->close();
|
||||
|
||||
switch(pack.action)
|
||||
{
|
||||
case LobbyGuiAction::NO_TAB:
|
||||
@@ -138,7 +135,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
|
||||
lobby->toggleTab(lobby->tabExtraOptions);
|
||||
break;
|
||||
case LobbyGuiAction::BATTLE_MODE:
|
||||
BattleOnlyMode::openBattleWindow();
|
||||
lobby->toggleTab(lobby->tabBattleOnlyMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -242,6 +239,6 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyShowMessage(LobbyShowMessage &
|
||||
|
||||
void ApplyOnLobbyScreenNetPackVisitor::visitLobbySetBattleOnlyModeStartInfo(LobbySetBattleOnlyModeStartInfo & pack)
|
||||
{
|
||||
if(auto topWindow = ENGINE->windows().topWindow<BattleOnlyModeWindow>())
|
||||
topWindow->applyStartInfo(pack.startInfo);
|
||||
if(lobby->tabBattleOnlyMode)
|
||||
lobby->tabBattleOnlyMode->applyStartInfo(pack.startInfo);
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ enum class EShortcut
|
||||
LOBBY_RANDOM_TOWN_VS,
|
||||
LOBBY_HANDICAP,
|
||||
LOBBY_CAMPAIGN_SETS,
|
||||
LOBBY_BATTLE_MODE,
|
||||
|
||||
MAPS_SIZE_S,
|
||||
MAPS_SIZE_M,
|
||||
|
||||
@@ -162,6 +162,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
|
||||
{"lobbyToggleChat", EShortcut::LOBBY_TOGGLE_CHAT },
|
||||
{"lobbyAdditionalOptions", EShortcut::LOBBY_ADDITIONAL_OPTIONS },
|
||||
{"lobbySelectScenario", EShortcut::LOBBY_SELECT_SCENARIO },
|
||||
{"lobbyBattleMode", EShortcut::LOBBY_BATTLE_MODE },
|
||||
{"gameEndTurn", EShortcut::ADVENTURE_END_TURN }, // compatibility ID - extra's use this string
|
||||
{"adventureEndTurn", EShortcut::ADVENTURE_END_TURN },
|
||||
{"adventureLoadGame", EShortcut::ADVENTURE_LOAD_GAME },
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
/*
|
||||
* BattleOnlyMode.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 "BattleOnlyMode.h"
|
||||
|
||||
#include "../CServerHandler.h"
|
||||
#include "../GameEngine.h"
|
||||
#include "../GameInstance.h"
|
||||
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/CanvasImage.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../windows/CHeroOverview.h"
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
|
||||
#include "../../lib/GameLibrary.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/networkPacks/PacksForLobby.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/VCMIDirs.h"
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
#include "../../lib/callback/EditorCallback.h"
|
||||
#include "../../lib/entities/hero/CHero.h"
|
||||
#include "../../lib/entities/hero/CHeroClass.h"
|
||||
#include "../../lib/entities/hero/CHeroHandler.h"
|
||||
#include "../../lib/entities/faction/CTown.h"
|
||||
#include "../../lib/entities/faction/CTownHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
#include "../../lib/mapping/CMapInfo.h"
|
||||
#include "../../lib/mapping/CMapEditManager.h"
|
||||
#include "../../lib/mapping/CMapService.h"
|
||||
#include "../../lib/mapping/MapFormat.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/texts/MetaString.h"
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
|
||||
void BattleOnlyMode::openBattleWindow()
|
||||
{
|
||||
GAME->server().sendGuiAction(LobbyGuiAction::BATTLE_MODE);
|
||||
ENGINE->windows().createAndPushWindow<BattleOnlyModeWindow>();
|
||||
}
|
||||
|
||||
BattleOnlyModeWindow::BattleOnlyModeWindow()
|
||||
: CWindowObject(BORDERED)
|
||||
, startInfo(std::make_shared<BattleOnlyModeStartInfo>())
|
||||
, disabledColor(GAME->server().isHost() ? Colors::WHITE : Colors::ORANGE)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
pos.w = 519;
|
||||
pos.h = 238;
|
||||
|
||||
updateShadow();
|
||||
center();
|
||||
|
||||
init();
|
||||
|
||||
backgroundTexture = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
|
||||
backgroundTexture->setPlayerColor(PlayerColor(1));
|
||||
buttonOk = std::make_shared<CButton>(Point(191, 203), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ startBattle(); }, EShortcut::GLOBAL_ACCEPT);
|
||||
buttonOk->block(true);
|
||||
buttonAbort = std::make_shared<CButton>(Point(265, 203), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){
|
||||
GAME->server().sendGuiAction(LobbyGuiAction::NO_TAB);
|
||||
close();
|
||||
}, EShortcut::GLOBAL_CANCEL);
|
||||
buttonAbort->block(true);
|
||||
title = std::make_shared<CLabel>(260, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode"));
|
||||
|
||||
battlefieldSelector = std::make_shared<CButton>(Point(29, 174), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
|
||||
auto & terrains = LIBRARY->terrainTypeHandler->objects;
|
||||
for (const auto & terrain : terrains)
|
||||
{
|
||||
if(!terrain->isPassable())
|
||||
continue;
|
||||
|
||||
texts.push_back(terrain->getNameTranslated());
|
||||
|
||||
const auto & patterns = LIBRARY->terviewh->getTerrainViewPatterns(terrain->getId());
|
||||
TerrainViewPattern pattern;
|
||||
for(auto & p : patterns)
|
||||
if(p[0].id == "n1")
|
||||
pattern = p[0];
|
||||
auto image = ENGINE->renderHandler().loadImage(terrain->tilesFilename, pattern.mapping[0].first, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
|
||||
auto factions = LIBRARY->townh->getDefaultAllowed();
|
||||
for (const auto & faction : factions)
|
||||
{
|
||||
texts.push_back(faction.toFaction()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("ITPA"), faction.toFaction()->town->clientInfo.icons[true][false] + 2, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefield"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefieldSelect"), [this, terrains, factions](int index){
|
||||
if(terrains.size() > index)
|
||||
{
|
||||
startInfo->selectedTerrain = terrains[index]->getId();
|
||||
startInfo->selectedTown = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo->selectedTerrain = std::nullopt;
|
||||
auto it = std::next(factions.begin(), index - terrains.size());
|
||||
if (it != factions.end())
|
||||
startInfo->selectedTown = *it;
|
||||
}
|
||||
onChange();
|
||||
}, (startInfo->selectedTerrain ? static_cast<int>(*startInfo->selectedTerrain) : static_cast<int>(*startInfo->selectedTown + terrains.size())), images, true, true);
|
||||
});
|
||||
battlefieldSelector->block(GAME->server().isGuest());
|
||||
buttonReset = std::make_shared<CButton>(Point(289, 174), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){
|
||||
if(GAME->server().isHost())
|
||||
{
|
||||
startInfo->selectedTerrain = TerrainId::DIRT;
|
||||
startInfo->selectedTown = std::nullopt;
|
||||
startInfo->selectedHero[0] = std::nullopt;
|
||||
startInfo->selectedArmy[0].fill(CStackBasicDescriptor(CreatureID::NONE, 1));
|
||||
for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
|
||||
heroSelector1->selectedArmyInput.at(i)->disable();
|
||||
}
|
||||
startInfo->selectedHero[1] = std::nullopt;
|
||||
startInfo->selectedArmy[1].fill(CStackBasicDescriptor(CreatureID::NONE, 1));
|
||||
for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
|
||||
heroSelector2->selectedArmyInput.at(i)->disable();
|
||||
onChange();
|
||||
});
|
||||
buttonReset->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeReset"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
|
||||
heroSelector1 = std::make_shared<BattleOnlyModeHeroSelector>(0, *this, Point(0, 40));
|
||||
heroSelector2 = std::make_shared<BattleOnlyModeHeroSelector>(1, *this, Point(260, 40));
|
||||
|
||||
heroSelector1->setInputEnabled(GAME->server().isHost());
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::init()
|
||||
{
|
||||
map = std::make_unique<CMap>(nullptr);
|
||||
map->version = EMapFormat::VCMI;
|
||||
map->creationDateTime = std::time(nullptr);
|
||||
map->width = 10;
|
||||
map->height = 10;
|
||||
map->mapLevels = 1;
|
||||
map->battleOnly = true;
|
||||
map->name = MetaString::createFromTextID("vcmi.lobby.battleOnlyMode");
|
||||
|
||||
cb = std::make_unique<EditorCallback>(map.get());
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::onChange()
|
||||
{
|
||||
GAME->server().setBattleOnlyModeStartInfo(startInfo);
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::update()
|
||||
{
|
||||
setTerrainButtonText();
|
||||
setOkButtonEnabled();
|
||||
|
||||
heroSelector1->setHeroIcon();
|
||||
heroSelector1->setCreatureIcons();
|
||||
heroSelector2->setHeroIcon();
|
||||
heroSelector2->setCreatureIcons();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::applyStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> si)
|
||||
{
|
||||
startInfo = si;
|
||||
update();
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::setTerrainButtonText()
|
||||
{
|
||||
battlefieldSelector->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefield") + ": " + (startInfo->selectedTerrain ? (*startInfo->selectedTerrain).toEntity(LIBRARY)->getNameTranslated() : (*startInfo->selectedTown).toEntity(LIBRARY)->getNameTranslated()), EFonts::FONT_SMALL, disabledColor);
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::setOkButtonEnabled()
|
||||
{
|
||||
bool army2Empty = std::all_of(startInfo->selectedArmy[1].begin(), startInfo->selectedArmy[1].end(), [](const auto x) { return x.getId() == CreatureID::NONE; });
|
||||
|
||||
bool canStart = (startInfo->selectedTerrain || startInfo->selectedTown);
|
||||
canStart &= (startInfo->selectedHero[0] && ((startInfo->selectedHero[1]) || (startInfo->selectedTown && !army2Empty)));
|
||||
buttonOk->block(!canStart || GAME->server().isGuest());
|
||||
buttonAbort->block(GAME->server().isGuest());
|
||||
}
|
||||
|
||||
std::shared_ptr<IImage> drawBlackBox(Point size, std::string text, ColorRGBA color)
|
||||
{
|
||||
auto image = ENGINE->renderHandler().createImage(size, CanvasScalingPolicy::AUTO);
|
||||
Canvas canvas = image->getCanvas();
|
||||
canvas.drawColor(Rect(0, 0, size.x, size.y), Colors::BLACK);
|
||||
canvas.drawText(Point(size.x / 2, size.y / 2), FONT_TINY, color, ETextAlignment::CENTER, text);
|
||||
return image;
|
||||
}
|
||||
|
||||
BattleOnlyModeHeroSelector::BattleOnlyModeHeroSelector(int id, BattleOnlyModeWindow& p, Point position)
|
||||
: parent(p)
|
||||
, id(id)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
pos.x += position.x;
|
||||
pos.y += position.y;
|
||||
|
||||
backgroundImage = std::make_shared<CPicture>(ImagePath::builtin("heroSlotsBlue"), Point(3, 4));
|
||||
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
{
|
||||
auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), i, 0, 78 + i * 36, 26);
|
||||
primSkills.push_back(image);
|
||||
primSkillsBorder.push_back(std::make_shared<GraphicalPrimitiveCanvas>(Rect(78 + i * 36, 26, 32, 32)));
|
||||
primSkillsBorder.back()->addRectangle(Point(0, 0), Point(32, 32), ColorRGBA(44, 108, 255));
|
||||
primSkillsInput.push_back(std::make_shared<CTextInput>(Rect(78 + i * 36, 58, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
|
||||
primSkillsInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
|
||||
primSkillsInput.back()->setFilterNumber(0, 100);
|
||||
primSkillsInput.back()->setText("0");
|
||||
primSkillsInput.back()->setCallback([this, i, id](const std::string & text){
|
||||
parent.startInfo->primSkillLevel[id][i] = std::stoi(primSkillsInput[i]->getText());
|
||||
parent.onChange();
|
||||
});
|
||||
}
|
||||
|
||||
creatureImage.resize(GameConstants::ARMY_SIZE);
|
||||
for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
|
||||
{
|
||||
selectedArmyInput.push_back(std::make_shared<CTextInput>(Rect(5 + i * 36, 113, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
|
||||
selectedArmyInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
|
||||
selectedArmyInput.back()->setFilterNumber(1, 10000000, 3);
|
||||
selectedArmyInput.back()->setText("1");
|
||||
selectedArmyInput.back()->setCallback([this, i, id](const std::string & text){
|
||||
if(parent.startInfo->selectedArmy[id][i].getId() != CreatureID::NONE)
|
||||
{
|
||||
parent.startInfo->selectedArmy[id][i].setCount(TextOperations::parseMetric<int>(text));
|
||||
parent.onChange();
|
||||
selectedArmyInput[i]->enable();
|
||||
}
|
||||
else
|
||||
selectedArmyInput[i]->disable();
|
||||
});
|
||||
}
|
||||
|
||||
setHeroIcon();
|
||||
setCreatureIcons();
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::setHeroIcon()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
if(!parent.startInfo->selectedHero[id])
|
||||
{
|
||||
heroImage = std::make_shared<CPicture>(drawBlackBox(Point(58, 64), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect"), id == 1 ? Colors::WHITE : parent.disabledColor), Point(6, 7));
|
||||
heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
primSkillsInput[i]->setText("0");
|
||||
}
|
||||
else
|
||||
{
|
||||
heroImage = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("PortraitsLarge"), EImageBlitMode::COLORKEY)->getImage((*parent.startInfo->selectedHero[id]).toHeroType()->imageIndex), Point(6, 7));
|
||||
heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, (*parent.startInfo->selectedHero[id]).toHeroType()->getNameTranslated());
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
primSkillsInput[i]->setText(std::to_string(parent.startInfo->primSkillLevel[id][i]));
|
||||
}
|
||||
|
||||
heroImage->addLClickCallback([this](){
|
||||
auto allowedSet = LIBRARY->heroh->getDefaultAllowed();
|
||||
std::vector<HeroTypeID> heroes(allowedSet.begin(), allowedSet.end());
|
||||
std::sort(heroes.begin(), heroes.end(), [](auto a, auto b) {
|
||||
auto heroA = a.toHeroType();
|
||||
auto heroB = b.toHeroType();
|
||||
if(heroA->heroClass->faction != heroB->heroClass->faction)
|
||||
return heroA->heroClass->faction < heroB->heroClass->faction;
|
||||
if(heroA->heroClass->getId() != heroB->heroClass->getId())
|
||||
return heroA->heroClass->getId() < heroB->heroClass->getId();
|
||||
return heroA->getNameTranslated() < heroB->getNameTranslated();
|
||||
});
|
||||
|
||||
int selectedIndex = !parent.startInfo->selectedHero[id] ? 0 : (1 + std::distance(heroes.begin(), std::find_if(heroes.begin(), heroes.end(), [this](auto heroID) {
|
||||
return heroID == (*parent.startInfo->selectedHero[id]);
|
||||
})));
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
// Add "no hero" option
|
||||
texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
images.push_back(nullptr);
|
||||
for (const auto & h : heroes)
|
||||
{
|
||||
texts.push_back(h.toHeroType()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("PortraitsSmall"), h.toHeroType()->imageIndex, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), [this, heroes](int index){
|
||||
if(index == 0)
|
||||
{
|
||||
parent.startInfo->selectedHero[id] = std::nullopt;
|
||||
parent.onChange();
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
|
||||
parent.startInfo->selectedHero[id] = heroes[index];
|
||||
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
parent.startInfo->primSkillLevel[id][i] = 0;
|
||||
parent.onChange();
|
||||
}, selectedIndex, images, true, true);
|
||||
window->onPopup = [heroes](int index) {
|
||||
if(index == 0)
|
||||
return;
|
||||
index--;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CHeroOverview>(heroes.at(index));
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
});
|
||||
|
||||
heroImage->addRClickCallback([this](){
|
||||
if(!parent.startInfo->selectedHero[id])
|
||||
return;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CHeroOverview>(parent.startInfo->selectedHero[id]->toHeroType()->getId());
|
||||
});
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::setCreatureIcons()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
for(int i = 0; i < creatureImage.size(); i++)
|
||||
{
|
||||
if(parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE)
|
||||
{
|
||||
creatureImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect"), id == 1 ? Colors::WHITE : parent.disabledColor), Point(6 + i * 36, 78));
|
||||
selectedArmyInput[i]->disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto unit = parent.startInfo->selectedArmy[id][i];
|
||||
auto creatureID = unit.getId();
|
||||
creatureImage[i] = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"), EImageBlitMode::COLORKEY)->getImage(LIBRARY->creh->objects.at(creatureID)->getIconIndex()), Point(6 + i * 36, 78));
|
||||
selectedArmyInput[i]->setText(TextOperations::formatMetric(unit.getCount(), 3));
|
||||
selectedArmyInput[i]->enable();
|
||||
}
|
||||
|
||||
creatureImage[i]->addLClickCallback([this, i](){
|
||||
auto allowedSet = LIBRARY->creh->getDefaultAllowed();
|
||||
std::vector<CreatureID> creatures(allowedSet.begin(), allowedSet.end());
|
||||
std::sort(creatures.begin(), creatures.end(), [](auto a, auto b) {
|
||||
auto creatureA = a.toCreature();
|
||||
auto creatureB = b.toCreature();
|
||||
if(creatureA->getFactionID() != creatureB->getFactionID())
|
||||
return creatureA->getFactionID() < creatureB->getFactionID();
|
||||
if(creatureA->getLevel() != creatureB->getLevel())
|
||||
return creatureA->getLevel() < creatureB->getLevel();
|
||||
if(creatureA->upgrades.size() != creatureB->upgrades.size())
|
||||
return creatureA->upgrades.size() > creatureB->upgrades.size();
|
||||
return creatureA->getNameSingularTranslated() < creatureB->getNameSingularTranslated();
|
||||
});
|
||||
|
||||
int selectedIndex = parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE ? 0 : (1 + std::distance(creatures.begin(), std::find_if(creatures.begin(), creatures.end(), [this, i](auto creatureID) {
|
||||
return creatureID == parent.startInfo->selectedArmy[id][i].getId();
|
||||
})));
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
// Add "no creature" option
|
||||
texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
images.push_back(nullptr);
|
||||
for (const auto & c : creatures)
|
||||
{
|
||||
texts.push_back(c.toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("CPRSMALL"), c.toCreature()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), [this, creatures, i](int index){
|
||||
if(index == 0)
|
||||
{
|
||||
parent.startInfo->selectedArmy[id][i] = CStackBasicDescriptor(CreatureID::NONE, 1);
|
||||
parent.onChange();
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
|
||||
auto creature = creatures.at(index).toCreature();
|
||||
parent.startInfo->selectedArmy[id][i] = CStackBasicDescriptor(creature->getId(), 100);
|
||||
parent.onChange();
|
||||
}, selectedIndex, images, true, true);
|
||||
window->onPopup = [creatures](int index) {
|
||||
if(index == 0)
|
||||
return;
|
||||
index--;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CStackWindow>(creatures.at(index).toCreature(), true);
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
});
|
||||
|
||||
creatureImage[i]->addRClickCallback([this, i](){
|
||||
if(parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE)
|
||||
return;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CStackWindow>(LIBRARY->creh->objects.at(parent.startInfo->selectedArmy[id][i].getId()).get(), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleOnlyModeWindow::startBattle()
|
||||
{
|
||||
auto rng = &CRandomGenerator::getDefault();
|
||||
|
||||
map->initTerrain();
|
||||
map->getEditManager()->clearTerrain(rng);
|
||||
|
||||
map->getEditManager()->getTerrainSelection().selectAll();
|
||||
map->getEditManager()->drawTerrain(!startInfo->selectedTerrain ? TerrainId::DIRT : *startInfo->selectedTerrain, 0, rng);
|
||||
|
||||
map->players[0].canComputerPlay = true;
|
||||
map->players[0].canHumanPlay = true;
|
||||
map->players[1] = map->players[0];
|
||||
|
||||
auto knownHeroes = LIBRARY->objtypeh->knownSubObjects(Obj::HERO);
|
||||
|
||||
auto addHero = [&, this](int sel, PlayerColor color, const int3 & position)
|
||||
{
|
||||
auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::HERO, (*startInfo->selectedHero[sel]).toHeroType()->heroClass->getId());
|
||||
auto templates = factory->getTemplates();
|
||||
auto obj = std::dynamic_pointer_cast<CGHeroInstance>(factory->create(cb.get(), templates.front()));
|
||||
obj->setHeroType(*startInfo->selectedHero[sel]);
|
||||
|
||||
obj->setOwner(color);
|
||||
obj->pos = position;
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
obj->pushPrimSkill(PrimarySkill(i), startInfo->primSkillLevel[sel][i]);
|
||||
obj->clearSlots();
|
||||
for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
|
||||
if(startInfo->selectedArmy[sel][slot].getId() != CreatureID::NONE)
|
||||
obj->setCreature(SlotID(slot), startInfo->selectedArmy[sel][slot].getId(), startInfo->selectedArmy[sel][slot].getCount());
|
||||
map->getEditManager()->insertObject(obj);
|
||||
};
|
||||
|
||||
addHero(0, PlayerColor(0), int3(5, 6, 0));
|
||||
if(!startInfo->selectedTown)
|
||||
addHero(1, PlayerColor(1), int3(5, 5, 0));
|
||||
else
|
||||
{
|
||||
auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::TOWN, *startInfo->selectedTown);
|
||||
auto templates = factory->getTemplates();
|
||||
auto obj = factory->create(cb.get(), templates.front());
|
||||
auto townObj = std::dynamic_pointer_cast<CGTownInstance>(obj);
|
||||
obj->setOwner(PlayerColor(1));
|
||||
obj->pos = int3(5, 5, 0);
|
||||
for (const auto & building : townObj->getTown()->getAllBuildings())
|
||||
townObj->addBuilding(building);
|
||||
if(!startInfo->selectedHero[1])
|
||||
{
|
||||
for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
|
||||
if(startInfo->selectedArmy[1][slot].getId() != CreatureID::NONE)
|
||||
townObj->getArmy()->setCreature(SlotID(slot), startInfo->selectedArmy[1][slot].getId(), startInfo->selectedArmy[1][slot].getCount());
|
||||
}
|
||||
else
|
||||
addHero(1, PlayerColor(1), int3(5, 5, 0));
|
||||
|
||||
map->getEditManager()->insertObject(townObj);
|
||||
}
|
||||
|
||||
auto path = VCMIDirs::get().userDataPath() / "Maps";
|
||||
boost::filesystem::create_directories(path);
|
||||
const std::string fileName = "BattleOnlyMode.vmap";
|
||||
const auto fullPath = path / fileName;
|
||||
CMapService mapService;
|
||||
mapService.saveMap(map, fullPath);
|
||||
CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount) { return true; });
|
||||
|
||||
auto mapInfo = std::make_shared<CMapInfo>();
|
||||
mapInfo->mapInit("Maps/BattleOnlyMode");
|
||||
GAME->server().setMapInfo(mapInfo);
|
||||
ExtraOptionsInfo extraOptions;
|
||||
extraOptions.unlimitedReplay = true;
|
||||
GAME->server().setExtraOptionsInfo(extraOptions);
|
||||
GAME->server().sendStartGame();
|
||||
}
|
||||
975
client/lobby/BattleOnlyModeTab.cpp
Normal file
975
client/lobby/BattleOnlyModeTab.cpp
Normal file
@@ -0,0 +1,975 @@
|
||||
/*
|
||||
* BattleOnlyModeTab.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 "BattleOnlyModeTab.h"
|
||||
#include "CLobbyScreen.h"
|
||||
|
||||
#include "../CServerHandler.h"
|
||||
#include "../GameEngine.h"
|
||||
#include "../GameInstance.h"
|
||||
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/CAnimation.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/CanvasImage.h"
|
||||
#include "../render/IFont.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/CTextInput.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../windows/CHeroOverview.h"
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/GameLibrary.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/networkPacks/PacksForLobby.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/VCMIDirs.h"
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
#include "../../lib/CSkillHandler.h"
|
||||
#include "../../lib/callback/EditorCallback.h"
|
||||
#include "../../lib/entities/hero/CHero.h"
|
||||
#include "../../lib/entities/hero/CHeroClass.h"
|
||||
#include "../../lib/entities/hero/CHeroHandler.h"
|
||||
#include "../../lib/entities/faction/CTown.h"
|
||||
#include "../../lib/entities/faction/CTownHandler.h"
|
||||
#include "../../lib/entities/artifact/CArtifact.h"
|
||||
#include "../../lib/entities/artifact/CArtHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
#include "../../lib/mapping/CMapInfo.h"
|
||||
#include "../../lib/mapping/CMapEditManager.h"
|
||||
#include "../../lib/mapping/CMapService.h"
|
||||
#include "../../lib/mapping/MapFormat.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/SpellSchoolHandler.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/texts/MetaString.h"
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/serializer/JsonSerializer.h"
|
||||
#include "../../lib/serializer/JsonDeserializer.h"
|
||||
|
||||
BattleOnlyModeTab::BattleOnlyModeTab()
|
||||
: startInfo(std::make_shared<BattleOnlyModeStartInfo>())
|
||||
, disabledColor(GAME->server().isHost() ? Colors::WHITE : Colors::ORANGE)
|
||||
, boxColor(ColorRGBA(128, 128, 128))
|
||||
, disabledBoxColor(GAME->server().isHost() ? boxColor : ColorRGBA(116, 92, 16))
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
try
|
||||
{
|
||||
JsonNode node = persistentStorage["battleModeSettings"];
|
||||
if(!node.isNull())
|
||||
{
|
||||
node.setModScope(ModScope::scopeGame());
|
||||
JsonDeserializer handler(nullptr, node);
|
||||
startInfo->serializeJson(handler);
|
||||
}
|
||||
}
|
||||
catch(std::exception & e)
|
||||
{
|
||||
logGlobal->error("Error loading saved battleModeSettings, received exception: %s", e.what());
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
backgroundImage = std::make_shared<CPicture>(ImagePath::builtin("AdventureOptionsBackgroundClear"), 0, 6);
|
||||
title = std::make_shared<CLabel>(220, 35, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode"));
|
||||
subTitle = std::make_shared<CMultiLineLabel>(Rect(55, 40, 333, 40), FONT_SMALL, ETextAlignment::BOTTOMCENTER, Colors::WHITE, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSubTitle"));
|
||||
|
||||
battlefieldSelector = std::make_shared<CButton>(Point(57, 552), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){ selectTerrain(); });
|
||||
battlefieldSelector->block(GAME->server().isGuest());
|
||||
buttonReset = std::make_shared<CButton>(Point(259, 552), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), [this](){ reset(); });
|
||||
buttonReset->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeReset"), EFonts::FONT_SMALL, Colors::WHITE);
|
||||
|
||||
heroSelector1 = std::make_shared<BattleOnlyModeHeroSelector>(0, *this, Point(55, 90));
|
||||
heroSelector2 = std::make_shared<BattleOnlyModeHeroSelector>(1, *this, Point(55, 320));
|
||||
|
||||
heroSelector1->setInputEnabled(GAME->server().isHost());
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::reset()
|
||||
{
|
||||
if(GAME->server().isHost())
|
||||
{
|
||||
startInfo->selectedTerrain = TerrainId::DIRT;
|
||||
startInfo->selectedTown = FactionID::ANY;
|
||||
startInfo->selectedHero[0] = HeroTypeID::NONE;
|
||||
startInfo->selectedArmy[0].fill(CStackBasicDescriptor(CreatureID::NONE, 1));
|
||||
startInfo->secSkillLevel[0].fill(std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE));
|
||||
startInfo->artifacts[0].clear();
|
||||
startInfo->spellBook[0] = true;
|
||||
startInfo->warMachines[0] = false;
|
||||
startInfo->spells[0].clear();
|
||||
for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
|
||||
heroSelector1->selectedArmyInput.at(i)->disable();
|
||||
for(size_t i=0; i<8; i++)
|
||||
heroSelector1->selectedSecSkillInput.at(i)->disable();
|
||||
}
|
||||
startInfo->selectedHero[1] = HeroTypeID::NONE;
|
||||
startInfo->selectedArmy[1].fill(CStackBasicDescriptor(CreatureID::NONE, 1));
|
||||
startInfo->secSkillLevel[1].fill(std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE));
|
||||
startInfo->artifacts[1].clear();
|
||||
startInfo->spellBook[1] = true;
|
||||
startInfo->warMachines[1] = false;
|
||||
startInfo->spells[1].clear();
|
||||
for(size_t i=0; i<8; i++)
|
||||
heroSelector2->selectedSecSkillInput.at(i)->disable();
|
||||
onChange();
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::selectTerrain()
|
||||
{
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
|
||||
std::vector<std::shared_ptr<TerrainType>> terrains;
|
||||
std::copy_if(LIBRARY->terrainTypeHandler->objects.begin(), LIBRARY->terrainTypeHandler->objects.end(), std::back_inserter(terrains), [](auto terrain) { return terrain->isPassable(); });
|
||||
for (const auto & terrain : terrains)
|
||||
{
|
||||
texts.push_back(terrain->getNameTranslated());
|
||||
|
||||
const auto & patterns = LIBRARY->terviewh->getTerrainViewPatterns(terrain->getId());
|
||||
TerrainViewPattern pattern;
|
||||
for(auto & p : patterns)
|
||||
if(p[0].id == "n1")
|
||||
pattern = p[0];
|
||||
auto image = ENGINE->renderHandler().loadImage(terrain->tilesFilename, pattern.mapping[0].first, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
|
||||
auto factions = LIBRARY->townh->getDefaultAllowed();
|
||||
for (const auto & faction : factions)
|
||||
{
|
||||
texts.push_back(faction.toFaction()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("ITPA"), faction.toFaction()->town->clientInfo.icons[true][false] + 2, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefield"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefieldSelect"), [this, terrains, factions](int index){
|
||||
if(terrains.size() > index)
|
||||
{
|
||||
startInfo->selectedTerrain = terrains[index]->getId();
|
||||
startInfo->selectedTown = FactionID::ANY;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo->selectedTerrain = TerrainId::NONE;
|
||||
auto it = std::next(factions.begin(), index - terrains.size());
|
||||
if (it != factions.end())
|
||||
startInfo->selectedTown = *it;
|
||||
}
|
||||
onChange();
|
||||
}, (startInfo->selectedTerrain != TerrainId::NONE ? static_cast<int>(startInfo->selectedTerrain) : static_cast<int>(startInfo->selectedTown + terrains.size())), images, true, true);
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::init()
|
||||
{
|
||||
map = std::make_unique<CMap>(nullptr);
|
||||
map->version = EMapFormat::VCMI;
|
||||
map->creationDateTime = std::time(nullptr);
|
||||
map->width = 10;
|
||||
map->height = 10;
|
||||
map->mapLevels = 1;
|
||||
map->battleOnly = true;
|
||||
map->name = MetaString::createFromTextID("vcmi.lobby.battleOnlyMode");
|
||||
|
||||
cb = std::make_unique<EditorCallback>(map.get());
|
||||
map->cb = cb.get();
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::onChange()
|
||||
{
|
||||
GAME->server().setBattleOnlyModeStartInfo(startInfo);
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::update()
|
||||
{
|
||||
setTerrainButtonText();
|
||||
setStartButtonEnabled();
|
||||
|
||||
heroSelector1->setHeroIcon();
|
||||
heroSelector1->setCreatureIcons();
|
||||
heroSelector1->setSecSkillIcons();
|
||||
heroSelector1->setArtifactIcons();
|
||||
heroSelector1->spellBook->setSelectedSilent(startInfo->spellBook[0]);
|
||||
heroSelector1->warMachines->setSelectedSilent(startInfo->warMachines[0]);
|
||||
heroSelector2->setHeroIcon();
|
||||
heroSelector2->setCreatureIcons();
|
||||
heroSelector2->setSecSkillIcons();
|
||||
heroSelector2->setArtifactIcons();
|
||||
heroSelector2->spellBook->setSelectedSilent(startInfo->spellBook[1]);
|
||||
heroSelector2->warMachines->setSelectedSilent(startInfo->warMachines[1]);
|
||||
redraw();
|
||||
|
||||
JsonNode node;
|
||||
JsonSerializer handler(nullptr, node);
|
||||
startInfo->serializeJson(handler);
|
||||
Settings storage = persistentStorage.write["battleModeSettings"];
|
||||
storage->Struct() = node.Struct();
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::applyStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> si)
|
||||
{
|
||||
startInfo = si;
|
||||
update();
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::setTerrainButtonText()
|
||||
{
|
||||
battlefieldSelector->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefield") + ": " + (startInfo->selectedTerrain != TerrainId::NONE ? startInfo->selectedTerrain.toEntity(LIBRARY)->getNameTranslated() : startInfo->selectedTown.toEntity(LIBRARY)->getNameTranslated()), EFonts::FONT_SMALL, disabledColor);
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::setStartButtonEnabled()
|
||||
{
|
||||
bool army2Empty = std::all_of(startInfo->selectedArmy[1].begin(), startInfo->selectedArmy[1].end(), [](const auto x) { return x.getId() == CreatureID::NONE; });
|
||||
|
||||
bool canStart = (startInfo->selectedTerrain != TerrainId::NONE || startInfo->selectedTown != FactionID::ANY);
|
||||
canStart &= (startInfo->selectedHero[0] != HeroTypeID::NONE && ((startInfo->selectedHero[1] != HeroTypeID::NONE) || (startInfo->selectedTown != FactionID::ANY && !army2Empty)));
|
||||
(static_cast<CLobbyScreen *>(parent))->buttonStart->block(!canStart || GAME->server().isGuest());
|
||||
}
|
||||
|
||||
std::shared_ptr<IImage> drawBlackBox(Point size, std::string text, ColorRGBA color)
|
||||
{
|
||||
auto image = ENGINE->renderHandler().createImage(size, CanvasScalingPolicy::AUTO);
|
||||
Canvas canvas = image->getCanvas();
|
||||
canvas.drawColor(Rect(0, 0, size.x, size.y), Colors::BLACK);
|
||||
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, text, boost::is_any_of("\n"));
|
||||
int lineH = ENGINE->renderHandler().loadFont(FONT_TINY)->getLineHeight();
|
||||
int totalH = lines.size() * lineH;
|
||||
int startY = (size.y - totalH) / 2 + lineH / 2;
|
||||
|
||||
for (size_t i = 0; i < lines.size(); ++i)
|
||||
canvas.drawText(Point(size.x / 2, startY + i * lineH), FONT_TINY, color, ETextAlignment::CENTER, lines[i]);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
BattleOnlyModeHeroSelector::BattleOnlyModeHeroSelector(int id, BattleOnlyModeTab& p, Point position)
|
||||
: parent(p)
|
||||
, id(id)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
pos.x += position.x;
|
||||
pos.y += position.y;
|
||||
|
||||
backgroundImage = std::make_shared<CPicture>(ImagePath::builtin("heroSlotsBlue"), Point(3, 4));
|
||||
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
{
|
||||
auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), i, 0, 78 + i * 36, 26);
|
||||
primSkills.push_back(image);
|
||||
primSkillsBorder.push_back(std::make_shared<GraphicalPrimitiveCanvas>(Rect(78 + i * 36, 26, 32, 32)));
|
||||
primSkillsBorder.back()->addRectangle(Point(0, 0), Point(32, 32), ColorRGBA(44, 108, 255));
|
||||
primSkillsInput.push_back(std::make_shared<CTextInput>(Rect(78 + i * 36, 58, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
|
||||
primSkillsInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
|
||||
primSkillsInput.back()->setFilterNumber(0, 100);
|
||||
primSkillsInput.back()->setText("0");
|
||||
primSkillsInput.back()->setCallback([this, i, id](const std::string & text){
|
||||
parent.startInfo->primSkillLevel[id][i] = std::stoi(primSkillsInput[i]->getText());
|
||||
parent.onChange();
|
||||
});
|
||||
}
|
||||
|
||||
creatureImage.resize(GameConstants::ARMY_SIZE);
|
||||
for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
|
||||
{
|
||||
selectedArmyInput.push_back(std::make_shared<CTextInput>(Rect(5 + i * 36, 113, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
|
||||
selectedArmyInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
|
||||
selectedArmyInput.back()->setFilterNumber(0, 10000000, 3);
|
||||
selectedArmyInput.back()->setText("1");
|
||||
selectedArmyInput.back()->setCallback([this, i, id](const std::string & text){
|
||||
if(parent.startInfo->selectedArmy[id][i].getId() != CreatureID::NONE)
|
||||
{
|
||||
parent.startInfo->selectedArmy[id][i].setCount(TextOperations::parseMetric<int>(text));
|
||||
parent.onChange();
|
||||
selectedArmyInput[i]->enable();
|
||||
}
|
||||
else
|
||||
selectedArmyInput[i]->disable();
|
||||
});
|
||||
}
|
||||
|
||||
for(size_t i=0; i<8; i++)
|
||||
{
|
||||
bool isLeft = (i % 2 == 0);
|
||||
int line = (i / 2);
|
||||
Point textPos(261 + (isLeft ? 0 : 36), 41 + line * 54);
|
||||
|
||||
selectedSecSkillInput.push_back(std::make_shared<CTextInput>(Rect(textPos, Point(32, 16)), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
|
||||
selectedSecSkillInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
|
||||
selectedSecSkillInput.back()->setFilterNumber(0, 3);
|
||||
selectedSecSkillInput.back()->setText("3");
|
||||
selectedSecSkillInput.back()->setCallback([this, i, id](const std::string & text){
|
||||
if(parent.startInfo->secSkillLevel[id][i].second != MasteryLevel::NONE)
|
||||
{
|
||||
parent.startInfo->secSkillLevel[id][i].second = static_cast<MasteryLevel::Type>(std::stoi(text));
|
||||
parent.onChange();
|
||||
selectedSecSkillInput[i]->enable();
|
||||
}
|
||||
else
|
||||
selectedSecSkillInput[i]->disable();
|
||||
});
|
||||
}
|
||||
secSkillImage.resize(8);
|
||||
|
||||
artifactImage.resize(14);
|
||||
|
||||
auto tmpIcon = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), ArtifactID(ArtifactID::SPELLBOOK).toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
|
||||
tmpIcon->scaleTo(Point(16, 16), EScalingAlgorithm::NEAREST);
|
||||
addIcon.push_back(std::make_shared<CPicture>(tmpIcon, Point(220, 32)));
|
||||
addIcon.back()->addLClickCallback([this](){ manageSpells(); });
|
||||
addIcon.back()->addRClickCallback([this, id](){
|
||||
std::vector<std::shared_ptr<CComponent>> comps;
|
||||
for(auto & spell : parent.startInfo->spells[id])
|
||||
comps.push_back(std::make_shared<CComponent>(ComponentType::SPELL, spell, std::nullopt, CComponent::ESize::large));
|
||||
CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("artifact.core.spellBook.name"), comps);
|
||||
});
|
||||
|
||||
spellBook = std::make_shared<CToggleButton>(Point(235, 31), AnimationPath::builtin("lobby/checkboxSmall"), CButton::tooltip(), [this, id](bool enabled){
|
||||
parent.startInfo->spellBook[id] = enabled;
|
||||
parent.onChange();
|
||||
redraw();
|
||||
});
|
||||
spellBook->setSelectedSilent(parent.startInfo->spellBook[id]);
|
||||
|
||||
tmpIcon = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), ArtifactID(ArtifactID::BALLISTA).toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
|
||||
tmpIcon->scaleTo(Point(16, 16), EScalingAlgorithm::NEAREST);
|
||||
addIcon.push_back(std::make_shared<CPicture>(tmpIcon, Point(220, 56)));
|
||||
warMachines = std::make_shared<CToggleButton>(Point(235, 55), AnimationPath::builtin("lobby/checkboxSmall"), CButton::tooltip(), [this, id](bool enabled){
|
||||
parent.startInfo->warMachines[id] = enabled;
|
||||
parent.onChange();
|
||||
redraw();
|
||||
});
|
||||
warMachines->setSelectedSilent(parent.startInfo->warMachines[id]);
|
||||
|
||||
setHeroIcon();
|
||||
setCreatureIcons();
|
||||
setSecSkillIcons();
|
||||
setArtifactIcons();
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::manageSpells()
|
||||
{
|
||||
std::vector<std::shared_ptr<CComponent>> resComps;
|
||||
for(auto & spellId : parent.startInfo->spells[id])
|
||||
resComps.push_back(std::make_shared<CComponent>(ComponentType::SPELL, spellId, std::nullopt, CComponent::ESize::large));
|
||||
|
||||
std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom;
|
||||
for(int i = 0; i < 3; i++)
|
||||
pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr);
|
||||
|
||||
auto allowedSet = LIBRARY->spellh->getDefaultAllowed();
|
||||
std::vector<SpellID> allSpells(allowedSet.begin(), allowedSet.end());
|
||||
allSpells.erase(std::remove_if(allSpells.begin(), allSpells.end(), [](const SpellID& spell) {
|
||||
return !spell.toSpell()->isCombat();
|
||||
}), allSpells.end());
|
||||
std::sort(allSpells.begin(), allSpells.end(), [](auto a, auto b) {
|
||||
auto A = a.toSpell();
|
||||
auto B = b.toSpell();
|
||||
if(A->getLevel() != B->getLevel())
|
||||
return A->getLevel() < B->getLevel();
|
||||
for (const auto schoolId : LIBRARY->spellSchoolHandler->getAllObjects())
|
||||
{
|
||||
if(A->schools.count(schoolId) && !B->schools.count(schoolId))
|
||||
return true;
|
||||
if(!A->schools.count(schoolId) && B->schools.count(schoolId))
|
||||
return false;
|
||||
}
|
||||
return TextOperations::compareLocalizedStrings(A->getNameTranslated(), B->getNameTranslated());
|
||||
});
|
||||
|
||||
std::vector<SpellID> toAdd;
|
||||
std::vector<SpellID> toRemove;
|
||||
for (const auto& spell : allSpells)
|
||||
{
|
||||
bool inCurrent = std::find(parent.startInfo->spells[id].begin(), parent.startInfo->spells[id].end(), spell) != parent.startInfo->spells[id].end();
|
||||
if (inCurrent)
|
||||
toRemove.push_back(spell);
|
||||
else
|
||||
toAdd.push_back(spell);
|
||||
}
|
||||
|
||||
auto openList = [this](std::vector<SpellID> list, bool add){
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
for (const auto & s : list)
|
||||
{
|
||||
texts.push_back(s.toSpell()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("SpellInt"), s.toSpell()->getIconIndex() + 1, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
|
||||
std::string title = LIBRARY->generaltexth->translate(add ? "vcmi.lobby.battleOnlySpellAdd" : "vcmi.lobby.battleOnlySpellRemove");
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, title, title, [this, list, add](int index){
|
||||
auto & v = parent.startInfo->spells[id];
|
||||
if(add)
|
||||
v.push_back(list[index]);
|
||||
else
|
||||
v.erase(std::remove(v.begin(), v.end(), list[index]), v.end());
|
||||
|
||||
parent.onChange();
|
||||
manageSpells();
|
||||
}, 0, images, true, true);
|
||||
window->onPopup = [list](int index) {
|
||||
std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::SPELL, list[index]);
|
||||
CRClickPopup::createAndPush(list[index].toSpell()->getDescriptionTranslated(0), CInfoWindow::TCompsInfo(1, comp));
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
};
|
||||
|
||||
auto temp = std::make_shared<CInfoWindow>(LIBRARY->generaltexth->translate(parent.startInfo->spells[id].size() ? "vcmi.lobby.battleOnlySpellSelectCurrent" : "vcmi.lobby.battleOnlySpellSelect"), PlayerColor(0), resComps, pom);
|
||||
temp->buttons[0]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/addChannel")));
|
||||
temp->buttons[0]->addCallback([openList, toAdd](){ openList(toAdd, true); });
|
||||
temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlySpellAdd")); });
|
||||
temp->buttons[1]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/removeChannel")));
|
||||
temp->buttons[1]->addCallback([openList, toRemove](){ openList(toRemove, false); });
|
||||
temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlySpellRemove")); });
|
||||
temp->buttons[1]->setEnabled(parent.startInfo->spells[id].size());
|
||||
temp->buttons[2]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("spellResearch/close")));
|
||||
temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("core.genrltxt.600")); });
|
||||
|
||||
ENGINE->windows().pushWindow(temp);
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::selectHero()
|
||||
{
|
||||
auto allowedSet = LIBRARY->heroh->getDefaultAllowed();
|
||||
std::vector<HeroTypeID> heroes(allowedSet.begin(), allowedSet.end());
|
||||
std::sort(heroes.begin(), heroes.end(), [](auto a, auto b) {
|
||||
auto heroA = a.toHeroType();
|
||||
auto heroB = b.toHeroType();
|
||||
if(heroA->heroClass->faction != heroB->heroClass->faction)
|
||||
return heroA->heroClass->faction < heroB->heroClass->faction;
|
||||
if(heroA->heroClass->getId() != heroB->heroClass->getId())
|
||||
return heroA->heroClass->getId() < heroB->heroClass->getId();
|
||||
return heroA->getNameTranslated() < heroB->getNameTranslated();
|
||||
});
|
||||
|
||||
int selectedIndex = parent.startInfo->selectedHero[id] == HeroTypeID::NONE ? 0 : (1 + std::distance(heroes.begin(), std::find_if(heroes.begin(), heroes.end(), [this](auto heroID) {
|
||||
return heroID == parent.startInfo->selectedHero[id];
|
||||
})));
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
// Add "no hero" option
|
||||
texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
images.push_back(nullptr);
|
||||
for (const auto & h : heroes)
|
||||
{
|
||||
texts.push_back(h.toHeroType()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("PortraitsSmall"), h.toHeroType()->imageIndex, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), [this, heroes](int index){
|
||||
if(index == 0)
|
||||
{
|
||||
parent.startInfo->selectedHero[id] = HeroTypeID::NONE;
|
||||
parent.onChange();
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
|
||||
parent.startInfo->selectedHero[id] = heroes[index];
|
||||
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
parent.startInfo->primSkillLevel[id][i] = heroes[index].toHeroType()->heroClass->primarySkillInitial[i];
|
||||
|
||||
for(size_t i=0; i<8; i++)
|
||||
if(heroes[index].toHeroType()->secSkillsInit.size() > i)
|
||||
parent.startInfo->secSkillLevel[id][i] = std::make_pair(heroes[index].toHeroType()->secSkillsInit[i].first, MasteryLevel::Type(heroes[index].toHeroType()->secSkillsInit[i].second));
|
||||
else
|
||||
parent.startInfo->secSkillLevel[id][i] = std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE);
|
||||
|
||||
parent.startInfo->spellBook[id] = heroes[index].toHeroType()->haveSpellBook;
|
||||
|
||||
parent.onChange();
|
||||
}, selectedIndex, images, true, true);
|
||||
window->onPopup = [heroes](int index) {
|
||||
if(index == 0)
|
||||
return;
|
||||
index--;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CHeroOverview>(heroes.at(index));
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::setHeroIcon()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
if(parent.startInfo->selectedHero[id] == HeroTypeID::NONE)
|
||||
{
|
||||
heroImage = std::make_shared<CPicture>(drawBlackBox(Point(58, 64), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelectHero"), id == 1 ? parent.boxColor : parent.disabledBoxColor), Point(6, 7));
|
||||
heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
primSkillsInput[i]->setText("0");
|
||||
}
|
||||
else
|
||||
{
|
||||
heroImage = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("PortraitsLarge"), EImageBlitMode::COLORKEY)->getImage(parent.startInfo->selectedHero[id].toHeroType()->imageIndex), Point(6, 7));
|
||||
heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, parent.startInfo->selectedHero[id].toHeroType()->getNameTranslated());
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
primSkillsInput[i]->setText(std::to_string(parent.startInfo->primSkillLevel[id][i]));
|
||||
}
|
||||
|
||||
heroImage->addLClickCallback([this](){ selectHero(); });
|
||||
|
||||
heroImage->addRClickCallback([this](){
|
||||
if(parent.startInfo->selectedHero[id] == HeroTypeID::NONE)
|
||||
return;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CHeroOverview>(parent.startInfo->selectedHero[id].toHeroType()->getId());
|
||||
});
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::selectCreature(int slot)
|
||||
{
|
||||
auto allowedSet = LIBRARY->creh->getDefaultAllowed();
|
||||
std::vector<CreatureID> creatures(allowedSet.begin(), allowedSet.end());
|
||||
std::sort(creatures.begin(), creatures.end(), [](auto a, auto b) {
|
||||
auto creatureA = a.toCreature();
|
||||
auto creatureB = b.toCreature();
|
||||
if ((creatureA->getFactionID() == FactionID::NEUTRAL) != (creatureB->getFactionID() == FactionID::NEUTRAL))
|
||||
return creatureA->getFactionID() != FactionID::NEUTRAL;
|
||||
if(creatureA->getFactionID() != creatureB->getFactionID())
|
||||
return creatureA->getFactionID() < creatureB->getFactionID();
|
||||
if(creatureA->getLevel() != creatureB->getLevel())
|
||||
return creatureA->getLevel() < creatureB->getLevel();
|
||||
if(creatureA->upgrades.size() != creatureB->upgrades.size())
|
||||
return creatureA->upgrades.size() > creatureB->upgrades.size();
|
||||
return creatureA->getNameSingularTranslated() < creatureB->getNameSingularTranslated();
|
||||
});
|
||||
|
||||
int selectedIndex = parent.startInfo->selectedArmy[id][slot].getId() == CreatureID::NONE ? 0 : (1 + std::distance(creatures.begin(), std::find_if(creatures.begin(), creatures.end(), [this, slot](auto creatureID) {
|
||||
return creatureID == parent.startInfo->selectedArmy[id][slot].getId();
|
||||
})));
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
// Add "no creature" option
|
||||
texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
images.push_back(nullptr);
|
||||
for (const auto & c : creatures)
|
||||
{
|
||||
texts.push_back(c.toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("CPRSMALL"), c.toCreature()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), [this, creatures, slot](int index){
|
||||
if(index == 0)
|
||||
{
|
||||
parent.startInfo->selectedArmy[id][slot] = CStackBasicDescriptor(CreatureID::NONE, 1);
|
||||
parent.onChange();
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
|
||||
auto creature = creatures.at(index).toCreature();
|
||||
parent.startInfo->selectedArmy[id][slot] = CStackBasicDescriptor(creature->getId(), 100);
|
||||
parent.onChange();
|
||||
}, selectedIndex, images, true, true);
|
||||
window->onPopup = [creatures](int index) {
|
||||
if(index == 0)
|
||||
return;
|
||||
index--;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CStackWindow>(creatures.at(index).toCreature(), true);
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::setCreatureIcons()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
for(int i = 0; i < creatureImage.size(); i++)
|
||||
{
|
||||
if(parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE)
|
||||
{
|
||||
MetaString str;
|
||||
str.appendTextID("vcmi.lobby.battleOnlyModeSelectUnit");
|
||||
str.replaceNumber(i + 1);
|
||||
creatureImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), str.toString(), id == 1 ? parent.boxColor : parent.disabledBoxColor), Point(6 + i * 36, 78));
|
||||
selectedArmyInput[i]->disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto unit = parent.startInfo->selectedArmy[id][i];
|
||||
auto creatureID = unit.getId();
|
||||
creatureImage[i] = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"), EImageBlitMode::COLORKEY)->getImage(LIBRARY->creh->objects.at(creatureID)->getIconIndex()), Point(6 + i * 36, 78));
|
||||
selectedArmyInput[i]->setText(TextOperations::formatMetric(unit.getCount(), 3));
|
||||
selectedArmyInput[i]->enable();
|
||||
}
|
||||
|
||||
creatureImage[i]->addLClickCallback([this, i](){ selectCreature(i); });
|
||||
|
||||
creatureImage[i]->addRClickCallback([this, i](){
|
||||
if(parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE)
|
||||
return;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CStackWindow>(LIBRARY->creh->objects.at(parent.startInfo->selectedArmy[id][i].getId()).get(), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::selectSecSkill(int slot)
|
||||
{
|
||||
auto allowedSet = LIBRARY->skillh->getDefaultAllowed();
|
||||
std::vector<SecondarySkill> skills(allowedSet.begin(), allowedSet.end());
|
||||
skills.erase( // remove already added skills from selection
|
||||
std::remove_if(
|
||||
skills.begin(),
|
||||
skills.end(),
|
||||
[this, slot](auto & skill) {
|
||||
return std::any_of(
|
||||
parent.startInfo->secSkillLevel[id].begin(), parent.startInfo->secSkillLevel[id].end(),
|
||||
[&skill](auto & s) { return s.first == skill; }
|
||||
) && parent.startInfo->secSkillLevel[id][slot].first != skill;
|
||||
}
|
||||
),
|
||||
skills.end()
|
||||
);
|
||||
std::sort(skills.begin(), skills.end(), [](auto a, auto b) {
|
||||
auto skillA = a.toSkill();
|
||||
auto skillB = b.toSkill();
|
||||
return skillA->getNameTranslated() < skillB->getNameTranslated();
|
||||
});
|
||||
|
||||
int selectedIndex = parent.startInfo->secSkillLevel[id][slot].second == MasteryLevel::NONE ? 0 : (1 + std::distance(skills.begin(), std::find_if(skills.begin(), skills.end(), [this, slot](auto skillID) {
|
||||
return skillID == parent.startInfo->secSkillLevel[id][slot].first;
|
||||
})));
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
// Add "no skill" option
|
||||
texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
images.push_back(nullptr);
|
||||
for (const auto & c : skills)
|
||||
{
|
||||
texts.push_back(c.toSkill()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("SECSK32"), c.toSkill()->getIconIndex(0), 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSecSkillSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSecSkillSelect"), [this, skills, slot](int index){
|
||||
if(index == 0)
|
||||
{
|
||||
parent.startInfo->secSkillLevel[id][slot] = std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE);
|
||||
parent.onChange();
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
|
||||
auto skill = skills.at(index).toSkill();
|
||||
parent.startInfo->secSkillLevel[id][slot] = std::make_pair(skill->getId(), MasteryLevel::EXPERT);
|
||||
parent.onChange();
|
||||
}, selectedIndex, images, true, true);
|
||||
window->onPopup = [skills](int index) {
|
||||
if(index == 0)
|
||||
return;
|
||||
index--;
|
||||
|
||||
auto skillId = skills.at(index);
|
||||
std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::SEC_SKILL, skillId, MasteryLevel::EXPERT);
|
||||
CRClickPopup::createAndPush(skillId.toSkill()->getDescriptionTranslated(MasteryLevel::EXPERT), CInfoWindow::TCompsInfo(1, comp));
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::setSecSkillIcons()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
for(int i = 0; i < secSkillImage.size(); i++)
|
||||
{
|
||||
bool isLeft = (i % 2 == 0);
|
||||
int line = (i / 2);
|
||||
Point imgPos(261 + (isLeft ? 0 : 36), 7 + line * 54);
|
||||
auto skillInfo = parent.startInfo->secSkillLevel[id][i];
|
||||
if(skillInfo.second == MasteryLevel::NONE)
|
||||
{
|
||||
MetaString str;
|
||||
str.appendTextID("vcmi.lobby.battleOnlyModeSelectSkill");
|
||||
str.replaceNumber(i + 1);
|
||||
secSkillImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), str.toString(), id == 1 ? parent.boxColor : parent.disabledBoxColor), imgPos);
|
||||
selectedSecSkillInput[i]->disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
secSkillImage[i] = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("SECSK32"), EImageBlitMode::COLORKEY)->getImage(skillInfo.first.toSkill()->getIconIndex(skillInfo.second - 1)), imgPos);
|
||||
selectedSecSkillInput[i]->setText(std::to_string(skillInfo.second));
|
||||
selectedSecSkillInput[i]->enable();
|
||||
}
|
||||
|
||||
secSkillImage[i]->addLClickCallback([this, i](){ selectSecSkill(i); });
|
||||
|
||||
secSkillImage[i]->addRClickCallback([this, i](){
|
||||
auto skillId = parent.startInfo->secSkillLevel[id][i].first;
|
||||
auto skillLevel = parent.startInfo->secSkillLevel[id][i].second;
|
||||
|
||||
if(skillLevel == MasteryLevel::NONE)
|
||||
return;
|
||||
|
||||
std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::SEC_SKILL, skillId, skillLevel);
|
||||
CRClickPopup::createAndPush(skillId.toSkill()->getDescriptionTranslated(skillLevel), CInfoWindow::TCompsInfo(1, comp));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ArtifactPosition> getArtPos()
|
||||
{
|
||||
std::vector<ArtifactPosition> artPos = {
|
||||
ArtifactPosition::HEAD, ArtifactPosition::SHOULDERS, ArtifactPosition::NECK, ArtifactPosition::RIGHT_HAND, ArtifactPosition::LEFT_HAND, ArtifactPosition::TORSO, ArtifactPosition::FEET,
|
||||
ArtifactPosition::RIGHT_RING, ArtifactPosition::LEFT_RING, ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5
|
||||
};
|
||||
return artPos;
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::selectArtifact(int slot, ArtifactID artifactId)
|
||||
{
|
||||
auto artPos = getArtPos();
|
||||
|
||||
auto allowedSet = LIBRARY->arth->getDefaultAllowed();
|
||||
std::vector<ArtifactID> artifacts(allowedSet.begin(), allowedSet.end());
|
||||
artifacts.erase( // remove already added and not for that slot allowed artifacts from selection
|
||||
std::remove_if(
|
||||
artifacts.begin(),
|
||||
artifacts.end(),
|
||||
[this, slot, artPos](auto & artifact) {
|
||||
auto possibleSlots = artifact.toArtifact()->getPossibleSlots();
|
||||
std::vector<ArtifactPosition> allowedSlots;
|
||||
if(possibleSlots.find(ArtBearer::HERO) != possibleSlots.end() && !possibleSlots.at(ArtBearer::HERO).empty())
|
||||
allowedSlots = possibleSlots.at(ArtBearer::HERO);
|
||||
|
||||
return (std::any_of(parent.startInfo->artifacts[id].begin(), parent.startInfo->artifacts[id].end(), [&artifact](auto & a) {return a.second == artifact;})
|
||||
&& parent.startInfo->artifacts[id][artPos[slot]] != artifact)
|
||||
|| !std::any_of(allowedSlots.begin(), allowedSlots.end(), [slot, artPos](auto & p){ return p == artPos[slot]; });
|
||||
}
|
||||
),
|
||||
artifacts.end()
|
||||
);
|
||||
std::sort(artifacts.begin(), artifacts.end(), [](auto a, auto b) {
|
||||
auto artifactA = a.toArtifact();
|
||||
auto artifactB = b.toArtifact();
|
||||
return artifactA->getNameTranslated() < artifactB->getNameTranslated();
|
||||
});
|
||||
|
||||
int selectedIndex = artifactId == ArtifactID::NONE ? 0 : (1 + std::distance(artifacts.begin(), std::find_if(artifacts.begin(), artifacts.end(), [artifactId](auto artID) {
|
||||
return artID == artifactId;
|
||||
})));
|
||||
|
||||
std::vector<std::string> texts;
|
||||
std::vector<std::shared_ptr<IImage>> images;
|
||||
// Add "no artifact" option
|
||||
texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
|
||||
images.push_back(nullptr);
|
||||
for (const auto & a : artifacts)
|
||||
{
|
||||
texts.push_back(a.toArtifact()->getNameTranslated());
|
||||
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), a.toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
|
||||
images.push_back(image);
|
||||
}
|
||||
auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeArtifactSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeArtifactSelect"), [this, artifacts, slot, artPos](int index){
|
||||
if(index == 0)
|
||||
{
|
||||
parent.startInfo->artifacts[id][artPos[slot]] = ArtifactID::NONE;
|
||||
parent.onChange();
|
||||
return;
|
||||
}
|
||||
index--;
|
||||
|
||||
auto artifact = artifacts.at(index);
|
||||
parent.startInfo->artifacts[id][artPos[slot]] = artifact;
|
||||
parent.onChange();
|
||||
}, selectedIndex, images, true, true);
|
||||
window->onPopup = [artifacts](int index) {
|
||||
if(index == 0)
|
||||
return;
|
||||
index--;
|
||||
|
||||
auto artifactId = artifacts.at(index);
|
||||
std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::ARTIFACT, artifactId);
|
||||
CRClickPopup::createAndPush(artifactId.toArtifact()->getDescriptionTranslated(), CInfoWindow::TCompsInfo(1, comp));
|
||||
};
|
||||
ENGINE->windows().pushWindow(window);
|
||||
}
|
||||
|
||||
void BattleOnlyModeHeroSelector::setArtifactIcons()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
auto artPos = getArtPos();
|
||||
|
||||
for(int i = 0; i < artifactImage.size(); i++)
|
||||
{
|
||||
int xPos = i % 7;
|
||||
int yPos = i / 7;
|
||||
Point imgPos(6 + xPos * 36, 137 + yPos * 36);
|
||||
auto artifactId = parent.startInfo->artifacts[id][artPos[i]];
|
||||
if(artifactId == ArtifactID::NONE)
|
||||
{
|
||||
MetaString str;
|
||||
str.appendTextID("vcmi.lobby.battleOnlyModeSelectArtifact");
|
||||
str.replaceTextID("vcmi.lobby.battleOnlyModeSelectArtifact." + std::to_string(artPos[i]));
|
||||
artifactImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), str.toString(), id == 1 ? parent.boxColor : parent.disabledBoxColor), imgPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), artifactId.toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleTo(Point(32, 32), EScalingAlgorithm::NEAREST);
|
||||
artifactImage[i] = std::make_shared<CPicture>(image, imgPos);
|
||||
}
|
||||
|
||||
artifactImage[i]->addLClickCallback([this, i, artifactId](){ selectArtifact(i, artifactId); });
|
||||
|
||||
artifactImage[i]->addRClickCallback([this, i, artPos](){
|
||||
auto artId = parent.startInfo->artifacts[id][artPos[i]];
|
||||
if(artId == ArtifactID::NONE)
|
||||
return;
|
||||
|
||||
std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::ARTIFACT, artId);
|
||||
CRClickPopup::createAndPush(artId.toArtifact()->getDescriptionTranslated(), CInfoWindow::TCompsInfo(1, comp));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleOnlyModeTab::startBattle()
|
||||
{
|
||||
auto rng = &CRandomGenerator::getDefault();
|
||||
|
||||
map->initTerrain();
|
||||
map->getEditManager()->clearTerrain(rng);
|
||||
|
||||
map->getEditManager()->getTerrainSelection().selectAll();
|
||||
map->getEditManager()->drawTerrain(startInfo->selectedTerrain == TerrainId::NONE ? TerrainId::DIRT : startInfo->selectedTerrain, 0, rng);
|
||||
|
||||
map->players[0].canComputerPlay = true;
|
||||
map->players[0].canHumanPlay = true;
|
||||
map->players[1] = map->players[0];
|
||||
|
||||
auto knownHeroes = LIBRARY->objtypeh->knownSubObjects(Obj::HERO);
|
||||
|
||||
auto addHero = [&, this](int sel, PlayerColor color, const int3 & position)
|
||||
{
|
||||
auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::HERO, startInfo->selectedHero[sel].toHeroType()->heroClass->getId());
|
||||
auto templates = factory->getTemplates();
|
||||
auto obj = std::dynamic_pointer_cast<CGHeroInstance>(factory->create(cb.get(), templates.front()));
|
||||
obj->setHeroType(startInfo->selectedHero[sel]);
|
||||
|
||||
obj->setOwner(color);
|
||||
obj->pos = position;
|
||||
for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
|
||||
obj->pushPrimSkill(PrimarySkill(i), startInfo->primSkillLevel[sel][i]);
|
||||
obj->clearSlots();
|
||||
for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
|
||||
if(startInfo->selectedArmy[sel][slot].getId() != CreatureID::NONE && startInfo->selectedArmy[sel][slot].getCount() > 0)
|
||||
obj->setCreature(SlotID(slot), startInfo->selectedArmy[sel][slot].getId(), startInfo->selectedArmy[sel][slot].getCount());
|
||||
|
||||
// give spellbook
|
||||
if(!obj->getArt(ArtifactPosition::SPELLBOOK) && startInfo->spellBook[sel])
|
||||
obj->putArtifact(ArtifactPosition::SPELLBOOK, map->createArtifact(ArtifactID::SPELLBOOK));
|
||||
else if(obj->getArt(ArtifactPosition::SPELLBOOK) && !startInfo->spellBook[sel])
|
||||
obj->removeArtifact(ArtifactPosition::SPELLBOOK);
|
||||
|
||||
if(startInfo->warMachines[sel])
|
||||
{
|
||||
obj->putArtifact(ArtifactPosition::MACH1, map->createArtifact(ArtifactID::BALLISTA));
|
||||
obj->putArtifact(ArtifactPosition::MACH2, map->createArtifact(ArtifactID::AMMO_CART));
|
||||
obj->putArtifact(ArtifactPosition::MACH3, map->createArtifact(ArtifactID::FIRST_AID_TENT));
|
||||
}
|
||||
|
||||
for(const auto & spell : startInfo->spells[sel])
|
||||
obj->addSpellToSpellbook(spell);
|
||||
|
||||
for(auto & artifact : startInfo->artifacts[sel])
|
||||
if(artifact.second != ArtifactID::NONE)
|
||||
obj->putArtifact(artifact.first, map->createArtifact(artifact.second));
|
||||
|
||||
for(const auto & skill : LIBRARY->skillh->objects) // reset all standard skills
|
||||
obj->setSecSkillLevel(SecondarySkill(skill->getId()), MasteryLevel::NONE, ChangeValueMode::ABSOLUTE);
|
||||
for(int skillSlot = 0; skillSlot < 8; skillSlot++)
|
||||
obj->setSecSkillLevel(startInfo->secSkillLevel[sel][skillSlot].first, startInfo->secSkillLevel[sel][skillSlot].second, ChangeValueMode::ABSOLUTE);
|
||||
|
||||
map->getEditManager()->insertObject(obj);
|
||||
};
|
||||
|
||||
addHero(0, PlayerColor(0), int3(5, 6, 0));
|
||||
if(startInfo->selectedTown == FactionID::ANY)
|
||||
addHero(1, PlayerColor(1), int3(5, 5, 0));
|
||||
else
|
||||
{
|
||||
auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::TOWN, startInfo->selectedTown);
|
||||
auto templates = factory->getTemplates();
|
||||
auto obj = factory->create(cb.get(), templates.front());
|
||||
auto townObj = std::dynamic_pointer_cast<CGTownInstance>(obj);
|
||||
obj->setOwner(PlayerColor(1));
|
||||
obj->pos = int3(5, 5, 0);
|
||||
for (const auto & building : townObj->getTown()->getAllBuildings())
|
||||
townObj->addBuilding(building);
|
||||
if(startInfo->selectedHero[1] == HeroTypeID::NONE)
|
||||
{
|
||||
for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
|
||||
if(startInfo->selectedArmy[1][slot].getId() != CreatureID::NONE && startInfo->selectedArmy[1][slot].getCount() > 0)
|
||||
townObj->getArmy()->setCreature(SlotID(slot), startInfo->selectedArmy[1][slot].getId(), startInfo->selectedArmy[1][slot].getCount());
|
||||
}
|
||||
else
|
||||
addHero(1, PlayerColor(1), int3(5, 5, 0));
|
||||
|
||||
map->getEditManager()->insertObject(townObj);
|
||||
}
|
||||
|
||||
auto path = VCMIDirs::get().userDataPath() / "Maps";
|
||||
boost::filesystem::create_directories(path);
|
||||
const std::string fileName = "BattleOnlyMode.vmap";
|
||||
const auto fullPath = path / fileName;
|
||||
CMapService mapService;
|
||||
mapService.saveMap(map, fullPath);
|
||||
CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount) { return true; });
|
||||
|
||||
auto mapInfo = std::make_shared<CMapInfo>();
|
||||
mapInfo->mapInit("Maps/BattleOnlyMode");
|
||||
GAME->server().setMapInfo(mapInfo);
|
||||
ExtraOptionsInfo extraOptions;
|
||||
extraOptions.unlimitedReplay = true;
|
||||
GAME->server().setExtraOptionsInfo(extraOptions);
|
||||
GAME->server().sendStartGame();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* BattleOnlyMode.h, part of VCMI engine
|
||||
* BattleOnlyModeTab.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "../windows/CWindowObject.h"
|
||||
#include "../../lib/constants/EntityIdentifiers.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@@ -25,27 +26,32 @@ class FilledTexturePlayerColored;
|
||||
class CButton;
|
||||
class CPicture;
|
||||
class CLabel;
|
||||
class BattleOnlyModeWindow;
|
||||
class CMultiLineLabel;
|
||||
class BattleOnlyModeTab;
|
||||
class CAnimImage;
|
||||
class GraphicalPrimitiveCanvas;
|
||||
class CTextInput;
|
||||
class TransparentFilledRectangle;
|
||||
|
||||
class BattleOnlyMode
|
||||
{
|
||||
public:
|
||||
static void openBattleWindow();
|
||||
};
|
||||
class CToggleButton;
|
||||
|
||||
class BattleOnlyModeHeroSelector : public CIntObject
|
||||
{
|
||||
private:
|
||||
BattleOnlyModeWindow& parent;
|
||||
BattleOnlyModeTab& parent;
|
||||
|
||||
std::shared_ptr<CPicture> backgroundImage;
|
||||
std::shared_ptr<CPicture> heroImage;
|
||||
std::shared_ptr<CLabel> heroLabel;
|
||||
std::vector<std::shared_ptr<CPicture>> creatureImage;
|
||||
std::vector<std::shared_ptr<CPicture>> secSkillImage;
|
||||
std::vector<std::shared_ptr<CPicture>> artifactImage;
|
||||
|
||||
std::vector<std::shared_ptr<CPicture>> addIcon;
|
||||
|
||||
void selectHero();
|
||||
void selectCreature(int slot);
|
||||
void selectSecSkill(int slot);
|
||||
void selectArtifact(int slot, ArtifactID artifactId);
|
||||
|
||||
int id;
|
||||
public:
|
||||
@@ -54,13 +60,20 @@ public:
|
||||
std::vector<std::shared_ptr<CTextInput>> primSkillsInput;
|
||||
|
||||
std::vector<std::shared_ptr<CTextInput>> selectedArmyInput;
|
||||
std::vector<std::shared_ptr<CTextInput>> selectedSecSkillInput;
|
||||
|
||||
std::shared_ptr<CToggleButton> spellBook;
|
||||
std::shared_ptr<CToggleButton> warMachines;
|
||||
|
||||
void setHeroIcon();
|
||||
void setCreatureIcons();
|
||||
BattleOnlyModeHeroSelector(int id, BattleOnlyModeWindow& parent, Point position);
|
||||
void setSecSkillIcons();
|
||||
void setArtifactIcons();
|
||||
void manageSpells();
|
||||
BattleOnlyModeHeroSelector(int id, BattleOnlyModeTab& parent, Point position);
|
||||
};
|
||||
|
||||
class BattleOnlyModeWindow : public CWindowObject
|
||||
class BattleOnlyModeTab : public CIntObject
|
||||
{
|
||||
friend class BattleOnlyModeHeroSelector;
|
||||
private:
|
||||
@@ -68,10 +81,9 @@ private:
|
||||
std::unique_ptr<CMap> map;
|
||||
std::shared_ptr<EditorCallback> cb;
|
||||
|
||||
std::shared_ptr<FilledTexturePlayerColored> backgroundTexture;
|
||||
std::shared_ptr<CButton> buttonOk;
|
||||
std::shared_ptr<CButton> buttonAbort;
|
||||
std::shared_ptr<CPicture> backgroundImage;
|
||||
std::shared_ptr<CLabel> title;
|
||||
std::shared_ptr<CMultiLineLabel> subTitle;
|
||||
|
||||
std::shared_ptr<CButton> battlefieldSelector;
|
||||
std::shared_ptr<CButton> buttonReset;
|
||||
@@ -79,14 +91,18 @@ private:
|
||||
std::shared_ptr<BattleOnlyModeHeroSelector> heroSelector2;
|
||||
|
||||
ColorRGBA disabledColor;
|
||||
ColorRGBA boxColor;
|
||||
ColorRGBA disabledBoxColor;
|
||||
|
||||
void init();
|
||||
void onChange();
|
||||
void update();
|
||||
void setTerrainButtonText();
|
||||
void setOkButtonEnabled();
|
||||
void startBattle();
|
||||
void selectTerrain();
|
||||
void reset();
|
||||
public:
|
||||
BattleOnlyModeWindow();
|
||||
BattleOnlyModeTab();
|
||||
void applyStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> si);
|
||||
void startBattle();
|
||||
void setStartButtonEnabled();
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "OptionsTab.h"
|
||||
#include "RandomMapTab.h"
|
||||
#include "SelectionTab.h"
|
||||
#include "BattleOnlyModeTab.h"
|
||||
|
||||
#include "../CServerHandler.h"
|
||||
#include "../GameEngine.h"
|
||||
@@ -87,7 +88,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType, bool hideScreen)
|
||||
|
||||
card->iconDifficulty->addCallback(std::bind(&IServerAPI::setDifficulty, &GAME->server(), _1));
|
||||
|
||||
buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRBEG.DEF"), LIBRARY->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), EShortcut::LOBBY_BEGIN_STANDARD_GAME);
|
||||
buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRBEG.DEF"), LIBRARY->generaltexth->zelp[103], std::bind(&CLobbyScreen::start, this, false), EShortcut::LOBBY_BEGIN_STANDARD_GAME);
|
||||
initLobby();
|
||||
break;
|
||||
}
|
||||
@@ -96,13 +97,13 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType, bool hideScreen)
|
||||
tabOpt = std::make_shared<OptionsTab>();
|
||||
tabTurnOptions = std::make_shared<TurnOptionsTab>();
|
||||
tabExtraOptions = std::make_shared<ExtraOptionsTab>();
|
||||
buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), LIBRARY->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), EShortcut::LOBBY_LOAD_GAME);
|
||||
buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), LIBRARY->generaltexth->zelp[103], std::bind(&CLobbyScreen::start, this, false), EShortcut::LOBBY_LOAD_GAME);
|
||||
initLobby();
|
||||
break;
|
||||
}
|
||||
case ESelectionScreen::campaignList:
|
||||
tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, &GAME->server(), _1, nullptr);
|
||||
buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CButton::tooltip(), std::bind(&CLobbyScreen::startCampaign, this), EShortcut::LOBBY_BEGIN_CAMPAIGN);
|
||||
buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CButton::tooltip(), std::bind(&CLobbyScreen::start, this, true), EShortcut::LOBBY_BEGIN_CAMPAIGN);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -146,9 +147,33 @@ void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
|
||||
GAME->server().sendGuiAction(LobbyGuiAction::OPEN_TURN_OPTIONS);
|
||||
else if(tab == tabExtraOptions)
|
||||
GAME->server().sendGuiAction(LobbyGuiAction::OPEN_EXTRA_OPTIONS);
|
||||
else if(tab == tabBattleOnlyMode)
|
||||
GAME->server().sendGuiAction(LobbyGuiAction::BATTLE_MODE);
|
||||
|
||||
if(tab == tabBattleOnlyMode)
|
||||
{
|
||||
tabBattleOnlyMode->setStartButtonEnabled();
|
||||
card->clearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonStart->block(GAME->server().mi == nullptr || GAME->server().isGuest());
|
||||
card->changeSelection();
|
||||
}
|
||||
|
||||
CSelectionBase::toggleTab(tab);
|
||||
}
|
||||
|
||||
void CLobbyScreen::start(bool campaign)
|
||||
{
|
||||
if(curTab == tabBattleOnlyMode)
|
||||
tabBattleOnlyMode->startBattle();
|
||||
else if(campaign)
|
||||
startCampaign();
|
||||
else
|
||||
startScenario(false);
|
||||
}
|
||||
|
||||
void CLobbyScreen::startCampaign()
|
||||
{
|
||||
if(!GAME->server().mi)
|
||||
@@ -239,6 +264,16 @@ void CLobbyScreen::toggleChat()
|
||||
|
||||
void CLobbyScreen::updateAfterStateChange()
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(!tabBattleOnlyMode)
|
||||
{
|
||||
tabBattleOnlyMode = std::make_shared<BattleOnlyModeTab>();
|
||||
tabBattleOnlyMode->setEnabled(false);
|
||||
|
||||
if(GAME->server().battleMode)
|
||||
toggleTab(tabBattleOnlyMode);
|
||||
}
|
||||
|
||||
if(GAME->server().isHost() && screenType == ESelectionScreen::newGame)
|
||||
{
|
||||
bool isMultiplayer = GAME->server().loadMode == ELoadMode::MULTI;
|
||||
@@ -259,9 +294,12 @@ void CLobbyScreen::updateAfterStateChange()
|
||||
tabExtraOptions->recreate();
|
||||
}
|
||||
|
||||
buttonStart->block(GAME->server().mi == nullptr || GAME->server().isGuest());
|
||||
if(curTab && curTab != tabBattleOnlyMode)
|
||||
{
|
||||
buttonStart->block(GAME->server().mi == nullptr || GAME->server().isGuest());
|
||||
card->changeSelection();
|
||||
}
|
||||
|
||||
card->changeSelection();
|
||||
if (card->iconDifficulty)
|
||||
{
|
||||
if (screenType == ESelectionScreen::loadGame)
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
CLobbyScreen(ESelectionScreen type, bool hideScreen = false);
|
||||
~CLobbyScreen();
|
||||
void toggleTab(std::shared_ptr<CIntObject> tab) final;
|
||||
void start(bool campaign);
|
||||
void startCampaign();
|
||||
void startScenario(bool allowOnlyAI = false);
|
||||
void toggleMode(bool host);
|
||||
|
||||
@@ -264,6 +264,7 @@ void InfoCard::changeSelection()
|
||||
labelLossConditionText->setText(header->defeatMessage.toString());
|
||||
flagbox->recreate();
|
||||
labelDifficulty->setText(LIBRARY->generaltexth->arraytxt[142 + vstd::to_underlying(mapInfo->mapHeader->difficulty)]);
|
||||
iconDifficulty->activate();
|
||||
iconDifficulty->setSelected(SEL->getCurrentDifficulty());
|
||||
if(SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::saveGame)
|
||||
for(auto & button : iconDifficulty->buttons)
|
||||
@@ -294,6 +295,24 @@ void InfoCard::changeSelection()
|
||||
}
|
||||
}
|
||||
|
||||
void InfoCard::clearSelection()
|
||||
{
|
||||
labelSaveDate->setText("");
|
||||
mapName->setText("");
|
||||
mapDescription->setText("");
|
||||
|
||||
if(SEL->screenType == ESelectionScreen::campaignList)
|
||||
return;
|
||||
|
||||
labelMapSize->setText("");
|
||||
|
||||
labelVictoryConditionText->setText("");
|
||||
labelLossConditionText->setText("");
|
||||
iconDifficulty->deactivate();
|
||||
labelDifficulty->setText("");
|
||||
labelDifficultyPercent->setText("");
|
||||
}
|
||||
|
||||
void InfoCard::toggleChat()
|
||||
{
|
||||
setChat(!showChat);
|
||||
|
||||
@@ -29,6 +29,7 @@ class OptionsTab;
|
||||
class TurnOptionsTab;
|
||||
class ExtraOptionsTab;
|
||||
class SelectionTab;
|
||||
class BattleOnlyModeTab;
|
||||
class InfoCard;
|
||||
class CChatBox;
|
||||
class PvPBox;
|
||||
@@ -78,6 +79,7 @@ public:
|
||||
std::shared_ptr<TurnOptionsTab> tabTurnOptions;
|
||||
std::shared_ptr<ExtraOptionsTab> tabExtraOptions;
|
||||
std::shared_ptr<RandomMapTab> tabRand;
|
||||
std::shared_ptr<BattleOnlyModeTab> tabBattleOnlyMode;
|
||||
std::shared_ptr<CIntObject> curTab;
|
||||
|
||||
CSelectionBase(ESelectionScreen type);
|
||||
@@ -127,6 +129,7 @@ public:
|
||||
InfoCard();
|
||||
void disableLabelRedraws();
|
||||
void changeSelection();
|
||||
void clearSelection();
|
||||
void toggleChat();
|
||||
void setChat(bool activateChat);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "SelectionTab.h"
|
||||
#include "CSelectionBase.h"
|
||||
#include "CLobbyScreen.h"
|
||||
#include "BattleOnlyMode.h"
|
||||
#include "BattleOnlyModeTab.h"
|
||||
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CServerHandler.h"
|
||||
@@ -244,9 +244,10 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
|
||||
|
||||
if(tabType == ESelectionScreen::newGame)
|
||||
{
|
||||
buttonBattleOnlyMode = std::make_shared<CButton>(Point(23, 18), AnimationPath::builtin("lobby/battleButton"), CButton::tooltip("", LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode")), [tabTitle, tabTitleDelete](){
|
||||
BattleOnlyMode::openBattleWindow();
|
||||
});
|
||||
buttonBattleOnlyMode = std::make_shared<CButton>(Point(23, 18), AnimationPath::builtin("lobby/battleButton"), CButton::tooltip("", LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode")), [this](){
|
||||
auto lobby = static_cast<CLobbyScreen *>(parent);
|
||||
lobby->toggleTab(lobby->tabBattleOnlyMode);
|
||||
}, EShortcut::LOBBY_BATTLE_MODE);
|
||||
}
|
||||
|
||||
if(tabType == ESelectionScreen::loadGame || tabType == ESelectionScreen::newGame)
|
||||
|
||||
@@ -144,7 +144,7 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
|
||||
{
|
||||
static const std::vector<std::string> commandType = {"to", "campaigns", "start", "load", "exit", "highscores"};
|
||||
|
||||
static const std::vector<std::string> gameType = {"single", "multi", "campaign", "tutorial"};
|
||||
static const std::vector<std::string> gameType = {"single", "multi", "campaign", "tutorial", "battle"};
|
||||
|
||||
std::list<std::string> commands;
|
||||
boost::split(commands, string, boost::is_any_of("\t "));
|
||||
@@ -174,13 +174,15 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
|
||||
switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
|
||||
{
|
||||
case 0:
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE); };
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE, false); };
|
||||
case 1:
|
||||
return []() { ENGINE->windows().createAndPushWindow<CMultiMode>(ESelectionScreen::newGame); };
|
||||
case 2:
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::campaignList, true, {}, ELoadMode::NONE); };
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::campaignList, true, {}, ELoadMode::NONE, false); };
|
||||
case 3:
|
||||
return []() { CMainMenu::startTutorial(); };
|
||||
case 4:
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE, true); };
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -189,13 +191,13 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
|
||||
switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
|
||||
{
|
||||
case 0:
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::SINGLE); };
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::SINGLE, false); };
|
||||
case 1:
|
||||
return []() { ENGINE->windows().createAndPushWindow<CMultiMode>(ESelectionScreen::loadGame); };
|
||||
case 2:
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::CAMPAIGN); };
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::CAMPAIGN, false); };
|
||||
case 3:
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::TUTORIAL); };
|
||||
return []() { CMainMenu::openLobby(ESelectionScreen::loadGame, true, {}, ELoadMode::TUTORIAL, false); };
|
||||
|
||||
}
|
||||
}
|
||||
@@ -402,10 +404,11 @@ void CMainMenu::makeActiveInterface()
|
||||
menu->switchToTab(menu->getActiveTab());
|
||||
}
|
||||
|
||||
void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> & names, ELoadMode loadMode)
|
||||
void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> & names, ELoadMode loadMode, bool battleMode)
|
||||
{
|
||||
GAME->server().resetStateForLobby(screenType == ESelectionScreen::newGame ? EStartMode::NEW_GAME : EStartMode::LOAD_GAME, screenType, EServerMode::LOCAL, names);
|
||||
GAME->server().loadMode = loadMode;
|
||||
GAME->server().battleMode = battleMode;
|
||||
|
||||
ENGINE->windows().createAndPushWindow<CSimpleJoinScreen>(host);
|
||||
}
|
||||
@@ -448,7 +451,7 @@ void CMainMenu::startTutorial()
|
||||
|
||||
auto mapInfo = std::make_shared<CMapInfo>();
|
||||
mapInfo->mapInit(tutorialMap.getName());
|
||||
CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE);
|
||||
CMainMenu::openLobby(ESelectionScreen::newGame, true, {}, ELoadMode::NONE, false);
|
||||
GAME->server().startMapAfterConnection(mapInfo);
|
||||
}
|
||||
|
||||
@@ -615,7 +618,7 @@ void CMultiPlayers::enterSelectionScreen()
|
||||
playerName->clear();
|
||||
}
|
||||
|
||||
CMainMenu::openLobby(screenType, host, playerNames, loadMode);
|
||||
CMainMenu::openLobby(screenType, host, playerNames, loadMode, false);
|
||||
}
|
||||
|
||||
CSimpleJoinScreen::CSimpleJoinScreen(bool host)
|
||||
|
||||
@@ -154,7 +154,7 @@ public:
|
||||
void activate() override;
|
||||
void onScreenResize() override;
|
||||
void makeActiveInterface();
|
||||
static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> & names, ELoadMode loadMode);
|
||||
static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> & names, ELoadMode loadMode, bool battleMode);
|
||||
static void openCampaignLobby(const std::string & campaignFileName, std::string campaignSet = "");
|
||||
static void openCampaignLobby(std::shared_ptr<CampaignState> campaign);
|
||||
static void startTutorial();
|
||||
|
||||
@@ -954,11 +954,24 @@ AssetGenerator::CanvasPtr AssetGenerator::createHeroSlotsColored(PlayerColor bac
|
||||
static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = getColorFilters();
|
||||
img->adjustPalette(filters[backColor.getNum()], 0);
|
||||
|
||||
auto image = ENGINE->renderHandler().createImage(Point(260, 150), CanvasScalingPolicy::IGNORE);
|
||||
auto image = ENGINE->renderHandler().createImage(Point(327, 216), CanvasScalingPolicy::IGNORE);
|
||||
Canvas canvas = image->getCanvas();
|
||||
canvas.draw(img, Point(0, 0), Rect(3, 4, 253, 107));
|
||||
for(int i = 0; i<7; i++)
|
||||
canvas.draw(img, Point(1 + i * 36, 108), Rect(76, 57, 35, 17));
|
||||
|
||||
// sec skill
|
||||
for(int x = 0; x<2; x++)
|
||||
for(int y = 0; y<4; y++)
|
||||
{
|
||||
canvas.draw(img, Point(255 + x * 36, y * (36 + 18)), Rect(3, 75, 36, 36));
|
||||
canvas.draw(img, Point(256 + x * 36, 37 + y * (36 + 18)), Rect(76, 57, 35, 17));
|
||||
}
|
||||
|
||||
// artifacts
|
||||
for(int x = 0; x<7; x++)
|
||||
for(int y = 0; y<2; y++)
|
||||
canvas.draw(img, Point(x * 36, 130 + y * 36), Rect(3, 75, 36, 36));
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ Rect ScalableImageInstance::contentRect() const
|
||||
Point ScalableImageInstance::dimensions() const
|
||||
{
|
||||
if (scaledImage)
|
||||
return scaledImage->dimensions() / ENGINE->screenHandler().getScalingFactor();
|
||||
return scaledImage->dimensions();
|
||||
return image->dimensions();
|
||||
}
|
||||
|
||||
|
||||
@@ -381,7 +381,16 @@ void CTextInput::numberFilter(std::string & text, const std::string & oldText, i
|
||||
if (value < minValue)
|
||||
text = metricDigits ? TextOperations::formatMetric(minValue, metricDigits) : std::to_string(minValue);
|
||||
else if (value > maxValue)
|
||||
text = metricDigits ? TextOperations::formatMetric(maxValue, metricDigits) : std::to_string(maxValue);
|
||||
{
|
||||
if(text.length() == 2 && oldText.length() == 1)
|
||||
{
|
||||
// special case: if max val < 10 you cannot erase number to add another -> in this case just use last typed number
|
||||
int newNum = std::stoi(text.substr(text.size() - 1));
|
||||
text = std::to_string(newNum < maxValue ? newNum : maxValue);
|
||||
}
|
||||
else
|
||||
text = metricDigits ? TextOperations::formatMetric(maxValue, metricDigits) : std::to_string(maxValue);
|
||||
}
|
||||
}
|
||||
|
||||
void CTextInput::activate()
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/ColorFilter.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
|
||||
#include "../battle/BattleInterface.h"
|
||||
|
||||
@@ -150,7 +151,10 @@ void CPicture::addRClickCallback(const std::function<void()> & callback)
|
||||
void CPicture::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
if(lCallback)
|
||||
{
|
||||
ENGINE->input().hapticFeedback();
|
||||
lCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void CPicture::showPopupWindow(const Point & cursorPosition)
|
||||
|
||||
@@ -351,12 +351,13 @@ Rect CMultiLineLabel::getTextLocation()
|
||||
|
||||
switch(alignment)
|
||||
{
|
||||
case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSizeComputed);
|
||||
case ETextAlignment::TOPCENTER: return Rect(pos.topLeft(), textSizeComputed);
|
||||
case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSizeComputed);
|
||||
case ETextAlignment::CENTERLEFT: return Rect(pos.topLeft() + Point(0, textOffset.y / 2), textSizeComputed);
|
||||
case ETextAlignment::CENTERRIGHT: return Rect(pos.topLeft() + Point(textOffset.x, textOffset.y / 2), textSizeComputed);
|
||||
case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSizeComputed);
|
||||
case ETextAlignment::TOPLEFT: return Rect(pos.topLeft(), textSizeComputed);
|
||||
case ETextAlignment::TOPCENTER: return Rect(pos.topLeft(), textSizeComputed);
|
||||
case ETextAlignment::CENTER: return Rect(pos.topLeft() + textOffset / 2, textSizeComputed);
|
||||
case ETextAlignment::CENTERLEFT: return Rect(pos.topLeft() + Point(0, textOffset.y / 2), textSizeComputed);
|
||||
case ETextAlignment::CENTERRIGHT: return Rect(pos.topLeft() + Point(textOffset.x, textOffset.y / 2), textSizeComputed);
|
||||
case ETextAlignment::BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSizeComputed);
|
||||
case ETextAlignment::BOTTOMCENTER: return Rect(pos.topLeft() + Point(textOffset.x / 2, textOffset.y), textSizeComputed);
|
||||
}
|
||||
assert(0);
|
||||
return Rect();
|
||||
|
||||
Reference in New Issue
Block a user