1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Merge branch 'develop' into feature/nullkiller2

This commit is contained in:
Mircea TheHonestCTO
2025-11-15 17:15:46 +01:00
151 changed files with 17411 additions and 9818 deletions

View File

@@ -52,6 +52,7 @@ set(vcmiclientcommon_SRCS
gui/ShortcutHandler.cpp
gui/WindowHandler.cpp
lobby/BattleOnlyMode.cpp
lobby/CBonusSelection.cpp
lobby/CCampaignInfoScreen.cpp
lobby/CLobbyScreen.cpp
@@ -262,6 +263,7 @@ set(vcmiclientcommon_HEADERS
gui/TextAlignment.h
gui/WindowHandler.h
lobby/BattleOnlyMode.h
lobby/CBonusSelection.h
lobby/CCampaignInfoScreen.h
lobby/CLobbyScreen.h

View File

@@ -97,6 +97,7 @@
#include "../lib/mapObjects/MiscObjects.h"
#include "../lib/mapObjects/ObjectTemplate.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapHeader.h"
#include "../lib/networkPacks/PacksForClient.h"
@@ -658,7 +659,7 @@ void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet
{
EVENT_HANDLER_CALLED_BY_CLIENT;
bool useQuickCombat = settings["adventure"]["quickCombat"].Bool();
bool useQuickCombat = settings["adventure"]["quickCombat"].Bool() || GAME->map().getMap()->battleOnly;
bool forceQuickCombat = settings["adventure"]["forceQuickCombat"].Bool();
if ((replayAllowed && useQuickCombat) || forceQuickCombat)
@@ -1482,7 +1483,7 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
{
if(reason == PlayerBlocked::EReason::UPCOMING_BATTLE)
{
if(GAME->server().howManyPlayerInterfaces() > 1 && GAME->interface() != this && GAME->interface()->makingTurn == false)
if(GAME->server().howManyPlayerInterfaces() > 1 && GAME->interface() != this && GAME->interface()->makingTurn == false && !GAME->map().getMap()->battleOnly)
{
//one of our players who isn't last in order got attacked not by our another player (happens for example in hotseat mode)
GAME->setInterfaceInstance(this);

View File

@@ -432,6 +432,13 @@ void CServerHandler::setCampaignBonus(int bonusId) const
sendLobbyPack(lscb);
}
void CServerHandler::setBattleOnlyModeStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> startInfo) const
{
LobbySetBattleOnlyModeStartInfo lsbomsui;
lsbomsui.startInfo = startInfo;
sendLobbyPack(lsbomsui);
}
void CServerHandler::setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts) const
{
LobbySetMap lsm;

View File

@@ -77,6 +77,7 @@ public:
virtual void setCampaignState(std::shared_ptr<CampaignState> newCampaign) = 0;
virtual void setCampaignMap(CampaignScenarioID mapId) const = 0;
virtual void setCampaignBonus(int bonusId) const = 0;
virtual void setBattleOnlyModeStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> startInfo) const = 0;
virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
virtual void setPlayer(PlayerColor color) const = 0;
virtual void setPlayerName(PlayerColor color, const std::string & name) const = 0;
@@ -186,6 +187,7 @@ public:
void setCampaignState(std::shared_ptr<CampaignState> newCampaign) override;
void setCampaignMap(CampaignScenarioID mapId) const override;
void setCampaignBonus(int bonusId) const override;
void setBattleOnlyModeStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> startInfo) const override;
void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
void setPlayer(PlayerColor color) const override;
void setPlayerName(PlayerColor color, const std::string & name) const override;

View File

@@ -59,4 +59,5 @@ public:
void visitLobbyLoadProgress(LobbyLoadProgress & pack) override;
void visitLobbyUpdateState(LobbyUpdateState & pack) override;
void visitLobbyShowMessage(LobbyShowMessage & pack) override;
void visitLobbySetBattleOnlyModeStartInfo(LobbySetBattleOnlyModeStartInfo & pack) override;
};

View File

@@ -15,6 +15,7 @@
#include "windows/GUIClasses.h"
#include "windows/CCastleInterface.h"
#include "mapView/mapHandler.h"
#include "mainmenu/CMainMenu.h"
#include "adventureMap/AdventureMapInterface.h"
#include "adventureMap/CInGameConsole.h"
#include "battle/BattleInterface.h"
@@ -400,7 +401,7 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
bool localHumanWinsGame = vstd::contains(cl.playerint, pack.player) && cl.gameInfo().getPlayerState(pack.player)->human && pack.victoryLossCheckResult.victory();
bool lastHumanEndsGame = GAME->server().howManyPlayerInterfaces() == 1 && vstd::contains(cl.playerint, pack.player) && cl.gameInfo().getPlayerState(pack.player)->human && !settings["session"]["spectate"].Bool();
if(lastHumanEndsGame || localHumanWinsGame)
if(lastHumanEndsGame || localHumanWinsGame || pack.silentEnd)
{
assert(adventureInt);
if(adventureInt)
@@ -409,7 +410,13 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
adventureInt.reset();
}
GAME->server().showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory(), pack.statistic);
if(!pack.silentEnd)
GAME->server().showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory(), pack.statistic);
else
{
GAME->server().endGameplay();
GAME->mainmenu()->menu->switchToTab("main");
}
}
// In auto testing pack.mode we always close client if red pack.player won or lose

View File

@@ -19,6 +19,7 @@
#include "lobby/ExtraOptionsTab.h"
#include "lobby/SelectionTab.h"
#include "lobby/CBonusSelection.h"
#include "lobby/BattleOnlyMode.h"
#include "globalLobby/GlobalLobbyWindow.h"
#include "globalLobby/GlobalLobbyServerSetup.h"
#include "globalLobby/GlobalLobbyClient.h"
@@ -113,6 +114,9 @@ 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:
@@ -133,6 +137,9 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyGuiAction(LobbyGuiAction & pack
case LobbyGuiAction::OPEN_EXTRA_OPTIONS:
lobby->toggleTab(lobby->tabExtraOptions);
break;
case LobbyGuiAction::BATTLE_MODE:
BattleOnlyMode::openBattleWindow();
break;
}
}
@@ -232,3 +239,9 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyShowMessage(LobbyShowMessage &
lobby->buttonStart->block(false);
handler.showServerError(pack.message.toString());
}
void ApplyOnLobbyScreenNetPackVisitor::visitLobbySetBattleOnlyModeStartInfo(LobbySetBattleOnlyModeStartInfo & pack)
{
if(auto topWindow = ENGINE->windows().topWindow<BattleOnlyModeWindow>())
topWindow->applyStartInfo(pack.startInfo);
}

View File

@@ -264,6 +264,7 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
case PossiblePlayerBattleAction::NO_LOCATION:
case PossiblePlayerBattleAction::FREE_LOCATION:
case PossiblePlayerBattleAction::OBSTACLE:
case PossiblePlayerBattleAction::SACRIFICE:
if(!stack->hasBonusOfType(BonusType::NO_SPELLCAST_BY_DEFAULT) && targetStack != nullptr)
{
PlayerColor stackOwner = owner.getBattle()->battleGetOwner(targetStack);
@@ -652,10 +653,11 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, c
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
{
if (owner.fieldController->isTileAttackable(targetHex)) // move isTileAttackable to be part of battleCanAttack?
auto activeStack = owner.stacksController->getActiveStack();
if (targetStack && targetStack != activeStack && owner.fieldController->isTileAttackable(targetHex)) // move isTileAttackable to be part of battleCanAttack?
{
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
if(owner.getBattle()->battleCanAttack(owner.stacksController->getActiveStack(), targetStack, attackFromHex))
if(owner.getBattle()->battleCanAttack(activeStack, targetStack, attackFromHex))
return true;
}
return false;
@@ -698,7 +700,14 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, c
return selectedStack && isCastingPossibleHere(action.spell().toSpell(), selectedStack, targetHex);
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
return targetStack && targetStack != selectedStack && targetStackOwned && targetStack->alive();
{
if(!targetStack)
return false;
auto unit = targetStack->acquire();
return targetStack != selectedStack && targetStackOwned && targetStack->alive()
&& unit->isLiving() && !unit->hasBonusOfType(BonusType::MECHANICAL);
}
case PossiblePlayerBattleAction::OBSTACLE:
case PossiblePlayerBattleAction::FREE_LOCATION:

View File

@@ -819,7 +819,7 @@ bool BattleFieldController::isTileAttackable(const BattleHex & number) const
for (auto & elem : occupiableHexes)
{
if (BattleHex::mutualPosition(elem, number) != BattleHex::EDir::NONE || elem == number)
if (BattleHex::mutualPosition(elem, number) != BattleHex::EDir::NONE)
return true;
}
return false;

View File

@@ -161,7 +161,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
if(creature->getId() == CreatureID::ARROW_TOWERS)
creature = owner.siegeController->getTurretCreature(stack->initialPosition);
if(creature->animation.missileFrameAngles.empty())
if(creature->animation.missileFrameAngles.empty() && creature->animation.projectileRay.empty())
{
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
creature = CreatureID(CreatureID::ARCHER).toCreature();

View File

@@ -12,6 +12,9 @@
#include "BattleWindow.h"
#include "../GameInstance.h"
#include "../Client.h"
#include "../CServerHandler.h"
#include "../CPlayerInterface.h"
#include "../GameEngine.h"
#include "../gui/Shortcut.h"
@@ -23,6 +26,7 @@
#include "../widgets/VideoWidget.h"
#include "../../lib/CStack.h"
#include "../../lib/CPlayerState.h"
#include "../../lib/ConditionalWait.h"
#include "../../lib/GameLibrary.h"
#include "../../lib/StartInfo.h"
@@ -45,7 +49,14 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
exit = std::make_shared<CButton>(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [this](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
exit->setBorderColor(Colors::METALLIC_GOLD);
if(allowReplay || owner.cb->getStartInfo()->extraOptionsInfo.unlimitedReplay)
auto battle = owner.cb->getBattle(br.battleID);
const auto * attackerPlayer = GAME->server().client->gameInfo().getPlayerState(battle->sideToPlayer(BattleSide::ATTACKER));
const auto * defenderPlayer = GAME->server().client->gameInfo().getPlayerState(battle->sideToPlayer(BattleSide::DEFENDER));
bool isAttackerHuman = attackerPlayer && attackerPlayer->isHuman();
bool isDefenderHuman = defenderPlayer && defenderPlayer->isHuman();
bool onlyOnePlayerHuman = isAttackerHuman != isDefenderHuman;
if((allowReplay || owner.cb->getStartInfo()->extraOptionsInfo.unlimitedReplay) && onlyOnePlayerHuman)
{
repeat = std::make_shared<CButton>(Point(24, 505), AnimationPath::builtin("icn6432.def"), std::make_pair("", ""), [this](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
repeat->setBorderColor(Colors::METALLIC_GOLD);

View File

@@ -51,6 +51,7 @@
#include "../../lib/entities/artifact/CArtHandler.h"
#include "../../lib/filesystem/ResourcePath.h"
#include "../../lib/gameState/InfoAboutArmy.h"
#include "../../lib/mapping/CMapHeader.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/texts/CGeneralTextHandler.h"
@@ -851,6 +852,8 @@ void BattleWindow::endWithAutocombat()
void BattleWindow::showAll(Canvas & to)
{
if(owner.curInt->cb->getMapHeader()->battleOnly)
to.fillTexture(ENGINE->renderHandler().loadImage(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE));
CIntObject::showAll(to);
if (ENGINE->screenDimensions().x != 800 || ENGINE->screenDimensions().y !=600)

View File

@@ -132,7 +132,7 @@ class CursorHandler final
Point pos;
float frameTime;
int32_t currentCursorIndex;
int32_t currentFrame;
int32_t currentFrame {};
Cursor::ShowType showType;
bool showing;

View File

@@ -0,0 +1,517 @@
/*
* 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();
}

View File

@@ -0,0 +1,92 @@
/*
* BattleOnlyMode.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../windows/CWindowObject.h"
#include "../../lib/constants/EntityIdentifiers.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CCreatureSet;
class CMap;
class EditorCallback;
class BattleOnlyModeStartInfo;
VCMI_LIB_NAMESPACE_END
class FilledTexturePlayerColored;
class CButton;
class CPicture;
class CLabel;
class BattleOnlyModeWindow;
class CAnimImage;
class GraphicalPrimitiveCanvas;
class CTextInput;
class TransparentFilledRectangle;
class BattleOnlyMode
{
public:
static void openBattleWindow();
};
class BattleOnlyModeHeroSelector : public CIntObject
{
private:
BattleOnlyModeWindow& parent;
std::shared_ptr<CPicture> backgroundImage;
std::shared_ptr<CPicture> heroImage;
std::shared_ptr<CLabel> heroLabel;
std::vector<std::shared_ptr<CPicture>> creatureImage;
int id;
public:
std::vector<std::shared_ptr<CAnimImage>> primSkills;
std::vector<std::shared_ptr<GraphicalPrimitiveCanvas>> primSkillsBorder;
std::vector<std::shared_ptr<CTextInput>> primSkillsInput;
std::vector<std::shared_ptr<CTextInput>> selectedArmyInput;
void setHeroIcon();
void setCreatureIcons();
BattleOnlyModeHeroSelector(int id, BattleOnlyModeWindow& parent, Point position);
};
class BattleOnlyModeWindow : public CWindowObject
{
friend class BattleOnlyModeHeroSelector;
private:
std::shared_ptr<BattleOnlyModeStartInfo> startInfo;
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<CLabel> title;
std::shared_ptr<CButton> battlefieldSelector;
std::shared_ptr<CButton> buttonReset;
std::shared_ptr<BattleOnlyModeHeroSelector> heroSelector1;
std::shared_ptr<BattleOnlyModeHeroSelector> heroSelector2;
ColorRGBA disabledColor;
void init();
void onChange();
void update();
void setTerrainButtonText();
void setOkButtonEnabled();
void startBattle();
public:
BattleOnlyModeWindow();
void applyStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> si);
};

View File

@@ -172,14 +172,26 @@ RandomMapTab::RandomMapTab():
{
std::vector<std::string> texts;
texts.push_back(readText(variables["randomTemplate"]));
for(auto & t : getTemplates())
texts.push_back(t->getName());
auto selectedTemplate = mapGenOptions->getMapTemplate();
const auto& templates = getTemplates();
for(int i = 0; i < templates.size(); i++)
{
if(selectedTemplate)
{
if(templates[i]->getId() == selectedTemplate->getId())
templateIndex = i + 1;
}
else
templateIndex = 0;
texts.push_back(templates[i]->getName());
}
ENGINE->windows().popWindows(1);
ENGINE->windows().createAndPushWindow<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.templatesSelect.hover"), LIBRARY->generaltexth->translate("vcmi.lobby.templatesSelect.help"), [this](int index){
widget<ComboBox>("templateList")->setItem(index);
templateIndex = index;
}, templateIndex, std::vector<std::shared_ptr<IImage>>(), true);
}, templateIndex, std::vector<std::shared_ptr<IImage>>(), true, true);
});
}

View File

@@ -12,6 +12,7 @@
#include "SelectionTab.h"
#include "CSelectionBase.h"
#include "CLobbyScreen.h"
#include "BattleOnlyMode.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
@@ -241,6 +242,13 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
sortByDate->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/selectionTabSortDate")));
buttonsSortBy.push_back(sortByDate);
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();
});
}
if(tabType == ESelectionScreen::loadGame || tabType == ESelectionScreen::newGame)
{
buttonDeleteMode = std::make_shared<CButton>(Point(367, 18), AnimationPath::builtin("lobby/deleteButton"), CButton::tooltip("", LIBRARY->generaltexth->translate("vcmi.lobby.deleteMode")), [this, tabTitle, tabTitleDelete](){
@@ -315,6 +323,8 @@ void SelectionTab::toggleMode()
{
if(slider)
slider->block(true);
if(buttonBattleOnlyMode)
buttonBattleOnlyMode->block(true);
}
else
{
@@ -325,6 +335,7 @@ void SelectionTab::toggleMode()
inputName->disable();
auto files = getFiles("Maps/", EResType::MAP);
files.erase(ResourcePath("Maps/Tutorial.tut", EResType::MAP));
files.erase(ResourcePath("Maps/BattleOnlyMode.vmap", EResType::MAP));
parseMaps(files);
break;
}

View File

@@ -128,6 +128,8 @@ private:
std::shared_ptr<CButton> buttonDeleteMode;
bool deleteMode;
std::shared_ptr<CButton> buttonBattleOnlyMode;
bool enableUiEnhancements;
std::shared_ptr<CButton> buttonCampaignSet;

View File

@@ -52,9 +52,11 @@ void PackRollbackGeneratorVisitor::visitRebalanceStacks(RebalanceStacks & pack)
const auto * srcArmy = dynamic_cast<const CArmedInstance *>(srcObject);
const auto * dstArmy = dynamic_cast<const CArmedInstance *>(dstObject);
const auto * artifact = srcArmy->getStack(pack.srcSlot).getSlot(ArtifactPosition::CREATURE_SLOT);
if (srcArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
dstArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
srcArmy->getStack(pack.srcSlot).getSlot(ArtifactPosition::CREATURE_SLOT)->artifactID.hasValue())
(artifact && artifact->artifactID.hasValue()))
{
// TODO: rollback creature artifacts & stack experience
return;

View File

@@ -93,6 +93,14 @@ void AssetGenerator::initialize()
animationFiles[AnimationPath::builtin("SPRITES/GSPButtonClear")] = createGSPButtonClear();
for (PlayerColor color(-1); color < PlayerColor::PLAYER_LIMIT; ++color)
{
std::string name = "TownPortalBackgroundBlue" + (color == -1 ? "" : "-" + color.toString());
imageFiles[ImagePath::builtin(name)] = [this, color](){ return createGateListColored(std::max(PlayerColor(0), color), PlayerColor(1)); };
}
imageFiles[ImagePath::builtin("heroSlotsBlue.png")] = [this](){ return createHeroSlotsColored(PlayerColor(1));};
createPaletteShiftedSprites();
}
@@ -129,6 +137,22 @@ void AssetGenerator::addAnimationFile(const AnimationPath & path, AnimationLayou
animationFiles[path] = anim;
}
auto getColorFilters()
{
auto filterSettings = LIBRARY->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"];
static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
ColorFilter::genRangeShifter( filterSettings["red" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["blue" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["tan" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["green" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["orange"].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["purple"].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["teal" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["pink" ].convertTo<std::vector<float>>() )
};
return filters;
}
AssetGenerator::CanvasPtr AssetGenerator::createAdventureOptionsCleanBackground() const
{
auto locator = ImageLocator(ImagePath::builtin("ADVOPTBK"), EImageBlitMode::OPAQUE);
@@ -208,17 +232,7 @@ AssetGenerator::CanvasPtr AssetGenerator::createPlayerColoredBackground(const Pl
std::shared_ptr<IImage> texture = ENGINE->renderHandler().loadImage(locator);
// transform to make color of brown DIBOX.PCX texture match color of specified player
auto filterSettings = LIBRARY->settingsHandler->getFullConfig()["interface"]["playerColoredBackground"];
static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
ColorFilter::genRangeShifter( filterSettings["red" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["blue" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["tan" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["green" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["orange"].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["purple"].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["teal" ].convertTo<std::vector<float>>() ),
ColorFilter::genRangeShifter( filterSettings["pink" ].convertTo<std::vector<float>>() )
};
static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = getColorFilters();
assert(player.isValidPlayer());
if (!player.isValidPlayer())
@@ -884,3 +898,67 @@ AssetGenerator::AnimationLayoutMap AssetGenerator::createGSPButtonClear()
return layout;
}
AssetGenerator::CanvasPtr AssetGenerator::createGateListColored(PlayerColor color, PlayerColor backColor) const
{
auto locator = ImageLocator(ImagePath::builtin("TpGate"), EImageBlitMode::COLORKEY);
std::shared_ptr<IImage> img = ENGINE->renderHandler().loadImage(locator);
img->playerColored(color);
std::shared_ptr<IImage> imgColored = ENGINE->renderHandler().loadImage(locator);
static const std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = getColorFilters();
imgColored->adjustPalette(filters[backColor.getNum()], 0);
auto image = ENGINE->renderHandler().createImage(img->dimensions(), CanvasScalingPolicy::IGNORE);
Canvas canvas = image->getCanvas();
canvas.draw(imgColored, Point(0, 0));
std::vector<Rect> keepOriginalRects = {
Rect(0, 0, 14, 393),
Rect(293, 0, 13, 393),
Rect(0, 393, 8, 76),
Rect(299, 393, 6, 76),
Rect(0, 0, 306, 16),
Rect(0, 383, 306, 10),
Rect(0, 441, 306, 2),
Rect(0, 462, 306, 7),
// Edges
Rect(14, 15, 2, 5),
Rect(16, 15, 3, 2),
Rect(16, 17, 1, 1),
Rect(14, 379, 3, 4),
Rect(16, 381, 2, 2),
Rect(16, 380, 1, 1),
Rect(289, 16, 2, 2),
Rect(291, 16, 2, 4),
Rect(289, 381, 2, 2),
Rect(291, 379, 2, 4)
};
for(auto & rect : keepOriginalRects)
canvas.draw(img, Point(rect.x, rect.y), rect);
std::vector<Rect> blackRect = {
Rect(14, 401, 66, 32),
Rect(227, 401, 66, 32)
};
for(auto & rect : blackRect)
canvas.drawBorder(rect, Colors::BLACK);
return image;
}
AssetGenerator::CanvasPtr AssetGenerator::createHeroSlotsColored(PlayerColor backColor) const
{
auto locator = ImageLocator(AnimationPath::builtin("OVSLOT"), 4, 0, EImageBlitMode::COLORKEY);
std::shared_ptr<IImage> img = ENGINE->renderHandler().loadImage(locator);
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);
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));
return image;
}

View File

@@ -66,6 +66,8 @@ private:
CanvasPtr createCreatureInfoPanelElement(CreatureInfoPanelElement element) const;
CanvasPtr createQuestWindow() const;
AnimationLayoutMap createGSPButtonClear();
CanvasPtr createGateListColored(PlayerColor color, PlayerColor backColor) const;
CanvasPtr createHeroSlotsColored(PlayerColor backColor) const;
void createPaletteShiftedSprites();
void generatePaletteShiftedAnimation(const AnimationPath & source, const std::vector<PaletteAnimation> & animation);

View File

@@ -65,6 +65,8 @@ Rect CanvasImage::contentRect() const
Point CanvasImage::dimensions() const
{
if (scalingPolicy != CanvasScalingPolicy::IGNORE)
return Point(surface->w, surface->h) / ENGINE->screenHandler().getScalingFactor();
return {surface->w, surface->h};
}

View File

@@ -200,9 +200,9 @@ void CTextInput::setFilterFilename()
onTextFiltering = std::bind(&CTextInput::filenameFilter, _1, _2);
}
void CTextInput::setFilterNumber(int minValue, int maxValue)
void CTextInput::setFilterNumber(int minValue, int maxValue, int metricDigits)
{
onTextFiltering = std::bind(&CTextInput::numberFilter, _1, _2, minValue, maxValue);
onTextFiltering = std::bind(&CTextInput::numberFilter, _1, _2, minValue, maxValue, metricDigits);
}
std::string CTextInput::getVisibleText() const
@@ -256,6 +256,10 @@ void CTextInput::keyPressed(EShortcut key)
if(redrawNeeded)
{
std::string oldText = currentText;
if(onTextFiltering)
onTextFiltering(currentText, oldText);
updateLabel();
if(onTextEdited)
onTextEdited(currentText);
@@ -295,9 +299,9 @@ void CTextInput::textInputted(const std::string & enteredText)
if(onTextFiltering)
onTextFiltering(currentText, oldText);
updateLabel();
if(currentText != oldText)
{
updateLabel();
if(onTextEdited)
onTextEdited(currentText);
}
@@ -321,40 +325,63 @@ void CTextInput::filenameFilter(std::string & text, const std::string &oldText)
text.erase(pos, 1);
}
void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue)
std::optional<char> getMetricSuffix(const std::string& text)
{
const std::string suffixes = "kKmMgGtTpPeE";
std::vector<char> found;
// Collect all suffixes in the string
for (char c : text) {
if (suffixes.find(c) != std::string::npos) {
// Normalize: 'k' lowercase, others uppercase
found.push_back((c == 'k' || c == 'K') ? 'k' : static_cast<char>(std::toupper(c)));
}
}
if (found.empty()) return std::nullopt; // No suffix
if (found.size() == 1) return found[0]; // Single suffix
// More than one suffix
bool allSame = std::all_of(found.begin(), found.end(), [&](char c){ return c == found[0]; });
if (allSame) return std::nullopt; // Multiple but identical → nullopt
return found.back(); // Multiple different → last suffix
}
void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue, int metricDigits)
{
assert(minValue < maxValue);
if(text.empty())
bool isNegative = std::count_if(text.begin(), text.end(), [](char c){ return c == '-'; }) == 1 && minValue < 0;
auto suffix = getMetricSuffix(text);
if(metricDigits == 0)
suffix = std::nullopt;
// Remove all non-digit characters
text.erase(std::remove_if(text.begin(), text.end(), [](char c){ return !isdigit(c); }), text.end());
// Remove leading zeros
size_t firstNonZero = text.find_first_not_of('0');
if (firstNonZero > 0)
text.erase(0, firstNonZero);
if (text.empty())
text = "0";
size_t pos = 0;
if(text[0] == '-') //allow '-' sign as first symbol only
pos++;
// Add negative sign
text = (isNegative ? "-" : "") + text;
while(pos < text.size())
{
if(text[pos] < '0' || text[pos] > '9')
{
text = oldText;
return; //new text is not number.
}
pos++;
}
try
{
int value = boost::lexical_cast<int>(text);
if(value < minValue)
text = std::to_string(minValue);
else if(value > maxValue)
text = std::to_string(maxValue);
}
catch(boost::bad_lexical_cast &)
{
//Should never happen. Unless I missed some cases
logGlobal->warn("Warning: failed to convert %s to number!", text);
text = oldText;
}
// Restore suffix if it exists
if (suffix)
text += *suffix;
// Clamp value
int value = TextOperations::parseMetric<int>(text);
if (metricDigits)
text = (isNegative && value == 0 ? "-" : "") + TextOperations::formatMetric(value, metricDigits);
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);
}
void CTextInput::activate()

View File

@@ -66,7 +66,7 @@ protected:
static void filenameFilter(std::string & text, const std::string & oldText);
//Filter that will allow only input of numbers in range min-max (min-max are allowed)
//min-max should be set via something like std::bind
static void numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue);
static void numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue, int metricDigits);
std::string getVisibleText() const;
void createLabel(bool giveFocusToInput);
@@ -99,7 +99,7 @@ public:
/// Enables filtering entered text that ensures that text is valid filename (existing or not)
void setFilterFilename();
/// Enable filtering entered text that ensures that text is valid number in provided range [min, max]
void setFilterNumber(int minValue, int maxValue);
void setFilterNumber(int minValue, int maxValue, int metricDigits=0);
void setFont(EFonts Font);
void setColor(const ColorRGBA & Color);

View File

@@ -46,7 +46,7 @@ CPicture::CPicture(std::shared_ptr<IImage> image, const Point & position)
pos.w = bg->width();
pos.h = bg->height();
addUsedEvents(SHOW_POPUP);
addUsedEvents(LCLICK | SHOW_POPUP);
}
CPicture::CPicture( const ImagePath &bmpname, int x, int y )
@@ -75,7 +75,7 @@ CPicture::CPicture( const ImagePath & bmpname, const Point & position, EImageBli
pos.w = pos.h = 0;
}
addUsedEvents(SHOW_POPUP);
addUsedEvents(LCLICK | SHOW_POPUP);
}
CPicture::CPicture( const ImagePath & bmpname, const Point & position )
@@ -89,7 +89,7 @@ CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y)
pos.w = srcRect->w;
pos.h = srcRect->h;
addUsedEvents(SHOW_POPUP);
addUsedEvents(LCLICK | SHOW_POPUP);
}
CPicture::CPicture(std::shared_ptr<IImage> image, const Rect &SrcRect, int x, int y)
@@ -99,7 +99,7 @@ CPicture::CPicture(std::shared_ptr<IImage> image, const Rect &SrcRect, int x, in
pos.w = srcRect->w;
pos.h = srcRect->h;
addUsedEvents(SHOW_POPUP);
addUsedEvents(LCLICK | SHOW_POPUP);
}
void CPicture::show(Canvas & to)
@@ -137,11 +137,22 @@ void CPicture::setPlayerColor(PlayerColor player)
bg->playerColored(player);
}
void CPicture::addLClickCallback(const std::function<void()> & callback)
{
lCallback = callback;
}
void CPicture::addRClickCallback(const std::function<void()> & callback)
{
rCallback = callback;
}
void CPicture::clickPressed(const Point & cursorPosition)
{
if(lCallback)
lCallback();
}
void CPicture::showPopupWindow(const Point & cursorPosition)
{
if(rCallback)

View File

@@ -27,6 +27,7 @@ enum class EImageBlitMode : uint8_t;
class CPicture : public CIntObject
{
std::shared_ptr<IImage> bg;
std::function<void()> lCallback;
std::function<void()> rCallback;
public:
@@ -60,10 +61,12 @@ public:
void scaleTo(Point size);
void setPlayerColor(PlayerColor player);
void addLClickCallback(const std::function<void()> & callback);
void addRClickCallback(const std::function<void()> & callback);
void show(Canvas & to) override;
void showAll(Canvas & to) override;
void clickPressed(const Point & cursorPosition) override;
void showPopupWindow(const Point & cursorPosition) override;
};

View File

@@ -623,13 +623,15 @@ void MoraleLuckBox::set(const AFactionMember * node)
else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
{
auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
text += "\n" + noMorale->Description(GAME->interface()->cb.get());
if(GAME->interface())
text += "\n" + noMorale->Description(GAME->interface()->cb.get());
component.value = 0;
}
else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
{
auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
text += "\n" + noLuck->Description(GAME->interface()->cb.get());
if(GAME->interface())
text += "\n" + noLuck->Description(GAME->interface()->cb.get());
component.value = 0;
}
else
@@ -637,7 +639,7 @@ void MoraleLuckBox::set(const AFactionMember * node)
std::string addInfo = "";
for(auto & bonus : * modifierList)
{
if(bonus->val) {
if(GAME->interface() && bonus->val) {
const std::string& description = bonus->Description(GAME->interface()->cb.get());
//arraytxt already contains \n
if (description.size() && description[0] != '\n')

View File

@@ -2184,8 +2184,16 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
uint32_t spellCount = town->spellsAtLevel(i+1,false); //spell at level with -1 hmmm?
for(uint32_t j=0; j<spellCount; j++)
{
if(i<town->mageGuildLevel() && town->spells[i].size()>j)
if (town->hasBuilt(BuildingSubID::AURORA_BOREALIS))
{
std::string auroraBorealisName = town->getTown()->getSpecialBuilding(BuildingSubID::AURORA_BOREALIS)->getNameTranslated();
auroraBorealisScrolls.push_back(std::make_shared<ScrollAllSpells>(positions[i][j], auroraBorealisName));
}
else if(i<town->mageGuildLevel() && town->spells[i].size()>j)
{
spells.push_back(std::make_shared<Scroll>(positions[i][j], town->spells[i][j].toSpell(), townId));
}
else
emptyScrolls.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y));
}
@@ -2194,6 +2202,22 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
redraw();
}
CMageGuildScreen::ScrollAllSpells::ScrollAllSpells(Point position, const std::string & buildingName)
{
constexpr int auroraBorealisImageIndex = 70;
OBJECT_CONSTRUCTION;
pos += position;
image = std::make_shared<CAnimImage>(AnimationPath::builtin("SPELLSCR"), auroraBorealisImageIndex);
pos = image->pos;
MetaString description;
description.appendTextID("core.genrltxt.714");
description.replaceRawString(buildingName);
text = std::make_shared<LRClickableAreaWText>(Rect(Point(), pos.dimensions()), description.toString(), description.toString() );
}
CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId)
: spell(Spell), townId(townId)
{

View File

@@ -37,6 +37,7 @@ class CGarrisonInt;
class CComponent;
class CComponentBox;
class LRClickableArea;
class LRClickableAreaWText;
class CTextInputWithConfirm;
/// Building "button"
@@ -394,10 +395,21 @@ class CMageGuildScreen : public CStatusbarWindow
void showPopupWindow(const Point & cursorPosition) override;
void hover(bool on) override;
};
class ScrollAllSpells : public CIntObject
{
std::shared_ptr<CAnimImage> image;
std::shared_ptr<LRClickableAreaWText> text;
public:
ScrollAllSpells(Point position, const std::string & buildingName);
};
std::shared_ptr<CPicture> window;
std::shared_ptr<CButton> exit;
std::vector<std::shared_ptr<Scroll>> spells;
std::vector<std::shared_ptr<CAnimImage>> emptyScrolls;
std::vector<std::shared_ptr<ScrollAllSpells>> auroraBorealisScrolls;
std::shared_ptr<CMinorResDataBar> resdatabar;

View File

@@ -428,7 +428,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
};
std::string tooltipText = "vcmi.creatureWindow." + btnIDs[buttonIndex];
parent->switchButtons[buttonIndex] = std::make_shared<CButton>(Point(302 + (int)buttonIndex*40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CButton::tooltipLocalized(tooltipText), onSwitch);
parent->switchButtons[buttonIndex] = std::make_shared<CButton>(Point(342, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CButton::tooltipLocalized(tooltipText), onSwitch);
parent->switchButtons[buttonIndex]->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("stackWindow/switchModeIcons"), buttonIndex));
}
parent->switchButtons[parent->activeTab]->disable();
@@ -952,8 +952,8 @@ void CStackWindow::initSections()
{
OBJECT_CONSTRUCTION;
bool showArt = GAME->interface()->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_ARTIFACT) && info->commander == nullptr && info->stackNode;
bool showExp = (GAME->interface()->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE) || info->commander != nullptr) && info->stackNode;
bool showArt = GAME->interface() && GAME->interface()->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_ARTIFACT) && info->commander == nullptr && info->stackNode;
bool showExp = ((GAME->interface() && GAME->interface()->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) || info->commander != nullptr) && info->stackNode;
mainSection = std::make_shared<MainSection>(this, pos.h, showExp, showArt);

View File

@@ -214,8 +214,8 @@ void CHeroOverview::genControls()
{
secSkills.push_back(std::make_shared<CSecSkillPlace>(Point(302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)),
CSecSkillPlace::ImageSize::SMALL, skill.first, skill.second));
labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, LIBRARY->generaltexth->levels[skill.second - 1]));
labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*LIBRARY->skillh)[skill.first]->getNameTranslated()));
labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, LIBRARY->generaltexth->levels[skill.second - 1], 90));
labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*LIBRARY->skillh)[skill.first]->getNameTranslated(), 90));
i++;
}
@@ -240,7 +240,8 @@ void CHeroOverview::genControls()
}
imageSpells.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SPELLBON"), (*LIBRARY->spellh)[spell]->getIconIndex(), Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0));
labelSpellsNames.push_back(std::make_shared<CLabel>(302 + (292 / 2) + 3 * borderOffset + 32 + borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 3, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*LIBRARY->spellh)[spell]->getNameTranslated()));
Rect labelPos(302 + (292 / 2) + 2 * borderOffset + 32 + borderOffset + 5, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), (292 / 2) - 32 - 3 * borderOffset - 10, 32);
labelSpellsNames.push_back(std::make_shared<CMultiLineLabel>(labelPos, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, (*LIBRARY->spellh)[spell]->getNameTranslated()));
i++;
}
}

View File

@@ -58,7 +58,7 @@ class CHeroOverview : public CWindowObject
std::shared_ptr<CLabel> labelSpellTitle;
std::vector<std::shared_ptr<CAnimImage>> imageSpells;
std::vector<std::shared_ptr<CLabel>> labelSpellsNames;
std::vector<std::shared_ptr<CMultiLineLabel>> labelSpellsNames;
std::shared_ptr<CLabel> labelSecSkillTitle;
std::vector<std::shared_ptr<CSecSkillPlace>> secSkills;
@@ -69,4 +69,4 @@ class CHeroOverview : public CWindowObject
public:
CHeroOverview(const HeroTypeID & h);
};
};

View File

@@ -34,6 +34,7 @@
#include "../widgets/Buttons.h"
#include "../widgets/VideoWidget.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../eventsSDL/InputHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/GameConstants.h"
@@ -99,6 +100,7 @@ CSpellWindow::InteractiveArea::InteractiveArea(const Rect & myRect, const std::f
void CSpellWindow::InteractiveArea::clickPressed(const Point & cursorPosition)
{
ENGINE->input().hapticFeedback();
onLeft();
}
@@ -687,6 +689,8 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
{
if(mySpell)
{
ENGINE->input().hapticFeedback();
if(owner->onSpellSelect)
{
owner->onSpellSelect(mySpell->id);

View File

@@ -86,7 +86,9 @@ std::shared_ptr<CPicture> CWindowObject::createBg(const ImagePath & imageName, b
return nullptr;
auto image = std::make_shared<CPicture>(imageName, Point(0,0), EImageBlitMode::OPAQUE);
if(playerColored && GAME->interface())
if(!GAME->interface())
image->setPlayerColor(PlayerColor(1)); // in main menu we use blue
else if(playerColored)
image->setPlayerColor(GAME->interface()->playerID);
return image;
}

View File

@@ -1535,8 +1535,9 @@ CObjectListWindow::CItem::CItem(CObjectListWindow * _parent, size_t _id, std::st
index(_id)
{
OBJECT_CONSTRUCTION;
if(parent->images.size() > index)
icon = std::make_shared<CPicture>(parent->images[index], Point(1, 1));
auto imgIndex = parent->itemsVisible[index].first;
if(parent->images.size() > index && parent->images[imgIndex])
icon = std::make_shared<CPicture>(parent->images[imgIndex], Point(1, 1));
border = std::make_shared<CPicture>(ImagePath::builtin("TPGATES"));
pos = border->pos;
@@ -1577,12 +1578,13 @@ void CObjectListWindow::CItem::clickDouble(const Point & cursorPosition)
void CObjectListWindow::CItem::showPopupWindow(const Point & cursorPosition)
{
int where = parent->itemsVisible[index].first;
if(parent->onPopup)
parent->onPopup(index);
parent->onPopup(where);
}
CObjectListWindow::CObjectListWindow(const std::vector<int> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> images, bool searchBoxEnabled)
: CWindowObject(PLAYER_COLORED, ImagePath::builtin("TPGATE")),
CObjectListWindow::CObjectListWindow(const std::vector<int> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> images, bool searchBoxEnabled, bool blue)
: CWindowObject(PLAYER_COLORED, ImagePath::builtin(blue ? "TownPortalBackgroundBlue" : "TPGATE")),
onSelect(Callback),
selected(initialSelection),
images(images)
@@ -1601,12 +1603,12 @@ CObjectListWindow::CObjectListWindow(const std::vector<int> & _items, std::share
}
itemsVisible = items;
init(titleWidget_, _title, _descr, searchBoxEnabled);
init(titleWidget_, _title, _descr, searchBoxEnabled, blue);
list->scrollTo(std::min(static_cast<int>(initialSelection + 4), static_cast<int>(items.size() - 1))); // 4 is for centering (list have 9 elements)
}
CObjectListWindow::CObjectListWindow(const std::vector<std::string> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> images, bool searchBoxEnabled)
: CWindowObject(PLAYER_COLORED, ImagePath::builtin("TPGATE")),
CObjectListWindow::CObjectListWindow(const std::vector<std::string> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> images, bool searchBoxEnabled, bool blue)
: CWindowObject(PLAYER_COLORED, ImagePath::builtin(blue ? "TownPortalBackgroundBlue" : "TPGATE")),
onSelect(Callback),
selected(initialSelection),
images(images)
@@ -1625,17 +1627,17 @@ CObjectListWindow::CObjectListWindow(const std::vector<std::string> & _items, st
}
itemsVisible = items;
init(titleWidget_, _title, _descr, searchBoxEnabled);
init(titleWidget_, _title, _descr, searchBoxEnabled, blue);
list->scrollTo(std::min(static_cast<int>(initialSelection + 4), static_cast<int>(items.size() - 1))); // 4 is for centering (list have 9 elements)
}
void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled)
void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled, bool blue)
{
titleWidget = titleWidget_;
title = std::make_shared<CLabel>(152, 27, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, _title);
descr = std::make_shared<CLabel>(145, 133, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, _descr);
exit = std::make_shared<CButton>( Point(228, 402), AnimationPath::builtin("ICANCEL.DEF"), CButton::tooltip(), std::bind(&CObjectListWindow::exitPressed, this), EShortcut::GLOBAL_CANCEL);
exit = std::make_shared<CButton>( Point(228, 402), AnimationPath::builtin(blue ? "MuBcanc" : "ICANCEL.DEF"), CButton::tooltip(), std::bind(&CObjectListWindow::exitPressed, this), EShortcut::GLOBAL_CANCEL);
if(titleWidget)
{
@@ -1644,10 +1646,10 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri
titleWidget->pos.y = 75 + pos.y - titleWidget->pos.h/2;
}
list = std::make_shared<CListBox>(std::bind(&CObjectListWindow::genItem, this, _1),
Point(14, 151), Point(0, 25), 9, itemsVisible.size(), 0, 1, Rect(262, -32, 256, 256) );
Point(14, 151), Point(0, 25), 9, itemsVisible.size(), 0, 1 + (blue ? 4 : 0), Rect(262, -32, 256, 256) );
list->setRedrawParent(true);
ok = std::make_shared<CButton>(Point(15, 402), AnimationPath::builtin("IOKAY.DEF"), CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), EShortcut::GLOBAL_ACCEPT);
ok = std::make_shared<CButton>(Point(15, 402), AnimationPath::builtin(blue ? "MuBchck" : "IOKAY.DEF"), CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), EShortcut::GLOBAL_ACCEPT);
ok->block(!list->size());
if(!searchBoxEnabled)
@@ -1655,8 +1657,8 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri
Rect r(50, 90, pos.w - 100, 16);
const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75);
const ColorRGBA borderColor = ColorRGBA(128, 100, 75);
const ColorRGBA grayedColor = ColorRGBA(158, 130, 105);
const ColorRGBA borderColor = blue ? ColorRGBA(75, 84, 128) : ColorRGBA(128, 100, 75);
const ColorRGBA grayedColor = blue ? ColorRGBA(105, 127, 159) : ColorRGBA(158, 130, 105);
searchBoxRectangle = std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor);
searchBoxDescription = std::make_shared<CLabel>(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, LIBRARY->generaltexth->translate("vcmi.spellBook.search"));
@@ -1676,9 +1678,12 @@ void CObjectListWindow::trimTextIfTooWide(std::string & text, bool preserveCount
{
auto posBrace = text.find_last_of("(");
auto posClosing = text.find_last_of(")");
std::string objCount = text.substr(posBrace, posClosing - posBrace) + ')';
suffix += " ";
suffix += objCount;
if (posBrace != std::string::npos && posClosing != std::string::npos && posClosing > posBrace)
{
std::string objCount = text.substr(posBrace, posClosing - posBrace) + ')';
suffix += " ";
suffix += objCount;
}
}
const auto & font = ENGINE->renderHandler().loadFont(FONT_SMALL);

View File

@@ -205,7 +205,7 @@ class CObjectListWindow : public CWindowObject
std::vector< std::pair<int, std::string> > items; //all items present in list
std::vector< std::pair<int, std::string> > itemsVisible; //visible items present in list
void init(std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled);
void init(std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled, bool blue);
void trimTextIfTooWide(std::string & text, bool preserveCountSuffix) const; // trim item's text to fit within window's width
void itemsSearchCallback(const std::string & text);
void exitPressed();
@@ -219,8 +219,8 @@ public:
/// Callback will be called when OK button is pressed, returns id of selected item. initState = initially selected item
/// Image can be nullptr
///item names will be taken from map objects
CObjectListWindow(const std::vector<int> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {}, bool searchBoxEnabled = false);
CObjectListWindow(const std::vector<std::string> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {}, bool searchBoxEnabled = false);
CObjectListWindow(const std::vector<int> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {}, bool searchBoxEnabled = false, bool blue = false);
CObjectListWindow(const std::vector<std::string> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {}, bool searchBoxEnabled = false, bool blue = false);
std::shared_ptr<CIntObject> genItem(size_t index);
void elementSelected();//call callback and close this window