mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-28 08:48:48 +02:00
commit
25d5a1555c
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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!");
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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'
|
||||
|
23
ChangeLog.md
23
ChangeLog.md
@ -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 |
BIN
Mods/vcmi/Data/debug/spellRange.png
Normal file
BIN
Mods/vcmi/Data/debug/spellRange.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 B |
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ class CSelectionBase;
|
||||
class CSavingScreen : public CSelectionBase
|
||||
{
|
||||
public:
|
||||
const StartInfo * localSi;
|
||||
std::shared_ptr<CMapInfo> localMi;
|
||||
|
||||
CSavingScreen();
|
||||
|
@ -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()));
|
||||
|
@ -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"));
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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>;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
8
debian/changelog
vendored
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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);
|
||||
|
@ -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>())
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user