diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 516a33514..b27f75fb0 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1059,27 +1059,6 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re } } -bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const -{ - //TODO: make gathering gold, building tavern or conquering town (?) possible subgoals - if(!t) - t = findTownWithTavern(); - - if(!t || !townHasFreeTavern(t)) - return false; - - if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager - return false; - if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES) - return false; - if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) - return false; - if(!cb->getAvailableHeroes(t).size()) - return false; - - return true; -} - void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) { NET_EVENT_HANDLER; @@ -1155,16 +1134,6 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj) } } -HeroPtr AIGateway::getHeroWithGrail() const -{ - for(const CGHeroInstance * h : cb->getHeroesInfo()) - { - if(h->hasArt(ArtifactID::GRAIL)) - return h; - } - return nullptr; -} - bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) { if(h->inTownGarrison && h->visitedTown) @@ -1432,15 +1401,6 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade } } -const CGTownInstance * AIGateway::findTownWithTavern() const -{ - for(const CGTownInstance * t : cb->getTownsInfo()) - if(townHasFreeTavern(t)) - return t; - - return nullptr; -} - void AIGateway::endTurn() { logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr()); diff --git a/AI/Nullkiller/AIGateway.h b/AI/Nullkiller/AIGateway.h index 1443392f3..489aecf9d 100644 --- a/AI/Nullkiller/AIGateway.h +++ b/AI/Nullkiller/AIGateway.h @@ -198,11 +198,6 @@ public: void retrieveVisitableObjs(); virtual std::vector getFlaggedObjects() const; - HeroPtr getHeroWithGrail() const; - - const CGTownInstance * findTownWithTavern() const; - bool canRecruitAnyHero(const CGTownInstance * t = NULL) const; - void requestSent(const CPackForServer * pack, int requestID) override; void answerQuery(QueryID queryID, int selection); //special function that can be called ONLY from game events handling thread and will send request ASAP diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 572a88694..3322976af 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -12,6 +12,8 @@ #include "../Engine/Nullkiller.h" #include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/CHeroHandler.h" +#include "../../../lib/GameSettings.h" +#include "../../../lib/CGameState.h" namespace NKAI { @@ -179,6 +181,51 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const return evaluateFightingStrength(hero); } +bool HeroManager::canRecruitHero(const CGTownInstance * town) const +{ + if(!town) + town = findTownWithTavern(); + + if(!town || !townHasFreeTavern(town)) + return false; + + if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) + return false; + + const bool includeGarnisoned = true; + int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned); + + if(heroCount >= ALLOWED_ROAMING_HEROES) + return false; + + if(heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) + return false; + + if(!cb->getAvailableHeroes(town).size()) + return false; + + return true; +} + +const CGTownInstance * HeroManager::findTownWithTavern() const +{ + for(const CGTownInstance * t : cb->getTownsInfo()) + if(townHasFreeTavern(t)) + return t; + + return nullptr; +} + +const CGHeroInstance * HeroManager::findHeroWithGrail() const +{ + for(const CGHeroInstance * h : cb->getHeroesInfo()) + { + if(h->hasArt(ArtifactID::GRAIL)) + return h; + } + return nullptr; +} + SecondarySkillScoreMap::SecondarySkillScoreMap(std::map scoreMap) :scoreMap(scoreMap) { diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 459604bef..9c98443f3 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -30,6 +30,8 @@ public: virtual void update() = 0; virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0; virtual float evaluateHero(const CGHeroInstance * hero) const = 0; + virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0; + virtual const CGHeroInstance * findHeroWithGrail() const = 0; }; class DLL_EXPORT ISecondarySkillRule @@ -57,20 +59,24 @@ private: static SecondarySkillEvaluator scountSkillsScores; CCallback * cb; //this is enough, but we downcast from CCallback + const Nullkiller * ai; std::map heroRoles; public: - HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {} + HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} const std::map & getHeroRoles() const override; HeroRole getHeroRole(const HeroPtr & hero) const override; int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; void update() override; float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; float evaluateHero(const CGHeroInstance * hero) const override; + bool canRecruitHero(const CGTownInstance * t = nullptr) const override; + const CGHeroInstance * findHeroWithGrail() const override; private: float evaluateFightingStrength(const CGHeroInstance * hero) const; float evaluateSpeciality(const CGHeroInstance * hero) const; + const CGTownInstance * findTownWithTavern() const; }; // basic skill scores. missing skills will have score of 0 diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 8717900b6..e63e26c3a 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -209,7 +209,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const { captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); - if(tasks.empty()) + if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL) captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 78c8fe261..6d1a51a92 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -58,13 +58,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town); auto treats = { treatNode.maximumDanger, treatNode.fastestDanger }; - if(!treatNode.fastestDanger.hero) - { - logAi->trace("No treat found for town %s", town->getNameTranslated()); - - return; - } - int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK); if(town->garrisonHero) @@ -91,6 +84,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta return; } + + if(!treatNode.fastestDanger.hero) + { + logAi->trace("No treat found for town %s", town->getNameTranslated()); + + return; + } uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town); diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 82a146549..8e6e9c1eb 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -53,7 +53,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const for(auto town : towns) { - if(ai->canRecruitAnyHero(town)) + if(ai->nullkiller->heroManager->canRecruitHero(town)) { auto availableHeroes = cb->getAvailableHeroes(town); diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index c05c31726..06647e5d2 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -66,7 +66,7 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town) bool needToRecruitHero(const CGTownInstance * startupTown) { - if(!ai->canRecruitAnyHero(startupTown)) + if(!ai->nullkiller->heroManager->canRecruitHero(startupTown)) return false; if(!startupTown->garrisonHero && !startupTown->visitingHero) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b4e85fc97..c5aa3324f 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -117,7 +117,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi void Nullkiller::resetAiState() { lockedResources = TResources(); - scanDepth = ScanDepth::SMALL; + scanDepth = ScanDepth::FULL; playerID = ai->playerID; lockedHeroes.clear(); dangerHitMap->reset(); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index fb3bb5cc1..c28529386 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -88,6 +88,7 @@ public: int32_t getFreeGold() const { return getFreeResources()[Res::GOLD]; } void lockResources(const TResources & res); const TResources & getLockedResources() const { return lockedResources; } + ScanDepth getScanDepth() const { return scanDepth; } private: void resetAiState(); diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index d6cfe2908..ab596f801 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -33,8 +33,6 @@ void RecruitHero::accept(AIGateway * ai) { auto t = town; - if(!t) t = ai->findTownWithTavern(); - if(!t) { throw cannotFulfillGoalException("No town to recruit hero!"); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 8fd97a975..7d6f2c1e2 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1093,7 +1093,7 @@ void AINodeStorage::calculateTownPortal( if(nodeOptional) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 1 - logAi->trace("Adding town portal node at %s", targetTown->name); + logAi->trace("Adding town portal node at %s", targetTown->getObjectName()); #endif output.push_back(nodeOptional.get()); } diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index bce2a4284..f3143a239 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -53,15 +53,13 @@ namespace AIPathfinding for(const CGTownInstance * t : cb->getTownsInfo()) { - // do not allow ally shipyards because of bug - if(t->hasBuilt(BuildingID::SHIPYARD) && t->getOwner() == ai->playerID) + if(t->hasBuilt(BuildingID::SHIPYARD)) shipyards.push_back(t); } for(const CGObjectInstance * obj : ai->memory->visitableObjs) { - // do not allow ally shipyards because of bug - if(obj->ID != Obj::TOWN && obj->getOwner() == ai->playerID) //towns were handled in the previous loop + if(obj->ID != Obj::TOWN) //towns were handled in the previous loop { if(const IShipyard * shipyard = IShipyard::castFrom(obj)) shipyards.push_back(shipyard); diff --git a/CMakeLists.txt b/CMakeLists.txt index 84fe7a797..f9d2cc2d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,6 +246,7 @@ if(MINGW OR MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_SCL_SECURE_NO_WARNINGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250") # 4250: 'class1' : inherits 'class2::member' via dominance set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # 4251: class 'xxx' needs to have dll-interface to be used by clients of class 'yyy' diff --git a/ChangeLog.md b/ChangeLog.md index 0f3f0ec46..0bce56f4f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,23 @@ +# 1.2.0 -> 1.2.1 + +### GENERAL: +* Implemented spell range overlay for Dimension Door and Scuttle Boat +* Fixed movement cost penalty from terrain +* Fixed empty Black Market on game start +* Fixed bad morale happening after waiting +* Fixed good morale happening after defeating last enemy unit +* Fixed death animation of Efreeti killed by petrification attack +* Fixed crash on leaving to main menu from battle in hotseat mode +* Adventure map spells are no longer visible on units in battle +* Attempt to cast spell with no valid targets in hotseat will show appropriate error message +* RMG settings will now show all existing in game templates and not just those suitable for current settings +* RMG settings (map size and two-level maps) that are not compatible with current template will be blocked +* Fixed centering of scenario information window +* Fixed crash on empty save game list after filtering +* Fixed blocked progress in Launcher on language detection failure +* Launcher will now correctly handle selection of Ddata directory in H3 install +* Map editor will now correctly save message property for events and pandoras + # 1.1.1 -> 1.2.0 ### GENERAL: diff --git a/Mods/vcmi/Data/debug/cached.png b/Mods/vcmi/Data/debug/cached.png deleted file mode 100644 index 7f7dbefae..000000000 Binary files a/Mods/vcmi/Data/debug/cached.png and /dev/null differ diff --git a/Mods/vcmi/Data/debug/spellRange.png b/Mods/vcmi/Data/debug/spellRange.png new file mode 100644 index 000000000..4618c58c0 Binary files /dev/null and b/Mods/vcmi/Data/debug/spellRange.png differ diff --git a/client/Client.cpp b/client/Client.cpp index 686395719..1160a00d1 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -374,6 +374,7 @@ void CClient::endGame() //threads cleanup has to be after gs cleanup and before battleints cleanup to stop tacticThread cleanThreads(); + CPlayerInterface::battleInt.reset(); playerint.clear(); battleints.clear(); battleCallbacks.clear(); diff --git a/client/adventureMap/CAdvMapInt.cpp b/client/adventureMap/CAdvMapInt.cpp index e9f1d7b4a..b1f2ca435 100644 --- a/client/adventureMap/CAdvMapInt.cpp +++ b/client/adventureMap/CAdvMapInt.cpp @@ -1076,8 +1076,11 @@ void CAdvMapInt::onTileLeftClicked(const int3 &mapPos) const CGObjectInstance *topBlocking = getActiveObject(mapPos); int3 selPos = selection->getSightCenter(); - if(spellBeingCasted && isInScreenRange(selPos, mapPos)) + if(spellBeingCasted) { + if (!isInScreenRange(selPos, mapPos)) + return; + const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos); switch(spellBeingCasted->id) @@ -1179,11 +1182,15 @@ void CAdvMapInt::onTileHovered(const int3 &mapPos) switch(spellBeingCasted->id) { case SpellID::SCUTTLE_BOAT: - if(objAtTile && objAtTile->ID == Obj::BOAT) + { + int3 hpos = selection->getSightCenter(); + + if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos)) CCS->curh->set(Cursor::Map::SCUTTLE_BOAT); else CCS->curh->set(Cursor::Map::POINTER); return; + } case SpellID::DIMENSION_DOOR: { const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false); @@ -1342,6 +1349,8 @@ void CAdvMapInt::enterCastingMode(const CSpell * sp) { assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR); spellBeingCasted = sp; + Settings config = settings.write["session"]["showSpellRange"]; + config->Bool() = true; deactivate(); terrain->activate(); @@ -1356,6 +1365,9 @@ void CAdvMapInt::leaveCastingMode(bool cast, int3 dest) terrain->deactivate(); activate(); + Settings config = settings.write["session"]["showSpellRange"]; + config->Bool() = false; + if(cast) LOCPLINT->cb->castSpell(curHero(), id, dest); else diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 96ffffbd4..dbb7c1705 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -137,7 +137,7 @@ bool StackActionAnimation::init() StackActionAnimation::~StackActionAnimation() { - if (stack->isFrozen()) + if (stack->isFrozen() && currGroup != ECreatureAnimType::DEATH && currGroup != ECreatureAnimType::DEATH_RANGED) myAnim->setType(ECreatureAnimType::HOLDING); else myAnim->setType(nextGroup); diff --git a/client/lobby/CLobbyScreen.cpp b/client/lobby/CLobbyScreen.cpp index dbe54e0cb..7c862d30f 100644 --- a/client/lobby/CLobbyScreen.cpp +++ b/client/lobby/CLobbyScreen.cpp @@ -197,7 +197,7 @@ void CLobbyScreen::updateAfterStateChange() } } - if(curTab == tabRand && CSH->si->mapGenOptions) + if(curTab && curTab == tabRand && CSH->si->mapGenOptions) tabRand->setMapGenOptions(CSH->si->mapGenOptions); } diff --git a/client/lobby/CSavingScreen.cpp b/client/lobby/CSavingScreen.cpp index daf154349..59741d916 100644 --- a/client/lobby/CSavingScreen.cpp +++ b/client/lobby/CSavingScreen.cpp @@ -31,8 +31,6 @@ CSavingScreen::CSavingScreen() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; center(pos); - // TODO: we should really use std::shared_ptr for passing StartInfo around. - localSi = new StartInfo(*LOCPLINT->cb->getStartInfo()); localMi = std::make_shared(); localMi->mapHeader = std::unique_ptr(new CMapHeader(*LOCPLINT->cb->getMapHeader())); @@ -52,7 +50,9 @@ const CMapInfo * CSavingScreen::getMapInfo() const StartInfo * CSavingScreen::getStartInfo() { - return localSi; + if (localMi) + return localMi->scenarioOptionsOfSave; + return LOCPLINT->cb->getStartInfo(); } void CSavingScreen::changeSelection(std::shared_ptr to) @@ -61,7 +61,6 @@ void CSavingScreen::changeSelection(std::shared_ptr to) return; localMi = to; - localSi = localMi->scenarioOptionsOfSave; card->changeSelection(); } diff --git a/client/lobby/CSavingScreen.h b/client/lobby/CSavingScreen.h index b7ac05012..e99809407 100644 --- a/client/lobby/CSavingScreen.h +++ b/client/lobby/CSavingScreen.h @@ -23,7 +23,6 @@ class CSelectionBase; class CSavingScreen : public CSelectionBase { public: - const StartInfo * localSi; std::shared_ptr localMi; CSavingScreen(); diff --git a/client/lobby/CScenarioInfoScreen.cpp b/client/lobby/CScenarioInfoScreen.cpp index a64676a4f..6296f0b18 100644 --- a/client/lobby/CScenarioInfoScreen.cpp +++ b/client/lobby/CScenarioInfoScreen.cpp @@ -26,6 +26,10 @@ CScenarioInfoScreen::CScenarioInfoScreen() { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + pos.w = 800; + pos.h = 600; + pos = center(); + localSi = new StartInfo(*LOCPLINT->cb->getStartInfo()); localMi = new CMapInfo(); localMi->mapHeader = std::unique_ptr(new CMapHeader(*LOCPLINT->cb->getMapHeader())); diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp index 339e838df..9c90f5157 100644 --- a/client/lobby/RandomMapTab.cpp +++ b/client/lobby/RandomMapTab.cpp @@ -242,9 +242,29 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr opts) } if(auto w = widget("groupMapSize")) + { + for(auto toggle : w->buttons) + { + if(auto button = std::dynamic_pointer_cast(toggle.second)) + { + const auto & mapSizes = getPossibleMapSizes(); + int3 size( mapSizes[toggle.first], mapSizes[toggle.first], 1 + mapGenOptions->getHasTwoLevels()); + + bool sizeAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); + button->block(!sizeAllowed); + } + } w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth())); + } if(auto w = widget("buttonTwoLevels")) + { + int3 size( opts->getWidth(), opts->getWidth(), 2); + + bool undergoundAllowed = !mapGenOptions->getMapTemplate() || mapGenOptions->getMapTemplate()->matchesSize(size); + w->setSelected(opts->getHasTwoLevels()); + w->block(!undergoundAllowed); + } if(auto w = widget("groupMaxPlayers")) { w->setSelected(opts->getPlayerCount()); @@ -408,7 +428,11 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size): REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem); curItems = VLC->tplh->getTemplates(); - vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);}); + + boost::range::sort(curItems, [](const CRmgTemplate * a, const CRmgTemplate * b){ + return a->getName() < b->getName(); + }); + curItems.insert(curItems.begin(), nullptr); //default template const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json")); diff --git a/client/mapView/IMapRendererContext.h b/client/mapView/IMapRendererContext.h index 4f5cf7917..049204c08 100644 --- a/client/mapView/IMapRendererContext.h +++ b/client/mapView/IMapRendererContext.h @@ -86,4 +86,8 @@ public: virtual bool showGrid() const = 0; virtual bool showVisitable() const = 0; virtual bool showBlocked() const = 0; + + /// if true, spell range for teleport / scuttle boat will be visible + virtual bool showSpellRange(const int3 & position) const = 0; + }; diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 86d67dcff..9b89e4937 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -584,15 +584,16 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 & return 0xff-1; } -MapRendererDebug::MapRendererDebug() +MapRendererOverlay::MapRendererOverlay() : imageGrid(IImage::createFromFile("debug/grid", EImageBlitMode::ALPHA)) , imageBlocked(IImage::createFromFile("debug/blocked", EImageBlitMode::ALPHA)) , imageVisitable(IImage::createFromFile("debug/visitable", EImageBlitMode::ALPHA)) + , imageSpellRange(IImage::createFromFile("debug/spellRange", EImageBlitMode::ALPHA)) { } -void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates) +void MapRendererOverlay::renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates) { if(context.showGrid()) target.draw(imageGrid, Point(0,0)); @@ -618,9 +619,12 @@ void MapRendererDebug::renderTile(IMapRendererContext & context, Canvas & target if (context.showVisitable() && visitable) target.draw(imageVisitable, Point(0,0)); } + + if (context.showSpellRange(coordinates)) + target.draw(imageSpellRange, Point(0,0)); } -uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & coordinates) +uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 & coordinates) { uint8_t result = 0; @@ -633,6 +637,9 @@ uint8_t MapRendererDebug::checksum(IMapRendererContext & context, const int3 & c if (context.showGrid()) result += 4; + if (context.showSpellRange(coordinates)) + result += 8; + return result; } @@ -766,7 +773,7 @@ MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & con result[3] = rendererRoad.checksum(context, coordinates); result[4] = rendererObjects.checksum(context, coordinates); result[5] = rendererPath.checksum(context, coordinates); - result[6] = rendererDebug.checksum(context, coordinates); + result[6] = rendererOverlay.checksum(context, coordinates); if(!context.isVisible(coordinates)) result[7] = rendererFow.checksum(context, coordinates); @@ -800,7 +807,7 @@ void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, con rendererObjects.renderTile(context, target, coordinates); rendererPath.renderTile(context, target, coordinates); - rendererDebug.renderTile(context, target, coordinates); + rendererOverlay.renderTile(context, target, coordinates); if(!context.isVisible(coordinates)) rendererFow.renderTile(context, target, coordinates); diff --git a/client/mapView/MapRenderer.h b/client/mapView/MapRenderer.h index c1fb78d86..2ee036f88 100644 --- a/client/mapView/MapRenderer.h +++ b/client/mapView/MapRenderer.h @@ -129,21 +129,12 @@ public: void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates); }; -class MapRendererDebug +class MapRendererOverlay { std::shared_ptr imageGrid; std::shared_ptr imageVisitable; std::shared_ptr imageBlocked; -public: - MapRendererDebug(); - - uint8_t checksum(IMapRendererContext & context, const int3 & coordinates); - void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates); -}; - -class MapRendererOverlay -{ - std::unique_ptr iconsStorage; + std::shared_ptr imageSpellRange; public: MapRendererOverlay(); @@ -160,7 +151,7 @@ class MapRenderer MapRendererFow rendererFow; MapRendererObjects rendererObjects; MapRendererPath rendererPath; - MapRendererDebug rendererDebug; + MapRendererOverlay rendererOverlay; public: using TileChecksum = std::array; diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 4e3c95d5b..3060e41c5 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -22,6 +22,7 @@ #include "../../lib/CPathfinder.h" #include "../../lib/Point.h" #include "../../lib/mapObjects/CGHeroInstance.h" +#include "../../lib/spells/CSpellHandler.h" #include "../../lib/mapping/CMap.h" MapRendererBaseContext::MapRendererBaseContext(const MapRendererContextState & viewState) @@ -199,6 +200,11 @@ bool MapRendererBaseContext::showBlocked() const return false; } +bool MapRendererBaseContext::showSpellRange(const int3 & position) const +{ + return false; +} + MapRendererAdventureContext::MapRendererAdventureContext(const MapRendererContextState & viewState) : MapRendererBaseContext(viewState) { @@ -266,6 +272,19 @@ bool MapRendererAdventureContext::showBlocked() const return settingShowBlocked; } +bool MapRendererAdventureContext::showSpellRange(const int3 & position) const +{ + if (!settingSpellRange) + return false; + + auto hero = adventureInt->curHero(); + + if (!hero) + return false; + + return !isInScreenRange(hero->getSightCenter(), position); +} + MapRendererAdventureTransitionContext::MapRendererAdventureTransitionContext(const MapRendererContextState & viewState) : MapRendererAdventureContext(viewState) { diff --git a/client/mapView/MapRendererContext.h b/client/mapView/MapRendererContext.h index 0a299fbac..ee8061535 100644 --- a/client/mapView/MapRendererContext.h +++ b/client/mapView/MapRendererContext.h @@ -58,6 +58,7 @@ public: bool showGrid() const override; bool showVisitable() const override; bool showBlocked() const override; + bool showSpellRange(const int3 & position) const override; }; class MapRendererAdventureContext : public MapRendererBaseContext @@ -67,6 +68,7 @@ public: bool settingShowGrid = false; bool settingShowVisitable = false; bool settingShowBlocked = false; + bool settingSpellRange= false; bool settingsAdventureObjectAnimation = true; bool settingsAdventureTerrainAnimation = true; @@ -80,6 +82,8 @@ public: bool showGrid() const override; bool showVisitable() const override; bool showBlocked() const override; + + bool showSpellRange(const int3 & position) const override; }; class MapRendererAdventureTransitionContext : public MapRendererAdventureContext diff --git a/client/mapView/MapView.cpp b/client/mapView/MapView.cpp index a666e23fa..dd5e3d750 100644 --- a/client/mapView/MapView.cpp +++ b/client/mapView/MapView.cpp @@ -160,6 +160,7 @@ void MapView::onViewMapActivated() PuzzleMapView::PuzzleMapView(const Point & offset, const Point & dimensions, const int3 & tileToCenter) : BasicMapView(offset, dimensions) { - controller->setViewCenter(tileToCenter); controller->activatePuzzleMapContext(tileToCenter); + controller->setViewCenter(tileToCenter); + } diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index 2f0fd06bd..099ba4979 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -63,7 +63,7 @@ void MapViewController::setViewCenter(const Point & position, int level) model->setViewCenter(betterPosition); model->setLevel(vstd::clamp(level, 0, context->getMapSize().z)); - if(adventureInt) // may be called before adventureInt is initialized + if(adventureInt && !puzzleMapContext) // may be called before adventureInt is initialized adventureInt->onMapViewMoved(model->getTilesTotalRect(), model->getLevel()); } @@ -154,6 +154,7 @@ void MapViewController::updateBefore(uint32_t timeDelta) adventureContext->settingShowGrid = settings["gameTweaks"]["showGrid"].Bool(); adventureContext->settingShowVisitable = settings["session"]["showVisitable"].Bool(); adventureContext->settingShowBlocked = settings["session"]["showBlocked"].Bool(); + adventureContext->settingSpellRange = settings["session"]["showSpellRange"].Bool(); } } diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 6150ab154..1bc1327e9 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -509,7 +509,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) auto spellCost = owner->myInt->cb->getSpellCost(mySpell, owner->myHero); if(spellCost > owner->myHero->mana) //insufficient mana { - owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); + LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); return; } @@ -529,7 +529,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) if((combatSpell ^ inCombat) || inCastle) { std::vector> hlp(1, std::make_shared(CComponent::spell, mySpell->id, 0)); - owner->myInt->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); + LOCPLINT->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp); } else if(combatSpell) { @@ -544,9 +544,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) std::vector texts; problem.getAll(texts); if(!texts.empty()) - owner->myInt->showInfoDialog(texts.front()); + LOCPLINT->showInfoDialog(texts.front()); else - owner->myInt->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem")); + LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem")); } } else //adventure spell diff --git a/cmake_modules/VersionDefinition.cmake b/cmake_modules/VersionDefinition.cmake index e66c42bd4..eb75e1797 100644 --- a/cmake_modules/VersionDefinition.cmake +++ b/cmake_modules/VersionDefinition.cmake @@ -1,6 +1,6 @@ set(VCMI_VERSION_MAJOR 1) set(VCMI_VERSION_MINOR 2) -set(VCMI_VERSION_PATCH 0) +set(VCMI_VERSION_PATCH 1) add_definitions( -DVCMI_VERSION_MAJOR=${VCMI_VERSION_MAJOR} -DVCMI_VERSION_MINOR=${VCMI_VERSION_MINOR} diff --git a/debian/changelog b/debian/changelog index f3f6e2b8d..184956dbc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,15 @@ +vcmi (1.2.1) jammy; urgency=medium + + * New upstream release + + -- Ivan Savenko Fri, 28 Apr 2023 16:00:00 +0200 + vcmi (1.2.0) jammy; urgency=medium * New upstream release -- Ivan Savenko Fri, 14 Apr 2023 16:00:00 +0200 - + vcmi (1.1.1) jammy; urgency=medium * New upstream release diff --git a/launcher/eu.vcmi.VCMI.metainfo.xml b/launcher/eu.vcmi.VCMI.metainfo.xml index 7ede06f19..68e43122a 100644 --- a/launcher/eu.vcmi.VCMI.metainfo.xml +++ b/launcher/eu.vcmi.VCMI.metainfo.xml @@ -38,6 +38,7 @@ https://github.com/vcmi/vcmi/issues https://vcmi.eu/faq/ + diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index 10253d26e..2836cf41f 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -190,7 +190,7 @@ void FirstLaunchView::heroesDataMissing() ui->labelDataCopy->setVisible(true); ui->labelDataFound->setVisible(false); - ui->pushButtonDataNext->setEnabled(true); + ui->pushButtonDataNext->setEnabled(false); if(hasVCMIBuilderScript) { diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index 9b144cb8a..6906da1dc 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -96,14 +96,14 @@ JsonNode toJson(QVariant object) { JsonNode ret; - if(object.canConvert()) - ret.Struct() = VariantToMap(object.toMap()); - else if(object.canConvert()) - ret.Vector() = VariantToList(object.toList()); - else if(object.userType() == QMetaType::QString) + if(object.userType() == QMetaType::QString) ret.String() = object.toString().toUtf8().data(); else if(object.userType() == QMetaType::Bool) ret.Bool() = object.toBool(); + else if(object.canConvert()) + ret.Struct() = VariantToMap(object.toMap()); + else if(object.canConvert()) + ret.Vector() = VariantToList(object.toList()); else if(object.canConvert()) ret.Integer() = object.toInt(); else if(object.canConvert()) diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 44439c615..d51d0325b 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -133,7 +133,7 @@ std::vector CStack::activeSpells() const CSelector selector = Selector::sourceType()(Bonus::SPELL_EFFECT) .And(CSelector([](const Bonus * b)->bool { - return b->type != Bonus::NONE; + return b->type != Bonus::NONE && SpellID(b->sid).toSpell() && !SpellID(b->sid).toSpell()->isAdventure(); })); TConstBonusListPtr spellEffects = getBonuses(selector, Selector::all, cachingStr.str()); diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 762088784..71204e86c 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -279,10 +279,10 @@ void CGBlackMarket::newTurn(CRandomGenerator & rand) const { int resetPeriod = VLC->settings()->getInteger(EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD); - if(resetPeriod == 0) //check if feature changing OH3 behavior is enabled - return; + bool isFirstDay = cb->getDate(Date::DAY) == 1; + bool regularResetTriggered = resetPeriod != 0 && ((cb->getDate(Date::DAY)-1) % resetPeriod) != 0; - if (((cb->getDate(Date::DAY)-1) % resetPeriod) != 0) + if (!isFirstDay && !regularResetTriggered) return; SetAvailableArtifacts saa; diff --git a/mapeditor/inspector/inspector.cpp b/mapeditor/inspector/inspector.cpp index 74a3aa836..a4bbb62ad 100644 --- a/mapeditor/inspector/inspector.cpp +++ b/mapeditor/inspector/inspector.cpp @@ -472,6 +472,9 @@ void Inspector::setProperty(CGLighthouse * o, const QString & key, const QVarian void Inspector::setProperty(CGPandoraBox * o, const QString & key, const QVariant & value) { if(!o) return; + + if(key == "Message") + o->message = value.toString().toStdString(); } void Inspector::setProperty(CGEvent * o, const QString & key, const QVariant & value) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 41e218e29..886ef9e69 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -6600,9 +6600,9 @@ void CGameHandler::runBattle() if(!removeGhosts.changedStacks.empty()) sendAndApply(&removeGhosts); - //check for bad morale => freeze + // check for bad morale => freeze int nextStackMorale = next->MoraleVal(); - if (nextStackMorale < 0) + if(!next->hadMorale && !next->waited() && nextStackMorale < 0) { auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_BAD_MORALE_DICE); size_t diceIndex = std::min(diceSize.size()-1, -nextStackMorale); @@ -6788,12 +6788,13 @@ void CGameHandler::runBattle() { //check for good morale nextStackMorale = next->MoraleVal(); - if(!next->hadMorale //only one extra move per turn possible + if( !battleResult.get() + && !next->hadMorale && !next->defending && !next->waited() && !next->fear - && next->alive() - && nextStackMorale > 0) + && next->alive() + && nextStackMorale > 0) { auto diceSize = VLC->settings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE); size_t diceIndex = std::min(diceSize.size()-1, nextStackMorale);