/* * 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() : CWindowObject(BORDERED) , startInfo(std::make_shared()) , disabledColor(GAME->server().isHost() ? Colors::WHITE : Colors::ORANGE) { OBJECT_CONSTRUCTION; pos.w = 519; pos.h = 238; updateShadow(); center(); init(); backgroundTexture = std::make_shared(Rect(0, 0, pos.w, pos.h)); backgroundTexture->setPlayerColor(PlayerColor(1)); buttonOk = std::make_shared(Point(191, 203), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ startBattle(); }, EShortcut::GLOBAL_ACCEPT); buttonOk->block(true); buttonAbort = std::make_shared(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(260, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode")); battlefieldSelector = std::make_shared(Point(29, 174), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){ std::vector texts; std::vector> 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(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(*startInfo->selectedTerrain) : static_cast(*startInfo->selectedTown + terrains.size())), images, true, true); }); battlefieldSelector->block(GAME->server().isGuest()); buttonReset = std::make_shared(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; iselectedArmyInput.at(i)->disable(); } startInfo->selectedHero[1] = std::nullopt; startInfo->selectedArmy[1].fill(CStackBasicDescriptor(CreatureID::NONE, 1)); for(size_t i=0; iselectedArmyInput.at(i)->disable(); onChange(); }); buttonReset->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeReset"), EFonts::FONT_SMALL, Colors::WHITE); heroSelector1 = std::make_shared(0, *this, Point(0, 40)); heroSelector2 = std::make_shared(1, *this, Point(260, 40)); heroSelector1->setInputEnabled(GAME->server().isHost()); onChange(); } void BattleOnlyModeWindow::init() { map = std::make_unique(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(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 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 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(ImagePath::builtin("heroSlotsBlue"), Point(3, 4)); for(size_t i=0; i(AnimationPath::builtin("PSKIL32"), i, 0, 78 + i * 36, 26); primSkills.push_back(image); primSkillsBorder.push_back(std::make_shared(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(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(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(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(drawBlackBox(Point(58, 64), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect"), id == 1 ? Colors::WHITE : parent.disabledColor), Point(6, 7)); heroLabel = std::make_shared(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, LIBRARY->generaltexth->translate("core.genrltxt.507")); for(size_t i=0; isetText("0"); } else { heroImage = std::make_shared(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("PortraitsLarge"), EImageBlitMode::COLORKEY)->getImage((*parent.startInfo->selectedHero[id]).toHeroType()->imageIndex), Point(6, 7)); heroLabel = std::make_shared(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, (*parent.startInfo->selectedHero[id]).toHeroType()->getNameTranslated()); for(size_t i=0; isetText(std::to_string(parent.startInfo->primSkillLevel[id][i])); } heroImage->addLClickCallback([this](){ auto allowedSet = LIBRARY->heroh->getDefaultAllowed(); std::vector 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 texts; std::vector> 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(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; iprimSkillLevel[id][i] = 0; parent.onChange(); }, selectedIndex, images, true, true); window->onPopup = [heroes](int index) { if(index == 0) return; index--; ENGINE->windows().createAndPushWindow(heroes.at(index)); }; ENGINE->windows().pushWindow(window); }); heroImage->addRClickCallback([this](){ if(!parent.startInfo->selectedHero[id]) return; ENGINE->windows().createAndPushWindow(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(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(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 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 texts; std::vector> 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(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(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(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(factory->create(cb.get(), templates.front())); obj->setHeroType(*startInfo->selectedHero[sel]); obj->setOwner(color); obj->pos = position; for(size_t i=0; ipushPrimSkill(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(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(); mapInfo->mapInit("Maps/BattleOnlyMode"); GAME->server().setMapInfo(mapInfo); ExtraOptionsInfo extraOptions; extraOptions.unlimitedReplay = true; GAME->server().setExtraOptionsInfo(extraOptions); GAME->server().sendStartGame(); }