diff --git a/Mods/vcmi/Content/Sprites/lobby/checkboxSmall.json b/Mods/vcmi/Content/Sprites/lobby/checkboxSmall.json new file mode 100644 index 000000000..f71c7f736 --- /dev/null +++ b/Mods/vcmi/Content/Sprites/lobby/checkboxSmall.json @@ -0,0 +1,8 @@ +{ + "basepath" : "lobby/", + "images" : + [ + { "frame" : 0, "file" : "checkboxSmallOff.png"}, + { "frame" : 1, "file" : "checkboxSmallOn.png"} + ] +} diff --git a/Mods/vcmi/Content/Sprites/lobby/checkboxSmallOff.png b/Mods/vcmi/Content/Sprites/lobby/checkboxSmallOff.png new file mode 100644 index 000000000..6a422b5d4 Binary files /dev/null and b/Mods/vcmi/Content/Sprites/lobby/checkboxSmallOff.png differ diff --git a/Mods/vcmi/Content/Sprites/lobby/checkboxSmallOn.png b/Mods/vcmi/Content/Sprites/lobby/checkboxSmallOn.png new file mode 100644 index 000000000..e255a4cad Binary files /dev/null and b/Mods/vcmi/Content/Sprites/lobby/checkboxSmallOn.png differ diff --git a/Mods/vcmi/Content/config/english.json b/Mods/vcmi/Content/config/english.json index 37cfdb8b3..742be71ce 100644 --- a/Mods/vcmi/Content/config/english.json +++ b/Mods/vcmi/Content/config/english.json @@ -144,6 +144,8 @@ "vcmi.lobby.battleOnlyModeBattlefieldSelect" : "Select Battlefield", "vcmi.lobby.battleOnlyModeHeroSelect" : "Select Hero", "vcmi.lobby.battleOnlyModeCreatureSelect" : "Select Creature", + "vcmi.lobby.battleOnlyModeSecSkillSelect" : "Select Secondary Skill", + "vcmi.lobby.battleOnlyModeArtifactSelect" : "Select Artifact", "vcmi.lobby.battleOnlyModeSelect" : "Select", "vcmi.lobby.battleOnlyModeReset" : "Reset", "vcmi.lobby.templatesSelect.hover" : "Templates", diff --git a/Mods/vcmi/Content/config/german.json b/Mods/vcmi/Content/config/german.json index edcec5c94..d5a5a8fb7 100644 --- a/Mods/vcmi/Content/config/german.json +++ b/Mods/vcmi/Content/config/german.json @@ -144,6 +144,8 @@ "vcmi.lobby.battleOnlyModeBattlefieldSelect" : "Schlachtfeld auswählen", "vcmi.lobby.battleOnlyModeHeroSelect" : "Helden auswählen", "vcmi.lobby.battleOnlyModeCreatureSelect" : "Kreatur auswählen", + "vcmi.lobby.battleOnlyModeSecSkillSelect" : "Sekundären Skill auswählen", + "vcmi.lobby.battleOnlyModeArtifactSelect" : "Artifakt auswählen", "vcmi.lobby.battleOnlyModeSelect" : "Wählen", "vcmi.lobby.battleOnlyModeReset" : "Zurücksetzen", "vcmi.lobby.templatesSelect.hover" : "Templates", diff --git a/client/lobby/BattleOnlyModeTab.cpp b/client/lobby/BattleOnlyModeTab.cpp index 186f51257..4745c2375 100644 --- a/client/lobby/BattleOnlyModeTab.cpp +++ b/client/lobby/BattleOnlyModeTab.cpp @@ -27,10 +27,11 @@ #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 "../eventsSDL/InputHandler.h" +#include "../windows/InfoWindows.h" #include "../../lib/GameLibrary.h" #include "../../lib/gameState/CGameState.h" @@ -38,12 +39,15 @@ #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" @@ -53,6 +57,7 @@ #include "../../lib/mapping/CMapEditManager.h" #include "../../lib/mapping/CMapService.h" #include "../../lib/mapping/MapFormat.h" +#include "../../lib/spells/CSpellHandler.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../../lib/texts/MetaString.h" #include "../../lib/texts/TextOperations.h" @@ -70,7 +75,7 @@ BattleOnlyModeTab::BattleOnlyModeTab() title = std::make_shared(220, 35, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode")); subTitle = std::make_shared(Rect(55, 40, 333, 40), FONT_SMALL, ETextAlignment::BOTTOMCENTER, Colors::WHITE, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSubTitle")); - battlefieldSelector = std::make_shared(Point(120, 370), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){ + battlefieldSelector = std::make_shared(Point(57, 552), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){ std::vector texts; std::vector> images; @@ -117,26 +122,36 @@ BattleOnlyModeTab::BattleOnlyModeTab() }, (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(120, 400), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){ + buttonReset = std::make_shared(Point(259, 552), AnimationPath::builtin("GSPBUT2"), 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)); + startInfo->secSkillLevel[0].fill(std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE)); + startInfo->artifacts[0].clear(); + startInfo->spellBook[0] = true; + startInfo->warMachines[0] = false; for(size_t i=0; iselectedArmyInput.at(i)->disable(); + for(size_t i=0; i<8; i++) + heroSelector1->selectedSecSkillInput.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(); + startInfo->secSkillLevel[1].fill(std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE)); + startInfo->artifacts[1].clear(); + startInfo->spellBook[1] = true; + startInfo->warMachines[1] = false; + for(size_t i=0; i<8; i++) + heroSelector2->selectedSecSkillInput.at(i)->disable(); onChange(); }); buttonReset->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeReset"), EFonts::FONT_SMALL, Colors::WHITE); - heroSelector1 = std::make_shared(0, *this, Point(91, 90)); - heroSelector2 = std::make_shared(1, *this, Point(91, 225)); + heroSelector1 = std::make_shared(0, *this, Point(55, 88)); + heroSelector2 = std::make_shared(1, *this, Point(55, 307)); heroSelector1->setInputEnabled(GAME->server().isHost()); @@ -155,6 +170,7 @@ void BattleOnlyModeTab::init() map->name = MetaString::createFromTextID("vcmi.lobby.battleOnlyMode"); cb = std::make_unique(map.get()); + map->cb = cb.get(); } void BattleOnlyModeTab::onChange() @@ -169,8 +185,16 @@ void BattleOnlyModeTab::update() 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(); } @@ -249,8 +273,55 @@ BattleOnlyModeHeroSelector::BattleOnlyModeHeroSelector(int id, BattleOnlyModeTab }); } + 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(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(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(tmpIcon, Point(220, 32))); + spellBook = std::make_shared(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(tmpIcon, Point(220, 56))); + warMachines = std::make_shared(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::setHeroIcon() @@ -273,7 +344,6 @@ void BattleOnlyModeHeroSelector::setHeroIcon() } heroImage->addLClickCallback([this](){ - ENGINE->input().hapticFeedback(); auto allowedSet = LIBRARY->heroh->getDefaultAllowed(); std::vector heroes(allowedSet.begin(), allowedSet.end()); std::sort(heroes.begin(), heroes.end(), [](auto a, auto b) { @@ -357,7 +427,6 @@ void BattleOnlyModeHeroSelector::setCreatureIcons() } creatureImage[i]->addLClickCallback([this, i](){ - ENGINE->input().hapticFeedback(); auto allowedSet = LIBRARY->creh->getDefaultAllowed(); std::vector creatures(allowedSet.begin(), allowedSet.end()); std::sort(creatures.begin(), creatures.end(), [](auto a, auto b) { @@ -421,6 +490,209 @@ void BattleOnlyModeHeroSelector::setCreatureIcons() } } +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) + { + secSkillImage[i] = std::make_shared(drawBlackBox(Point(32, 32), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect"), id == 1 ? Colors::WHITE : parent.disabledColor), imgPos); + selectedSecSkillInput[i]->disable(); + } + else + { + secSkillImage[i] = std::make_shared(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](){ + auto allowedSet = LIBRARY->skillh->getDefaultAllowed(); + std::vector skills(allowedSet.begin(), allowedSet.end()); + skills.erase( // remove already added skills from selection + std::remove_if( + skills.begin(), + skills.end(), + [this, i](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][i].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][i].second == MasteryLevel::NONE ? 0 : (1 + std::distance(skills.begin(), std::find_if(skills.begin(), skills.end(), [this, i](auto skillID) { + return skillID == parent.startInfo->secSkillLevel[id][i].first; + }))); + + std::vector texts; + std::vector> 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(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSecSkillSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSecSkillSelect"), [this, skills, i](int index){ + if(index == 0) + { + parent.startInfo->secSkillLevel[id][i] = std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE); + parent.onChange(); + return; + } + index--; + + auto skill = skills.at(index).toSkill(); + parent.startInfo->secSkillLevel[id][i] = 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 comp = std::make_shared(ComponentType::SEC_SKILL, skillId, MasteryLevel::EXPERT); + CRClickPopup::createAndPush(skillId.toSkill()->getDescriptionTranslated(MasteryLevel::EXPERT), CInfoWindow::TCompsInfo(1, comp)); + }; + ENGINE->windows().pushWindow(window); + }); + + 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 comp = std::make_shared(ComponentType::SEC_SKILL, skillId, skillLevel); + CRClickPopup::createAndPush(skillId.toSkill()->getDescriptionTranslated(skillLevel), CInfoWindow::TCompsInfo(1, comp)); + }); + } +} + + +void BattleOnlyModeHeroSelector::setArtifactIcons() +{ + OBJECT_CONSTRUCTION; + + std::vector 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 + }; + + 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) + artifactImage[i] = std::make_shared(drawBlackBox(Point(32, 32), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect"), id == 1 ? Colors::WHITE : parent.disabledColor), 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(image, imgPos); + } + + artifactImage[i]->addLClickCallback([this, i, artPos, artifactId](){ + auto allowedSet = LIBRARY->arth->getDefaultAllowed(); + std::vector 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, i, artPos](auto & artifact) { + auto possibleSlots = artifact.toArtifact()->getPossibleSlots(); + std::vector 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[i]] != artifact) + || !std::any_of(allowedSlots.begin(), allowedSlots.end(),[i, artPos](auto & p){ return p == artPos[i]; }); + } + ), + 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(), [this, i, artifactId](auto artID) { + return artID == artifactId; + }))); + + std::vector texts; + std::vector> 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(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeArtifactSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeArtifactSelect"), [this, artifacts, i, artPos](int index){ + if(index == 0) + { + parent.startInfo->artifacts[id][artPos[i]] = ArtifactID::NONE; + parent.onChange(); + return; + } + index--; + + auto artifact = artifacts.at(index); + parent.startInfo->artifacts[id][artPos[i]] = 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 comp = std::make_shared(ComponentType::ARTIFACT, artifactId); + CRClickPopup::createAndPush(artifactId.toArtifact()->getDescriptionTranslated(), CInfoWindow::TCompsInfo(1, comp)); + }; + ENGINE->windows().pushWindow(window); + }); + + artifactImage[i]->addRClickCallback([this, i, artPos](){ + auto artId = parent.startInfo->artifacts[id][artPos[i]]; + if(artId == ArtifactID::NONE) + return; + + std::shared_ptr comp = std::make_shared(ComponentType::ARTIFACT, artId); + CRClickPopup::createAndPush(artId.toArtifact()->getDescriptionTranslated(), CInfoWindow::TCompsInfo(1, comp)); + }); + } +} + void BattleOnlyModeTab::startBattle() { auto rng = &CRandomGenerator::getDefault(); @@ -452,6 +724,37 @@ void BattleOnlyModeTab::startBattle() 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()); + + // 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)); + } + + // give all spells (TODO: make selectable) + for(const auto & spell : LIBRARY->spellh->getDefaultAllowed()) + obj->addSpellToSpellbook(spell); + + for(auto & artifact : startInfo->artifacts[sel]) + if(artifact.second != ArtifactID::NONE) + obj->putArtifact(artifact.first, map->createArtifact(artifact.second)); + + bool skillSetted = std::any_of(startInfo->secSkillLevel[sel].begin(), startInfo->secSkillLevel[sel].end(),[](const auto& p){ return p.second != MasteryLevel::NONE; }); + if(skillSetted) + { + 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); }; diff --git a/client/lobby/BattleOnlyModeTab.h b/client/lobby/BattleOnlyModeTab.h index 402ad16bc..aec6f15e8 100644 --- a/client/lobby/BattleOnlyModeTab.h +++ b/client/lobby/BattleOnlyModeTab.h @@ -32,6 +32,7 @@ class CAnimImage; class GraphicalPrimitiveCanvas; class CTextInput; class TransparentFilledRectangle; +class CToggleButton; class BattleOnlyModeHeroSelector : public CIntObject { @@ -42,6 +43,10 @@ private: std::shared_ptr heroImage; std::shared_ptr heroLabel; std::vector> creatureImage; + std::vector> secSkillImage; + std::vector> artifactImage; + + std::vector> addIcon; int id; public: @@ -50,9 +55,15 @@ public: std::vector> primSkillsInput; std::vector> selectedArmyInput; + std::vector> selectedSecSkillInput; + + std::shared_ptr spellBook; + std::shared_ptr warMachines; void setHeroIcon(); void setCreatureIcons(); + void setSecSkillIcons(); + void setArtifactIcons(); BattleOnlyModeHeroSelector(int id, BattleOnlyModeTab& parent, Point position); }; diff --git a/client/render/AssetGenerator.cpp b/client/render/AssetGenerator.cpp index 5b9f9fe28..abdc3ba6f 100644 --- a/client/render/AssetGenerator.cpp +++ b/client/render/AssetGenerator.cpp @@ -954,11 +954,24 @@ AssetGenerator::CanvasPtr AssetGenerator::createHeroSlotsColored(PlayerColor bac static const std::array 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; } diff --git a/client/renderSDL/ScalableImage.cpp b/client/renderSDL/ScalableImage.cpp index b490994a7..102aca4a9 100644 --- a/client/renderSDL/ScalableImage.cpp +++ b/client/renderSDL/ScalableImage.cpp @@ -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(); } diff --git a/client/widgets/CTextInput.cpp b/client/widgets/CTextInput.cpp index 0320e925c..7d9813c7f 100644 --- a/client/widgets/CTextInput.cpp +++ b/client/widgets/CTextInput.cpp @@ -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() diff --git a/client/widgets/Images.cpp b/client/widgets/Images.cpp index 68f282aaa..bc10eac04 100644 --- a/client/widgets/Images.cpp +++ b/client/widgets/Images.cpp @@ -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 & callback) void CPicture::clickPressed(const Point & cursorPosition) { if(lCallback) + { + ENGINE->input().hapticFeedback(); lCallback(); + } } void CPicture::showPopupWindow(const Point & cursorPosition) diff --git a/lib/StartInfo.cpp b/lib/StartInfo.cpp index 50181bfe0..d95e52288 100644 --- a/lib/StartInfo.cpp +++ b/lib/StartInfo.cpp @@ -248,6 +248,10 @@ BattleOnlyModeStartInfo::BattleOnlyModeStartInfo() for(auto & element : primSkillLevel) for(size_t i=0; i selectedTown; std::array, 2> selectedHero; - std::array, 2> selectedArmy; + std::array, 2> selectedArmy; std::array, 2> primSkillLevel; + std::array, 8>, 2> secSkillLevel; + + std::array, 2> artifacts; + + std::array warMachines; + + std::array spellBook; BattleOnlyModeStartInfo(); @@ -260,6 +267,10 @@ public: h & selectedHero; h & selectedArmy; h & primSkillLevel; + h & secSkillLevel; + h & artifacts; + h & warMachines; + h & spellBook; } };