1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Merge pull request #2088 from vcmi/beta

Release 1.2.1
This commit is contained in:
Ivan Savenko 2023-04-28 19:47:07 +03:00 committed by GitHub
commit 25d5a1555c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 254 additions and 122 deletions

View File

@ -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());

View File

@ -198,11 +198,6 @@ public:
void retrieveVisitableObjs();
virtual std::vector<const CGObjectInstance *> 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

View File

@ -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<SecondarySkill, float> scoreMap)
:scoreMap(scoreMap)
{

View File

@ -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<HeroPtr, HeroRole> heroRoles;
public:
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {}
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {}
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
HeroRole getHeroRole(const HeroPtr & hero) const override;
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & 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

View File

@ -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());
}

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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();

View File

@ -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();

View File

@ -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!");

View File

@ -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());
}

View File

@ -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);

View File

@ -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'

View File

@ -1,3 +1,26 @@
# 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
* Fixed music playback on switching between towns
* Special months (double growth and plague) will now appear correctly
* 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
* Fixed incorrect saving of heroes portraits in editor
# 1.1.1 -> 1.2.0
### GENERAL:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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<CMapInfo>();
localMi->mapHeader = std::unique_ptr<CMapHeader>(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<CMapInfo> to)
@ -61,7 +61,6 @@ void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to)
return;
localMi = to;
localSi = localMi->scenarioOptionsOfSave;
card->changeSelection();
}

View File

@ -23,7 +23,6 @@ class CSelectionBase;
class CSavingScreen : public CSelectionBase
{
public:
const StartInfo * localSi;
std::shared_ptr<CMapInfo> localMi;
CSavingScreen();

View File

@ -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<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));

View File

@ -242,9 +242,29 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
}
if(auto w = widget<CToggleGroup>("groupMapSize"))
{
for(auto toggle : w->buttons)
{
if(auto button = std::dynamic_pointer_cast<CToggleButton>(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<CToggleButton>("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<CToggleGroup>("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"));

View File

@ -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;
};

View File

@ -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);

View File

@ -129,21 +129,12 @@ public:
void renderTile(IMapRendererContext & context, Canvas & target, const int3 & coordinates);
};
class MapRendererDebug
class MapRendererOverlay
{
std::shared_ptr<IImage> imageGrid;
std::shared_ptr<IImage> imageVisitable;
std::shared_ptr<IImage> 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<CAnimation> iconsStorage;
std::shared_ptr<IImage> imageSpellRange;
public:
MapRendererOverlay();
@ -160,7 +151,7 @@ class MapRenderer
MapRendererFow rendererFow;
MapRendererObjects rendererObjects;
MapRendererPath rendererPath;
MapRendererDebug rendererDebug;
MapRendererOverlay rendererOverlay;
public:
using TileChecksum = std::array<uint8_t, 8>;

View File

@ -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)
{

View File

@ -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

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -1206,13 +1206,17 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
townlist->onSelect = std::bind(&CCastleInterface::townChange, this);
recreateIcons();
adventureInt->onAudioPaused();
if (!from)
adventureInt->onAudioPaused();
CCS->musich->playMusic(town->town->clientInfo.musicTheme, true, false);
}
CCastleInterface::~CCastleInterface()
{
if (adventureInt) // may happen on exiting client with open castle interface
// resume map audio if:
// adventureInt exists (may happen on exiting client with open castle interface)
// castleInt has not been replaced (happens on switching between towns inside castle interface)
if (adventureInt && LOCPLINT->castleInt == this)
adventureInt->onAudioResumed();
if(LOCPLINT->castleInt == this)
LOCPLINT->castleInt = nullptr;

View File

@ -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<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(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<std::string> 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

View File

@ -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}

8
debian/changelog vendored
View File

@ -1,9 +1,15 @@
vcmi (1.2.1) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 28 Apr 2023 16:00:00 +0200
vcmi (1.2.0) jammy; urgency=medium
* New upstream release
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 14 Apr 2023 16:00:00 +0200
vcmi (1.1.1) jammy; urgency=medium
* New upstream release

View File

@ -38,6 +38,7 @@
<url type="bugtracker">https://github.com/vcmi/vcmi/issues</url>
<url type="faq">https://vcmi.eu/faq/</url>
<releases>
<release version="1.2.1" date="2023-04-28" />
<release version="1.2.0" date="2023-04-14" />
<release version="1.1.1" date="2023-02-03" />
<release version="1.1.0" date="2022-12-23" />

View File

@ -190,6 +190,7 @@ void FirstLaunchView::heroesDataMissing()
ui->labelDataCopy->setVisible(true);
ui->labelDataFound->setVisible(false);
ui->pushButtonDataNext->setEnabled(false);
if(hasVCMIBuilderScript)
{
@ -218,6 +219,7 @@ void FirstLaunchView::heroesDataDetected()
}
ui->labelDataFound->setVisible(true);
ui->pushButtonDataNext->setEnabled(true);
heroesLanguageUpdate();
}
@ -247,7 +249,6 @@ void FirstLaunchView::heroesLanguageUpdate()
ui->labelDataFailure->setVisible(!success);
ui->labelDataSuccess->setVisible(success);
ui->pushButtonDataNext->setEnabled(success);
}
void FirstLaunchView::forceHeroesLanguage(const QString & language)
@ -264,6 +265,18 @@ void FirstLaunchView::copyHeroesData()
if(!sourceRoot.exists())
return;
if (sourceRoot.dirName().compare("data", Qt::CaseInsensitive) == 0)
{
// We got Data folder. Possibly user selected "Data" folder of Heroes III install. Check whether valid data might exist 1 level above
QStringList dirData = sourceRoot.entryList({"data"}, QDir::Filter::Dirs);
if (dirData.empty())
{
// This is "Data" folder without any "Data" folders inside. Try to check for data 1 level above
sourceRoot.cdUp();
}
}
QStringList dirData = sourceRoot.entryList({"data"}, QDir::Filter::Dirs);
QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs);
QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs);

View File

@ -96,14 +96,14 @@ JsonNode toJson(QVariant object)
{
JsonNode ret;
if(object.canConvert<QVariantMap>())
ret.Struct() = VariantToMap(object.toMap());
else if(object.canConvert<QVariantList>())
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<QVariantMap>())
ret.Struct() = VariantToMap(object.toMap());
else if(object.canConvert<QVariantList>())
ret.Vector() = VariantToList(object.toList());
else if(object.canConvert<int>())
ret.Integer() = object.toInt();
else if(object.canConvert<double>())

View File

@ -44,7 +44,7 @@ QString Languages::getHeroesDataLanguage()
QString language = QString::fromStdString(settings["session"]["language"].String());
double deviation = settings["session"]["languageDeviation"].Float();
if(deviation > 0.05)
if(deviation > 0.1)
return QString();
return language;
}

View File

@ -133,7 +133,7 @@ std::vector<si32> 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());

View File

@ -76,7 +76,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile & dest, const TerrainTile & f
!ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType->getIndex())) //no special movement bonus
{
ret = VLC->heroh->terrCosts[from.terType->getId()];
ret = VLC->terrainTypeHandler->getById(dest.terType->getId())->moveCost;
ret -= ti->valOfBonuses(Bonus::ROUGH_TERRAIN_DISCOUNT);
if(ret < GameConstants::BASE_MOVEMENT_COST)
ret = GameConstants::BASE_MOVEMENT_COST;
@ -1469,7 +1469,12 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
if(portrait >= 0)
{
if(portrait < legacyHeroes || portrait >= moddedStart)
handler.serializeId<si32, si32, HeroTypeID>("portrait", portrait, -1);
{
int tempPortrait = portrait >= moddedStart
? portrait - GameConstants::HERO_PORTRAIT_SHIFT
: portrait;
handler.serializeId<si32, si32, HeroTypeID>("portrait", tempPortrait, -1);
}
else
handler.serializeInt("portrait", portrait, -1);
}
@ -1479,7 +1484,11 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
const JsonNode & portraitNode = handler.getCurrent()["portrait"];
if(portraitNode.getType() == JsonNode::JsonType::DATA_STRING)
{
handler.serializeId<si32, si32, HeroTypeID>("portrait", portrait, -1);
if(portrait >= legacyHeroes)
portrait += GameConstants::HERO_PORTRAIT_SHIFT;
}
else
handler.serializeInt("portrait", portrait, -1);
}

View File

@ -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;

View File

@ -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)

View File

@ -789,12 +789,12 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con
sendMoveArtifact(art, &ma);
}
}
while(!finishingBattle->loserHero->artifactsInBackpack.empty())
for(int slotNumber = finishingBattle->loserHero->artifactsInBackpack.size() - 1; slotNumber >= 0; slotNumber--)
{
//we assume that no big artifacts can be found
MoveArtifact ma;
ma.src = ArtifactLocation(finishingBattle->loserHero,
ArtifactPosition(GameConstants::BACKPACK_START)); //backpack automatically shifts arts to beginning
ArtifactPosition(GameConstants::BACKPACK_START + slotNumber)); //backpack automatically shifts arts to beginning
const CArtifactInstance * art = ma.src.getArt();
if (art->artType->getId() != ArtifactID::GRAIL) //grail may not be won
{
@ -1812,7 +1812,7 @@ void CGameHandler::newTurn()
n.specialWeek = NewTurn::DEITYOFFIRE;
n.creatureid = CreatureID::IMP;
}
else if(!VLC->settings()->getBoolean(EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS))
else if(VLC->settings()->getBoolean(EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS))
{
int monthType = getRandomGenerator().nextInt(99);
if (newMonth) //new month
@ -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<size_t>(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<size_t>(diceSize.size()-1, nextStackMorale);