diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 079e3de1a..1d0432297 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -154,6 +154,9 @@ "vcmi.battleOptions.showStickyHeroInfoWindows.help": "{Show heroes statistics windows}\n\nPermanently toggle on heroes statistics windows that show primary stats and spell points.", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle.", + + "vcmi.adventureMap.revisitObject.hover" : "Revisit Object", + "vcmi.adventureMap.revisitObject.help" : "{Revisit Object}\n\nIf a hero currently stands on a Map Object, he can revisit the location.", "vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately", "vcmi.battleWindow.damageEstimation.melee" : "Attack %CREATURE (%DAMAGE).", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index 9773fc7af..81a7244db 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -130,6 +130,9 @@ "vcmi.battleOptions.skipBattleIntroMusic.hover": "Пропускати вступну музику", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.", + "vcmi.adventureMap.revisitObject.hover" : "Відвідати Об'єкт", + "vcmi.adventureMap.revisitObject.help" : "{Відвідати Об'єкт}\n\nЯкщо герой в даний момент стоїть на об'єкті мапи, він може знову відвідати цю локацію.", + "vcmi.battleWindow.pressKeyToSkipIntro" : "Натисніть будь-яку клавішу, щоб розпочати бій", "vcmi.battleWindow.damageEstimation.melee" : "Атакувати %CREATURE (%DAMAGE).", "vcmi.battleWindow.damageEstimation.meleeKills" : "Атакувати %CREATURE (%DAMAGE, %KILLS).", diff --git a/client/CMT.cpp b/client/CMT.cpp index 8f38897f7..f3741bbbf 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -24,6 +24,7 @@ #include "CServerHandler.h" #include "ClientCommandManager.h" #include "windows/CMessage.h" +#include "windows/InfoWindows.h" #include "render/IScreenHandler.h" #include "render/Graphics.h" @@ -502,10 +503,14 @@ static void quitApplication() void handleQuit(bool ask) { - if(CSH->client && LOCPLINT && ask) + if(ask) { CCS->curh->set(Cursor::Map::POINTER); - LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr); + + if (LOCPLINT) + LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr); + else + CInfoWindow::showYesNoDialog(CGI->generaltexth->allTexts[69], {}, quitApplication, {}, PlayerColor(1)); } else { diff --git a/client/eventsSDL/InputHandler.cpp b/client/eventsSDL/InputHandler.cpp index 23aa2ea25..257ee70d0 100644 --- a/client/eventsSDL/InputHandler.cpp +++ b/client/eventsSDL/InputHandler.cpp @@ -116,7 +116,11 @@ void InputHandler::preprocessEvent(const SDL_Event & ev) if(ev.type == SDL_QUIT) { boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex); +#ifdef VCMI_ANDROID handleQuit(false); +#else + handleQuit(true); +#endif return; } else if(ev.type == SDL_KEYDOWN) diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index e62bf26c4..a2f5027b0 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -561,27 +561,35 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeader() void OptionsTab::CPlayerOptionTooltipBox::genTownWindow() { + auto factionIndex = playerSettings.getCastleValidated(); + + if (playerSettings.castle == FactionID::RANDOM) + return genBonusWindow(); + pos = Rect(0, 0, 228, 290); genHeader(); labelAssociatedCreatures = std::make_shared(pos.w / 2 + 8, 122, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]); - auto factionIndex = playerSettings.getCastleValidated(); std::vector> components; const CTown * town = (*CGI->townh)[factionIndex]->town; for(auto & elem : town->creatures) { if(!elem.empty()) - components.push_back(std::make_shared(ComponentType::CREATURE, elem.front(), 0, CComponent::tiny)); + components.push_back(std::make_shared(ComponentType::CREATURE, elem.front(), std::nullopt, CComponent::tiny)); } boxAssociatedCreatures = std::make_shared(components, Rect(10, 140, pos.w - 20, 140)); } void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow() { + auto heroIndex = playerSettings.getHeroValidated(); + + if (playerSettings.hero == HeroTypeID::RANDOM) + return genBonusWindow(); + pos = Rect(0, 0, 292, 226); genHeader(); labelHeroSpeciality = std::make_shared(pos.w / 2 + 4, 117, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]); - auto heroIndex = playerSettings.getHeroValidated(); imageSpeciality = std::make_shared(AnimationPath::builtin("UN44"), (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134); labelSpecialityName = std::make_shared(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->getSpecialtyNameTranslated()); diff --git a/config/objects/rewardableOncePerHero.json b/config/objects/rewardableOncePerHero.json index 43065ed8d..779575307 100644 --- a/config/objects/rewardableOncePerHero.json +++ b/config/objects/rewardableOncePerHero.json @@ -249,7 +249,7 @@ "description" : "@core.arraytxt.202", "message" : 148, "appearChance" : { "max" : 34 }, - "gainedLevels" : 1 + "heroLevel" : 1 }, { "description" : "@core.arraytxt.203", @@ -257,7 +257,7 @@ "appearChance" : { "min" : 34, "max" : 67 }, "limiter" : { "resources" : { "gold" : 2000 } }, "resources" : { "gold" : -2000 }, - "gainedLevels" : 1 + "heroLevel" : 1 }, { "description" : "@core.arraytxt.204", @@ -265,7 +265,7 @@ "appearChance" : { "min" : 67 }, "limiter" : { "resources" : { "gems" : 10 } }, "resources" : { "gems" : -10 }, - "gainedLevels" : 1 + "heroLevel" : 1 }, ] } diff --git a/lib/ArtifactUtils.cpp b/lib/ArtifactUtils.cpp index c306e1ef4..d5b9154ed 100644 --- a/lib/ArtifactUtils.cpp +++ b/lib/ArtifactUtils.cpp @@ -255,10 +255,13 @@ DLL_LINKAGE void ArtifactUtils::insertScrrollSpellName(std::string & description // However other language versions don't have name placeholder at all, so we have to be careful auto nameStart = description.find_first_of('['); auto nameEnd = description.find_first_of(']', nameStart); - if(sid.getNum() >= 0) + + if(nameStart != std::string::npos && nameEnd != std::string::npos) { - if(nameStart != std::string::npos && nameEnd != std::string::npos) + if(sid.getNum() >= 0) description = description.replace(nameStart, nameEnd - nameStart + 1, sid.toEntity(VLC->spells())->getNameTranslated()); + else + description = description.erase(nameStart, nameEnd - nameStart + 2); // erase "[spell name] " - including space } } diff --git a/lib/gameState/CGameStateCampaign.cpp b/lib/gameState/CGameStateCampaign.cpp index f67ed7311..6ba1a44d6 100644 --- a/lib/gameState/CGameStateCampaign.cpp +++ b/lib/gameState/CGameStateCampaign.cpp @@ -217,6 +217,10 @@ void CGameStateCampaign::placeCampaignHeroes() for(auto & heroID : heroesToRemove) { + // Do not replace reserved heroes initially, e.g. in 1st campaign scenario in which they appear + if (campaignState->getHeroByType(heroID).isNull()) + continue; + auto * hero = gameState->getUsedHero(heroID); if(hero) { diff --git a/lib/gameState/TavernHeroesPool.cpp b/lib/gameState/TavernHeroesPool.cpp index 00aec3a73..40161ff2e 100644 --- a/lib/gameState/TavernHeroesPool.cpp +++ b/lib/gameState/TavernHeroesPool.cpp @@ -126,7 +126,7 @@ void TavernHeroesPool::onNewDay() continue; hero.second->setMovementPoints(hero.second->movementPointsLimit(true)); - hero.second->mana = hero.second->manaLimit(); + hero.second->mana = hero.second->getManaNewTurn(); } } diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 9f32ae1c2..14ca4b93e 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -70,6 +70,9 @@ std::vector CBank::getPopupComponents(PlayerColor player) const if (!VLC->settings()->getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION)) return {}; + if (bc == nullptr) + return {}; + std::map guardsAmounts; std::vector result; diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 957c81757..ed1871ef1 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -208,9 +208,9 @@ void CQuest::addTextReplacements(MetaString & text, std::vector & com addKillTargetReplacements(text); } - if(killTarget != ObjectInstanceID::NONE && stackToKill.type) + if(killTarget != ObjectInstanceID::NONE && stackToKill != CreatureID::NONE) { - components.emplace_back(ComponentType::CREATURE, stackToKill.getId(), stackToKill.getCount()); + components.emplace_back(ComponentType::CREATURE, stackToKill); addKillTargetReplacements(text); } @@ -314,7 +314,7 @@ void CQuest::defineQuestName() if(!mission.spells.empty()) questName = CQuest::missionName(2); if(!mission.secondary.empty()) questName = CQuest::missionName(2); if(killTarget != ObjectInstanceID::NONE && !heroName.empty()) questName = CQuest::missionName(3); - if(killTarget != ObjectInstanceID::NONE && stackToKill.getType()) questName = CQuest::missionName(4); + if(killTarget != ObjectInstanceID::NONE && stackToKill != CreatureID::NONE) questName = CQuest::missionName(4); if(!mission.artifacts.empty()) questName = CQuest::missionName(5); if(!mission.creatures.empty()) questName = CQuest::missionName(6); if(mission.resources.nonZero()) questName = CQuest::missionName(7); @@ -327,9 +327,9 @@ void CQuest::addKillTargetReplacements(MetaString &out) const { if(!heroName.empty()) out.replaceTextID(heroName); - if(stackToKill.type) + if(stackToKill != CreatureID::NONE) { - out.replaceName(stackToKill); + out.replaceNamePlural(stackToKill); out.replaceRawString(VLC->generaltexth->arraytxt[147+stackDirection]); } } @@ -429,9 +429,8 @@ void CGSeerHut::setObjToKill() if(getCreatureToKill(true)) { - quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :? - assert(quest->stackToKill.type); - quest->stackToKill.count = 0; //no count in info window + quest->stackToKill = getCreatureToKill(false)->getCreature(); + assert(quest->stackToKill != CreatureID::NONE); quest->stackDirection = checkDirection(); } else if(getHeroToKill(true)) diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 3dba2903d..bfeaecd97 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -40,7 +40,7 @@ public: // needed for messages / hover text ui8 textOption; ui8 completedOption; - CStackBasicDescriptor stackToKill; + CreatureID stackToKill; ui8 stackDirection; std::string heroName; //backup of hero name HeroTypeID heroPortrait; diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index eaf9b797a..c05fbd9aa 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -301,7 +301,7 @@ std::string CRewardableObject::getDescriptionMessage(PlayerColor player, const C return configuration.description.toString(); auto rewardIndices = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT); - if (rewardIndices.empty() && !configuration.info[0].description.empty()) + if (rewardIndices.empty() || !configuration.info[0].description.empty()) return configuration.info[0].description.toString(); if (!configuration.info[rewardIndices.front()].description.empty()) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 8d3a3dc3c..82c5cfe72 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -779,7 +779,8 @@ std::string CGArtifact::getPopupText(PlayerColor player) const if (settings["general"]["enableUiEnhancements"].Bool()) { std::string description = VLC->artifacts()->getById(getArtifact())->getDescriptionTranslated(); - ArtifactUtils::insertScrrollSpellName(description, SpellID::NONE); // erase text placeholder + if (getArtifact() == ArtifactID::SPELL_SCROLL) + ArtifactUtils::insertScrrollSpellName(description, SpellID::NONE); // erase text placeholder return description; } else