1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-10-31 00:07:39 +02:00

Merge pull request #133 from vcmi/feature/pathfinderLayers

Pathfinder layers and related changes: fly and water walking implemented
This commit is contained in:
ArseniyShestakov
2015-11-24 13:09:06 +03:00
20 changed files with 1343 additions and 669 deletions

View File

@@ -2061,101 +2061,6 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col
return PlayerRelations::ENEMIES;
}
void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing)
{
static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
//vec.reserve(8); //optimization
for (auto & dir : dirs)
{
const int3 hlp = tile + dir;
if(!map->isInTheMap(hlp))
continue;
const TerrainTile &hlpt = map->getTile(hlp);
// //we cannot visit things from blocked tiles
// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE)
// {
// continue;
// }
if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water
{
int3 hlp1 = tile,
hlp2 = tile;
hlp1.x += dir.x;
hlp2.y += dir.y;
if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER)
continue;
}
if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) )
&& hlpt.terType != ETerrainType::ROCK)
{
vec.push_back(hlp);
}
}
}
int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, bool checkLast)
{
if(src == dest) //same tile
return 0;
TerrainTile &s = map->getTile(src),
&d = map->getTile(dest);
//get basic cost
int ret = h->getTileCost(d,s);
if(d.blocked && h->canFly())
{
ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0;
}
else if(d.terType == ETerrainType::WATER)
{
if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds
ret *= 0.666;
else if(!h->boat && h->canWalkOnSea())
{
ret *= (100.0 + h->valOfBonuses(Bonus::WATER_WALKING)) / 100.0;
}
}
if(src.x != dest.x && src.y != dest.y) //it's diagonal move
{
int old = ret;
ret *= 1.414213;
//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points
if(ret > remainingMovePoints && remainingMovePoints >= old)
{
return remainingMovePoints;
}
}
int left = remainingMovePoints-ret;
if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points
{
std::vector<int3> vec;
vec.reserve(8); //optimization
getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true);
for(auto & elem : vec)
{
int fcost = getMovementCost(h, dest, elem, left, false);
if(fcost <= left)
{
return ret;
}
}
ret = remainingMovePoints;
}
return ret;
}
void CGameState::apply(CPack *pack)
{
ui16 typ = typeList.getTypeID(pack);

View File

@@ -339,8 +339,6 @@ public:
bool isVisible(int3 pos, PlayerColor player);
bool isVisible(const CGObjectInstance *obj, boost::optional<PlayerColor> player);
void getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing);
int getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints=-1, bool checkLast=true);
int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
// ----- getters, setters -----

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,8 @@
#include "IGameCallback.h"
#include "int3.h"
#include <boost/heap/priority_queue.hpp>
/*
* CPathfinder.h, part of VCMI engine
*
@@ -18,26 +20,45 @@
class CGHeroInstance;
class CGObjectInstance;
struct TerrainTile;
class CPathfinderHelper;
struct DLL_LINKAGE CGPathNode
{
enum EAccessibility
typedef EPathfindingLayer ELayer;
enum ENodeAction : ui8
{
UNKNOWN = 0,
EMBARK = 1,
DISEMBARK,
NORMAL,
BATTLE,
VISIT,
BLOCKING_VISIT
};
enum EAccessibility : ui8
{
NOT_SET = 0,
ACCESSIBLE = 1, //tile can be entered and passed
VISITABLE, //tile can be entered as the last tile in path
BLOCKVIS, //visitable from neighbouring tile but not passable
FLYABLE, //can only be accessed in air layer
BLOCKED //tile can't be entered nor visited
};
EAccessibility accessible;
ui8 land;
ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn
ui32 moveRemains; //remaining tiles after hero reaches the tile
CGPathNode * theNodeBefore;
int3 coord; //coordinates
ui32 moveRemains; //remaining tiles after hero reaches the tile
ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn
ELayer layer;
EAccessibility accessible;
ENodeAction action;
bool locked;
CGPathNode();
void reset();
void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible);
bool reachable() const;
};
@@ -52,27 +73,36 @@ struct DLL_LINKAGE CGPath
struct DLL_LINKAGE CPathsInfo
{
typedef EPathfindingLayer ELayer;
mutable boost::mutex pathMx;
const CGHeroInstance *hero;
const CGHeroInstance * hero;
int3 hpos;
int3 sizes;
CGPathNode ***nodes; //[w][h][level]
boost::multi_array<CGPathNode, 4> nodes; //[w][h][level][layer]
CPathsInfo(const int3 &Sizes);
CPathsInfo(const int3 & Sizes);
~CPathsInfo();
const CGPathNode * getPathInfo( int3 tile ) const;
bool getPath(const int3 &dst, CGPath &out) const;
int getDistance( int3 tile ) const;
const CGPathNode * getPathInfo(const int3 & tile) const;
bool getPath(CGPath & out, const int3 & dst) const;
int getDistance(const int3 & tile) const;
const CGPathNode * getNode(const int3 & coord) const;
CGPathNode * getNode(const int3 & coord, const ELayer layer);
};
class CPathfinder : private CGameInfoCallback
{
public:
CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
friend class CPathfinderHelper;
CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero);
void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
private:
typedef EPathfindingLayer ELayer;
struct PathfinderOptions
{
bool useFlying;
@@ -83,42 +113,153 @@ private:
bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
/// TODO: Find out with client and server code, merge with normal teleporters.
/// Likely proper implementation would require some refactoring of CGTeleport.
/// So for now this is unfinished and disabled by default.
bool useCastleGate;
/// If true transition into air layer only possible from initial node.
/// This is drastically decrease path calculation complexity (and time).
/// Downside is less MP effective paths calculation.
///
/// TODO: If this option end up useful for slow devices it's can be improved:
/// - Allow transition into air layer not only from initial position, but also from teleporters.
/// Movement into air can be also allowed when hero disembarked.
/// - Other idea is to allow transition into air within certain radius of N tiles around hero.
/// Patrol support need similar functionality so it's won't be ton of useless code.
/// Such limitation could be useful as it's can be scaled depend on device performance.
bool lightweightFlyingMode;
/// This option enable one turn limitation for flying and water walking.
/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue.
///
/// Following imitation is default H3 mechanics, but someone may want to disable it in mods.
/// After all this limit should benefit performance on maps with tons of water or blocked tiles.
///
/// TODO:
/// - Behavior when option is disabled not implemented and will lead to crashes.
bool oneTurnSpecialLayersLimit;
/// VCMI have different movement rules to solve flaws original engine has.
/// If this option enabled you'll able to do following things in fly:
/// - Move from blocked tiles to visitable one
/// - Move from guarded tiles to blockvis tiles without being attacked
/// - Move from guarded tiles to guarded visitable tiles with being attacked after
/// TODO:
/// - Option should also allow same tile land <-> air layer transitions.
/// Current implementation only allow go into (from) air layer only to neighbour tiles.
/// I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
bool originalMovementRules;
PathfinderOptions();
} options;
CPathsInfo &out;
const CGHeroInstance *hero;
CPathsInfo & out;
const CGHeroInstance * hero;
const std::vector<std::vector<std::vector<ui8> > > &FoW;
unique_ptr<CPathfinderHelper> hlp;
std::list<CGPathNode*> mq; //BFS queue -> nodes to be checked
struct NodeComparer
{
bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const
{
if(rhs->turns > lhs->turns)
return false;
else if(rhs->turns == lhs->turns && rhs->moveRemains < lhs->moveRemains)
return false;
return true;
}
};
boost::heap::priority_queue<CGPathNode *, boost::heap::compare<NodeComparer> > pq;
std::vector<int3> neighbourTiles;
std::vector<int3> neighbours;
CGPathNode *cp; //current (source) path node -> we took it from the queue
CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider
const TerrainTile *ct, *dt; //tile info for both nodes
const CGObjectInstance *sTileObj;
ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark
CGPathNode * cp; //current (source) path node -> we took it from the queue
CGPathNode * dp; //destination node -> it's a neighbour of cp that we consider
const TerrainTile * ct, * dt; //tile info for both nodes
const CGObjectInstance * ctObj, * dtObj;
CGPathNode::ENodeAction destAction;
void addNeighbours(const int3 &coord);
void addTeleportExits(bool noTeleportExcludes = false);
void addNeighbours();
void addTeleportExits();
bool isMovementPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost
bool checkDestinationTile();
bool isLayerTransitionPossible(const ELayer dstLayer) const;
bool isLayerTransitionPossible() const;
bool isMovementToDestPossible() const;
bool isMovementAfterDestPossible() const;
CGPathNode::ENodeAction getDestAction() const;
int3 getSourceGuardPosition();
bool isSourceGuarded();
bool isDestinationGuarded();
bool isDestinationGuardian();
bool isSourceInitialPosition() const;
bool isSourceVisitableObj() const;
bool isSourceGuarded() const;
bool isDestVisitableObj() const;
bool isDestinationGuarded(const bool ignoreAccessibility = true) const;
bool isDestinationGuardian() const;
void initializeGraph();
CGPathNode *getNode(const int3 &coord);
CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const;
bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;
bool isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const;
bool canSeeObj(const CGObjectInstance * obj) const;
bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
bool addTeleportTwoWay(const CGTeleport * obj) const;
bool addTeleportOneWay(const CGTeleport * obj) const;
bool addTeleportOneWayRandom(const CGTeleport * obj) const;
bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
};
struct DLL_LINKAGE TurnInfo
{
/// This is certainly not the best design ever and certainly can be improved
/// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead
struct BonusCache {
std::vector<bool> noTerrainPenalty;
bool freeShipBoarding;
bool flyingMovement;
int flyingMovementVal;
bool waterWalking;
int waterWalkingVal;
BonusCache(TBonusListPtr bonusList);
};
unique_ptr<BonusCache> bonusCache;
const CGHeroInstance * hero;
TBonusListPtr bonuses;
mutable int maxMovePointsLand;
mutable int maxMovePointsWater;
int nativeTerrain;
TurnInfo(const CGHeroInstance * Hero, const int Turn = 0);
bool isLayerAvailable(const EPathfindingLayer layer) const;
bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const;
int getMaxMovePoints(const EPathfindingLayer layer) const;
};
class DLL_LINKAGE CPathfinderHelper
{
public:
CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options);
void updateTurnInfo(const int turn = 0);
bool isLayerAvailable(const EPathfindingLayer layer) const;
const TurnInfo * getTurnInfo() const;
bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
int getMaxMovePoints(const EPathfindingLayer layer) const;
static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true);
static int getMovementCost(const CGHeroInstance * h, const int3 & dst);
private:
int turn;
const CGHeroInstance * hero;
std::vector<TurnInfo *> turnsInfo;
const CPathfinder::PathfinderOptions & options;
};

View File

@@ -25,56 +25,6 @@ const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255);
const PlayerColor PlayerColor::PLAYER_LIMIT = PlayerColor(PLAYER_LIMIT_I);
const TeamID TeamID::NO_TEAM = TeamID(255);
#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN) \
bool operator==(const A & a, const B & b) \
{ \
return AN == BN ; \
} \
bool operator!=(const A & a, const B & b) \
{ \
return AN != BN ; \
} \
bool operator<(const A & a, const B & b) \
{ \
return AN < BN ; \
} \
bool operator<=(const A & a, const B & b) \
{ \
return AN <= BN ; \
} \
bool operator>(const A & a, const B & b) \
{ \
return AN > BN ; \
} \
bool operator>=(const A & a, const B & b) \
{ \
return AN >= BN ; \
}
#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME) \
ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num) \
ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b) \
ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num)
ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill)
ID_LIKE_OPERATORS(Obj, Obj::EObj)
ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType)
ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID)
ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition)
ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID)
ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
CArtifact * ArtifactID::toArtifact() const
{
return VLC->arth->artifacts[*this];
@@ -130,7 +80,7 @@ std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType
else return os << it->second;
}
std::ostream & operator<<(std::ostream & os, const ETerrainType actionType)
std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType)
{
static const std::map<ETerrainType::EETerrainType, std::string> terrainTypeToString =
{
@@ -147,9 +97,10 @@ std::ostream & operator<<(std::ostream & os, const ETerrainType actionType)
DEFINE_ELEMENT(LAVA),
DEFINE_ELEMENT(WATER),
DEFINE_ELEMENT(ROCK)
#undef DEFINE_ELEMENT
};
auto it = terrainTypeToString.find(actionType.num);
auto it = terrainTypeToString.find(terrainType.num);
if (it == terrainTypeToString.end()) return os << "<Unknown type>";
else return os << it->second;
}
@@ -160,3 +111,23 @@ std::string ETerrainType::toString() const
ss << *this;
return ss.str();
}
std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer)
{
static const std::map<EPathfindingLayer::EEPathfindingLayer, std::string> pathfinderLayerToString
{
#define DEFINE_ELEMENT(element) {EPathfindingLayer::element, #element}
DEFINE_ELEMENT(WRONG),
DEFINE_ELEMENT(AUTO),
DEFINE_ELEMENT(LAND),
DEFINE_ELEMENT(SAIL),
DEFINE_ELEMENT(WATER),
DEFINE_ELEMENT(AIR),
DEFINE_ELEMENT(NUM_LAYERS)
#undef DEFINE_ELEMENT
};
auto it = pathfinderLayerToString.find(pathfindingLayer.num);
if (it == pathfinderLayerToString.end()) return os << "<Unknown type>";
else return os << it->second;
}

View File

@@ -93,18 +93,37 @@ CLASS_NAME & advance(int i) \
}
#define ID_LIKE_OPERATORS_INTERNAL_DECLS(A, B) \
bool DLL_LINKAGE operator==(const A & a, const B & b); \
bool DLL_LINKAGE operator!=(const A & a, const B & b); \
bool DLL_LINKAGE operator<(const A & a, const B & b); \
bool DLL_LINKAGE operator<=(const A & a, const B & b); \
bool DLL_LINKAGE operator>(const A & a, const B & b); \
bool DLL_LINKAGE operator>=(const A & a, const B & b);
// Operators are performance-critical and to be inlined they must be in header
#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN) \
STRONG_INLINE bool operator==(const A & a, const B & b) \
{ \
return AN == BN ; \
} \
STRONG_INLINE bool operator!=(const A & a, const B & b) \
{ \
return AN != BN ; \
} \
STRONG_INLINE bool operator<(const A & a, const B & b) \
{ \
return AN < BN ; \
} \
STRONG_INLINE bool operator<=(const A & a, const B & b) \
{ \
return AN <= BN ; \
} \
STRONG_INLINE bool operator>(const A & a, const B & b) \
{ \
return AN > BN ; \
} \
STRONG_INLINE bool operator>=(const A & a, const B & b) \
{ \
return AN >= BN ; \
}
#define ID_LIKE_OPERATORS_DECLS(CLASS_NAME, ENUM_NAME) \
ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, CLASS_NAME) \
ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, ENUM_NAME) \
ID_LIKE_OPERATORS_INTERNAL_DECLS(ENUM_NAME, CLASS_NAME)
#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME) \
ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num) \
ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b) \
ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num)
#define OP_DECL_INT(CLASS_NAME, OP) \
@@ -296,7 +315,7 @@ public:
ESecondarySkill num;
};
ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill)
ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill)
namespace EAlignment
{
@@ -384,7 +403,7 @@ public:
EBuildingID num;
};
ID_LIKE_OPERATORS_DECLS(BuildingID, BuildingID::EBuildingID)
ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID)
namespace EBuildingState
{
@@ -664,7 +683,7 @@ public:
EObj num;
};
ID_LIKE_OPERATORS_DECLS(Obj, Obj::EObj)
ID_LIKE_OPERATORS(Obj, Obj::EObj)
namespace SecSkillLevel
{
@@ -754,10 +773,29 @@ public:
std::string toString() const;
};
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType actionType);
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType);
ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType)
ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType)
class DLL_LINKAGE EPathfindingLayer
{
public:
enum EEPathfindingLayer : ui8
{
LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS, WRONG, AUTO
};
EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num)
{}
ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer)
EEPathfindingLayer num;
};
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer);
ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer)
class BFieldType
{
@@ -780,7 +818,7 @@ public:
EBFieldType num;
};
ID_LIKE_OPERATORS_DECLS(BFieldType, BFieldType::EBFieldType)
ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType)
namespace EPlayerStatus
{
@@ -820,7 +858,7 @@ public:
EArtifactPosition num;
};
ID_LIKE_OPERATORS_DECLS(ArtifactPosition, ArtifactPosition::EArtifactPosition)
ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition)
class ArtifactID
{
@@ -865,7 +903,7 @@ public:
EArtifactID num;
};
ID_LIKE_OPERATORS_DECLS(ArtifactID, ArtifactID::EArtifactID)
ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID)
class CreatureID
{
@@ -909,7 +947,7 @@ public:
ECreatureID num;
};
ID_LIKE_OPERATORS_DECLS(CreatureID, CreatureID::ECreatureID)
ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID)
class SpellID
{
@@ -953,7 +991,7 @@ public:
ESpellID num;
};
ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
enum class ESpellSchool: ui8
{
@@ -963,8 +1001,6 @@ enum class ESpellSchool: ui8
EARTH = 3
};
ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
// Typedef declarations
typedef ui8 TFaction;
typedef si64 TExpType;
@@ -975,8 +1011,8 @@ typedef si32 TQuantity;
typedef int TRmgTemplateZoneId;
#undef ID_LIKE_CLASS_COMMON
#undef ID_LIKE_OPERATORS_DECLS
#undef ID_LIKE_OPERATORS_INTERNAL_DECLS
#undef ID_LIKE_OPERATORS
#undef ID_LIKE_OPERATORS_INTERNAL
#undef INSTID_LIKE_CLASS_COMMON
#undef OP_DECL_INT

View File

@@ -56,7 +56,7 @@ static int lowestSpeed(const CGHeroInstance * chi)
return ret;
}
ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const
ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const
{
unsigned ret = GameConstants::BASE_MOVEMENT_COST;
@@ -80,30 +80,40 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro
break;
}
}
else if(!hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType))
{
// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
// This is clearly bug in H3 however intended behaviour is not clear.
// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI
// will always have best penalty without any influence from player-defined stacks order
for(auto stack : stacks)
{
int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain;
if(nativeTerrain != -1 && nativeTerrain != from.terType)
{
ret = VLC->heroh->terrCosts[from.terType];
ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25;
if(ret < GameConstants::BASE_MOVEMENT_COST)
ret = GameConstants::BASE_MOVEMENT_COST;
break;
}
}
ret = VLC->heroh->terrCosts[from.terType];
ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25;
if(ret < GameConstants::BASE_MOVEMENT_COST)
ret = GameConstants::BASE_MOVEMENT_COST;
}
return ret;
}
int CGHeroInstance::getNativeTerrain() const
{
// NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army.
// This is clearly bug in H3 however intended behaviour is not clear.
// Current VCMI behaviour will ignore neutrals in calculations so army in VCMI
// will always have best penalty without any influence from player-defined stacks order
// TODO: What should we do if all hero stacks are neutral creatures?
int nativeTerrain = -1;
for(auto stack : stacks)
{
int stackNativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain;
if(stackNativeTerrain == -1)
continue;
if(nativeTerrain == -1)
nativeTerrain = stackNativeTerrain;
else if(nativeTerrain != stackNativeTerrain)
return -1;
}
return nativeTerrain;
}
int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
{
if (toh3m)
@@ -129,16 +139,6 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o
}
}
bool CGHeroInstance::canFly() const
{
return hasBonusOfType(Bonus::FLYING_MOVEMENT);
}
bool CGHeroInstance::canWalkOnSea() const
{
return hasBonusOfType(Bonus::WATER_WALKING);
}
ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const
{
for(auto & elem : secSkills)
@@ -181,8 +181,11 @@ bool CGHeroInstance::canLearnSkill() const
return secSkills.size() < GameConstants::SKILL_PER_HERO;
}
int CGHeroInstance::maxMovePoints(bool onLand) const
int CGHeroInstance::maxMovePoints(bool onLand, const TurnInfo * ti) const
{
if(!ti)
ti = new TurnInfo(this);
int base;
if(onLand)
@@ -201,10 +204,10 @@ int CGHeroInstance::maxMovePoints(bool onLand) const
}
const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT;
const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt);
const int bonus = ti->valOfBonuses(Bonus::MOVEMENT) + ti->valOfBonuses(bt);
const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION;
const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0;
const double modifier = ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0;
return int(base* (1+modifier)) + bonus;
}
@@ -1171,10 +1174,15 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
return CArmedInstance::whereShouldBeAttached(gs);
}
int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const
int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/, const TurnInfo * ti) const
{
if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
return (MPsBefore - basicCost) * static_cast<double>(maxMovePoints(disembark)) / maxMovePoints(!disembark);
if(!ti)
ti = new TurnInfo(this);
int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
return (MPsBefore - basicCost) * static_cast<double>(mp1) / mp2;
return 0; //take all MPs otherwise
}

View File

@@ -21,6 +21,7 @@ class CHero;
class CGBoat;
class CGTownInstance;
struct TerrainTile;
struct TurnInfo;
class CGHeroPlaceholder : public CGObjectInstance
{
@@ -129,12 +130,11 @@ public:
EAlignment::EAlignment getAlignment() const;
const std::string &getBiography() const;
bool needsLastStack()const override;
ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
int getNativeTerrain() const;
ui32 getLowestCreatureSpeed() const;
int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation'
si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
bool canFly() const;
bool canWalkOnSea() const;
int getCurrentLuck(int stack=-1, bool town=false) const;
int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored
@@ -161,8 +161,8 @@ public:
void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value
void levelUp(std::vector<SecondarySkill> skills);
int maxMovePoints(bool onLand) const;
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
int maxMovePoints(bool onLand, const TurnInfo * ti = nullptr) const;
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const;
static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest
double getFightingStrength() const; // takes attack / defense skill into account

View File

@@ -132,11 +132,13 @@ Obj TerrainTile::topVisitableId(bool excludeTop) const
CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
{
auto visitableObj = visitableObjects;
if(excludeTop && visitableObj.size())
visitableObj.pop_back();
if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1))
return nullptr;
return visitableObj.size() ? visitableObj.back() : nullptr;
if(excludeTop)
return visitableObjects[visitableObjects.size()-2];
return visitableObjects.back();
}
bool TerrainTile::isCoastal() const