Merge branch 'beta' into 'develop'
1
.github/workflows/github.yml
vendored
@ -267,6 +267,7 @@ jobs:
|
||||
env:
|
||||
HEROES_3_DATA_PASSWORD: ${{ secrets.HEROES_3_DATA_PASSWORD }}
|
||||
if: ${{ env.HEROES_3_DATA_PASSWORD != '' && matrix.test == 1 }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
ctest --preset ${{matrix.preset}}
|
||||
|
||||
|
@ -857,15 +857,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
exchangeBattle->nextRound();
|
||||
}
|
||||
|
||||
// avoid blocking path for stronger stack by weaker stack
|
||||
// the method checks if all stacks can be placed around enemy
|
||||
std::map<BattleHex, battle::Units> reachabilityMap;
|
||||
|
||||
auto hexes = ap.attack.defender->getSurroundingHexes();
|
||||
|
||||
for(auto hex : hexes)
|
||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||
|
||||
auto score = v.getScore();
|
||||
|
||||
if(simulationTurnsCount < totalTurnsCount)
|
||||
|
@ -285,8 +285,8 @@ void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
|
||||
addVisitableObj(obj);
|
||||
}
|
||||
|
||||
if (nullkiller->settings->isUpdateHitmapOnTileReveal())
|
||||
nullkiller->dangerHitMap->reset();
|
||||
if (nullkiller->settings->isUpdateHitmapOnTileReveal() && !pos.empty())
|
||||
nullkiller->dangerHitMap->resetTileOwners();
|
||||
}
|
||||
|
||||
void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query)
|
||||
@ -389,9 +389,10 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
|
||||
}
|
||||
|
||||
if(obj->ID == Obj::HERO && cb->getPlayerRelations(obj->tempOwner, playerID) == PlayerRelations::ENEMIES)
|
||||
{
|
||||
nullkiller->dangerHitMap->reset();
|
||||
}
|
||||
nullkiller->dangerHitMap->resetHitmap();
|
||||
|
||||
if(obj->ID == Obj::TOWN)
|
||||
nullkiller->dangerHitMap->resetTileOwners();
|
||||
}
|
||||
|
||||
void AIGateway::showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor)
|
||||
@ -507,7 +508,7 @@ void AIGateway::objectPropertyChanged(const SetObjectProperty * sop)
|
||||
else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN)
|
||||
{
|
||||
// reevaluate defence for a new town
|
||||
nullkiller->dangerHitMap->reset();
|
||||
nullkiller->dangerHitMap->resetHitmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1246,7 +1247,7 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj)
|
||||
|
||||
if(obj->ID == Obj::HERO && cb->getPlayerRelations(obj->tempOwner, playerID) == PlayerRelations::ENEMIES)
|
||||
{
|
||||
nullkiller->dangerHitMap->reset();
|
||||
nullkiller->dangerHitMap->resetHitmap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,49 +11,42 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/entities/building/CBuilding.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
{
|
||||
std::map<BuildingID, BuildingID> parentMap;
|
||||
|
||||
for(auto &pair : developmentInfo.town->getTown()->buildings)
|
||||
{
|
||||
if(pair.second->upgrade != BuildingID::NONE)
|
||||
{
|
||||
parentMap[pair.second->upgrade] = pair.first;
|
||||
}
|
||||
}
|
||||
|
||||
for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
|
||||
{
|
||||
logAi->trace("Checking dwelling level %d", level);
|
||||
BuildingInfo nextToBuild = BuildingInfo();
|
||||
std::vector<BuildingID> dwellingsInTown;
|
||||
|
||||
BuildingID buildID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
|
||||
for(BuildingID buildID = BuildingID::getDwellingFromLevel(level, 0); buildID.hasValue(); BuildingID::advanceDwelling(buildID))
|
||||
if(developmentInfo.town->getTown()->buildings.count(buildID) != 0)
|
||||
dwellingsInTown.push_back(buildID);
|
||||
|
||||
for(; developmentInfo.town->getBuildings().count(buildID); BuildingID::advanceDwelling(buildID))
|
||||
// find best, already built dwelling
|
||||
for (const auto & buildID : boost::adaptors::reverse(dwellingsInTown))
|
||||
{
|
||||
if(!developmentInfo.town->hasBuilt(buildID))
|
||||
continue; // no such building in town
|
||||
if (!developmentInfo.town->hasBuilt(buildID))
|
||||
continue;
|
||||
|
||||
auto info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
||||
|
||||
if(info.exists)
|
||||
{
|
||||
developmentInfo.addExistingDwelling(info);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
nextToBuild = info;
|
||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
||||
developmentInfo.addExistingDwelling(info);
|
||||
break;
|
||||
}
|
||||
|
||||
if(nextToBuild.id != BuildingID::NONE)
|
||||
// find all non-built dwellings that can be built and add them for consideration
|
||||
for (const auto & buildID : dwellingsInTown)
|
||||
{
|
||||
developmentInfo.addBuildingToBuild(nextToBuild);
|
||||
if (developmentInfo.town->hasBuilt(buildID))
|
||||
continue;
|
||||
|
||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
||||
if (info.canBuild || info.notEnoughRes)
|
||||
developmentInfo.addBuildingToBuild(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,6 +141,9 @@ void BuildAnalyzer::update()
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
if(town->built >= cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
|
||||
continue; // Not much point in trying anything - can't built in this town anymore today
|
||||
|
||||
logAi->trace("Checking town %s", town->getNameTranslated());
|
||||
|
||||
developmentInfos.push_back(TownDevelopmentInfo(town));
|
||||
@ -272,11 +268,11 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
|
||||
if(vstd::contains_if(missingBuildings, otherDwelling))
|
||||
{
|
||||
logAi->trace("cant build. Need other dwelling");
|
||||
logAi->trace("cant build %d. Need other dwelling %d", toBuild.getNum(), missingBuildings.front().getNum());
|
||||
}
|
||||
else if(missingBuildings[0] != toBuild)
|
||||
{
|
||||
logAi->trace("cant build. Need %d", missingBuildings[0].num);
|
||||
logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num);
|
||||
|
||||
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
|
||||
|
||||
@ -307,10 +303,14 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
return info;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logAi->trace("Cant build. Reason: %d", static_cast<int>(canBuild));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logAi->trace("exists");
|
||||
logAi->trace("Dwelling %d exists", toBuild.getNum());
|
||||
info.exists = true;
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = ai->settings->getMainHeroTurnDistanceLimit();
|
||||
ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = ai->settings->getThreatTurnDistanceLimit();
|
||||
ps.useHeroChain = false;
|
||||
|
||||
ai->pathfinder->updatePaths(pair.second, ps);
|
||||
@ -345,10 +345,4 @@ std::set<const CGObjectInstance *> DangerHitMapAnalyzer::getOneTurnAccessibleObj
|
||||
return result;
|
||||
}
|
||||
|
||||
void DangerHitMapAnalyzer::reset()
|
||||
{
|
||||
hitMapUpToDate = false;
|
||||
tileOwnersUpToDate = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ public:
|
||||
const HitMapNode & getObjectThreat(const CGObjectInstance * obj) const;
|
||||
const HitMapNode & getTileThreat(const int3 & tile) const;
|
||||
std::set<const CGObjectInstance *> getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
|
||||
void reset();
|
||||
void resetHitmap() {hitMapUpToDate = false;}
|
||||
void resetTileOwners() { tileOwnersUpToDate = false; }
|
||||
PlayerColor getTileOwner(const int3 & tile) const;
|
||||
const CGTownInstance * getClosestTown(const int3 & tile) const;
|
||||
|
@ -59,17 +59,19 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
closestThreat = std::min(closestThreat, threat.turn);
|
||||
}
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
|
||||
if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE)
|
||||
{
|
||||
if (closestThreat <= 1 && developmentInfo.town->fortLevel() < CGTownInstance::EFortLevel::CASTLE && !buildingInfo.notEnoughRes)
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
if (buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE)
|
||||
if ( !buildingInfo.notEnoughRes && (buildingInfo.id == BuildingID::CITADEL || buildingInfo.id == BuildingID::CASTLE))
|
||||
{
|
||||
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
|
||||
emergencyDefense = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!emergencyDefense)
|
||||
{
|
||||
for (auto& buildingInfo : developmentInfo.toBuild)
|
||||
|
@ -229,7 +229,7 @@ void Nullkiller::resetAiState()
|
||||
lockedResources = TResources();
|
||||
scanDepth = ScanDepth::MAIN_FULL;
|
||||
lockedHeroes.clear();
|
||||
dangerHitMap->reset();
|
||||
dangerHitMap->resetHitmap();
|
||||
useHeroChain = true;
|
||||
objectClusterizer->reset();
|
||||
|
||||
|
@ -28,6 +28,7 @@ namespace NKAI
|
||||
: maxRoamingHeroes(8),
|
||||
mainHeroTurnDistanceLimit(10),
|
||||
scoutHeroTurnDistanceLimit(5),
|
||||
threatTurnDistanceLimit(5),
|
||||
maxGoldPressure(0.3f),
|
||||
retreatThresholdRelative(0.3),
|
||||
retreatThresholdAbsolute(10000),
|
||||
|
@ -24,6 +24,7 @@ namespace NKAI
|
||||
int maxRoamingHeroes;
|
||||
int mainHeroTurnDistanceLimit;
|
||||
int scoutHeroTurnDistanceLimit;
|
||||
int threatTurnDistanceLimit;
|
||||
int maxPass;
|
||||
int maxPriorityPass;
|
||||
int pathfinderBucketsCount;
|
||||
@ -52,6 +53,7 @@ namespace NKAI
|
||||
int getMaxRoamingHeroes() const { return maxRoamingHeroes; }
|
||||
int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; }
|
||||
int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; }
|
||||
int getThreatTurnDistanceLimit() const { return threatTurnDistanceLimit; }
|
||||
int getPathfinderBucketsCount() const { return pathfinderBucketsCount; }
|
||||
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
|
||||
bool isObjectGraphAllowed() const { return allowObjectGraph; }
|
||||
|
@ -11,7 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#define NKAI_PATHFINDER_TRACE_LEVEL 0
|
||||
constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
|
||||
constexpr int NKAI_GRAPH_TRACE_LEVEL = 0; // To actually enable graph visualization, enter `/vslog graph` in game chat
|
||||
#define NKAI_TRACE_LEVEL 0
|
||||
|
||||
#include "../../../lib/pathfinder/CGPathNode.h"
|
||||
|
@ -47,11 +47,10 @@ ChainActor::ChainActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t
|
||||
initialTurn = 0;
|
||||
armyValue = getHeroArmyStrengthWithCommander(hero, hero);
|
||||
heroFightingStrength = hero->getHeroStrength();
|
||||
tiCache.reset(new TurnInfo(hero));
|
||||
}
|
||||
|
||||
ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, const CCreatureSet * heroArmy)
|
||||
:hero(carrier->hero), tiCache(carrier->tiCache), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
|
||||
:hero(carrier->hero), heroRole(carrier->heroRole), isMovable(true), creatureSet(heroArmy), chainMask(carrier->chainMask | other->chainMask),
|
||||
baseActor(this), carrierParent(carrier), otherParent(other), heroFightingStrength(carrier->heroFightingStrength),
|
||||
actorExchangeCount(carrier->actorExchangeCount + other->actorExchangeCount), armyCost(carrier->armyCost + other->armyCost), actorAction()
|
||||
{
|
||||
@ -75,7 +74,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
|
||||
throw std::logic_error("Asking movement points for static actor");
|
||||
#endif
|
||||
|
||||
return hero->movementPointsLimitCached(layer, tiCache.get());
|
||||
return hero->movementPointsLimit(layer);
|
||||
}
|
||||
|
||||
std::string ChainActor::toString() const
|
||||
@ -133,7 +132,6 @@ void ChainActor::setBaseActor(HeroActor * base)
|
||||
heroFightingStrength = base->heroFightingStrength;
|
||||
armyCost = base->armyCost;
|
||||
actorAction = base->actorAction;
|
||||
tiCache = base->tiCache;
|
||||
actorExchangeCount = base->actorExchangeCount;
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,6 @@ public:
|
||||
float heroFightingStrength;
|
||||
uint8_t actorExchangeCount;
|
||||
TResources armyCost;
|
||||
std::shared_ptr<TurnInfo> tiCache;
|
||||
|
||||
ChainActor() = default;
|
||||
virtual ~ChainActor() = default;
|
||||
|
@ -367,6 +367,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
|
||||
// final node
|
||||
n.coord = tile;
|
||||
n.cost = targetNode.cost;
|
||||
n.turns = static_cast<ui8>(targetNode.cost);
|
||||
n.danger = danger;
|
||||
n.parentIndex = path.nodes.size();
|
||||
path.nodes.push_back(n);
|
||||
|
31
ChangeLog.md
@ -1,5 +1,36 @@
|
||||
# VCMI Project Changelog
|
||||
|
||||
## 1.6.2 -> 1.6.3
|
||||
|
||||
### Stability
|
||||
|
||||
* Fixed possible crash on attempt to play corrupted video file
|
||||
* Fixed possible crash on invalid or corrupted game data
|
||||
* Fixed possible crash on invalid upscaling filter
|
||||
|
||||
### Interface
|
||||
|
||||
* Added right-click popup to Keymasters, Border Guards, and Border Gates that show all discovered objects of the same color
|
||||
* Added right-click popup to Obelisks that shows all discovered objects and their visitation status
|
||||
* Added support for randomly selected main menu backgrounds
|
||||
* Fixed display of long text in text-only right-click popups
|
||||
* Hero overview screen on map setup will now have scrollbars for hero desription when necessary
|
||||
* Fixed teleporter right-click popup appearing out of screen when interacting with teleporter near screen edge
|
||||
* Scenario Information window will now correctly replace random hero with actual starting hero
|
||||
|
||||
### AI
|
||||
|
||||
* Improved performance of Battle AI
|
||||
* Improved performance of pathfinding calculations
|
||||
* Reduced calculation of threat range, especially on low difficulties to improve performance
|
||||
* Fixed Nullkiller AI not considering nearby objects for visiting in some cases, breaking its exploration logic
|
||||
* Fixed Nullkiller AI not building town dwellings
|
||||
|
||||
### Map Editor
|
||||
|
||||
* Added option to convert .h3c files into .vcmp
|
||||
* It is now possible to configure town to have same faction as player
|
||||
|
||||
## 1.6.1 -> 1.6.2
|
||||
|
||||
### General
|
||||
|
9
Global.h
@ -369,6 +369,15 @@ namespace vstd
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// given a map from keys to values, creates a new map from values to keys
|
||||
template<typename K, typename V>
|
||||
static std::map<V, K> reverseMap(const std::map<K, V>& m) {
|
||||
std::map<V, K> r;
|
||||
for (const auto& kv : m)
|
||||
r[kv.second] = kv.first;
|
||||
return r;
|
||||
}
|
||||
|
||||
//returns first key that maps to given value if present, returns success via found if provided
|
||||
template <typename Key, typename T>
|
||||
Key findKey(const std::map<Key, T> & map, const T & value, bool * found = nullptr)
|
||||
|
BIN
Mods/vcmi/Content/Sprites/minimapIcons/bordergate.png
Normal file
After Width: | Height: | Size: 147 B |
BIN
Mods/vcmi/Content/Sprites/minimapIcons/borderguard.png
Normal file
After Width: | Height: | Size: 141 B |
BIN
Mods/vcmi/Content/Sprites/minimapIcons/keymaster.png
Normal file
After Width: | Height: | Size: 252 B |
BIN
Mods/vcmi/Content/Sprites/minimapIcons/obelisk.png
Normal file
After Width: | Height: | Size: 119 B |
BIN
Mods/vcmi/Content/Sprites/minimapIcons/obeliskVisited.png
Normal file
After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 138 B After Width: | Height: | Size: 138 B |
Before Width: | Height: | Size: 137 B After Width: | Height: | Size: 137 B |
Before Width: | Height: | Size: 139 B After Width: | Height: | Size: 139 B |
BIN
Mods/vcmi/Content/Sprites2x/minimapIcons/bordergate.png
Normal file
After Width: | Height: | Size: 206 B |
BIN
Mods/vcmi/Content/Sprites2x/minimapIcons/borderguard.png
Normal file
After Width: | Height: | Size: 208 B |
BIN
Mods/vcmi/Content/Sprites2x/minimapIcons/keymaster.png
Normal file
After Width: | Height: | Size: 391 B |
BIN
Mods/vcmi/Content/Sprites2x/minimapIcons/obelisk.png
Normal file
After Width: | Height: | Size: 154 B |
BIN
Mods/vcmi/Content/Sprites2x/minimapIcons/obeliskVisited.png
Normal file
After Width: | Height: | Size: 150 B |
Before Width: | Height: | Size: 182 B After Width: | Height: | Size: 182 B |
Before Width: | Height: | Size: 210 B After Width: | Height: | Size: 210 B |
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
@ -111,9 +111,9 @@
|
||||
},
|
||||
|
||||
"vietnamese": {
|
||||
"name": "VCMI essential files",
|
||||
"name": "VCMI - Tập tin chính",
|
||||
"description": "Các tập tin cần thiết để chạy VCMI",
|
||||
"author": "Vũ Đắc Hoàng Ân",
|
||||
"author": "Hunter8x - Bé Còi",
|
||||
"skipValidation": true,
|
||||
"translations": [
|
||||
"config/vietnamese.json"
|
||||
|
@ -821,7 +821,7 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack &
|
||||
|
||||
const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack);
|
||||
PlayerColor playerToCall; //pack.player that will move activated stack
|
||||
if(activated->hasBonusOfType(BonusType::HYPNOTIZED))
|
||||
if(activated->isHypnotized())
|
||||
{
|
||||
playerToCall = gs.getBattle(pack.battleID)->getSide(BattleSide::ATTACKER).color == activated->unitOwner()
|
||||
? gs.getBattle(pack.battleID)->getSide(BattleSide::DEFENDER).color
|
||||
|
@ -76,7 +76,13 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
background = std::make_shared<CPicture>(ImagePath::fromJson(config["background"]));
|
||||
const auto& bgConfig = config["background"];
|
||||
if (bgConfig.isVector() && !bgConfig.Vector().empty())
|
||||
background = std::make_shared<CPicture>(ImagePath::fromJson(*RandomGeneratorUtil::nextItem(bgConfig.Vector(), CRandomGenerator::getDefault())));
|
||||
|
||||
if (bgConfig.isString())
|
||||
background = std::make_shared<CPicture>(ImagePath::fromJson(bgConfig));
|
||||
|
||||
if(config["scalable"].Bool())
|
||||
background->scaleTo(GH.screenDimensions());
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/EFont.h"
|
||||
#include "../render/IFont.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../gui/TextAlignment.h"
|
||||
@ -30,24 +31,20 @@ MapOverlayLogVisualizer::MapOverlayLogVisualizer(Canvas & target, std::shared_pt
|
||||
|
||||
void MapOverlayLogVisualizer::drawLine(int3 start, int3 end)
|
||||
{
|
||||
const Point offset = Point(30, 30);
|
||||
|
||||
auto level = model->getLevel();
|
||||
|
||||
if(start.z != level || end.z != level)
|
||||
return;
|
||||
|
||||
auto pStart = model->getTargetTileArea(start).topLeft();
|
||||
auto pEnd = model->getTargetTileArea(end).topLeft();
|
||||
auto viewPort = target.getRenderArea();
|
||||
int scaling = GH.screenHandler().getScalingFactor();
|
||||
auto pStart = model->getTargetTileArea(start).center();
|
||||
auto pEnd = model->getTargetTileArea(end).center();
|
||||
Rect viewPortRaw = target.getRenderArea();
|
||||
Rect viewPort(viewPortRaw.topLeft() / scaling, viewPortRaw.dimensions() / scaling );
|
||||
|
||||
pStart.x += 3;
|
||||
pEnd.x -= 3;
|
||||
Point workaroundOffset(8,8); // not sure why it is needed. Removing leads to incorrect clipping near view edges
|
||||
|
||||
pStart += offset;
|
||||
pEnd += offset;
|
||||
|
||||
if(viewPort.isInside(pStart) && viewPort.isInside(pEnd))
|
||||
if(viewPort.isInside(pStart + workaroundOffset) && viewPort.isInside(pEnd + workaroundOffset))
|
||||
{
|
||||
target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 0, 0));
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ void BasicMapView::render(Canvas & target, bool fullUpdate)
|
||||
tilesCache->update(controller->getContext());
|
||||
tilesCache->render(controller->getContext(), targetClipped, fullUpdate);
|
||||
|
||||
MapOverlayLogVisualizer r(target, model);
|
||||
MapOverlayLogVisualizer r(targetClipped, model);
|
||||
logVisual->visualize(r);
|
||||
}
|
||||
|
||||
|
@ -59,8 +59,18 @@ static si64 lodSeek(void * opaque, si64 pos, int whence)
|
||||
return data->seek(pos);
|
||||
}
|
||||
|
||||
static void logFFmpegError(int errorCode)
|
||||
{
|
||||
std::array<char, AV_ERROR_MAX_STRING_SIZE> errorMessage{};
|
||||
av_strerror(errorCode, errorMessage.data(), errorMessage.size());
|
||||
|
||||
logGlobal->warn("Failed to open video file! Reason: %s", errorMessage.data());
|
||||
}
|
||||
|
||||
[[noreturn]] static void throwFFmpegError(int errorCode)
|
||||
{
|
||||
logFFmpegError(errorCode);
|
||||
|
||||
std::array<char, AV_ERROR_MAX_STRING_SIZE> errorMessage{};
|
||||
av_strerror(errorCode, errorMessage.data(), errorMessage.size());
|
||||
|
||||
@ -95,7 +105,7 @@ bool FFMpegStream::openInput(const VideoPath & videoToOpen)
|
||||
return input != nullptr;
|
||||
}
|
||||
|
||||
void FFMpegStream::openContext()
|
||||
bool FFMpegStream::openContext()
|
||||
{
|
||||
static const int BUFFER_SIZE = 4096;
|
||||
input->seek(0);
|
||||
@ -109,13 +119,21 @@ void FFMpegStream::openContext()
|
||||
int avfopen = avformat_open_input(&formatContext, "dummyFilename", nullptr, nullptr);
|
||||
|
||||
if(avfopen != 0)
|
||||
throwFFmpegError(avfopen);
|
||||
{
|
||||
logFFmpegError(avfopen);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve stream information
|
||||
int findStreamInfo = avformat_find_stream_info(formatContext, nullptr);
|
||||
|
||||
if(avfopen < 0)
|
||||
throwFFmpegError(findStreamInfo);
|
||||
{
|
||||
logFFmpegError(findStreamInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FFMpegStream::openCodec(int desiredStreamIndex)
|
||||
@ -169,10 +187,13 @@ const AVFrame * FFMpegStream::getCurrentFrame() const
|
||||
return frame;
|
||||
}
|
||||
|
||||
void CVideoInstance::openVideo()
|
||||
bool CVideoInstance::openVideo()
|
||||
{
|
||||
openContext();
|
||||
if (!openContext())
|
||||
return false;
|
||||
|
||||
openCodec(findVideoStream());
|
||||
return true;
|
||||
}
|
||||
|
||||
void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput)
|
||||
@ -526,7 +547,9 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
|
||||
{
|
||||
if (!openInput(videoToOpen))
|
||||
return { nullptr, 0};
|
||||
openContext();
|
||||
|
||||
if (!openContext())
|
||||
return { nullptr, 0};
|
||||
|
||||
int audioStreamIndex = findAudioStream();
|
||||
if (audioStreamIndex == -1)
|
||||
@ -653,7 +676,9 @@ std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, float
|
||||
if (!result->openInput(name))
|
||||
return nullptr;
|
||||
|
||||
result->openVideo();
|
||||
if (!result->openVideo())
|
||||
return nullptr;
|
||||
|
||||
result->prepareOutput(scaleFactor, false);
|
||||
result->loadNextFrame(); // prepare 1st frame
|
||||
|
||||
|
@ -42,7 +42,7 @@ class FFMpegStream : boost::noncopyable
|
||||
AVFrame * frame = nullptr;
|
||||
|
||||
protected:
|
||||
void openContext();
|
||||
bool openContext();
|
||||
void openCodec(int streamIndex);
|
||||
|
||||
int findVideoStream() const;
|
||||
@ -91,7 +91,7 @@ public:
|
||||
CVideoInstance();
|
||||
~CVideoInstance();
|
||||
|
||||
void openVideo();
|
||||
bool openVideo();
|
||||
bool loadNextFrame();
|
||||
|
||||
double timeStamp() final;
|
||||
|
@ -20,8 +20,6 @@
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode & config)
|
||||
{
|
||||
std::string filename = "Data/" + config["file"].String();
|
||||
|
@ -11,14 +11,14 @@
|
||||
|
||||
#include "../render/IFont.h"
|
||||
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class JsonNode;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CBitmapFont;
|
||||
|
||||
using TTF_Font = struct _TTF_Font;
|
||||
|
||||
class CTrueTypeFont final : public IFont
|
||||
{
|
||||
const std::pair<std::unique_ptr<ui8[]>, ui64> data;
|
||||
|
@ -350,7 +350,7 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const
|
||||
};
|
||||
|
||||
auto filterName = settings["video"]["upscalingFilter"].String();
|
||||
auto filter = upscalingFilterTypes.at(filterName);
|
||||
auto filter = upscalingFilterTypes.count(filterName) ? upscalingFilterTypes.at(filterName) : EUpscalingFilter::AUTO;
|
||||
|
||||
if (filter != EUpscalingFilter::AUTO)
|
||||
return filter;
|
||||
|
@ -19,9 +19,11 @@
|
||||
#include "../render/IImage.h"
|
||||
#include "../renderSDL/RenderHandler.h"
|
||||
#include "../widgets/CComponentHolder.h"
|
||||
#include "../widgets/Slider.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||
#include "../eventsSDL/InputHandler.h"
|
||||
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/entities/hero/CHeroHandler.h"
|
||||
@ -99,7 +101,9 @@ void CHeroOverview::genControls()
|
||||
// hero biography
|
||||
r = Rect(borderOffset, 5 * borderOffset + yOffset + 148, 284, 130);
|
||||
backgroundRectangles.push_back(std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor));
|
||||
labelHeroBiography = std::make_shared<CMultiLineLabel>(r.resize(-borderOffset), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->heroh)[heroIdx]->getBiographyTranslated());
|
||||
labelHeroBiography = std::make_shared<CTextBox>((*CGI->heroh)[heroIdx]->getBiographyTranslated(), r.resize(-borderOffset), CSlider::EStyle::BROWN, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
||||
if(labelHeroBiography->slider && GH.input().getCurrentInputMode() != InputMode::TOUCH)
|
||||
labelHeroBiography->slider->clearScrollBounds();
|
||||
|
||||
// speciality name
|
||||
r = Rect(2 * borderOffset + 44, 6 * borderOffset + yOffset + 278, 235, 44);
|
||||
@ -115,7 +119,9 @@ void CHeroOverview::genControls()
|
||||
// speciality description
|
||||
r = Rect(borderOffset, 7 * borderOffset + yOffset + 322, 284, 85);
|
||||
backgroundRectangles.push_back(std::make_shared<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor));
|
||||
labelSpecialityDescription = std::make_shared<CMultiLineLabel>(r.resize(-borderOffset), FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->heroh)[heroIdx]->getSpecialtyDescriptionTranslated());
|
||||
labelSpecialityDescription = std::make_shared<CTextBox>((*CGI->heroh)[heroIdx]->getSpecialtyDescriptionTranslated(), r.resize(-borderOffset), CSlider::EStyle::BROWN, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
|
||||
if(labelSpecialityDescription->slider && GH.input().getCurrentInputMode() != InputMode::TOUCH)
|
||||
labelSpecialityDescription->slider->clearScrollBounds();
|
||||
|
||||
// army title
|
||||
r = Rect(302, borderOffset + yOffset, 292, 30);
|
||||
|
@ -39,7 +39,7 @@ class CHeroOverview : public CWindowObject
|
||||
std::shared_ptr<CLabel> labelTitle;
|
||||
std::shared_ptr<CAnimImage> imageHero;
|
||||
std::shared_ptr<CLabel> labelHeroName;
|
||||
std::shared_ptr<CMultiLineLabel> labelHeroBiography;
|
||||
std::shared_ptr<CTextBox> labelHeroBiography;
|
||||
std::shared_ptr<CLabel> labelHeroClass;
|
||||
std::shared_ptr<CLabel> labelHeroSpeciality;
|
||||
std::shared_ptr<CAnimImage> imageSpeciality;
|
||||
@ -47,7 +47,7 @@ class CHeroOverview : public CWindowObject
|
||||
std::vector<std::shared_ptr<CAnimImage>> imageSkill;
|
||||
std::vector<std::shared_ptr<CLabel>> labelSkillFooter;
|
||||
std::shared_ptr<CLabel> labelSpecialityName;
|
||||
std::shared_ptr<CMultiLineLabel> labelSpecialityDescription;
|
||||
std::shared_ptr<CTextBox> labelSpecialityDescription;
|
||||
|
||||
std::shared_ptr<CLabel> labelArmyTitle;
|
||||
std::vector<std::shared_ptr<CAnimImage>> imageArmy;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
|
||||
constexpr int RIGHT_CLICK_POPUP_MIN_SIZE = 100;
|
||||
constexpr int RIGHT_CLICK_POPUP_MAX_HEIGHT_TEXTONLY = 450;
|
||||
constexpr int SIDE_MARGIN = 11;
|
||||
constexpr int TOP_MARGIN = 20;
|
||||
constexpr int BOTTOM_MARGIN = 16;
|
||||
@ -260,9 +261,17 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play
|
||||
if(ret->buttons.empty() && !ret->components)
|
||||
{
|
||||
// use more compact form for right-click popup with no buttons / components
|
||||
|
||||
ret->pos.w = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.x + 2 * SIDE_MARGIN);
|
||||
ret->pos.h = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.y + TOP_MARGIN + BOTTOM_MARGIN);
|
||||
if(ret->text->slider)
|
||||
{
|
||||
ret->text->resize(Point(ret->text->pos.w, std::min(ret->text->label->textSize.y, RIGHT_CLICK_POPUP_MAX_HEIGHT_TEXTONLY)));
|
||||
ret->pos.w = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->pos.w + 2 * SIDE_MARGIN);
|
||||
ret->pos.h = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->pos.h + TOP_MARGIN + BOTTOM_MARGIN);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret->pos.w = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.x + 2 * SIDE_MARGIN);
|
||||
ret->pos.h = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.y + TOP_MARGIN + BOTTOM_MARGIN);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -31,12 +31,13 @@
|
||||
#include "../../CCallback.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../ConditionalWait.h"
|
||||
#include "../../lib/gameState/InfoAboutArmy.h"
|
||||
#include "../../lib/mapObjects/CGCreature.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/CQuest.h"
|
||||
#include "../../lib/mapObjects/MiscObjects.h"
|
||||
#include "../ConditionalWait.h"
|
||||
|
||||
CSelWindow::CSelWindow( const std::string & Text, PlayerColor player, int charperline, const std::vector<std::shared_ptr<CSelectableComponent>> & comps, const std::vector<std::pair<AnimationPath, CFunctionList<void()>>> & Buttons, QueryID askID)
|
||||
{
|
||||
@ -333,12 +334,10 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature)
|
||||
fitToScreen(10);
|
||||
}
|
||||
|
||||
TeleporterPopup::TeleporterPopup(const Point & position, const CGTeleport * teleporter)
|
||||
: CWindowObject(BORDERED | RCLICK_POPUP)
|
||||
MinimapWithIcons::MinimapWithIcons(const Point & position)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.w = 322;
|
||||
pos.h = 200;
|
||||
pos += position;
|
||||
|
||||
Rect areaSurface(11, 41, 144, 144);
|
||||
Rect areaUnderground(167, 41, 144, 144);
|
||||
@ -346,16 +345,14 @@ TeleporterPopup::TeleporterPopup(const Point & position, const CGTeleport * tele
|
||||
Rect borderSurface(10, 40, 147, 147);
|
||||
Rect borderUnderground(166, 40, 147, 147);
|
||||
|
||||
bool singleLevelMap = LOCPLINT->cb->getMapSize().y == 0;
|
||||
bool singleLevelMap = LOCPLINT->cb->getMapSize().z == 1;
|
||||
|
||||
if (singleLevelMap)
|
||||
{
|
||||
areaSurface.x += 144;
|
||||
borderSurface.x += 144;
|
||||
areaSurface.x += 78;
|
||||
borderSurface.x += 78;
|
||||
}
|
||||
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
backgroundSurface = std::make_shared<TransparentFilledRectangle>(borderSurface, Colors::TRANSPARENCY, Colors::YELLOW);
|
||||
surface = std::make_shared<CMinimapInstance>(areaSurface.topLeft(), areaSurface.dimensions(), 0);
|
||||
|
||||
@ -364,8 +361,43 @@ TeleporterPopup::TeleporterPopup(const Point & position, const CGTeleport * tele
|
||||
backgroundUnderground = std::make_shared<TransparentFilledRectangle>(borderUnderground, Colors::TRANSPARENCY, Colors::YELLOW);
|
||||
undergroud = std::make_shared<CMinimapInstance>(areaUnderground.topLeft(), areaUnderground.dimensions(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MinimapWithIcons::addIcon(const int3 & coordinates, const ImagePath & image )
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
Rect areaSurface(11, 41, 144, 144);
|
||||
Rect areaUnderground(167, 41, 144, 144);
|
||||
bool singleLevelMap = LOCPLINT->cb->getMapSize().z == 1;
|
||||
if (singleLevelMap)
|
||||
areaSurface.x += 78;
|
||||
|
||||
int positionX = 144 * coordinates.x / LOCPLINT->cb->getMapSize().x;
|
||||
int positionY = 144 * coordinates.y / LOCPLINT->cb->getMapSize().y;
|
||||
|
||||
Point iconPosition(positionX, positionY);
|
||||
|
||||
iconPosition -= Point(8,8); // compensate for 16x16 icon half-size
|
||||
|
||||
if (coordinates.z == 0)
|
||||
iconPosition += areaSurface.topLeft();
|
||||
else
|
||||
iconPosition += areaUnderground.topLeft();
|
||||
|
||||
iconsOverlay.push_back(std::make_shared<CPicture>(image, iconPosition));
|
||||
}
|
||||
|
||||
TeleporterPopup::TeleporterPopup(const Point & position, const CGTeleport * teleporter)
|
||||
: CWindowObject(BORDERED | RCLICK_POPUP)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.w = 322;
|
||||
pos.h = 200;
|
||||
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>(pos.w / 2, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, teleporter->getPopupText(LOCPLINT->playerID));
|
||||
minimap = std::make_shared<MinimapWithIcons>(Point(0,0));
|
||||
|
||||
const auto & entrances = teleporter->getAllEntrances();
|
||||
const auto & exits = teleporter->getAllExits();
|
||||
@ -382,32 +414,89 @@ TeleporterPopup::TeleporterPopup(const Point & position, const CGTeleport * tele
|
||||
continue;
|
||||
|
||||
int3 position = exitObject->visitablePos();
|
||||
|
||||
int positionX = 144 * position.x / LOCPLINT->cb->getMapSize().x;
|
||||
int positionY = 144 * position.y / LOCPLINT->cb->getMapSize().y;
|
||||
|
||||
Point iconPosition(positionX, positionY);
|
||||
|
||||
iconPosition -= Point(8,8); // compensate for 16x16 icon half-size
|
||||
|
||||
if (position.z == 0)
|
||||
iconPosition += areaSurface.topLeft();
|
||||
else
|
||||
iconPosition += areaUnderground.topLeft();
|
||||
|
||||
ImagePath image;
|
||||
|
||||
if (!vstd::contains(entrances, exit))
|
||||
image = ImagePath::builtin("portalExit");
|
||||
image = ImagePath::builtin("minimapIcons/portalExit");
|
||||
else if (!vstd::contains(exits, exit))
|
||||
image = ImagePath::builtin("portalEntrance");
|
||||
image = ImagePath::builtin("minimapIcons/portalEntrance");
|
||||
else
|
||||
image = ImagePath::builtin("portalBidirectional");
|
||||
image = ImagePath::builtin("minimapIcons/portalBidirectional");
|
||||
|
||||
iconsOverlay.push_back(std::make_shared<CPicture>(image, iconPosition));
|
||||
minimap->addIcon(position, image);
|
||||
}
|
||||
|
||||
center(position);
|
||||
fitToScreen(10);
|
||||
}
|
||||
|
||||
KeymasterPopup::KeymasterPopup(const Point & position, const CGKeys * keymasterOrGuard)
|
||||
: CWindowObject(BORDERED | RCLICK_POPUP)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.w = 322;
|
||||
pos.h = 220;
|
||||
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>(pos.w / 2, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, keymasterOrGuard->getObjectName());
|
||||
labelDescription = std::make_shared<CLabel>(pos.w / 2, 40, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, keymasterOrGuard->getObjectDescription(LOCPLINT->playerID));
|
||||
minimap = std::make_shared<MinimapWithIcons>(Point(0,20));
|
||||
|
||||
const auto allObjects = LOCPLINT->cb->getAllVisitableObjs();
|
||||
|
||||
for (const auto mapObject : allObjects)
|
||||
{
|
||||
if (!mapObject)
|
||||
continue;
|
||||
|
||||
switch (mapObject->ID)
|
||||
{
|
||||
case Obj::KEYMASTER:
|
||||
if (mapObject->subID == keymasterOrGuard->subID)
|
||||
minimap->addIcon(mapObject->visitablePos(), ImagePath::builtin("minimapIcons/keymaster"));
|
||||
break;
|
||||
case Obj::BORDERGUARD:
|
||||
if (mapObject->subID == keymasterOrGuard->subID)
|
||||
minimap->addIcon(mapObject->visitablePos(), ImagePath::builtin("minimapIcons/borderguard"));
|
||||
break;
|
||||
case Obj::BORDER_GATE:
|
||||
if (mapObject->subID == keymasterOrGuard->subID)
|
||||
minimap->addIcon(mapObject->visitablePos(), ImagePath::builtin("minimapIcons/bordergate"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
center(position);
|
||||
fitToScreen(10);
|
||||
}
|
||||
|
||||
ObeliskPopup::ObeliskPopup(const Point & position, const CGObelisk * obelisk)
|
||||
: CWindowObject(BORDERED | RCLICK_POPUP)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.w = 322;
|
||||
pos.h = 220;
|
||||
|
||||
filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
|
||||
labelTitle = std::make_shared<CLabel>(pos.w / 2, 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, obelisk->getObjectName());
|
||||
labelDescription = std::make_shared<CLabel>(pos.w / 2, 40, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, obelisk->getObjectDescription(LOCPLINT->playerID));
|
||||
minimap = std::make_shared<MinimapWithIcons>(Point(0,20));
|
||||
|
||||
const auto allObjects = LOCPLINT->cb->getAllVisitableObjs();
|
||||
|
||||
for (const auto mapObject : allObjects)
|
||||
{
|
||||
if (!mapObject)
|
||||
continue;
|
||||
|
||||
if (mapObject->ID != Obj::OBELISK)
|
||||
continue;
|
||||
|
||||
if (mapObject->wasVisited(LOCPLINT->playerID))
|
||||
minimap->addIcon(mapObject->visitablePos(), ImagePath::builtin("minimapIcons/obeliskVisited"));
|
||||
else
|
||||
minimap->addIcon(mapObject->visitablePos(), ImagePath::builtin("minimapIcons/obelisk"));
|
||||
}
|
||||
center(position);
|
||||
fitToScreen(10);
|
||||
}
|
||||
|
||||
std::shared_ptr<WindowBase>
|
||||
@ -439,6 +528,12 @@ CRClickPopup::createCustomInfoWindow(Point position, const CGObjectInstance * sp
|
||||
case Obj::SUBTERRANEAN_GATE:
|
||||
case Obj::WHIRLPOOL:
|
||||
return std::make_shared<TeleporterPopup>(position, dynamic_cast<const CGTeleport *>(specific));
|
||||
case Obj::KEYMASTER:
|
||||
case Obj::BORDERGUARD:
|
||||
case Obj::BORDER_GATE:
|
||||
return std::make_shared<KeymasterPopup>(position, dynamic_cast<const CGKeys *>(specific));
|
||||
case Obj::OBELISK:
|
||||
return std::make_shared<ObeliskPopup>(position, dynamic_cast<const CGObelisk *>(specific));
|
||||
default:
|
||||
return std::shared_ptr<WindowBase>();
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ class CGHeroInstance;
|
||||
class CGGarrison;
|
||||
class CGCreature;
|
||||
class CGTeleport;
|
||||
class CGKeys;
|
||||
class CGObelisk;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@ -116,20 +118,50 @@ public:
|
||||
CSelWindow(const std::string & text, PlayerColor player, int charperline, const std::vector<std::shared_ptr<CSelectableComponent>> & comps, const std::vector<std::pair<AnimationPath,CFunctionList<void()> > > &Buttons, QueryID askID);
|
||||
};
|
||||
|
||||
class TeleporterPopup : public CWindowObject
|
||||
class MinimapWithIcons : public CIntObject
|
||||
{
|
||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||
|
||||
std::shared_ptr<TransparentFilledRectangle> backgroundSurface;
|
||||
std::shared_ptr<TransparentFilledRectangle> backgroundUnderground;
|
||||
|
||||
std::shared_ptr<CMinimapInstance> surface;
|
||||
std::shared_ptr<CMinimapInstance> undergroud;
|
||||
|
||||
std::vector<std::shared_ptr<CPicture>> iconsOverlay;
|
||||
|
||||
public:
|
||||
MinimapWithIcons(const Point & position);
|
||||
|
||||
void addIcon(const int3 & coordinates, const ImagePath & image);
|
||||
};
|
||||
|
||||
class TeleporterPopup : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||
std::shared_ptr<MinimapWithIcons> minimap;
|
||||
std::shared_ptr<CLabel> labelTitle;
|
||||
|
||||
std::vector<std::shared_ptr<CPicture>> iconsOverlay;
|
||||
public:
|
||||
TeleporterPopup(const Point & position, const CGTeleport * teleporter);
|
||||
};
|
||||
|
||||
class KeymasterPopup : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||
std::shared_ptr<MinimapWithIcons> minimap;
|
||||
std::shared_ptr<CLabel> labelTitle;
|
||||
std::shared_ptr<CLabel> labelDescription;
|
||||
|
||||
public:
|
||||
KeymasterPopup(const Point & position, const CGKeys * keymasterOrGuard);
|
||||
};
|
||||
|
||||
class ObeliskPopup : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<FilledTexturePlayerColored> filledBackground;
|
||||
std::shared_ptr<MinimapWithIcons> minimap;
|
||||
std::shared_ptr<CLabel> labelTitle;
|
||||
std::shared_ptr<CLabel> labelDescription;
|
||||
|
||||
public:
|
||||
ObeliskPopup(const Point & position, const CGObelisk * obelisk);
|
||||
};
|
||||
|
@ -38,13 +38,14 @@
|
||||
"maxPriorityPass" : 10,
|
||||
"mainHeroTurnDistanceLimit" : 10,
|
||||
"scoutHeroTurnDistanceLimit" : 5,
|
||||
"threatTurnDistanceLimit" : 1,
|
||||
"maxGoldPressure" : 0.3,
|
||||
"updateHitmapOnTileReveal" : false,
|
||||
"updateHitmapOnTileReveal" : true,
|
||||
"useTroopsFromGarrisons" : true,
|
||||
"openMap": true,
|
||||
"allowObjectGraph": true,
|
||||
"pathfinderBucketsCount" : 4, // old value: 3,
|
||||
"pathfinderBucketSize" : 8, // old value: 7,
|
||||
"allowObjectGraph": false,
|
||||
"pathfinderBucketsCount" : 3,
|
||||
"pathfinderBucketSize" : 7,
|
||||
"retreatThresholdRelative" : 0,
|
||||
"retreatThresholdAbsolute" : 0,
|
||||
"safeAttackRatio" : 1.1,
|
||||
@ -58,13 +59,14 @@
|
||||
"maxPriorityPass" : 10,
|
||||
"mainHeroTurnDistanceLimit" : 10,
|
||||
"scoutHeroTurnDistanceLimit" : 5,
|
||||
"threatTurnDistanceLimit" : 4,
|
||||
"maxGoldPressure" : 0.3,
|
||||
"updateHitmapOnTileReveal" : false,
|
||||
"updateHitmapOnTileReveal" : true,
|
||||
"useTroopsFromGarrisons" : true,
|
||||
"openMap": true,
|
||||
"allowObjectGraph": true,
|
||||
"pathfinderBucketsCount" : 4, // old value: 3,
|
||||
"pathfinderBucketSize" : 8, // old value: 7,
|
||||
"allowObjectGraph": false,
|
||||
"pathfinderBucketsCount" : 3,
|
||||
"pathfinderBucketSize" : 7,
|
||||
"retreatThresholdRelative" : 0.1,
|
||||
"retreatThresholdAbsolute" : 5000,
|
||||
"safeAttackRatio" : 1.1,
|
||||
@ -78,13 +80,14 @@
|
||||
"maxPriorityPass" : 10,
|
||||
"mainHeroTurnDistanceLimit" : 10,
|
||||
"scoutHeroTurnDistanceLimit" : 5,
|
||||
"threatTurnDistanceLimit" : 5,
|
||||
"maxGoldPressure" : 0.3,
|
||||
"updateHitmapOnTileReveal" : false,
|
||||
"updateHitmapOnTileReveal" : true,
|
||||
"useTroopsFromGarrisons" : true,
|
||||
"openMap": true,
|
||||
"allowObjectGraph": true,
|
||||
"pathfinderBucketsCount" : 4, // old value: 3,
|
||||
"pathfinderBucketSize" : 8, // old value: 7,
|
||||
"allowObjectGraph": false,
|
||||
"pathfinderBucketsCount" : 3,
|
||||
"pathfinderBucketSize" : 7,
|
||||
"retreatThresholdRelative" : 0.3,
|
||||
"retreatThresholdAbsolute" : 10000,
|
||||
"safeAttackRatio" : 1.1,
|
||||
@ -98,13 +101,14 @@
|
||||
"maxPriorityPass" : 10,
|
||||
"mainHeroTurnDistanceLimit" : 10,
|
||||
"scoutHeroTurnDistanceLimit" : 5,
|
||||
"threatTurnDistanceLimit" : 5,
|
||||
"maxGoldPressure" : 0.3,
|
||||
"updateHitmapOnTileReveal" : false,
|
||||
"updateHitmapOnTileReveal" : true,
|
||||
"useTroopsFromGarrisons" : true,
|
||||
"openMap": true,
|
||||
"allowObjectGraph": true,
|
||||
"pathfinderBucketsCount" : 4, // old value: 3,
|
||||
"pathfinderBucketSize" : 8, // old value: 7,
|
||||
"allowObjectGraph": false,
|
||||
"pathfinderBucketsCount" : 3,
|
||||
"pathfinderBucketSize" : 7,
|
||||
"retreatThresholdRelative" : 0.3,
|
||||
"retreatThresholdAbsolute" : 10000,
|
||||
"safeAttackRatio" : 1.1,
|
||||
@ -118,13 +122,14 @@
|
||||
"maxPriorityPass" : 10,
|
||||
"mainHeroTurnDistanceLimit" : 10,
|
||||
"scoutHeroTurnDistanceLimit" : 5,
|
||||
"threatTurnDistanceLimit" : 5,
|
||||
"maxGoldPressure" : 0.3,
|
||||
"updateHitmapOnTileReveal" : false,
|
||||
"updateHitmapOnTileReveal" : true,
|
||||
"useTroopsFromGarrisons" : true,
|
||||
"openMap": true,
|
||||
"allowObjectGraph": true,
|
||||
"pathfinderBucketsCount" : 4, // old value: 3,
|
||||
"pathfinderBucketSize" : 8, // old value: 7,
|
||||
"allowObjectGraph": false,
|
||||
"pathfinderBucketsCount" : 3,
|
||||
"pathfinderBucketSize" : 7,
|
||||
"retreatThresholdRelative" : 0.3,
|
||||
"retreatThresholdAbsolute" : 10000,
|
||||
"safeAttackRatio" : 1.1,
|
||||
|
@ -305,7 +305,7 @@
|
||||
{
|
||||
"graphics":
|
||||
{
|
||||
"icon": "zvs/Lib1.res/E_SHOOT"
|
||||
"icon": "zvs/Lib1.res/LIM_SHOOT"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -305,7 +305,11 @@
|
||||
// if heroes are invitable in tavern
|
||||
"tavernInvite" : false,
|
||||
// minimal primary skills for heroes
|
||||
"minimalPrimarySkills": [ 0, 0, 1, 1]
|
||||
"minimalPrimarySkills": [ 0, 0, 1, 1],
|
||||
/// movement points hero can get on start of the turn when on land, depending on speed of slowest creature (0-based list)
|
||||
"movementPointsLand" : [ 1500, 1500, 1500, 1500, 1560, 1630, 1700, 1760, 1830, 1900, 1960, 2000 ],
|
||||
/// movement points hero can get on start of the turn when on sea, depending on speed of slowest creature (0-based list)
|
||||
"movementPointsSea" : [ 1500 ]
|
||||
},
|
||||
|
||||
"towns":
|
||||
@ -560,29 +564,6 @@
|
||||
"type" : "MANA_PER_KNOWLEDGE_PERCENTAGE", //1000% mana per knowledge
|
||||
"val" : 1000,
|
||||
"valueType" : "BASE_NUMBER"
|
||||
},
|
||||
"landMovement" :
|
||||
{
|
||||
"type" : "MOVEMENT", //Basic land movement
|
||||
"subtype" : "heroMovementLand",
|
||||
"val" : 1300,
|
||||
"valueType" : "BASE_NUMBER",
|
||||
"updater" : {
|
||||
"type" : "ARMY_MOVEMENT", //Enable army movement bonus
|
||||
"parameters" : [
|
||||
20, // Movement points for lowest speed numerator
|
||||
3, // Movement points for lowest speed denominator
|
||||
10, // Resulting value, rounded down, will be multiplied by this number
|
||||
700 // All army movement bonus cannot be higher than this value (so, max movement will be 1300 + 700 for this settings)
|
||||
]
|
||||
}
|
||||
},
|
||||
"seaMovement" :
|
||||
{
|
||||
"type" : "MOVEMENT", //Basic sea movement
|
||||
"subtype" : "heroMovementSea",
|
||||
"val" : 1500,
|
||||
"valueType" : "BASE_NUMBER"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -44,7 +44,9 @@
|
||||
"startingStackChances" : { "type" : "array" },
|
||||
"backpackSize" : { "type" : "number" },
|
||||
"tavernInvite" : { "type" : "boolean" },
|
||||
"minimalPrimarySkills" : { "type" : "array" }
|
||||
"minimalPrimarySkills" : { "type" : "array" },
|
||||
"movementPointsLand" : { "type" : "array" },
|
||||
"movementPointsSea" : { "type" : "array" }
|
||||
}
|
||||
},
|
||||
"towns" : {
|
||||
|
6
debian/changelog
vendored
@ -4,6 +4,12 @@ vcmi (1.7.0) jammy; urgency=medium
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 30 May 2025 12:00:00 +0200
|
||||
|
||||
vcmi (1.6.3) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 10 Jan 2025 12:00:00 +0200
|
||||
|
||||
vcmi (1.6.2) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
@ -1,9 +1,9 @@
|
||||
# VCMI Project
|
||||
|
||||
[![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.6.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.6.0)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.6.1/total)](https://github.com/vcmi/vcmi/releases/tag/1.6.1)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.6.2/total)](https://github.com/vcmi/vcmi/releases/tag/1.6.2)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.6.3/total)](https://github.com/vcmi/vcmi/releases/tag/1.6.3)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
|
||||
|
||||
VCMI is an open-source recreation of Heroes of Might & Magic III engine, giving it new and extended possibilities.
|
||||
|
@ -23,9 +23,10 @@ class DLL_LINKAGE ACreature: public AFactionMember
|
||||
{
|
||||
public:
|
||||
bool isLiving() const; //non-undead, non-non living or alive
|
||||
ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators
|
||||
ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators
|
||||
virtual ui32 getMovementRange(int turn) const; //get speed (in moving tiles) of creature with all modificators
|
||||
virtual ui32 getMovementRange() const; //get speed (in moving tiles) of creature with all modificators
|
||||
virtual ui32 getMaxHealth() const; //get max HP of stack with all modifiers
|
||||
virtual int32_t getInitiative(int turn = 0) const;
|
||||
};
|
||||
|
||||
template <typename IdType>
|
||||
|
@ -44,10 +44,6 @@ public:
|
||||
Returns defence of creature or hero.
|
||||
*/
|
||||
virtual int getDefense(bool ranged) const;
|
||||
/**
|
||||
Returns primskill of creature or hero.
|
||||
*/
|
||||
int getPrimSkillLevel(PrimarySkill id) const;
|
||||
/**
|
||||
Returns morale of creature or hero. Taking absolute bonuses into account.
|
||||
For now, uses range from EGameSettings
|
||||
|
@ -91,6 +91,7 @@
|
||||
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
|
||||
<releases>
|
||||
<release version="1.7.0" date="2025-05-30" type="development"/>
|
||||
<release version="1.6.3" date="2025-01-10" type="stable"/>
|
||||
<release version="1.6.2" date="2025-01-03" type="stable"/>
|
||||
<release version="1.6.1" date="2024-12-27" type="stable"/>
|
||||
<release version="1.6.0" date="2024-12-20" type="stable"/>
|
||||
|
@ -74,7 +74,7 @@
|
||||
<message>
|
||||
<location filename="../aboutProject/aboutproject_moc.ui" line="227"/>
|
||||
<source>Configuration files directory</source>
|
||||
<translation>Verzeichnis der Konfiguarions-Dateien</translation>
|
||||
<translation>Verzeichnis der Konfigurationsdateien</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../aboutProject/aboutproject_moc.ui" line="290"/>
|
||||
@ -492,7 +492,7 @@ Installation erfolgreich heruntergeladen?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="996"/>
|
||||
<source>Handle back as right mouse button</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Behandle "Zurück" als rechte Maustaste</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1102"/>
|
||||
@ -823,7 +823,7 @@ Exklusiver Vollbildmodus - das Spiel nimmt den gesamten Bildschirm ein und verwe
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="144"/>
|
||||
<source>Heroes Chronicles %1 - %2</source>
|
||||
<translation type="unfinished">Heroes Chronicles %1 - %2</translation>
|
||||
<translation>Heroes Chronicles %1 - %2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1163,11 +1163,13 @@ Fehlerursache: </translation>
|
||||
Exe (%n bytes):
|
||||
%1</source>
|
||||
<comment>param is hash</comment>
|
||||
<translation type="unfinished">
|
||||
<translation>
|
||||
<numerusform>SHA1-Hash der bereitgestellten Dateien:
|
||||
Exe (%n Bytes):
|
||||
%1</numerusform>
|
||||
<numerusform>SHA1-Hash der bereitgestellten Dateien:
|
||||
Exe (%n Bytes):
|
||||
%1</numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
@ -1176,11 +1178,13 @@ Exe (%n Bytes):
|
||||
Bin (%n bytes):
|
||||
%1</source>
|
||||
<comment>param is hash</comment>
|
||||
<translation type="unfinished">
|
||||
<translation>
|
||||
<numerusform>
|
||||
Bin (%n Bytes):
|
||||
%1</numerusform>
|
||||
<numerusform>
|
||||
Bin (%n Bytes):
|
||||
%1</numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -1357,7 +1361,7 @@ Bin (%n Bytes):
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.cpp" line="46"/>
|
||||
<source>Error starting executable</source>
|
||||
<translation type="unfinished">Fehler beim Starten der ausführbaren Datei</translation>
|
||||
<translation>Fehler beim Starten der ausführbaren Datei</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.cpp" line="287"/>
|
||||
@ -1454,7 +1458,7 @@ Bin (%n Bytes):
|
||||
<message>
|
||||
<location filename="../modManager/modstatecontroller.cpp" line="248"/>
|
||||
<source>Mod data was not found</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Mod-Daten wurden nicht gefunden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/modstatecontroller.cpp" line="252"/>
|
||||
|
@ -492,7 +492,7 @@ Zainstalować pomyślnie pobrane?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="996"/>
|
||||
<source>Handle back as right mouse button</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Przycisk wstecz jako prawy przycisk myszy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1102"/>
|
||||
@ -1360,7 +1360,7 @@ Bin (%n bajtów):
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.cpp" line="46"/>
|
||||
<source>Error starting executable</source>
|
||||
<translation type="unfinished">Błąd podczas uruchamiania pliku wykonywalnego</translation>
|
||||
<translation>Błąd podczas uruchamiania pliku wykonywalnego</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.cpp" line="287"/>
|
||||
@ -1457,7 +1457,7 @@ Bin (%n bajtów):
|
||||
<message>
|
||||
<location filename="../modManager/modstatecontroller.cpp" line="248"/>
|
||||
<source>Mod data was not found</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Nie znaleziono danych moda</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/modstatecontroller.cpp" line="252"/>
|
||||
|
@ -69,14 +69,6 @@ int AFactionMember::getMaxDamage(bool ranged) const
|
||||
return getBonusBearer()->valOfBonuses(selector, cachingStr);
|
||||
}
|
||||
|
||||
int AFactionMember::getPrimSkillLevel(PrimarySkill id) const
|
||||
{
|
||||
auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL);
|
||||
int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(id)));
|
||||
int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, id.getNum());
|
||||
return std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect, sp=0 works in old saves
|
||||
}
|
||||
|
||||
int AFactionMember::moraleValAndBonusList(TConstBonusListPtr & bonusList) const
|
||||
{
|
||||
int32_t maxGoodMorale = VLC->engineSettings()->getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE).size();
|
||||
@ -160,6 +152,19 @@ ui32 ACreature::getMovementRange() const
|
||||
return getBonusBearer()->valOfBonuses(BonusType::STACKS_SPEED);
|
||||
}
|
||||
|
||||
int32_t ACreature::getInitiative(int turn) const
|
||||
{
|
||||
if (turn == 0)
|
||||
{
|
||||
return getBonusBearer()->valOfBonuses(BonusType::STACKS_SPEED);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string cachingStrSS = "type_STACKS_SPEED_turns_" + std::to_string(turn);
|
||||
return getBonusBearer()->valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStrSS);
|
||||
}
|
||||
}
|
||||
|
||||
ui32 ACreature::getMovementRange(int turn) const
|
||||
{
|
||||
if (turn == 0)
|
||||
|
@ -65,12 +65,12 @@ set(lib_MAIN_SRCS
|
||||
battle/Unit.cpp
|
||||
|
||||
bonuses/Bonus.cpp
|
||||
bonuses/BonusCache.cpp
|
||||
bonuses/BonusEnum.cpp
|
||||
bonuses/BonusList.cpp
|
||||
bonuses/BonusParams.cpp
|
||||
bonuses/BonusSelector.cpp
|
||||
bonuses/BonusCustomTypes.cpp
|
||||
bonuses/CBonusProxy.cpp
|
||||
bonuses/CBonusSystemNode.cpp
|
||||
bonuses/IBonusBearer.cpp
|
||||
bonuses/Limiters.cpp
|
||||
@ -435,12 +435,12 @@ set(lib_MAIN_HEADERS
|
||||
battle/Unit.h
|
||||
|
||||
bonuses/Bonus.h
|
||||
bonuses/BonusCache.h
|
||||
bonuses/BonusEnum.h
|
||||
bonuses/BonusList.h
|
||||
bonuses/BonusParams.h
|
||||
bonuses/BonusSelector.h
|
||||
bonuses/BonusCustomTypes.h
|
||||
bonuses/CBonusProxy.h
|
||||
bonuses/CBonusSystemNode.h
|
||||
bonuses/IBonusBearer.h
|
||||
bonuses/Limiters.h
|
||||
|
@ -76,6 +76,8 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
|
||||
{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" },
|
||||
{EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" },
|
||||
{EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" },
|
||||
{EGameSettings::HEROES_MOVEMENT_POINTS_LAND, "heroes", "movementPointsLand" },
|
||||
{EGameSettings::HEROES_MOVEMENT_POINTS_SEA, "heroes", "movementPointsSea" },
|
||||
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
|
||||
{EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" },
|
||||
{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" },
|
||||
|
@ -49,6 +49,8 @@ enum class EGameSettings
|
||||
HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
|
||||
HEROES_STARTING_STACKS_CHANCES,
|
||||
HEROES_TAVERN_INVITE,
|
||||
HEROES_MOVEMENT_POINTS_LAND,
|
||||
HEROES_MOVEMENT_POINTS_SEA,
|
||||
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
||||
MAP_FORMAT_CHRONICLES,
|
||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||
|
@ -151,36 +151,6 @@ std::vector<JsonNode> TerrainTypeHandler::loadLegacyData()
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TerrainType::isLand() const
|
||||
{
|
||||
return !isWater();
|
||||
}
|
||||
|
||||
bool TerrainType::isWater() const
|
||||
{
|
||||
return passabilityType & PassabilityType::WATER;
|
||||
}
|
||||
|
||||
bool TerrainType::isRock() const
|
||||
{
|
||||
return passabilityType & PassabilityType::ROCK;
|
||||
}
|
||||
|
||||
bool TerrainType::isPassable() const
|
||||
{
|
||||
return !isRock();
|
||||
}
|
||||
|
||||
bool TerrainType::isSurface() const
|
||||
{
|
||||
return passabilityType & PassabilityType::SURFACE;
|
||||
}
|
||||
|
||||
bool TerrainType::isUnderground() const
|
||||
{
|
||||
return passabilityType & PassabilityType::SUBTERRANEAN;
|
||||
}
|
||||
|
||||
bool TerrainType::isTransitionRequired() const
|
||||
{
|
||||
return transitionRequired;
|
||||
|
@ -83,14 +83,13 @@ public:
|
||||
|
||||
TerrainType() = default;
|
||||
|
||||
bool isLand() const;
|
||||
bool isWater() const;
|
||||
bool isRock() const;
|
||||
inline bool isLand() const;
|
||||
inline bool isWater() const;
|
||||
inline bool isRock() const;
|
||||
inline bool isPassable() const;
|
||||
inline bool isSurface() const;
|
||||
inline bool isUnderground() const;
|
||||
|
||||
bool isPassable() const;
|
||||
|
||||
bool isSurface() const;
|
||||
bool isUnderground() const;
|
||||
bool isTransitionRequired() const;
|
||||
};
|
||||
|
||||
@ -112,4 +111,34 @@ public:
|
||||
std::vector<JsonNode> loadLegacyData() override;
|
||||
};
|
||||
|
||||
inline bool TerrainType::isLand() const
|
||||
{
|
||||
return !isWater();
|
||||
}
|
||||
|
||||
inline bool TerrainType::isWater() const
|
||||
{
|
||||
return passabilityType & PassabilityType::WATER;
|
||||
}
|
||||
|
||||
inline bool TerrainType::isRock() const
|
||||
{
|
||||
return passabilityType & PassabilityType::ROCK;
|
||||
}
|
||||
|
||||
inline bool TerrainType::isPassable() const
|
||||
{
|
||||
return !isRock();
|
||||
}
|
||||
|
||||
inline bool TerrainType::isSurface() const
|
||||
{
|
||||
return passabilityType & PassabilityType::SURFACE;
|
||||
}
|
||||
|
||||
inline bool TerrainType::isUnderground() const
|
||||
{
|
||||
return passabilityType & PassabilityType::SUBTERRANEAN;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -714,18 +714,7 @@ bool CBattleInfoCallback::battleCanShoot(const battle::Unit * attacker) const
|
||||
if (!attacker->canShoot())
|
||||
return false;
|
||||
|
||||
//forgetfulness
|
||||
TConstBonusListPtr forgetfulList = attacker->getBonusesOfType(BonusType::FORGETFULL);
|
||||
if(!forgetfulList->empty())
|
||||
{
|
||||
int forgetful = forgetfulList->totalValue();
|
||||
|
||||
//advanced+ level
|
||||
if(forgetful > 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
return !battleIsUnitBlocked(attacker) || attacker->hasBonusOfType(BonusType::FREE_SHOOTING);
|
||||
return attacker->canShootBlocked() || !battleIsUnitBlocked(attacker);
|
||||
}
|
||||
|
||||
bool CBattleInfoCallback::battleCanTargetEmptyHex(const battle::Unit * attacker) const
|
||||
@ -1732,9 +1721,6 @@ bool CBattleInfoCallback::battleIsUnitBlocked(const battle::Unit * unit) const
|
||||
{
|
||||
RETURN_IF_NOT_BATTLE(false);
|
||||
|
||||
if(unit->hasBonusOfType(BonusType::SIEGE_WEAPON)) //siege weapons cannot be blocked
|
||||
return false;
|
||||
|
||||
for(const auto * adjacent : battleAdjacentUnits(unit))
|
||||
{
|
||||
if(adjacent->unitOwner() != unit->unitOwner()) //blocked by enemy stack
|
||||
|
@ -404,7 +404,7 @@ PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) con
|
||||
|
||||
PlayerColor initialOwner = getBattle()->getSidePlayer(unit->unitSide());
|
||||
|
||||
if(unit->hasBonusOfType(BonusType::HYPNOTIZED))
|
||||
if(unit->isHypnotized())
|
||||
return otherPlayer(initialOwner);
|
||||
else
|
||||
return initialOwner;
|
||||
|
@ -26,7 +26,7 @@ namespace battle
|
||||
CAmmo::CAmmo(const battle::Unit * Owner, CSelector totalSelector):
|
||||
used(0),
|
||||
owner(Owner),
|
||||
totalProxy(Owner, std::move(totalSelector))
|
||||
totalProxy(Owner, totalSelector)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@ -34,7 +34,6 @@ CAmmo::CAmmo(const battle::Unit * Owner, CSelector totalSelector):
|
||||
CAmmo & CAmmo::operator= (const CAmmo & other)
|
||||
{
|
||||
used = other.used;
|
||||
totalProxy = other.totalProxy;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -60,7 +59,7 @@ void CAmmo::reset()
|
||||
|
||||
int32_t CAmmo::total() const
|
||||
{
|
||||
return totalProxy->totalValue();
|
||||
return totalProxy.getValue();
|
||||
}
|
||||
|
||||
void CAmmo::use(int32_t amount)
|
||||
@ -85,20 +84,13 @@ void CAmmo::serializeJson(JsonSerializeFormat & handler)
|
||||
///CShots
|
||||
CShots::CShots(const battle::Unit * Owner)
|
||||
: CAmmo(Owner, Selector::type()(BonusType::SHOTS)),
|
||||
shooter(Owner, BonusType::SHOOTER)
|
||||
shooter(Owner, Selector::type()(BonusType::SHOOTER))
|
||||
{
|
||||
}
|
||||
|
||||
CShots & CShots::operator=(const CShots & other)
|
||||
{
|
||||
CAmmo::operator=(other);
|
||||
shooter = other.shooter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CShots::isLimited() const
|
||||
{
|
||||
return !shooter.getHasBonus() || !env->unitHasAmmoCart(owner);
|
||||
return !shooter.hasBonus() || !env->unitHasAmmoCart(owner);
|
||||
}
|
||||
|
||||
void CShots::setEnv(const IUnitEnvironment * env_)
|
||||
@ -108,7 +100,7 @@ void CShots::setEnv(const IUnitEnvironment * env_)
|
||||
|
||||
int32_t CShots::total() const
|
||||
{
|
||||
if(shooter.getHasBonus())
|
||||
if(shooter.hasBonus())
|
||||
return CAmmo::total();
|
||||
else
|
||||
return 0;
|
||||
@ -124,23 +116,23 @@ CCasts::CCasts(const battle::Unit * Owner):
|
||||
CRetaliations::CRetaliations(const battle::Unit * Owner)
|
||||
: CAmmo(Owner, Selector::type()(BonusType::ADDITIONAL_RETALIATION)),
|
||||
totalCache(0),
|
||||
noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION)), "CRetaliations::noRetaliation"),
|
||||
unlimited(Owner, BonusType::UNLIMITED_RETALIATIONS)
|
||||
noRetaliation(Owner, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::HYPNOTIZED)).Or(Selector::type()(BonusType::NO_RETALIATION))),
|
||||
unlimited(Owner, Selector::type()(BonusType::UNLIMITED_RETALIATIONS))
|
||||
{
|
||||
}
|
||||
|
||||
bool CRetaliations::isLimited() const
|
||||
{
|
||||
return !unlimited.getHasBonus() || noRetaliation.getHasBonus();
|
||||
return !unlimited.hasBonus() || noRetaliation.hasBonus();
|
||||
}
|
||||
|
||||
int32_t CRetaliations::total() const
|
||||
{
|
||||
if(noRetaliation.getHasBonus())
|
||||
if(noRetaliation.hasBonus())
|
||||
return 0;
|
||||
|
||||
//after dispel bonus should remain during current round
|
||||
int32_t val = 1 + totalProxy->totalValue();
|
||||
int32_t val = 1 + totalProxy.getValue();
|
||||
vstd::amax(totalCache, val);
|
||||
return totalCache;
|
||||
}
|
||||
@ -341,13 +333,9 @@ CUnitState::CUnitState():
|
||||
counterAttacks(this),
|
||||
health(this),
|
||||
shots(this),
|
||||
totalAttacks(this, Selector::type()(BonusType::ADDITIONAL_ATTACK), 1),
|
||||
minDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin)), 0),
|
||||
maxDamage(this, Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax)), 0),
|
||||
attack(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)), 0),
|
||||
defence(this, Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE)), 0),
|
||||
inFrenzy(this, Selector::type()(BonusType::IN_FRENZY)),
|
||||
cloneLifetimeMarker(this, Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE)))), "CUnitState::cloneLifetimeMarker"),
|
||||
stackSpeedPerTurn(this, Selector::type()(BonusType::STACKS_SPEED), BonusCacheMode::VALUE),
|
||||
immobilizedPerTurn(this, Selector::type()(BonusType::SIEGE_WEAPON).Or(Selector::type()(BonusType::BIND_EFFECT)), BonusCacheMode::PRESENCE),
|
||||
bonusCache(this),
|
||||
cloneID(-1)
|
||||
{
|
||||
|
||||
@ -372,15 +360,8 @@ CUnitState & CUnitState::operator=(const CUnitState & other)
|
||||
waitedThisTurn = other.waitedThisTurn;
|
||||
casts = other.casts;
|
||||
counterAttacks = other.counterAttacks;
|
||||
health = other.health;
|
||||
shots = other.shots;
|
||||
totalAttacks = other.totalAttacks;
|
||||
minDamage = other.minDamage;
|
||||
maxDamage = other.maxDamage;
|
||||
attack = other.attack;
|
||||
defence = other.defence;
|
||||
inFrenzy = other.inFrenzy;
|
||||
cloneLifetimeMarker = other.cloneLifetimeMarker;
|
||||
health = other.health;
|
||||
cloneID = other.cloneID;
|
||||
position = other.position;
|
||||
return *this;
|
||||
@ -542,9 +523,16 @@ bool CUnitState::isCaster() const
|
||||
return casts.total() > 0;//do not check specific cast abilities here
|
||||
}
|
||||
|
||||
bool CUnitState::canShootBlocked() const
|
||||
{
|
||||
return bonusCache.getBonusValue(UnitBonusValuesProxy::HAS_FREE_SHOOTING);
|
||||
}
|
||||
|
||||
bool CUnitState::canShoot() const
|
||||
{
|
||||
return shots.canUse(1);
|
||||
return
|
||||
shots.canUse(1) &&
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::FORGETFULL) <= 1; //advanced+ level
|
||||
}
|
||||
|
||||
bool CUnitState::isShooter() const
|
||||
@ -579,6 +567,11 @@ int64_t CUnitState::getTotalHealth() const
|
||||
return health.total();
|
||||
}
|
||||
|
||||
uint32_t CUnitState::getMaxHealth() const
|
||||
{
|
||||
return std::max(1, bonusCache.getBonusValue(UnitBonusValuesProxy::STACK_HEALTH));
|
||||
}
|
||||
|
||||
BattleHex CUnitState::getPosition() const
|
||||
{
|
||||
return position;
|
||||
@ -591,11 +584,20 @@ void CUnitState::setPosition(BattleHex hex)
|
||||
|
||||
int32_t CUnitState::getInitiative(int turn) const
|
||||
{
|
||||
if (turn == 0)
|
||||
return valOfBonuses(BonusType::STACKS_SPEED);
|
||||
return stackSpeedPerTurn.getValue(turn);
|
||||
}
|
||||
|
||||
std::string cachingStr = "type_STACKS_SPEED_turns_" + std::to_string(turn);
|
||||
return valOfBonuses(Selector::type()(BonusType::STACKS_SPEED).And(Selector::turns(turn)), cachingStr);
|
||||
ui32 CUnitState::getMovementRange(int turn) const
|
||||
{
|
||||
if (immobilizedPerTurn.getValue(0) != 0)
|
||||
return 0;
|
||||
|
||||
return stackSpeedPerTurn.getValue(0);
|
||||
}
|
||||
|
||||
ui32 CUnitState::getMovementRange() const
|
||||
{
|
||||
return getMovementRange(0);
|
||||
}
|
||||
|
||||
uint8_t CUnitState::getRangedFullDamageDistance() const
|
||||
@ -693,47 +695,69 @@ BattlePhases::Type CUnitState::battleQueuePhase(int turn) const
|
||||
}
|
||||
}
|
||||
|
||||
bool CUnitState::isHypnotized() const
|
||||
{
|
||||
return bonusCache.getBonusValue(UnitBonusValuesProxy::HYPNOTIZED);
|
||||
}
|
||||
|
||||
int CUnitState::getTotalAttacks(bool ranged) const
|
||||
{
|
||||
return ranged ? totalAttacks.getRangedValue() : totalAttacks.getMeleeValue();
|
||||
return 1 + (ranged ?
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_RANGED):
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::TOTAL_ATTACKS_MELEE));
|
||||
}
|
||||
|
||||
int CUnitState::getMinDamage(bool ranged) const
|
||||
{
|
||||
return ranged ? minDamage.getRangedValue() : minDamage.getMeleeValue();
|
||||
return ranged ?
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_RANGED):
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::MIN_DAMAGE_MELEE);
|
||||
|
||||
}
|
||||
|
||||
int CUnitState::getMaxDamage(bool ranged) const
|
||||
{
|
||||
return ranged ? maxDamage.getRangedValue() : maxDamage.getMeleeValue();
|
||||
return ranged ?
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_RANGED):
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::MAX_DAMAGE_MELEE);
|
||||
}
|
||||
|
||||
int CUnitState::getAttack(bool ranged) const
|
||||
{
|
||||
int ret = ranged ? attack.getRangedValue() : attack.getMeleeValue();
|
||||
int attack = ranged ?
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::ATTACK_RANGED):
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::ATTACK_MELEE);
|
||||
|
||||
if(!inFrenzy->empty())
|
||||
int frenzy = bonusCache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY);
|
||||
if(frenzy != 0)
|
||||
{
|
||||
double frenzyPower = static_cast<double>(inFrenzy->totalValue()) / 100;
|
||||
frenzyPower *= static_cast<double>(ranged ? defence.getRangedValue() : defence.getMeleeValue());
|
||||
ret += static_cast<int>(frenzyPower);
|
||||
int defence = ranged ?
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED):
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE);
|
||||
|
||||
int frenzyBonus = frenzy * defence / 100;
|
||||
attack += frenzyBonus;
|
||||
}
|
||||
|
||||
vstd::amax(ret, 0);
|
||||
return ret;
|
||||
vstd::amax(attack, 0);
|
||||
return attack;
|
||||
}
|
||||
|
||||
int CUnitState::getDefense(bool ranged) const
|
||||
{
|
||||
if(!inFrenzy->empty())
|
||||
int frenzy = bonusCache.getBonusValue(UnitBonusValuesProxy::IN_FRENZY);
|
||||
|
||||
if(frenzy != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ret = ranged ? defence.getRangedValue() : defence.getMeleeValue();
|
||||
vstd::amax(ret, 0);
|
||||
return ret;
|
||||
int defence = ranged ?
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_RANGED):
|
||||
bonusCache.getBonusValue(UnitBonusValuesProxy::DEFENCE_MELEE);
|
||||
vstd::amax(defence, 0);
|
||||
return defence;
|
||||
}
|
||||
}
|
||||
|
||||
@ -886,7 +910,7 @@ void CUnitState::afterNewRound()
|
||||
|
||||
if(alive() && isClone())
|
||||
{
|
||||
if(!cloneLifetimeMarker.getHasBonus())
|
||||
if(!bonusCache.hasBonus(UnitBonusValuesProxy::CLONE_MARKER))
|
||||
makeGhost();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Unit.h"
|
||||
#include "../bonuses/CBonusProxy.h"
|
||||
#include "../bonuses/BonusCache.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -32,10 +32,6 @@ class DLL_LINKAGE CAmmo
|
||||
public:
|
||||
explicit CAmmo(const battle::Unit * Owner, CSelector totalSelector);
|
||||
|
||||
//only copy construction is allowed for acquire(), serializeJson should be used for any other "assignment"
|
||||
CAmmo(const CAmmo & other) = default;
|
||||
CAmmo(CAmmo && other) = delete;
|
||||
|
||||
CAmmo & operator=(const CAmmo & other);
|
||||
CAmmo & operator=(CAmmo && other) = delete;
|
||||
|
||||
@ -50,15 +46,14 @@ public:
|
||||
protected:
|
||||
int32_t used;
|
||||
const battle::Unit * owner;
|
||||
CBonusProxy totalProxy;
|
||||
BonusValueCache totalProxy;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CShots : public CAmmo
|
||||
{
|
||||
public:
|
||||
explicit CShots(const battle::Unit * Owner);
|
||||
CShots(const CShots & other) = default;
|
||||
CShots & operator=(const CShots & other);
|
||||
|
||||
bool isLimited() const override;
|
||||
int32_t total() const override;
|
||||
|
||||
@ -66,23 +61,20 @@ public:
|
||||
private:
|
||||
const IUnitEnvironment * env;
|
||||
|
||||
CCheckProxy shooter;
|
||||
BonusValueCache shooter;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CCasts : public CAmmo
|
||||
{
|
||||
public:
|
||||
explicit CCasts(const battle::Unit * Owner);
|
||||
CCasts(const CCasts & other) = default;
|
||||
CCasts & operator=(const CCasts & other) = default;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CRetaliations : public CAmmo
|
||||
{
|
||||
public:
|
||||
explicit CRetaliations(const battle::Unit * Owner);
|
||||
CRetaliations(const CRetaliations & other) = default;
|
||||
CRetaliations & operator=(const CRetaliations & other) = default;
|
||||
|
||||
bool isLimited() const override;
|
||||
int32_t total() const override;
|
||||
void reset() override;
|
||||
@ -91,8 +83,8 @@ public:
|
||||
private:
|
||||
mutable int32_t totalCache;
|
||||
|
||||
CCheckProxy noRetaliation;
|
||||
CCheckProxy unlimited;
|
||||
BonusValueCache noRetaliation;
|
||||
BonusValueCache unlimited;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CHealth
|
||||
@ -154,11 +146,6 @@ public:
|
||||
CHealth health;
|
||||
CShots shots;
|
||||
|
||||
CTotalsProxy totalAttacks;
|
||||
|
||||
CTotalsProxy minDamage;
|
||||
CTotalsProxy maxDamage;
|
||||
|
||||
///id of alive clone of this stack clone if any
|
||||
si32 cloneID;
|
||||
|
||||
@ -205,11 +192,14 @@ public:
|
||||
bool isFrozen() const override;
|
||||
bool isValidTarget(bool allowDead = false) const override;
|
||||
|
||||
bool isHypnotized() const override;
|
||||
|
||||
bool isClone() const override;
|
||||
bool hasClone() const override;
|
||||
|
||||
bool canCast() const override;
|
||||
bool isCaster() const override;
|
||||
bool canShootBlocked() const override;
|
||||
bool canShoot() const override;
|
||||
bool isShooter() const override;
|
||||
|
||||
@ -218,6 +208,7 @@ public:
|
||||
int32_t getFirstHPleft() const override;
|
||||
int64_t getAvailableHealth() const override;
|
||||
int64_t getTotalHealth() const override;
|
||||
uint32_t getMaxHealth() const override;
|
||||
|
||||
BattleHex getPosition() const override;
|
||||
void setPosition(BattleHex hex) override;
|
||||
@ -225,6 +216,9 @@ public:
|
||||
uint8_t getRangedFullDamageDistance() const;
|
||||
uint8_t getShootingRangeDistance() const;
|
||||
|
||||
ui32 getMovementRange(int turn) const override;
|
||||
ui32 getMovementRange() const override;
|
||||
|
||||
bool canMove(int turn = 0) const override;
|
||||
bool defended(int turn = 0) const override;
|
||||
bool moved(int turn = 0) const override;
|
||||
@ -268,11 +262,9 @@ public:
|
||||
private:
|
||||
const IUnitEnvironment * env;
|
||||
|
||||
CTotalsProxy attack;
|
||||
CTotalsProxy defence;
|
||||
CBonusProxy inFrenzy;
|
||||
|
||||
CCheckProxy cloneLifetimeMarker;
|
||||
BonusCachePerTurn immobilizedPerTurn;
|
||||
BonusCachePerTurn stackSpeedPerTurn;
|
||||
UnitBonusValuesProxy bonusCache;
|
||||
|
||||
void reset();
|
||||
};
|
||||
@ -282,13 +274,12 @@ class DLL_LINKAGE CUnitStateDetached : public CUnitState
|
||||
public:
|
||||
explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_);
|
||||
|
||||
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
|
||||
const std::string & cachingStr = "") const override;
|
||||
CUnitStateDetached & operator= (const CUnitState & other);
|
||||
|
||||
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr = "") const override;
|
||||
|
||||
int64_t getTreeVersion() const override;
|
||||
|
||||
CUnitStateDetached & operator= (const CUnitState & other);
|
||||
|
||||
uint32_t unitId() const override;
|
||||
BattleSide unitSide() const override;
|
||||
|
||||
@ -297,7 +288,6 @@ public:
|
||||
|
||||
SlotID unitSlot() const override;
|
||||
|
||||
|
||||
int32_t unitBaseAmount() const override;
|
||||
|
||||
void spendMana(ServerCallback * server, const int spellCost) const override;
|
||||
|
@ -84,11 +84,14 @@ public:
|
||||
bool isTurret() const;
|
||||
virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
|
||||
|
||||
virtual bool isHypnotized() const = 0;
|
||||
|
||||
virtual bool isClone() const = 0;
|
||||
virtual bool hasClone() const = 0;
|
||||
|
||||
virtual bool canCast() const = 0;
|
||||
virtual bool isCaster() const = 0;
|
||||
virtual bool canShootBlocked() const = 0;
|
||||
virtual bool canShoot() const = 0;
|
||||
virtual bool isShooter() const = 0;
|
||||
|
||||
@ -112,8 +115,6 @@ public:
|
||||
virtual BattleHex getPosition() const = 0;
|
||||
virtual void setPosition(BattleHex hex) = 0;
|
||||
|
||||
virtual int32_t getInitiative(int turn = 0) const = 0;
|
||||
|
||||
virtual bool canMove(int turn = 0) const = 0; //if stack can move
|
||||
virtual bool defended(int turn = 0) const = 0;
|
||||
virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn
|
||||
|
212
lib/bonuses/BonusCache.cpp
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* BonusCache.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "BonusCache.h"
|
||||
#include "IBonusBearer.h"
|
||||
|
||||
#include "BonusSelector.h"
|
||||
#include "BonusList.h"
|
||||
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../IGameSettings.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
int BonusCacheBase::getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector, BonusCacheMode mode) const
|
||||
{
|
||||
if (target->getTreeVersion() == currentValue.version)
|
||||
{
|
||||
return currentValue.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: following code theoretically can fail if bonus tree was changed by another thread between two following lines
|
||||
// However, this situation should not be possible - gamestate modification should only happen in single-treaded mode with locked gamestate mutex
|
||||
int newValue;
|
||||
|
||||
if (mode == BonusCacheMode::VALUE)
|
||||
newValue = target->valOfBonuses(selector);
|
||||
else
|
||||
newValue = target->hasBonus(selector);
|
||||
currentValue.value = newValue;
|
||||
currentValue.version = target->getTreeVersion();
|
||||
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
BonusValueCache::BonusValueCache(const IBonusBearer * target, const CSelector & selector)
|
||||
:BonusCacheBase(target),selector(selector)
|
||||
{}
|
||||
|
||||
int BonusValueCache::getValue() const
|
||||
{
|
||||
return getBonusValueImpl(value, selector, BonusCacheMode::VALUE);
|
||||
}
|
||||
|
||||
bool BonusValueCache::hasBonus() const
|
||||
{
|
||||
return getBonusValueImpl(value, selector, BonusCacheMode::PRESENCE);
|
||||
}
|
||||
|
||||
MagicSchoolMasteryCache::MagicSchoolMasteryCache(const IBonusBearer * target)
|
||||
:target(target)
|
||||
{}
|
||||
|
||||
void MagicSchoolMasteryCache::update() const
|
||||
{
|
||||
static const CSelector allBonusesSelector = Selector::type()(BonusType::MAGIC_SCHOOL_SKILL);
|
||||
static const std::array schoolsSelector = {
|
||||
Selector::subtype()(SpellSchool::ANY),
|
||||
Selector::subtype()(SpellSchool::AIR),
|
||||
Selector::subtype()(SpellSchool::FIRE),
|
||||
Selector::subtype()(SpellSchool::WATER),
|
||||
Selector::subtype()(SpellSchool::EARTH),
|
||||
};
|
||||
|
||||
auto list = target->getBonuses(allBonusesSelector);
|
||||
for (int i = 0; i < schoolsSelector.size(); ++i)
|
||||
schools[i] = list->valOfBonuses(schoolsSelector[i]);
|
||||
|
||||
version = target->getTreeVersion();
|
||||
}
|
||||
|
||||
int32_t MagicSchoolMasteryCache::getMastery(const SpellSchool & school) const
|
||||
{
|
||||
if (target->getTreeVersion() != version)
|
||||
update();
|
||||
return schools[school.num + 1];
|
||||
}
|
||||
|
||||
PrimarySkillsCache::PrimarySkillsCache(const IBonusBearer * target)
|
||||
:target(target)
|
||||
{}
|
||||
|
||||
void PrimarySkillsCache::update() const
|
||||
{
|
||||
static const CSelector primarySkillsSelector = Selector::type()(BonusType::PRIMARY_SKILL);
|
||||
static const CSelector attackSelector = Selector::subtype()(PrimarySkill::ATTACK);
|
||||
static const CSelector defenceSelector = Selector::subtype()(PrimarySkill::DEFENSE);
|
||||
static const CSelector spellPowerSelector = Selector::subtype()(PrimarySkill::SPELL_POWER);
|
||||
static const CSelector knowledgeSelector = Selector::subtype()(PrimarySkill::KNOWLEDGE);
|
||||
|
||||
std::array<int, 4> minValues = {
|
||||
VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::ATTACK),
|
||||
VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::DEFENSE),
|
||||
VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::SPELL_POWER),
|
||||
VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, PrimarySkill::KNOWLEDGE)
|
||||
};
|
||||
|
||||
auto list = target->getBonuses(primarySkillsSelector);
|
||||
skills[PrimarySkill::ATTACK] = std::max(minValues[PrimarySkill::ATTACK], list->valOfBonuses(attackSelector));
|
||||
skills[PrimarySkill::DEFENSE] = std::max(minValues[PrimarySkill::DEFENSE], list->valOfBonuses(defenceSelector));
|
||||
skills[PrimarySkill::SPELL_POWER] = std::max(minValues[PrimarySkill::SPELL_POWER], list->valOfBonuses(spellPowerSelector));
|
||||
skills[PrimarySkill::KNOWLEDGE] = std::max(minValues[PrimarySkill::KNOWLEDGE], list->valOfBonuses(knowledgeSelector));
|
||||
|
||||
version = target->getTreeVersion();
|
||||
}
|
||||
|
||||
const std::array<std::atomic<int32_t>, 4> & PrimarySkillsCache::getSkills() const
|
||||
{
|
||||
if (target->getTreeVersion() != version)
|
||||
update();
|
||||
return skills;
|
||||
}
|
||||
|
||||
int BonusCachePerTurn::getValueUncached(int turns) const
|
||||
{
|
||||
std::lock_guard lock(bonusListMutex);
|
||||
|
||||
int nodeTreeVersion = target->getTreeVersion();
|
||||
|
||||
if (bonusListVersion != nodeTreeVersion)
|
||||
{
|
||||
bonusList = target->getBonuses(selector);
|
||||
bonusListVersion = nodeTreeVersion;
|
||||
}
|
||||
|
||||
if (mode == BonusCacheMode::VALUE)
|
||||
{
|
||||
if (turns != 0)
|
||||
return bonusList->valOfBonuses(Selector::turns(turns));
|
||||
else
|
||||
return bonusList->totalValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (turns != 0)
|
||||
return bonusList->getFirst(Selector::turns(turns)) != nullptr;
|
||||
else
|
||||
return !bonusList->empty();
|
||||
}
|
||||
}
|
||||
|
||||
int BonusCachePerTurn::getValue(int turns) const
|
||||
{
|
||||
int nodeTreeVersion = target->getTreeVersion();
|
||||
|
||||
if (turns < cachedTurns)
|
||||
{
|
||||
auto & entry = cache[turns];
|
||||
if (entry.version == nodeTreeVersion)
|
||||
{
|
||||
// best case: value is in cache and up-to-date
|
||||
return entry.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// else - compute value and update it in the cache
|
||||
int newValue = getValueUncached(turns);
|
||||
entry.value = newValue;
|
||||
entry.version = nodeTreeVersion;
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// non-cacheable value - compute and return (should be 0 / close to 0 calls)
|
||||
return getValueUncached(turns);
|
||||
}
|
||||
}
|
||||
|
||||
const UnitBonusValuesProxy::SelectorsArray * UnitBonusValuesProxy::generateSelectors()
|
||||
{
|
||||
static const CSelector additionalAttack = Selector::type()(BonusType::ADDITIONAL_ATTACK);
|
||||
static const CSelector selectorMelee = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT));
|
||||
static const CSelector selectorRanged = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT));
|
||||
static const CSelector minDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin));
|
||||
static const CSelector maxDamage = Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageBoth).Or(Selector::typeSubtype(BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax));
|
||||
static const CSelector attack = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK));
|
||||
static const CSelector defence = Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE));
|
||||
|
||||
static const UnitBonusValuesProxy::SelectorsArray selectors = {
|
||||
additionalAttack.And(selectorMelee), //TOTAL_ATTACKS_MELEE,
|
||||
additionalAttack.And(selectorRanged), //TOTAL_ATTACKS_RANGED,
|
||||
minDamage.And(selectorMelee), //MIN_DAMAGE_MELEE,
|
||||
minDamage.And(selectorRanged), //MIN_DAMAGE_RANGED,
|
||||
maxDamage.And(selectorMelee), //MAX_DAMAGE_MELEE,
|
||||
maxDamage.And(selectorRanged), //MAX_DAMAGE_RANGED,
|
||||
attack.And(selectorMelee),//ATTACK_MELEE,
|
||||
attack.And(selectorRanged),//ATTACK_RANGED,
|
||||
defence.And(selectorMelee),//DEFENCE_MELEE,
|
||||
defence.And(selectorRanged),//DEFENCE_RANGED,
|
||||
Selector::type()(BonusType::IN_FRENZY),//IN_FRENZY,
|
||||
Selector::type()(BonusType::HYPNOTIZED),//HYPNOTIZED,
|
||||
Selector::type()(BonusType::FORGETFULL),//FORGETFULL,
|
||||
Selector::type()(BonusType::FREE_SHOOTING).Or(Selector::type()(BonusType::SIEGE_WEAPON)),//HAS_FREE_SHOOTING,
|
||||
Selector::type()(BonusType::STACK_HEALTH),//STACK_HEALTH,
|
||||
Selector::type()(BonusType::NONE).And(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(SpellID(SpellID::CLONE))))
|
||||
};
|
||||
|
||||
return &selectors;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
201
lib/bonuses/BonusCache.h
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* BonusCache.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BonusSelector.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
enum class BonusCacheMode : int8_t
|
||||
{
|
||||
VALUE, // total value of bonus will be cached
|
||||
PRESENCE, // presence of bonus will be cached
|
||||
};
|
||||
|
||||
/// Internal base class with no own cache
|
||||
class BonusCacheBase
|
||||
{
|
||||
protected:
|
||||
const IBonusBearer * target;
|
||||
|
||||
explicit BonusCacheBase(const IBonusBearer * target):
|
||||
target(target)
|
||||
{}
|
||||
|
||||
struct BonusCacheEntry
|
||||
{
|
||||
std::atomic<int64_t> version = 0;
|
||||
std::atomic<int64_t> value = 0;
|
||||
|
||||
BonusCacheEntry() = default;
|
||||
BonusCacheEntry(const BonusCacheEntry & other)
|
||||
: version(other.version.load())
|
||||
, value(other.value.load())
|
||||
{
|
||||
}
|
||||
BonusCacheEntry & operator =(const BonusCacheEntry & other)
|
||||
{
|
||||
version = other.version.load();
|
||||
value = other.value.load();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
int getBonusValueImpl(BonusCacheEntry & currentValue, const CSelector & selector, BonusCacheMode) const;
|
||||
};
|
||||
|
||||
/// Cache that tracks a single query to bonus system
|
||||
class BonusValueCache : public BonusCacheBase
|
||||
{
|
||||
CSelector selector;
|
||||
mutable BonusCacheEntry value;
|
||||
public:
|
||||
BonusValueCache(const IBonusBearer * target, const CSelector & selector);
|
||||
int getValue() const;
|
||||
bool hasBonus() const;
|
||||
};
|
||||
|
||||
/// Cache that can track a list of queries to bonus system
|
||||
template<size_t SIZE>
|
||||
class BonusValuesArrayCache : public BonusCacheBase
|
||||
{
|
||||
public:
|
||||
using SelectorsArray = std::array<const CSelector, SIZE>;
|
||||
|
||||
BonusValuesArrayCache(const IBonusBearer * target, const SelectorsArray * selectors)
|
||||
: BonusCacheBase(target)
|
||||
, selectors(selectors)
|
||||
{}
|
||||
|
||||
int getBonusValue(int index) const
|
||||
{
|
||||
return getBonusValueImpl(cache[index], (*selectors)[index], BonusCacheMode::VALUE);
|
||||
}
|
||||
|
||||
int hasBonus(int index) const
|
||||
{
|
||||
return getBonusValueImpl(cache[index], (*selectors)[index], BonusCacheMode::PRESENCE);
|
||||
}
|
||||
|
||||
private:
|
||||
using CacheArray = std::array<BonusCacheEntry, SIZE>;
|
||||
|
||||
const SelectorsArray * selectors;
|
||||
mutable CacheArray cache;
|
||||
};
|
||||
|
||||
class UnitBonusValuesProxy
|
||||
{
|
||||
public:
|
||||
enum ECacheKeys : int8_t
|
||||
{
|
||||
TOTAL_ATTACKS_MELEE,
|
||||
TOTAL_ATTACKS_RANGED,
|
||||
|
||||
MIN_DAMAGE_MELEE,
|
||||
MIN_DAMAGE_RANGED,
|
||||
MAX_DAMAGE_MELEE,
|
||||
MAX_DAMAGE_RANGED,
|
||||
|
||||
ATTACK_MELEE,
|
||||
ATTACK_RANGED,
|
||||
|
||||
DEFENCE_MELEE,
|
||||
DEFENCE_RANGED,
|
||||
|
||||
IN_FRENZY,
|
||||
HYPNOTIZED,
|
||||
FORGETFULL,
|
||||
HAS_FREE_SHOOTING,
|
||||
STACK_HEALTH,
|
||||
|
||||
CLONE_MARKER,
|
||||
|
||||
TOTAL_KEYS,
|
||||
};
|
||||
static constexpr size_t KEYS_COUNT = static_cast<size_t>(ECacheKeys::TOTAL_KEYS);
|
||||
|
||||
using SelectorsArray = BonusValuesArrayCache<KEYS_COUNT>::SelectorsArray;
|
||||
|
||||
UnitBonusValuesProxy(const IBonusBearer * Target):
|
||||
cache(Target, generateSelectors())
|
||||
{}
|
||||
|
||||
int getBonusValue(ECacheKeys which) const
|
||||
{
|
||||
auto index = static_cast<size_t>(which);
|
||||
return cache.getBonusValue(index);
|
||||
}
|
||||
|
||||
int hasBonus(ECacheKeys which) const
|
||||
{
|
||||
auto index = static_cast<size_t>(which);
|
||||
return cache.hasBonus(index);
|
||||
}
|
||||
|
||||
private:
|
||||
const SelectorsArray * generateSelectors();
|
||||
|
||||
BonusValuesArrayCache<KEYS_COUNT> cache;
|
||||
};
|
||||
|
||||
/// Cache that tracks values of primary skill values in bonus system
|
||||
class PrimarySkillsCache
|
||||
{
|
||||
const IBonusBearer * target;
|
||||
mutable std::atomic<int64_t> version = 0;
|
||||
mutable std::array<std::atomic<int32_t>, 4> skills;
|
||||
|
||||
void update() const;
|
||||
public:
|
||||
PrimarySkillsCache(const IBonusBearer * target);
|
||||
|
||||
const std::array<std::atomic<int32_t>, 4> & getSkills() const;
|
||||
};
|
||||
|
||||
/// Cache that tracks values of spell school mastery in bonus system
|
||||
class MagicSchoolMasteryCache
|
||||
{
|
||||
const IBonusBearer * target;
|
||||
mutable std::atomic<int64_t> version = 0;
|
||||
mutable std::array<std::atomic<int32_t>, 4+1> schools;
|
||||
|
||||
void update() const;
|
||||
public:
|
||||
MagicSchoolMasteryCache(const IBonusBearer * target);
|
||||
|
||||
int32_t getMastery(const SpellSchool & school) const;
|
||||
};
|
||||
|
||||
/// Cache that tracks values for different values of bonus duration
|
||||
class BonusCachePerTurn : public BonusCacheBase
|
||||
{
|
||||
static constexpr int cachedTurns = 8;
|
||||
|
||||
const CSelector selector;
|
||||
mutable TConstBonusListPtr bonusList;
|
||||
mutable std::mutex bonusListMutex;
|
||||
mutable std::atomic<int64_t> bonusListVersion = 0;
|
||||
mutable std::array<BonusCacheEntry, cachedTurns> cache;
|
||||
const BonusCacheMode mode;
|
||||
|
||||
int getValueUncached(int turns) const;
|
||||
public:
|
||||
BonusCachePerTurn(const IBonusBearer * target, const CSelector & selector, BonusCacheMode mode)
|
||||
: BonusCacheBase(target)
|
||||
, selector(selector)
|
||||
, mode(mode)
|
||||
{}
|
||||
|
||||
int getValue(int turns) const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -83,10 +83,10 @@ void BonusList::stackBonuses()
|
||||
}
|
||||
}
|
||||
|
||||
int BonusList::totalValue() const
|
||||
int BonusList::totalValue(int baseValue) const
|
||||
{
|
||||
if (bonuses.empty())
|
||||
return 0;
|
||||
return baseValue;
|
||||
|
||||
struct BonusCollection
|
||||
{
|
||||
@ -104,6 +104,7 @@ int BonusList::totalValue() const
|
||||
};
|
||||
|
||||
BonusCollection accumulated;
|
||||
accumulated.base = baseValue;
|
||||
int indexMaxCount = 0;
|
||||
int indexMinCount = 0;
|
||||
|
||||
@ -208,12 +209,12 @@ void BonusList::getAllBonuses(BonusList &out) const
|
||||
out.push_back(b);
|
||||
}
|
||||
|
||||
int BonusList::valOfBonuses(const CSelector &select) const
|
||||
int BonusList::valOfBonuses(const CSelector &select, int baseValue) const
|
||||
{
|
||||
BonusList ret;
|
||||
CSelector limit = nullptr;
|
||||
getBonuses(ret, select, limit);
|
||||
return ret.totalValue();
|
||||
return ret.totalValue(baseValue);
|
||||
}
|
||||
|
||||
JsonNode BonusList::toJsonNode() const
|
||||
|
@ -58,14 +58,14 @@ public:
|
||||
|
||||
// BonusList functions
|
||||
void stackBonuses();
|
||||
int totalValue() const;
|
||||
int totalValue(int baseValue = 0) const;
|
||||
void getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit = nullptr) const;
|
||||
void getAllBonuses(BonusList &out) const;
|
||||
|
||||
//special find functions
|
||||
std::shared_ptr<Bonus> getFirst(const CSelector &select);
|
||||
std::shared_ptr<const Bonus> getFirst(const CSelector &select) const;
|
||||
int valOfBonuses(const CSelector &select) const;
|
||||
int valOfBonuses(const CSelector &select, int baseValue = 0) const;
|
||||
|
||||
// conversion / output
|
||||
JsonNode toJsonNode() const;
|
||||
|
@ -1,221 +0,0 @@
|
||||
/*
|
||||
* CBonusProxy.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "BonusList.h"
|
||||
#include "CBonusProxy.h"
|
||||
#include "IBonusBearer.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
///CBonusProxy
|
||||
CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector):
|
||||
bonusListCachedLast(0),
|
||||
target(Target),
|
||||
selector(std::move(Selector)),
|
||||
currentBonusListIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
CBonusProxy::CBonusProxy(const CBonusProxy & other):
|
||||
bonusListCachedLast(other.bonusListCachedLast),
|
||||
target(other.target),
|
||||
selector(other.selector),
|
||||
currentBonusListIndex(other.currentBonusListIndex)
|
||||
{
|
||||
bonusList[currentBonusListIndex] = other.bonusList[currentBonusListIndex];
|
||||
}
|
||||
|
||||
CBonusProxy::CBonusProxy(CBonusProxy && other) noexcept:
|
||||
bonusListCachedLast(0),
|
||||
target(other.target),
|
||||
currentBonusListIndex(0)
|
||||
{
|
||||
std::swap(bonusListCachedLast, other.bonusListCachedLast);
|
||||
std::swap(selector, other.selector);
|
||||
std::swap(bonusList, other.bonusList);
|
||||
std::swap(currentBonusListIndex, other.currentBonusListIndex);
|
||||
}
|
||||
|
||||
CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other)
|
||||
{
|
||||
boost::lock_guard<boost::mutex> lock(swapGuard);
|
||||
|
||||
selector = other.selector;
|
||||
swapBonusList(other.bonusList[other.currentBonusListIndex]);
|
||||
bonusListCachedLast = other.bonusListCachedLast;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CBonusProxy & CBonusProxy::operator=(CBonusProxy && other) noexcept
|
||||
{
|
||||
std::swap(bonusListCachedLast, other.bonusListCachedLast);
|
||||
std::swap(selector, other.selector);
|
||||
std::swap(bonusList, other.bonusList);
|
||||
std::swap(currentBonusListIndex, other.currentBonusListIndex);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CBonusProxy::swapBonusList(TConstBonusListPtr other) const
|
||||
{
|
||||
// The idea here is to avoid changing active bonusList while it can be read by a different thread.
|
||||
// Because such use of shared ptr is not thread safe
|
||||
// So to avoid this we change the second offline instance and swap active index
|
||||
auto newCurrent = 1 - currentBonusListIndex;
|
||||
bonusList[newCurrent] = std::move(other);
|
||||
currentBonusListIndex = newCurrent;
|
||||
}
|
||||
|
||||
TConstBonusListPtr CBonusProxy::getBonusList() const
|
||||
{
|
||||
auto needUpdateBonusList = [&]() -> bool
|
||||
{
|
||||
return target->getTreeVersion() != bonusListCachedLast || !bonusList[currentBonusListIndex];
|
||||
};
|
||||
|
||||
// avoid locking if everything is up-to-date
|
||||
if(needUpdateBonusList())
|
||||
{
|
||||
boost::lock_guard<boost::mutex>lock(swapGuard);
|
||||
|
||||
if(needUpdateBonusList())
|
||||
{
|
||||
//TODO: support limiters
|
||||
swapBonusList(target->getAllBonuses(selector, Selector::all));
|
||||
bonusListCachedLast = target->getTreeVersion();
|
||||
}
|
||||
}
|
||||
|
||||
return bonusList[currentBonusListIndex];
|
||||
}
|
||||
|
||||
const BonusList * CBonusProxy::operator->() const
|
||||
{
|
||||
return getBonusList().get();
|
||||
}
|
||||
|
||||
CTotalsProxy::CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue):
|
||||
CBonusProxy(Target, std::move(Selector)),
|
||||
initialValue(InitialValue),
|
||||
meleeCachedLast(0),
|
||||
meleeValue(0),
|
||||
rangedCachedLast(0),
|
||||
rangedValue(0)
|
||||
{
|
||||
}
|
||||
|
||||
CTotalsProxy::CTotalsProxy(const CTotalsProxy & other)
|
||||
: CBonusProxy(other),
|
||||
initialValue(other.initialValue),
|
||||
meleeCachedLast(other.meleeCachedLast),
|
||||
meleeValue(other.meleeValue),
|
||||
rangedCachedLast(other.rangedCachedLast),
|
||||
rangedValue(other.rangedValue)
|
||||
{
|
||||
}
|
||||
|
||||
int CTotalsProxy::getValue() const
|
||||
{
|
||||
const auto treeVersion = target->getTreeVersion();
|
||||
|
||||
if(treeVersion != valueCachedLast)
|
||||
{
|
||||
auto bonuses = getBonusList();
|
||||
|
||||
value = initialValue + bonuses->totalValue();
|
||||
valueCachedLast = treeVersion;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int CTotalsProxy::getValueAndList(TConstBonusListPtr & outBonusList) const
|
||||
{
|
||||
const auto treeVersion = target->getTreeVersion();
|
||||
outBonusList = getBonusList();
|
||||
|
||||
if(treeVersion != valueCachedLast)
|
||||
{
|
||||
value = initialValue + outBonusList->totalValue();
|
||||
valueCachedLast = treeVersion;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int CTotalsProxy::getMeleeValue() const
|
||||
{
|
||||
static const auto limit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_MELEE_FIGHT));
|
||||
|
||||
const auto treeVersion = target->getTreeVersion();
|
||||
|
||||
if(treeVersion != meleeCachedLast)
|
||||
{
|
||||
auto bonuses = target->getBonuses(selector, limit);
|
||||
meleeValue = initialValue + bonuses->totalValue();
|
||||
meleeCachedLast = treeVersion;
|
||||
}
|
||||
|
||||
return meleeValue;
|
||||
}
|
||||
|
||||
int CTotalsProxy::getRangedValue() const
|
||||
{
|
||||
static const auto limit = Selector::effectRange()(BonusLimitEffect::NO_LIMIT).Or(Selector::effectRange()(BonusLimitEffect::ONLY_DISTANCE_FIGHT));
|
||||
|
||||
const auto treeVersion = target->getTreeVersion();
|
||||
|
||||
if(treeVersion != rangedCachedLast)
|
||||
{
|
||||
auto bonuses = target->getBonuses(selector, limit);
|
||||
rangedValue = initialValue + bonuses->totalValue();
|
||||
rangedCachedLast = treeVersion;
|
||||
}
|
||||
|
||||
return rangedValue;
|
||||
}
|
||||
|
||||
///CCheckProxy
|
||||
CCheckProxy::CCheckProxy(const IBonusBearer * Target, BonusType bonusType):
|
||||
target(Target),
|
||||
selector(Selector::type()(bonusType)),
|
||||
cachingStr("type_" + std::to_string(static_cast<int>(bonusType))),
|
||||
cachedLast(0),
|
||||
hasBonus(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CCheckProxy::CCheckProxy(const IBonusBearer * Target, CSelector Selector, const std::string & cachingStr):
|
||||
target(Target),
|
||||
selector(std::move(Selector)),
|
||||
cachedLast(0),
|
||||
cachingStr(cachingStr),
|
||||
hasBonus(false)
|
||||
{
|
||||
}
|
||||
|
||||
//This constructor should be placed here to avoid side effects
|
||||
CCheckProxy::CCheckProxy(const CCheckProxy & other) = default;
|
||||
|
||||
bool CCheckProxy::getHasBonus() const
|
||||
{
|
||||
const auto treeVersion = target->getTreeVersion();
|
||||
|
||||
if(treeVersion != cachedLast)
|
||||
{
|
||||
hasBonus = target->hasBonus(selector, cachingStr);
|
||||
cachedLast = treeVersion;
|
||||
}
|
||||
|
||||
return hasBonus;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* CBonusProxy.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Bonus.h"
|
||||
#include "BonusSelector.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class DLL_LINKAGE CBonusProxy
|
||||
{
|
||||
public:
|
||||
CBonusProxy(const IBonusBearer * Target, CSelector Selector);
|
||||
CBonusProxy(const CBonusProxy & other);
|
||||
CBonusProxy(CBonusProxy && other) noexcept;
|
||||
|
||||
CBonusProxy & operator=(CBonusProxy && other) noexcept;
|
||||
CBonusProxy & operator=(const CBonusProxy & other);
|
||||
const BonusList * operator->() const;
|
||||
TConstBonusListPtr getBonusList() const;
|
||||
|
||||
protected:
|
||||
CSelector selector;
|
||||
const IBonusBearer * target;
|
||||
mutable int64_t bonusListCachedLast;
|
||||
mutable TConstBonusListPtr bonusList[2];
|
||||
mutable int currentBonusListIndex;
|
||||
mutable boost::mutex swapGuard;
|
||||
void swapBonusList(TConstBonusListPtr other) const;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CTotalsProxy : public CBonusProxy
|
||||
{
|
||||
public:
|
||||
CTotalsProxy(const IBonusBearer * Target, CSelector Selector, int InitialValue);
|
||||
CTotalsProxy(const CTotalsProxy & other);
|
||||
CTotalsProxy(CTotalsProxy && other) = delete;
|
||||
|
||||
CTotalsProxy & operator=(const CTotalsProxy & other) = default;
|
||||
CTotalsProxy & operator=(CTotalsProxy && other) = delete;
|
||||
|
||||
int getMeleeValue() const;
|
||||
int getRangedValue() const;
|
||||
int getValue() const;
|
||||
/**
|
||||
Returns total value of all selected bonuses and sets bonusList as a pointer to the list of selected bonuses
|
||||
@param bonusList is the out list of all selected bonuses
|
||||
@return total value of all selected bonuses and 0 otherwise
|
||||
*/
|
||||
int getValueAndList(TConstBonusListPtr & bonusList) const;
|
||||
|
||||
private:
|
||||
int initialValue;
|
||||
|
||||
mutable int64_t valueCachedLast = 0;
|
||||
mutable int value = 0;
|
||||
|
||||
mutable int64_t meleeCachedLast;
|
||||
mutable int meleeValue;
|
||||
|
||||
mutable int64_t rangedCachedLast;
|
||||
mutable int rangedValue;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CCheckProxy
|
||||
{
|
||||
public:
|
||||
CCheckProxy(const IBonusBearer * Target, CSelector Selector, const std::string & cachingStr);
|
||||
CCheckProxy(const IBonusBearer * Target, BonusType bonusType);
|
||||
CCheckProxy(const CCheckProxy & other);
|
||||
CCheckProxy& operator= (const CCheckProxy & other) = default;
|
||||
|
||||
bool getHasBonus() const;
|
||||
|
||||
private:
|
||||
const IBonusBearer * target;
|
||||
std::string cachingStr;
|
||||
CSelector selector;
|
||||
|
||||
mutable int64_t cachedLast;
|
||||
mutable bool hasBonus;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -105,7 +105,7 @@ bool IBonusBearer::hasBonusOfType(BonusType type, BonusSubtypeID subtype) const
|
||||
|
||||
bool IBonusBearer::hasBonusFrom(BonusSource source, BonusSourceID sourceID) const
|
||||
{
|
||||
std::string cachingStr = "source_" + std::to_string(static_cast<int>(source)) + "_" + sourceID.toString();
|
||||
std::string cachingStr = "source_" + std::to_string(static_cast<int>(source)) + "_" + std::to_string(sourceID.getNum());
|
||||
return hasBonus(Selector::source(source,sourceID), cachingStr);
|
||||
}
|
||||
|
||||
|
@ -111,18 +111,6 @@ ArmyMovementUpdater::ArmyMovementUpdater(int base, int divider, int multiplier,
|
||||
|
||||
std::shared_ptr<Bonus> ArmyMovementUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
|
||||
{
|
||||
if(b->type == BonusType::MOVEMENT && context.getNodeType() == CBonusSystemNode::HERO)
|
||||
{
|
||||
auto speed = static_cast<const CGHeroInstance &>(context).getLowestCreatureSpeed();
|
||||
si32 armySpeed = speed * base / divider;
|
||||
auto counted = armySpeed * multiplier;
|
||||
auto newBonus = std::make_shared<Bonus>(*b);
|
||||
newBonus->source = BonusSource::ARMY;
|
||||
newBonus->val += vstd::amin(counted, max);
|
||||
return newBonus;
|
||||
}
|
||||
if(b->type != BonusType::MOVEMENT)
|
||||
logGlobal->error("ArmyMovementUpdater should only be used for MOVEMENT bonus!");
|
||||
return b;
|
||||
}
|
||||
|
||||
|
@ -171,6 +171,26 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
|
||||
ret.outroVideo = VideoPath::fromJson(reader["outroVideo"]);
|
||||
}
|
||||
|
||||
JsonNode CampaignHandler::writeHeaderToJson(CampaignHeader & header)
|
||||
{
|
||||
JsonNode node;
|
||||
node["version"].Integer() = static_cast<ui64>(CampaignVersion::VCMI);
|
||||
node["regions"] = CampaignRegions::toJson(header.campaignRegions);
|
||||
node["name"].String() = header.name.toString();
|
||||
node["description"].String() = header.description.toString();
|
||||
node["author"].String() = header.author.toString();
|
||||
node["authorContact"].String() = header.authorContact.toString();
|
||||
node["campaignVersion"].String() = header.campaignVersion.toString();
|
||||
node["creationDateTime"].Integer() = header.creationDateTime;
|
||||
node["allowDifficultySelection"].Bool() = header.difficultyChosenByPlayer;
|
||||
node["music"].String() = header.music.getName();
|
||||
node["loadingBackground"].String() = header.loadingBackground.getName();
|
||||
node["videoRim"].String() = header.videoRim.getName();
|
||||
node["introVideo"].String() = header.introVideo.getName();
|
||||
node["outroVideo"].String() = header.outroVideo.getName();
|
||||
return node;
|
||||
}
|
||||
|
||||
CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
|
||||
{
|
||||
auto prologEpilogReader = [](JsonNode & identifier) -> CampaignScenarioPrologEpilog
|
||||
@ -203,56 +223,86 @@ CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
|
||||
return ret;
|
||||
}
|
||||
|
||||
JsonNode CampaignHandler::writeScenarioToJson(const CampaignScenario & scenario)
|
||||
{
|
||||
auto prologEpilogWriter = [](const CampaignScenarioPrologEpilog & elem) -> JsonNode
|
||||
{
|
||||
JsonNode node;
|
||||
if(elem.hasPrologEpilog)
|
||||
{
|
||||
node["video"].String() = elem.prologVideo.getName();
|
||||
node["music"].String() = elem.prologMusic.getName();
|
||||
node["voice"].String() = elem.prologVoice.getName();
|
||||
node["text"].String() = elem.prologText.toString();
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
JsonNode node;
|
||||
node["map"].String() = scenario.mapName;
|
||||
for(auto & g : scenario.preconditionRegions)
|
||||
node["preconditions"].Vector().push_back(JsonNode(static_cast<ui32>(g)));
|
||||
node["color"].Integer() = scenario.regionColor;
|
||||
node["difficulty"].Integer() = scenario.difficulty;
|
||||
node["regionText"].String() = scenario.regionText.toString();
|
||||
node["prolog"] = prologEpilogWriter(scenario.prolog);
|
||||
node["epilog"] = prologEpilogWriter(scenario.epilog);
|
||||
|
||||
writeScenarioTravelToJson(node, scenario.travelOptions);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static const std::map<std::string, CampaignStartOptions> startOptionsMap = {
|
||||
{"none", CampaignStartOptions::NONE},
|
||||
{"bonus", CampaignStartOptions::START_BONUS},
|
||||
{"crossover", CampaignStartOptions::HERO_CROSSOVER},
|
||||
{"hero", CampaignStartOptions::HERO_OPTIONS}
|
||||
};
|
||||
|
||||
static const std::map<std::string, CampaignBonusType> bonusTypeMap = {
|
||||
{"spell", CampaignBonusType::SPELL},
|
||||
{"creature", CampaignBonusType::MONSTER},
|
||||
{"building", CampaignBonusType::BUILDING},
|
||||
{"artifact", CampaignBonusType::ARTIFACT},
|
||||
{"scroll", CampaignBonusType::SPELL_SCROLL},
|
||||
{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
|
||||
{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
|
||||
{"resource", CampaignBonusType::RESOURCE},
|
||||
//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
|
||||
//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui32> primarySkillsMap = {
|
||||
{"attack", 0},
|
||||
{"defence", 8},
|
||||
{"spellpower", 16},
|
||||
{"knowledge", 24},
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui16> heroSpecialMap = {
|
||||
{"strongest", 0xFFFD},
|
||||
{"generated", 0xFFFE},
|
||||
{"random", 0xFFFF}
|
||||
};
|
||||
|
||||
static const std::map<std::string, ui8> resourceTypeMap = {
|
||||
//FD - wood+ore
|
||||
//FE - mercury+sulfur+crystal+gem
|
||||
{"wood", 0},
|
||||
{"mercury", 1},
|
||||
{"ore", 2},
|
||||
{"sulfur", 3},
|
||||
{"crystal", 4},
|
||||
{"gems", 5},
|
||||
{"gold", 6},
|
||||
{"common", 0xFD},
|
||||
{"rare", 0xFE}
|
||||
};
|
||||
|
||||
CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
{
|
||||
CampaignTravel ret;
|
||||
|
||||
std::map<std::string, CampaignStartOptions> startOptionsMap = {
|
||||
{"none", CampaignStartOptions::NONE},
|
||||
{"bonus", CampaignStartOptions::START_BONUS},
|
||||
{"crossover", CampaignStartOptions::HERO_CROSSOVER},
|
||||
{"hero", CampaignStartOptions::HERO_OPTIONS}
|
||||
};
|
||||
|
||||
std::map<std::string, CampaignBonusType> bonusTypeMap = {
|
||||
{"spell", CampaignBonusType::SPELL},
|
||||
{"creature", CampaignBonusType::MONSTER},
|
||||
{"building", CampaignBonusType::BUILDING},
|
||||
{"artifact", CampaignBonusType::ARTIFACT},
|
||||
{"scroll", CampaignBonusType::SPELL_SCROLL},
|
||||
{"primarySkill", CampaignBonusType::PRIMARY_SKILL},
|
||||
{"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
|
||||
{"resource", CampaignBonusType::RESOURCE},
|
||||
//{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
|
||||
//{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
|
||||
};
|
||||
|
||||
std::map<std::string, ui32> primarySkillsMap = {
|
||||
{"attack", 0},
|
||||
{"defence", 8},
|
||||
{"spellpower", 16},
|
||||
{"knowledge", 24},
|
||||
};
|
||||
|
||||
std::map<std::string, ui16> heroSpecialMap = {
|
||||
{"strongest", 0xFFFD},
|
||||
{"generated", 0xFFFE},
|
||||
{"random", 0xFFFF}
|
||||
};
|
||||
|
||||
std::map<std::string, ui8> resourceTypeMap = {
|
||||
//FD - wood+ore
|
||||
//FE - mercury+sulfur+crystal+gem
|
||||
{"wood", 0},
|
||||
{"mercury", 1},
|
||||
{"ore", 2},
|
||||
{"sulfur", 3},
|
||||
{"crystal", 4},
|
||||
{"gems", 5},
|
||||
{"gold", 6},
|
||||
{"common", 0xFD},
|
||||
{"rare", 0xFE}
|
||||
};
|
||||
|
||||
for(auto & k : reader["heroKeeps"].Vector())
|
||||
{
|
||||
@ -278,7 +328,7 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String());
|
||||
}
|
||||
|
||||
ret.startOptions = startOptionsMap[reader["startOptions"].String()];
|
||||
ret.startOptions = startOptionsMap.at(reader["startOptions"].String());
|
||||
switch(ret.startOptions)
|
||||
{
|
||||
case CampaignStartOptions::NONE:
|
||||
@ -290,11 +340,11 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
for(auto & bjson : reader["bonuses"].Vector())
|
||||
{
|
||||
CampaignBonus bonus;
|
||||
bonus.type = bonusTypeMap[bjson["what"].String()];
|
||||
bonus.type = bonusTypeMap.at(bjson["what"].String());
|
||||
switch (bonus.type)
|
||||
{
|
||||
case CampaignBonusType::RESOURCE:
|
||||
bonus.info1 = resourceTypeMap[bjson["type"].String()];
|
||||
bonus.info1 = resourceTypeMap.at(bjson["type"].String());
|
||||
bonus.info2 = bjson["amount"].Integer();
|
||||
break;
|
||||
|
||||
@ -305,7 +355,7 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
break;
|
||||
|
||||
default:
|
||||
if(int heroId = heroSpecialMap[bjson["hero"].String()])
|
||||
if(int heroId = heroSpecialMap.at(bjson["hero"].String()))
|
||||
bonus.info1 = heroId;
|
||||
else
|
||||
if(auto identifier = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", bjson["hero"].String()))
|
||||
@ -368,7 +418,7 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
bonus.type = CampaignBonusType::HERO;
|
||||
bonus.info1 = bjson["playerColor"].Integer(); //player color
|
||||
|
||||
if(int heroId = heroSpecialMap[bjson["hero"].String()])
|
||||
if(int heroId = heroSpecialMap.at(bjson["hero"].String()))
|
||||
bonus.info2 = heroId;
|
||||
else
|
||||
if (auto identifier = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "hero", bjson["hero"].String()))
|
||||
@ -390,6 +440,109 @@ CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CampaignHandler::writeScenarioTravelToJson(JsonNode & node, const CampaignTravel & travel)
|
||||
{
|
||||
if(travel.whatHeroKeeps.experience)
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("experience"));
|
||||
if(travel.whatHeroKeeps.primarySkills)
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("primarySkills"));
|
||||
if(travel.whatHeroKeeps.secondarySkills)
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("secondarySkills"));
|
||||
if(travel.whatHeroKeeps.spells)
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("spells"));
|
||||
if(travel.whatHeroKeeps.artifacts)
|
||||
node["heroKeeps"].Vector().push_back(JsonNode("artifacts"));
|
||||
for(auto & c : travel.monstersKeptByHero)
|
||||
node["keepCreatures"].Vector().push_back(JsonNode(CreatureID::encode(c)));
|
||||
for(auto & a : travel.artifactsKeptByHero)
|
||||
node["keepArtifacts"].Vector().push_back(JsonNode(ArtifactID::encode(a)));
|
||||
node["startOptions"].String() = vstd::reverseMap(startOptionsMap)[travel.startOptions];
|
||||
|
||||
switch(travel.startOptions)
|
||||
{
|
||||
case CampaignStartOptions::NONE:
|
||||
break;
|
||||
case CampaignStartOptions::START_BONUS:
|
||||
{
|
||||
node["playerColor"].String() = PlayerColor::encode(travel.playerColor);
|
||||
for(auto & bonus : travel.bonusesToChoose)
|
||||
{
|
||||
JsonNode bnode;
|
||||
bnode["what"].String() = vstd::reverseMap(bonusTypeMap)[bonus.type];
|
||||
switch (bonus.type)
|
||||
{
|
||||
case CampaignBonusType::RESOURCE:
|
||||
bnode["type"].String() = vstd::reverseMap(resourceTypeMap)[bonus.info1];
|
||||
bnode["amount"].Integer() = bonus.info2;
|
||||
break;
|
||||
case CampaignBonusType::BUILDING:
|
||||
bnode["type"].String() = EBuildingType::names[bonus.info1];
|
||||
break;
|
||||
default:
|
||||
if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info1))
|
||||
bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info1];
|
||||
else
|
||||
bnode["hero"].String() = HeroTypeID::encode(bonus.info1);
|
||||
bnode["amount"].Integer() = bonus.info3;
|
||||
switch(bonus.type)
|
||||
{
|
||||
case CampaignBonusType::SPELL:
|
||||
bnode["type"].String() = SpellID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::MONSTER:
|
||||
bnode["type"].String() = CreatureID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::SECONDARY_SKILL:
|
||||
bnode["type"].String() = SecondarySkill::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::ARTIFACT:
|
||||
bnode["type"].String() = ArtifactID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::SPELL_SCROLL:
|
||||
bnode["type"].String() = SpellID::encode(bonus.info2);
|
||||
break;
|
||||
case CampaignBonusType::PRIMARY_SKILL:
|
||||
for(auto & ps : primarySkillsMap)
|
||||
bnode[ps.first].Integer() = (bonus.info2 >> ps.second) & 0xff;
|
||||
break;
|
||||
default:
|
||||
bnode["type"].Integer() = bonus.info2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
node["bonuses"].Vector().push_back(bnode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_CROSSOVER:
|
||||
{
|
||||
for(auto & bonus : travel.bonusesToChoose)
|
||||
{
|
||||
JsonNode bnode;
|
||||
bnode["playerColor"].Integer() = bonus.info1;
|
||||
bnode["scenario"].Integer() = bonus.info2;
|
||||
node["bonuses"].Vector().push_back(bnode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CampaignStartOptions::HERO_OPTIONS:
|
||||
{
|
||||
for(auto & bonus : travel.bonusesToChoose)
|
||||
{
|
||||
JsonNode bnode;
|
||||
bnode["playerColor"].Integer() = bonus.info1;
|
||||
|
||||
if(vstd::contains(vstd::reverseMap(heroSpecialMap), bonus.info2))
|
||||
bnode["hero"].String() = vstd::reverseMap(heroSpecialMap)[bonus.info2];
|
||||
else
|
||||
bnode["hero"].String() = HeroTypeID::encode(bonus.info2);
|
||||
|
||||
node["bonuses"].Vector().push_back(bnode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CampaignHandler::readHeaderFromMemory( CampaignHeader & ret, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
|
||||
{
|
||||
|
@ -26,6 +26,9 @@ class DLL_LINKAGE CampaignHandler
|
||||
static CampaignScenario readScenarioFromJson(JsonNode & reader);
|
||||
static CampaignTravel readScenarioTravelFromJson(JsonNode & reader);
|
||||
|
||||
//writer for VCMI campaigns (*.vcmp)
|
||||
static void writeScenarioTravelToJson(JsonNode & node, const CampaignTravel & travel);
|
||||
|
||||
//parsers for original H3C campaigns
|
||||
static void readHeaderFromMemory(CampaignHeader & target, CBinaryReader & reader, std::string filename, std::string modName, std::string encoding);
|
||||
static CampaignScenario readScenarioFromMemory(CBinaryReader & reader, CampaignHeader & header);
|
||||
@ -43,6 +46,10 @@ public:
|
||||
static std::unique_ptr<Campaign> getHeader( const std::string & name); //name - name of appropriate file
|
||||
|
||||
static std::shared_ptr<CampaignState> getCampaign(const std::string & name); //name - name of appropriate file
|
||||
|
||||
//writer for VCMI campaigns (*.vcmp)
|
||||
static JsonNode writeHeaderToJson(CampaignHeader & header);
|
||||
static JsonNode writeScenarioToJson(const CampaignScenario & scenario);
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -45,6 +45,22 @@ CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(
|
||||
return rd;
|
||||
}
|
||||
|
||||
JsonNode CampaignRegions::RegionDescription::toJson(CampaignRegions::RegionDescription & rd)
|
||||
{
|
||||
JsonNode node;
|
||||
node["infix"].String() = rd.infix;
|
||||
node["x"].Float() = rd.pos.x;
|
||||
node["y"].Float() = rd.pos.y;
|
||||
if(rd.labelPos != std::nullopt)
|
||||
{
|
||||
node["labelPos"]["x"].Float() = (*rd.labelPos).x;
|
||||
node["labelPos"]["y"].Float() = (*rd.labelPos).y;
|
||||
}
|
||||
else
|
||||
node["labelPos"].clear();
|
||||
return node;
|
||||
}
|
||||
|
||||
CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
|
||||
{
|
||||
CampaignRegions cr;
|
||||
@ -59,6 +75,25 @@ CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
|
||||
return cr;
|
||||
}
|
||||
|
||||
JsonNode CampaignRegions::toJson(CampaignRegions cr)
|
||||
{
|
||||
JsonNode node;
|
||||
node["prefix"].String() = cr.campPrefix;
|
||||
node["colorSuffixLength"].Float() = cr.colorSuffixLength;
|
||||
if(!cr.campSuffix.size())
|
||||
node["suffix"].clear();
|
||||
else
|
||||
node["suffix"].Vector() = JsonVector{ JsonNode(cr.campSuffix[0]), JsonNode(cr.campSuffix[1]), JsonNode(cr.campSuffix[2]) };
|
||||
if(cr.campBackground.empty())
|
||||
node["background"].clear();
|
||||
else
|
||||
node["background"].String() = cr.campBackground;
|
||||
node["desc"].Vector() = JsonVector();
|
||||
for(auto & region : cr.regions)
|
||||
node["desc"].Vector().push_back(CampaignRegions::RegionDescription::toJson(region));
|
||||
return node;
|
||||
}
|
||||
|
||||
CampaignRegions CampaignRegions::getLegacy(int campId)
|
||||
{
|
||||
static std::vector<CampaignRegions> campDescriptions;
|
||||
|
@ -59,6 +59,7 @@ class DLL_LINKAGE CampaignRegions
|
||||
}
|
||||
|
||||
static CampaignRegions::RegionDescription fromJson(const JsonNode & node);
|
||||
static JsonNode toJson(CampaignRegions::RegionDescription & rd);
|
||||
};
|
||||
|
||||
std::vector<RegionDescription> regions;
|
||||
@ -86,6 +87,7 @@ public:
|
||||
}
|
||||
|
||||
static CampaignRegions fromJson(const JsonNode & node);
|
||||
static JsonNode toJson(CampaignRegions cr);
|
||||
static CampaignRegions getLegacy(int campId);
|
||||
};
|
||||
|
||||
|
@ -325,7 +325,14 @@ private:
|
||||
public:
|
||||
static Type getDwellingFromLevel(int level, int upgradeIndex)
|
||||
{
|
||||
return getDwellings()[upgradeIndex][level];
|
||||
try
|
||||
{
|
||||
return getDwellings().at(upgradeIndex).at(level);
|
||||
}
|
||||
catch (const std::out_of_range & e)
|
||||
{
|
||||
return Type::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static int getLevelFromDwelling(BuildingIDBase dwelling)
|
||||
|
@ -566,7 +566,7 @@ void CGameState::placeStartingHeroes()
|
||||
continue;
|
||||
|
||||
HeroTypeID heroTypeId = pickNextHeroType(playerColor);
|
||||
if(playerSettingPair.second.hero == HeroTypeID::NONE)
|
||||
if(playerSettingPair.second.hero == HeroTypeID::NONE || playerSettingPair.second.hero == HeroTypeID::RANDOM)
|
||||
playerSettingPair.second.hero = heroTypeId;
|
||||
|
||||
placeStartingHero(playerColor, HeroTypeID(heroTypeId), playerInfo.posOfMainTown);
|
||||
|
@ -35,6 +35,21 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
std::string JsonRandomizationException::cleanupJson(const JsonNode & value)
|
||||
{
|
||||
std::string result = value.toCompactString();
|
||||
for (size_t i = 0; i < result.size(); ++i)
|
||||
if (result[i] == '\n')
|
||||
result[i] = ' ';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
JsonRandomizationException::JsonRandomizationException(const std::string & message, const JsonNode & input)
|
||||
: std::runtime_error(message + " Input was: " + cleanupJson(input))
|
||||
{}
|
||||
|
||||
|
||||
si32 JsonRandom::loadVariable(const std::string & variableGroup, const std::string & value, const Variables & variables, si32 defaultValue)
|
||||
{
|
||||
if (value.empty() || value[0] != '@')
|
||||
@ -483,7 +498,10 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
if (!potentialPicks.empty())
|
||||
pickedCreature = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
|
||||
else
|
||||
logMod->warn("Failed to select suitable random creature!");
|
||||
throw JsonRandomizationException("No potential creatures to pick!", value);
|
||||
|
||||
if (!pickedCreature.hasValue())
|
||||
throw JsonRandomizationException("Invalid creature picked!", value);
|
||||
|
||||
stack.setType(pickedCreature.toCreature());
|
||||
stack.count = loadValue(value, rng, variables);
|
||||
|
@ -27,6 +27,13 @@ struct Bonus;
|
||||
struct Component;
|
||||
class CStackBasicDescriptor;
|
||||
|
||||
class JsonRandomizationException : public std::runtime_error
|
||||
{
|
||||
std::string cleanupJson(const JsonNode & value);
|
||||
public:
|
||||
JsonRandomizationException(const std::string & message, const JsonNode & input);
|
||||
};
|
||||
|
||||
class JsonRandom : public GameCallbackHolder
|
||||
{
|
||||
public:
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "CRewardableConstructor.h"
|
||||
|
||||
#include "../json/JsonUtils.h"
|
||||
#include "../json/JsonRandom.h"
|
||||
#include "../mapObjects/CRewardableObject.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../IGameCallback.h"
|
||||
@ -49,7 +50,14 @@ Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCal
|
||||
{
|
||||
Rewardable::Configuration result;
|
||||
result.variables.preset = presetVariables;
|
||||
objectInfo.configureObject(result, rand, cb);
|
||||
|
||||
try {
|
||||
objectInfo.configureObject(result, rand, cb);
|
||||
}
|
||||
catch (const JsonRandomizationException & e)
|
||||
{
|
||||
throw std::runtime_error("Failed to generate configuration for object '" + getJsonKey() + "'! Reason: " + e.what());
|
||||
}
|
||||
|
||||
for(auto & rewardInfo : result.info)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ CArmedInstance::CArmedInstance(IGameCallback *cb)
|
||||
CArmedInstance::CArmedInstance(IGameCallback *cb, bool isHypothetic):
|
||||
CGObjectInstance(cb),
|
||||
CBonusSystemNode(isHypothetic),
|
||||
nonEvilAlignmentMix(this, BonusType::NONEVIL_ALIGNMENT_MIX), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
|
||||
nonEvilAlignmentMix(this, Selector::type()(BonusType::NONEVIL_ALIGNMENT_MIX)), // Take Angelic Alliance troop-mixing freedom of non-evil units into account.
|
||||
battle(nullptr)
|
||||
{
|
||||
}
|
||||
@ -86,7 +86,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
|
||||
|
||||
size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account
|
||||
|
||||
if (nonEvilAlignmentMix.getHasBonus())
|
||||
if (nonEvilAlignmentMix.hasBonus())
|
||||
{
|
||||
size_t mixableFactions = 0;
|
||||
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
#include "CGObjectInstance.h"
|
||||
#include "../CCreatureSet.h"
|
||||
#include "../bonuses/CBonusProxy.h"
|
||||
#include "../bonuses/CBonusSystemNode.h"
|
||||
#include "../bonuses/BonusCache.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -23,8 +23,7 @@ class JsonSerializeFormat;
|
||||
class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet, public IConstBonusProvider
|
||||
{
|
||||
private:
|
||||
CCheckProxy nonEvilAlignmentMix;
|
||||
static CSelector nonEvilAlignmentMixSelector;
|
||||
BonusValueCache nonEvilAlignmentMix;
|
||||
|
||||
public:
|
||||
BattleInfo *battle; //set to the current battle, if engaged
|
||||
|
@ -73,31 +73,6 @@ void CGHeroPlaceholder::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
handler.serializeInt("powerRank", powerRank.value());
|
||||
}
|
||||
|
||||
static int lowestSpeed(const CGHeroInstance * chi)
|
||||
{
|
||||
static const CSelector selectorSTACKS_SPEED = Selector::type()(BonusType::STACKS_SPEED);
|
||||
static const std::string keySTACKS_SPEED = "type_" + std::to_string(static_cast<si32>(BonusType::STACKS_SPEED));
|
||||
|
||||
if(!chi->stacksCount())
|
||||
{
|
||||
if(chi->commander && chi->commander->alive)
|
||||
{
|
||||
return chi->commander->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED);
|
||||
}
|
||||
|
||||
logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->getNameTranslated());
|
||||
return 20;
|
||||
}
|
||||
|
||||
auto i = chi->Slots().begin();
|
||||
//TODO? should speed modifiers (eg from artifacts) affect hero movement?
|
||||
|
||||
int ret = (i++)->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED);
|
||||
for(; i != chi->Slots().end(); i++)
|
||||
ret = std::min(ret, i->second->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const
|
||||
{
|
||||
int64_t ret = GameConstants::BASE_MOVEMENT_COST;
|
||||
@ -107,13 +82,10 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain
|
||||
{
|
||||
ret = from.getRoad()->movementCost;
|
||||
}
|
||||
else if(ti->nativeTerrain != from.getTerrainID() &&//the terrain is not native
|
||||
ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus
|
||||
!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.getTerrainID()))) //no special movement bonus
|
||||
else if(!ti->hasNoTerrainPenalty(from.getTerrainID())) //no special movement bonus
|
||||
{
|
||||
|
||||
ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost;
|
||||
ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT);
|
||||
ret -= ti->getRoughTerrainDiscountValue();
|
||||
if(ret < GameConstants::BASE_MOVEMENT_COST)
|
||||
ret = GameConstants::BASE_MOVEMENT_COST;
|
||||
}
|
||||
@ -257,30 +229,41 @@ void CGHeroInstance::setMovementPoints(int points)
|
||||
|
||||
int CGHeroInstance::movementPointsLimit(bool onLand) const
|
||||
{
|
||||
return valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea);
|
||||
auto ti = getTurnInfo(0);
|
||||
return onLand ? ti->getMovePointsLimitLand() : ti->getMovePointsLimitWater();
|
||||
}
|
||||
|
||||
int CGHeroInstance::getLowestCreatureSpeed() const
|
||||
{
|
||||
return lowestCreatureSpeed;
|
||||
if(stacksCount() != 0)
|
||||
{
|
||||
int minimalSpeed = std::numeric_limits<int>::max();
|
||||
//TODO? should speed modifiers (eg from artifacts) affect hero movement?
|
||||
for(const auto & slot : Slots())
|
||||
minimalSpeed = std::min(minimalSpeed, slot.second->getInitiative());
|
||||
|
||||
return minimalSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(commander && commander->alive)
|
||||
return commander->getInitiative();
|
||||
}
|
||||
|
||||
return 10;
|
||||
}
|
||||
|
||||
void CGHeroInstance::updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const
|
||||
std::unique_ptr<TurnInfo> CGHeroInstance::getTurnInfo(int days) const
|
||||
{
|
||||
auto realLowestSpeed = lowestSpeed(this);
|
||||
if(lowestCreatureSpeed != realLowestSpeed)
|
||||
{
|
||||
lowestCreatureSpeed = realLowestSpeed;
|
||||
//Let updaters run again
|
||||
treeHasChanged();
|
||||
ti->updateHeroBonuses(BonusType::MOVEMENT);
|
||||
}
|
||||
return std::make_unique<TurnInfo>(turnInfoCache.get(), this, days);
|
||||
}
|
||||
|
||||
int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti) const
|
||||
{
|
||||
updateArmyMovementBonus(onLand, ti);
|
||||
return ti->valOfBonuses(BonusType::MOVEMENT, onLand ? BonusCustomSubtype::heroMovementLand : BonusCustomSubtype::heroMovementSea);
|
||||
if (onLand)
|
||||
return ti->getMovePointsLimitLand();
|
||||
else
|
||||
return ti->getMovePointsLimitWater();
|
||||
}
|
||||
|
||||
CGHeroInstance::CGHeroInstance(IGameCallback * cb)
|
||||
@ -293,7 +276,10 @@ CGHeroInstance::CGHeroInstance(IGameCallback * cb)
|
||||
level(1),
|
||||
exp(UNINITIALIZED_EXPERIENCE),
|
||||
gender(EHeroGender::DEFAULT),
|
||||
lowestCreatureSpeed(0)
|
||||
primarySkills(this),
|
||||
magicSchoolMastery(this),
|
||||
turnInfoCache(std::make_unique<TurnInfoCache>(this)),
|
||||
manaPerKnowledgeCached(this, Selector::type()(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))
|
||||
{
|
||||
setNodeType(HERO);
|
||||
ID = Obj::HERO;
|
||||
@ -704,40 +690,20 @@ void CGHeroInstance::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
|
||||
setStackCount(SlotID(0), identifier.getNum());
|
||||
}
|
||||
|
||||
std::array<int, 4> CGHeroInstance::getPrimarySkills() const
|
||||
int CGHeroInstance::getPrimSkillLevel(PrimarySkill id) const
|
||||
{
|
||||
std::array<int, 4> result;
|
||||
|
||||
auto allSkills = getBonusBearer()->getBonusesOfType(BonusType::PRIMARY_SKILL);
|
||||
for (auto skill : PrimarySkill::ALL_SKILLS())
|
||||
{
|
||||
int ret = allSkills->valOfBonuses(Selector::subtype()(BonusSubtypeID(skill)));
|
||||
int minSkillValue = VLC->engineSettings()->getVectorValue(EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, skill.getNum());
|
||||
result[skill] = std::max(ret, minSkillValue); //otherwise, some artifacts may cause negative skill value effect
|
||||
}
|
||||
|
||||
return result;
|
||||
return primarySkills.getSkills()[id];
|
||||
}
|
||||
|
||||
double CGHeroInstance::getFightingStrength() const
|
||||
{
|
||||
const auto & primarySkills = getPrimarySkills();
|
||||
return getFightingStrengthImpl(primarySkills);
|
||||
}
|
||||
|
||||
double CGHeroInstance::getFightingStrengthImpl(const std::array<int, 4> & primarySkills) const
|
||||
{
|
||||
return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::ATTACK]) * (1.0 + 0.05*primarySkills[PrimarySkill::DEFENSE]));
|
||||
const auto & skillValues = primarySkills.getSkills();
|
||||
return sqrt((1.0 + 0.05*skillValues[PrimarySkill::ATTACK]) * (1.0 + 0.05*skillValues[PrimarySkill::DEFENSE]));
|
||||
}
|
||||
|
||||
double CGHeroInstance::getMagicStrength() const
|
||||
{
|
||||
const auto & primarySkills = getPrimarySkills();
|
||||
return getMagicStrengthImpl(primarySkills);
|
||||
}
|
||||
|
||||
double CGHeroInstance::getMagicStrengthImpl(const std::array<int, 4> & primarySkills) const
|
||||
{
|
||||
const auto & skillValues = primarySkills.getSkills();
|
||||
if (!hasSpellbook())
|
||||
return 1;
|
||||
bool atLeastOneCombatSpell = false;
|
||||
@ -751,13 +717,12 @@ double CGHeroInstance::getMagicStrengthImpl(const std::array<int, 4> & primarySk
|
||||
}
|
||||
if (!atLeastOneCombatSpell)
|
||||
return 1;
|
||||
return sqrt((1.0 + 0.05*primarySkills[PrimarySkill::KNOWLEDGE] * mana / manaLimit()) * (1.0 + 0.05*primarySkills[PrimarySkill::SPELL_POWER] * mana / manaLimit()));
|
||||
return sqrt((1.0 + 0.05*skillValues[PrimarySkill::KNOWLEDGE] * mana / manaLimit()) * (1.0 + 0.05*skillValues[PrimarySkill::SPELL_POWER] * mana / manaLimit()));
|
||||
}
|
||||
|
||||
double CGHeroInstance::getHeroStrength() const
|
||||
{
|
||||
const auto & primarySkills = getPrimarySkills();
|
||||
return getFightingStrengthImpl(primarySkills) * getMagicStrengthImpl(primarySkills);
|
||||
return getFightingStrength() * getMagicStrength();
|
||||
}
|
||||
|
||||
uint64_t CGHeroInstance::getValueForDiplomacy() const
|
||||
@ -809,7 +774,7 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, SpellSc
|
||||
|
||||
spell->forEachSchool([&, this](const SpellSchool & cnf, bool & stop)
|
||||
{
|
||||
int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, BonusSubtypeID(cnf)); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
|
||||
int32_t thisSchool = magicSchoolMastery.getMastery(cnf); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies)
|
||||
if(thisSchool > skill)
|
||||
{
|
||||
skill = thisSchool;
|
||||
@ -818,7 +783,7 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, SpellSc
|
||||
}
|
||||
});
|
||||
|
||||
vstd::amax(skill, valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, BonusSubtypeID(SpellSchool::ANY))); //any school bonus
|
||||
vstd::amax(skill, magicSchoolMastery.getMastery(SpellSchool::ANY)); //any school bonus
|
||||
vstd::amax(skill, valOfBonuses(BonusType::SPELL, BonusSubtypeID(spell->getId()))); //given by artifact or other effect
|
||||
|
||||
vstd::amax(skill, 0); //in case we don't know any school
|
||||
@ -1207,8 +1172,7 @@ std::string CGHeroInstance::nodeName() const
|
||||
|
||||
si32 CGHeroInstance::manaLimit() const
|
||||
{
|
||||
return si32(getPrimSkillLevel(PrimarySkill::KNOWLEDGE)
|
||||
* (valOfBonuses(BonusType::MANA_PER_KNOWLEDGE_PERCENTAGE))) / 100;
|
||||
return getPrimSkillLevel(PrimarySkill::KNOWLEDGE) * manaPerKnowledgeCached.getValue() / 100;
|
||||
}
|
||||
|
||||
HeroTypeID CGHeroInstance::getPortraitSource() const
|
||||
@ -1381,14 +1345,7 @@ CBonusSystemNode & CGHeroInstance::whereShouldBeAttached(CGameState * gs)
|
||||
|
||||
int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark, const TurnInfo * ti) const
|
||||
{
|
||||
std::unique_ptr<TurnInfo> turnInfoLocal;
|
||||
if(!ti)
|
||||
{
|
||||
turnInfoLocal = std::make_unique<TurnInfo>(this);
|
||||
ti = turnInfoLocal.get();
|
||||
}
|
||||
|
||||
if(!ti->hasBonusOfType(BonusType::FREE_SHIP_BOARDING))
|
||||
if(!ti->hasFreeShipBoarding())
|
||||
return 0; // take all MPs by default
|
||||
|
||||
auto boatLayer = boat ? boat->layer : EPathfindingLayer::SAIL;
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "CArmedInstance.h"
|
||||
#include "IOwnableObject.h"
|
||||
|
||||
#include "../bonuses/BonusCache.h"
|
||||
#include "../entities/hero/EHeroGender.h"
|
||||
#include "../CArtHandler.h" // For CArtifactSet
|
||||
|
||||
@ -24,8 +25,10 @@ class CGBoat;
|
||||
class CGTownInstance;
|
||||
class CMap;
|
||||
class UpgradeInfo;
|
||||
class TurnInfo;
|
||||
|
||||
struct TerrainTile;
|
||||
struct TurnInfo;
|
||||
struct TurnInfoCache;
|
||||
|
||||
class DLL_LINKAGE CGHeroPlaceholder : public CGObjectInstance
|
||||
{
|
||||
@ -58,12 +61,13 @@ class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator,
|
||||
friend class CMapFormatJson;
|
||||
|
||||
private:
|
||||
std::set<SpellID> spells; //known spells (spell IDs)
|
||||
mutable int lowestCreatureSpeed;
|
||||
ui32 movement; //remaining movement points
|
||||
PrimarySkillsCache primarySkills;
|
||||
MagicSchoolMasteryCache magicSchoolMastery;
|
||||
BonusValueCache manaPerKnowledgeCached;
|
||||
std::unique_ptr<TurnInfoCache> turnInfoCache;
|
||||
|
||||
double getFightingStrengthImpl(const std::array<int, 4> & primarySkills) const;
|
||||
double getMagicStrengthImpl(const std::array<int, 4> & primarySkills) const;
|
||||
std::set<SpellID> spells; //known spells (spell IDs)
|
||||
ui32 movement; //remaining movement points
|
||||
|
||||
public:
|
||||
|
||||
@ -204,7 +208,7 @@ public:
|
||||
std::vector<SecondarySkill> getLevelUpProposedSecondarySkills(vstd::RNG & rand) const;
|
||||
|
||||
ui8 getSecSkillLevel(const SecondarySkill & skill) const; //0 - no skill
|
||||
std::array<int, 4> getPrimarySkills() const;
|
||||
int getPrimSkillLevel(PrimarySkill id) const;
|
||||
|
||||
/// Returns true if hero has free secondary skill slot.
|
||||
bool canLearnSkill() const;
|
||||
@ -222,10 +226,10 @@ public:
|
||||
int movementPointsLimit(bool onLand) const;
|
||||
//cached version is much faster, TurnInfo construction is costly
|
||||
int movementPointsLimitCached(bool onLand, const TurnInfo * ti) const;
|
||||
//update army movement bonus
|
||||
void updateArmyMovementBonus(bool onLand, const TurnInfo * ti) const;
|
||||
|
||||
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const;
|
||||
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark, const TurnInfo * ti) const;
|
||||
|
||||
std::unique_ptr<TurnInfo> getTurnInfo(int days) const;
|
||||
|
||||
double getFightingStrength() const; // takes attack / defense skill into account
|
||||
double getMagicStrength() const; // takes knowledge / spell power skill but also current mana, whether the hero owns a spell-book and whether that books contains anything into account
|
||||
|
@ -1134,6 +1134,7 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
eventsHandler.syncSize(events, JsonNode::JsonType::DATA_VECTOR);
|
||||
eventsHandler.serializeStruct(events);
|
||||
}
|
||||
handler.serializeId("alignmentToPlayer", alignmentToPlayer, PlayerColor::NEUTRAL);
|
||||
}
|
||||
|
||||
const CFaction * CGTownInstance::getFaction() const
|
||||
|
@ -803,6 +803,11 @@ std::string CGKeys::getObjectName() const
|
||||
return VLC->generaltexth->tentColors[subID.getNum()] + " " + CGObjectInstance::getObjectName();
|
||||
}
|
||||
|
||||
std::string CGKeys::getObjectDescription(PlayerColor player) const
|
||||
{
|
||||
return visitedTxt(wasMyColorVisited(player));
|
||||
}
|
||||
|
||||
bool CGKeymasterTent::wasVisited (PlayerColor player) const
|
||||
{
|
||||
return wasMyColorVisited (player);
|
||||
|
@ -199,7 +199,8 @@ public:
|
||||
|
||||
bool wasMyColorVisited(const PlayerColor & player) const;
|
||||
|
||||
std::string getObjectName() const override; //depending on color
|
||||
std::string getObjectName() const override;
|
||||
std::string getObjectDescription(PlayerColor player) const;
|
||||
std::string getHoverText(PlayerColor player) const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
|
@ -1289,6 +1289,11 @@ std::string CGObelisk::getHoverText(PlayerColor player) const
|
||||
return getObjectName() + " " + visitedTxt(wasVisited(player));
|
||||
}
|
||||
|
||||
std::string CGObelisk::getObjectDescription(PlayerColor player) const
|
||||
{
|
||||
return visitedTxt(wasVisited(player));
|
||||
}
|
||||
|
||||
void CGObelisk::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
|
||||
{
|
||||
switch(what)
|
||||
|
@ -406,6 +406,7 @@ public:
|
||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||
void initObj(vstd::RNG & rand) override;
|
||||
std::string getHoverText(PlayerColor player) const override;
|
||||
std::string getObjectDescription(PlayerColor player) const;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
|
@ -143,17 +143,6 @@ TerrainTile::TerrainTile():
|
||||
{
|
||||
}
|
||||
|
||||
bool TerrainTile::entrableTerrain(const TerrainTile * from) const
|
||||
{
|
||||
return entrableTerrain(from ? from->isLand() : true, from ? from->isWater() : true);
|
||||
}
|
||||
|
||||
bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const
|
||||
{
|
||||
return getTerrain()->isPassable()
|
||||
&& ((allowSea && isWater()) || (allowLand && isLand()));
|
||||
}
|
||||
|
||||
bool TerrainTile::isClear(const TerrainTile * from) const
|
||||
{
|
||||
return entrableTerrain(from) && !blocked();
|
||||
@ -187,72 +176,6 @@ EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const
|
||||
return EDiggingStatus::CAN_DIG;
|
||||
}
|
||||
|
||||
bool TerrainTile::hasFavorableWinds() const
|
||||
{
|
||||
return extTileFlags & 128;
|
||||
}
|
||||
|
||||
bool TerrainTile::isWater() const
|
||||
{
|
||||
return getTerrain()->isWater();
|
||||
}
|
||||
|
||||
bool TerrainTile::isLand() const
|
||||
{
|
||||
return getTerrain()->isLand();
|
||||
}
|
||||
|
||||
bool TerrainTile::visitable() const
|
||||
{
|
||||
return !visitableObjects.empty();
|
||||
}
|
||||
|
||||
bool TerrainTile::blocked() const
|
||||
{
|
||||
return !blockingObjects.empty();
|
||||
}
|
||||
|
||||
bool TerrainTile::hasRiver() const
|
||||
{
|
||||
return getRiverID() != RiverId::NO_RIVER;
|
||||
}
|
||||
|
||||
bool TerrainTile::hasRoad() const
|
||||
{
|
||||
return getRoadID() != RoadId::NO_ROAD;
|
||||
}
|
||||
|
||||
const TerrainType * TerrainTile::getTerrain() const
|
||||
{
|
||||
return terrainType.toEntity(VLC);
|
||||
}
|
||||
|
||||
const RiverType * TerrainTile::getRiver() const
|
||||
{
|
||||
return riverType.toEntity(VLC);
|
||||
}
|
||||
|
||||
const RoadType * TerrainTile::getRoad() const
|
||||
{
|
||||
return roadType.toEntity(VLC);
|
||||
}
|
||||
|
||||
TerrainId TerrainTile::getTerrainID() const
|
||||
{
|
||||
return terrainType;
|
||||
}
|
||||
|
||||
RiverId TerrainTile::getRiverID() const
|
||||
{
|
||||
return riverType;
|
||||
}
|
||||
|
||||
RoadId TerrainTile::getRoadID() const
|
||||
{
|
||||
return roadType;
|
||||
}
|
||||
|
||||
|
||||
CMap::CMap(IGameCallback * cb)
|
||||
: GameCallbackHolder(cb)
|
||||
, checksum(0)
|
||||
@ -365,7 +288,7 @@ bool CMap::isCoastalTile(const int3 & pos) const
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isWaterTile(pos))
|
||||
if(getTile(pos).isWater())
|
||||
return false;
|
||||
|
||||
for(const auto & dir : dirs)
|
||||
@ -382,22 +305,6 @@ bool CMap::isCoastalTile(const int3 & pos) const
|
||||
return false;
|
||||
}
|
||||
|
||||
TerrainTile & CMap::getTile(const int3 & tile)
|
||||
{
|
||||
assert(isInTheMap(tile));
|
||||
return terrain[tile.z][tile.x][tile.y];
|
||||
}
|
||||
|
||||
const TerrainTile & CMap::getTile(const int3 & tile) const
|
||||
{
|
||||
assert(isInTheMap(tile));
|
||||
return terrain[tile.z][tile.x][tile.y];
|
||||
}
|
||||
|
||||
bool CMap::isWaterTile(const int3 &pos) const
|
||||
{
|
||||
return isInTheMap(pos) && getTile(pos).isWater();
|
||||
}
|
||||
bool CMap::canMoveBetween(const int3 &src, const int3 &dst) const
|
||||
{
|
||||
const TerrainTile * dstTile = &getTile(dst);
|
||||
|
@ -86,18 +86,10 @@ public:
|
||||
void initTerrain();
|
||||
|
||||
CMapEditManager * getEditManager();
|
||||
TerrainTile & getTile(const int3 & tile);
|
||||
const TerrainTile & getTile(const int3 & tile) const;
|
||||
inline TerrainTile & getTile(const int3 & tile);
|
||||
inline const TerrainTile & getTile(const int3 & tile) const;
|
||||
bool isCoastalTile(const int3 & pos) const;
|
||||
bool isWaterTile(const int3 & pos) const;
|
||||
inline bool isInTheMap(const int3 & pos) const
|
||||
{
|
||||
// Check whether coord < 0 is done implicitly. Negative signed int overflows to unsigned number larger than all signed ints.
|
||||
return
|
||||
static_cast<uint32_t>(pos.x) < static_cast<uint32_t>(width) &&
|
||||
static_cast<uint32_t>(pos.y) < static_cast<uint32_t>(height) &&
|
||||
static_cast<uint32_t>(pos.z) <= (twoLevel ? 1 : 0);
|
||||
}
|
||||
inline bool isInTheMap(const int3 & pos) const;
|
||||
|
||||
bool canMoveBetween(const int3 &src, const int3 &dst) const;
|
||||
bool checkForVisitableDir(const int3 & src, const TerrainTile * pom, const int3 & dst) const;
|
||||
@ -250,4 +242,25 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
inline bool CMap::isInTheMap(const int3 & pos) const
|
||||
{
|
||||
// Check whether coord < 0 is done implicitly. Negative signed int overflows to unsigned number larger than all signed ints.
|
||||
return
|
||||
static_cast<uint32_t>(pos.x) < static_cast<uint32_t>(width) &&
|
||||
static_cast<uint32_t>(pos.y) < static_cast<uint32_t>(height) &&
|
||||
static_cast<uint32_t>(pos.z) <= (twoLevel ? 1 : 0);
|
||||
}
|
||||
|
||||
inline TerrainTile & CMap::getTile(const int3 & tile)
|
||||
{
|
||||
assert(isInTheMap(tile));
|
||||
return terrain[tile.z][tile.x][tile.y];
|
||||
}
|
||||
|
||||
inline const TerrainTile & CMap::getTile(const int3 & tile) const
|
||||
{
|
||||
assert(isInTheMap(tile));
|
||||
return terrain[tile.z][tile.x][tile.y];
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -12,7 +12,8 @@
|
||||
|
||||
#include "../ResourceSet.h"
|
||||
#include "../texts/MetaString.h"
|
||||
#include "../int3.h"
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../TerrainHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -103,31 +104,32 @@ struct DLL_LINKAGE TerrainTile
|
||||
TerrainTile();
|
||||
|
||||
/// Gets true if the terrain is not a rock. If from is water/land, same type is also required.
|
||||
bool entrableTerrain(const TerrainTile * from = nullptr) const;
|
||||
bool entrableTerrain(bool allowLand, bool allowSea) const;
|
||||
inline bool entrableTerrain() const;
|
||||
inline bool entrableTerrain(const TerrainTile * from) const;
|
||||
inline bool entrableTerrain(bool allowLand, bool allowSea) const;
|
||||
/// Checks for blocking objects and terraint type (water / land).
|
||||
bool isClear(const TerrainTile * from = nullptr) const;
|
||||
/// Gets the ID of the top visitable object or -1 if there is none.
|
||||
Obj topVisitableId(bool excludeTop = false) const;
|
||||
CGObjectInstance * topVisitableObj(bool excludeTop = false) const;
|
||||
bool isWater() const;
|
||||
bool isLand() const;
|
||||
inline bool isWater() const;
|
||||
inline bool isLand() const;
|
||||
EDiggingStatus getDiggingStatus(bool excludeTop = true) const;
|
||||
bool hasFavorableWinds() const;
|
||||
inline bool hasFavorableWinds() const;
|
||||
|
||||
bool visitable() const;
|
||||
bool blocked() const;
|
||||
inline bool visitable() const;
|
||||
inline bool blocked() const;
|
||||
|
||||
const TerrainType * getTerrain() const;
|
||||
const RiverType * getRiver() const;
|
||||
const RoadType * getRoad() const;
|
||||
inline const TerrainType * getTerrain() const;
|
||||
inline const RiverType * getRiver() const;
|
||||
inline const RoadType * getRoad() const;
|
||||
|
||||
TerrainId getTerrainID() const;
|
||||
RiverId getRiverID() const;
|
||||
RoadId getRoadID() const;
|
||||
inline TerrainId getTerrainID() const;
|
||||
inline RiverId getRiverID() const;
|
||||
inline RoadId getRoadID() const;
|
||||
|
||||
bool hasRiver() const;
|
||||
bool hasRoad() const;
|
||||
inline bool hasRiver() const;
|
||||
inline bool hasRoad() const;
|
||||
|
||||
TerrainId terrainType;
|
||||
RiverId riverType;
|
||||
@ -193,4 +195,86 @@ struct DLL_LINKAGE TerrainTile
|
||||
}
|
||||
};
|
||||
|
||||
inline bool TerrainTile::hasFavorableWinds() const
|
||||
{
|
||||
return extTileFlags & 128;
|
||||
}
|
||||
|
||||
inline bool TerrainTile::isWater() const
|
||||
{
|
||||
return getTerrain()->isWater();
|
||||
}
|
||||
|
||||
inline bool TerrainTile::isLand() const
|
||||
{
|
||||
return getTerrain()->isLand();
|
||||
}
|
||||
|
||||
inline bool TerrainTile::visitable() const
|
||||
{
|
||||
return !visitableObjects.empty();
|
||||
}
|
||||
|
||||
inline bool TerrainTile::blocked() const
|
||||
{
|
||||
return !blockingObjects.empty();
|
||||
}
|
||||
|
||||
inline bool TerrainTile::hasRiver() const
|
||||
{
|
||||
return getRiverID() != RiverId::NO_RIVER;
|
||||
}
|
||||
|
||||
inline bool TerrainTile::hasRoad() const
|
||||
{
|
||||
return getRoadID() != RoadId::NO_ROAD;
|
||||
}
|
||||
|
||||
inline const TerrainType * TerrainTile::getTerrain() const
|
||||
{
|
||||
return terrainType.toEntity(VLC);
|
||||
}
|
||||
|
||||
inline const RiverType * TerrainTile::getRiver() const
|
||||
{
|
||||
return riverType.toEntity(VLC);
|
||||
}
|
||||
|
||||
inline const RoadType * TerrainTile::getRoad() const
|
||||
{
|
||||
return roadType.toEntity(VLC);
|
||||
}
|
||||
|
||||
inline TerrainId TerrainTile::getTerrainID() const
|
||||
{
|
||||
return terrainType;
|
||||
}
|
||||
|
||||
inline RiverId TerrainTile::getRiverID() const
|
||||
{
|
||||
return riverType;
|
||||
}
|
||||
|
||||
inline RoadId TerrainTile::getRoadID() const
|
||||
{
|
||||
return roadType;
|
||||
}
|
||||
|
||||
inline bool TerrainTile::entrableTerrain() const
|
||||
{
|
||||
return entrableTerrain(true, true);
|
||||
}
|
||||
|
||||
inline bool TerrainTile::entrableTerrain(const TerrainTile * from) const
|
||||
{
|
||||
const TerrainType * terrainFrom = from->getTerrain();
|
||||
return entrableTerrain(terrainFrom->isLand(), terrainFrom->isWater());
|
||||
}
|
||||
|
||||
inline bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const
|
||||
{
|
||||
const TerrainType * terrain = getTerrain();
|
||||
return terrain->isPassable() && ((allowSea && terrain->isWater()) || (allowLand && terrain->isLand()));
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -144,7 +144,7 @@ TModID CModHandler::findResourceOrigin(const ResourcePath & name) const
|
||||
return "core";
|
||||
|
||||
if(CResourceHandler::get("mapEditor")->existsResource(name))
|
||||
return "core"; // Workaround for loading maps via map editor
|
||||
return "mapEditor"; // Workaround for loading maps via map editor
|
||||
}
|
||||
catch( const std::out_of_range & e)
|
||||
{
|
||||
@ -189,6 +189,8 @@ std::string CModHandler::getModLanguage(const TModID& modId) const
|
||||
return VLC->generaltexth->getInstalledLanguage();
|
||||
if(modId == "map")
|
||||
return VLC->generaltexth->getPreferredLanguage();
|
||||
if(modId == "mapEditor")
|
||||
return VLC->generaltexth->getPreferredLanguage();
|
||||
return getModInfo(modId).getBaseLanguage();
|
||||
}
|
||||
|
||||
|