mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Merged vcmi/beta with vcmi/develop
This commit is contained in:
commit
246281e62a
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report an issue to help us improve
|
||||
title: ''
|
||||
labels: ["bug"]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Game logs**
|
||||
Please attach game logs: `VCMI_client.txt`, `VCMI_server.txt` etc.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Actual behavior**
|
||||
A clear description what is currently happening
|
||||
|
||||
**Did it work earlier?**
|
||||
If this something which worked well some time ago, please let us know about version where it works or at date when it worked.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version**
|
||||
- OS: [e.g. Windows, macOS Intel, macOS ARM, Android, Linux, iOS]
|
||||
- Version: [VCMI version]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an improvement
|
||||
title: ''
|
||||
labels: ["enhancement"]
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe your proposal**
|
||||
Give us as many as possible details about your idea.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
1
.github/workflows/github.yml
vendored
1
.github/workflows/github.yml
vendored
@ -173,6 +173,7 @@ jobs:
|
||||
../.. -GNinja \
|
||||
${{matrix.cmake_args}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
|
||||
-DENABLE_TEST=${{matrix.test}} \
|
||||
-DENABLE_STRICT_COMPILATION=ON \
|
||||
-DPACKAGE_NAME_SUFFIX:STRING="$VCMI_PACKAGE_NAME_SUFFIX" \
|
||||
-DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME" \
|
||||
-DENABLE_GITVERSION="$VCMI_PACKAGE_GITVERSION"
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -60,3 +60,6 @@ CMakeUserPresets.json
|
||||
/AI/FuzzyLite.lib
|
||||
/deps
|
||||
.vs/
|
||||
|
||||
# CLion
|
||||
.idea/
|
||||
|
@ -156,7 +156,6 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
|
||||
|
||||
TDmgRange retaliation(0, 0);
|
||||
auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
|
||||
TDmgRange defenderDamageBeforeAttack = state.battleEstimateDamage(BattleAttackInfo(u, attacker, u->canShoot()));
|
||||
|
||||
vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
|
||||
|
@ -79,7 +79,7 @@ CBattleAI::~CBattleAI()
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
{
|
||||
setCbc(CB);
|
||||
env = ENV;
|
||||
@ -186,7 +186,6 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||
|
||||
if(evaluationResult.score > score)
|
||||
{
|
||||
auto & target = bestAttack;
|
||||
score = evaluationResult.score;
|
||||
std::string action;
|
||||
|
||||
|
@ -65,7 +65,7 @@ public:
|
||||
CBattleAI();
|
||||
~CBattleAI();
|
||||
|
||||
void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||
void attemptCastingSpell();
|
||||
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
@ -80,7 +80,7 @@ public:
|
||||
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
||||
//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||
//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
|
||||
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
|
||||
//void battleEnd(const BattleResult *br) override;
|
||||
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
||||
//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
|
||||
|
@ -215,8 +215,6 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
int64_t stackScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
|
||||
std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
|
||||
auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
|
||||
{
|
||||
|
@ -40,4 +40,6 @@ add_subdirectory(BattleAI)
|
||||
add_subdirectory(StupidAI)
|
||||
add_subdirectory(EmptyAI)
|
||||
add_subdirectory(VCAI)
|
||||
add_subdirectory(Nullkiller)
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
add_subdirectory(Nullkiller)
|
||||
endif()
|
||||
|
@ -20,7 +20,7 @@ void CEmptyAI::loadGame(BinaryDeserializer & h, const int version)
|
||||
{
|
||||
}
|
||||
|
||||
void CEmptyAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
void CEmptyAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
{
|
||||
cb = CB;
|
||||
env = ENV;
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
virtual void saveGame(BinarySerializer & h, const int version) override;
|
||||
virtual void loadGame(BinaryDeserializer & h, const int version) override;
|
||||
|
||||
void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void yourTurn() override;
|
||||
void heroGotLevel(const CGHeroInstance *hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
|
||||
void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
|
||||
|
@ -4,4 +4,6 @@
|
||||
|
||||
// This header should be treated as a pre compiled header file(PCH) in the compiler building settings.
|
||||
|
||||
// Here you can add specific libraries and macros which are specific to this project.
|
||||
// Here you can add specific libraries and macros which are specific to this project.
|
||||
|
||||
VCMI_LIB_USING_NAMESPACE
|
||||
|
@ -28,8 +28,8 @@ namespace NKAI
|
||||
{
|
||||
|
||||
// our to enemy strength ratio constants
|
||||
const float SAFE_ATTACK_CONSTANT = 1.2;
|
||||
const float RETREAT_THRESHOLD = 0.3;
|
||||
const float SAFE_ATTACK_CONSTANT = 1.2f;
|
||||
const float RETREAT_THRESHOLD = 0.3f;
|
||||
const double RETREAT_ABSOLUTE_THRESHOLD = 10000.;
|
||||
|
||||
//one thread may be turn of AI and another will be handling a side effect for AI2
|
||||
@ -92,8 +92,9 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
|
||||
validateObject(details.id); //enemy hero may have left visible area
|
||||
auto hero = cb->getHero(details.id);
|
||||
|
||||
const int3 from = CGHeroInstance::convertPosition(details.start, false);
|
||||
const int3 to = CGHeroInstance::convertPosition(details.end, false);
|
||||
const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
|
||||
const int3 to = hero ? hero->convertToVisitablePos(details.end) : (details.end - int3(0,1,0));
|
||||
|
||||
const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
|
||||
const CGObjectInstance * o2 = vstd::frontOrNull(cb->getVisitableObjs(to, verbose));
|
||||
|
||||
@ -514,7 +515,7 @@ boost::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(
|
||||
}
|
||||
|
||||
|
||||
void AIGateway::init(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB)
|
||||
void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
myCb = CB;
|
||||
@ -535,8 +536,7 @@ void AIGateway::yourTurn()
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
status.startedTurn();
|
||||
|
||||
makingTurn = make_unique<boost::thread>(&AIGateway::makeTurn, this);
|
||||
makingTurn = std::make_unique<boost::thread>(&AIGateway::makeTurn, this);
|
||||
}
|
||||
|
||||
void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
|
||||
@ -595,7 +595,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
|
||||
logAi->trace("Guarded object query hook: %s by %s danger ratio %f", target.toString(), hero.name, ratio);
|
||||
|
||||
if(text.find("guarded") >= 0 && (dangerUnknown || dangerTooHigh))
|
||||
if(text.find("guarded") != std::string::npos && (dangerUnknown || dangerTooHigh))
|
||||
answer = 0; // no
|
||||
}
|
||||
}
|
||||
@ -732,7 +732,7 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
|
||||
if(const CStackInstance * s = obj->getStackPtr(SlotID(i)))
|
||||
{
|
||||
UpgradeInfo ui;
|
||||
myCb->getUpgradeInfo(obj, SlotID(i), ui);
|
||||
myCb->fillUpgradeInfo(obj, SlotID(i), ui);
|
||||
if(ui.oldID >= 0 && nullkiller->getFreeResources().canAfford(ui.cost[0] * s->count))
|
||||
{
|
||||
myCb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
|
||||
@ -1179,7 +1179,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
{
|
||||
//FIXME: this assertion fails also if AI moves onto defeated guarded object
|
||||
assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object
|
||||
cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true));
|
||||
cb->moveHero(*h, h->convertFromVisitablePos(dst));
|
||||
afterMovementCheck(); // TODO: is it feasible to hero get killed there if game work properly?
|
||||
// If revisiting, teleport probing is never done, and so the entries into the list would remain unused and uncleared
|
||||
teleportChannelProbingList.clear();
|
||||
@ -1233,14 +1233,14 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
auto doMovement = [&](int3 dst, bool transit)
|
||||
{
|
||||
cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit);
|
||||
cb->moveHero(*h, h->convertFromVisitablePos(dst), transit);
|
||||
};
|
||||
|
||||
auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos)
|
||||
{
|
||||
destinationTeleport = exitId;
|
||||
if(exitPos.valid())
|
||||
destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true);
|
||||
destinationTeleportPos = h->convertFromVisitablePos(exitPos);
|
||||
cb->moveHero(*h, h->pos);
|
||||
destinationTeleport = ObjectInstanceID();
|
||||
destinationTeleportPos = int3(-1);
|
||||
@ -1249,7 +1249,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
auto doChannelProbing = [&]() -> void
|
||||
{
|
||||
auto currentPos = CGHeroInstance::convertPosition(h->pos, false);
|
||||
auto currentPos = h->visitablePos();
|
||||
auto currentExit = getObj(currentPos, true)->id;
|
||||
|
||||
status.setChannelProbing(true);
|
||||
@ -1266,7 +1266,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
int3 currentCoord = path.nodes[i].coord;
|
||||
int3 nextCoord = path.nodes[i - 1].coord;
|
||||
|
||||
auto currentObject = getObj(currentCoord, currentCoord == CGHeroInstance::convertPosition(h->pos, false));
|
||||
auto currentObject = getObj(currentCoord, currentCoord == h->visitablePos());
|
||||
auto nextObjectTop = getObj(nextCoord, false);
|
||||
auto nextObject = getObj(nextCoord, true);
|
||||
auto destTeleportObj = getDestTeleportObj(currentObject, nextObjectTop, nextObject);
|
||||
|
@ -110,7 +110,7 @@ public:
|
||||
|
||||
std::string getBattleAIName() const override;
|
||||
|
||||
void init(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB) override;
|
||||
void initGameInterface(std::shared_ptr<Environment> env, std::shared_ptr<CCallback> CB) override;
|
||||
void yourTurn() override;
|
||||
|
||||
void heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID) override; //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id
|
||||
|
@ -329,7 +329,7 @@ public:
|
||||
|
||||
if(!poolIsEmpty) pool.pop_back();
|
||||
|
||||
return std::move(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
|
@ -31,7 +31,7 @@ engineBase::engineBase()
|
||||
void engineBase::configure()
|
||||
{
|
||||
engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "Proportional");
|
||||
logAi->info(engine.toString());
|
||||
logAi->trace(engine.toString());
|
||||
}
|
||||
|
||||
void engineBase::addRule(const std::string & txt)
|
||||
|
@ -122,10 +122,8 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
case Obj::RESOURCE:
|
||||
{
|
||||
if(!vstd::contains(ai->memory->alreadyVisited, obj))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// passthrough
|
||||
FALLTHROUGH;
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
case Obj::HERO:
|
||||
|
@ -50,7 +50,7 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
new SharedPool<PriorityEvaluator>(
|
||||
[&]()->std::unique_ptr<PriorityEvaluator>
|
||||
{
|
||||
return make_unique<PriorityEvaluator>(this);
|
||||
return std::make_unique<PriorityEvaluator>(this);
|
||||
}));
|
||||
|
||||
dangerHitMap.reset(new DangerHitMapAnalyzer(this));
|
||||
|
@ -127,7 +127,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch(cannotFulfillGoalException)
|
||||
catch(const cannotFulfillGoalException &)
|
||||
{
|
||||
if(!heroPtr.validAndSet())
|
||||
{
|
||||
@ -173,7 +173,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
|
||||
blockedIndexes.insert(node.parentIndex);
|
||||
}
|
||||
catch(goalFulfilledException)
|
||||
catch(const goalFulfilledException &)
|
||||
{
|
||||
if(!heroPtr.validAndSet())
|
||||
{
|
||||
|
@ -401,6 +401,9 @@ public:
|
||||
|
||||
void execute(const blocked_range<size_t>& r)
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 randomEngine(randomDevice());
|
||||
|
||||
for(int i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto & pos = tiles[i];
|
||||
@ -422,7 +425,7 @@ public:
|
||||
existingChains.push_back(&node);
|
||||
}
|
||||
|
||||
std::random_shuffle(existingChains.begin(), existingChains.end());
|
||||
std::shuffle(existingChains.begin(), existingChains.end(), randomEngine);
|
||||
|
||||
for(AIPathNode * node : existingChains)
|
||||
{
|
||||
@ -480,6 +483,9 @@ public:
|
||||
|
||||
bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 randomEngine(randomDevice());
|
||||
|
||||
heroChainPass = EHeroChainPass::CHAIN;
|
||||
heroChain.clear();
|
||||
|
||||
@ -489,7 +495,7 @@ bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
boost::mutex resultMutex;
|
||||
|
||||
std::random_shuffle(data.begin(), data.end());
|
||||
std::shuffle(data.begin(), data.end(), randomEngine);
|
||||
|
||||
parallel_for(blocked_range<size_t>(0, data.size()), [&](const blocked_range<size_t>& r)
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ CStupidAI::~CStupidAI()
|
||||
print("destroyed");
|
||||
}
|
||||
|
||||
void CStupidAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
{
|
||||
print("init called, saving ptr to IBattleCallback");
|
||||
env = ENV;
|
||||
@ -177,7 +177,7 @@ void CStupidAI::battleAttack(const BattleAttack *ba)
|
||||
print("battleAttack called");
|
||||
}
|
||||
|
||||
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
|
||||
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
|
||||
{
|
||||
print("battleStacksAttacked called");
|
||||
}
|
||||
@ -202,7 +202,7 @@ void CStupidAI::battleNewRound(int round)
|
||||
print("battleNewRound called");
|
||||
}
|
||||
|
||||
void CStupidAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
|
||||
void CStupidAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
|
||||
{
|
||||
print("battleStackMoved called");
|
||||
}
|
||||
|
@ -25,18 +25,18 @@ public:
|
||||
CStupidAI();
|
||||
~CStupidAI();
|
||||
|
||||
void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||
void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
|
||||
void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
||||
void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
|
||||
|
||||
void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
|
||||
void battleEnd(const BattleResult *br) override;
|
||||
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
||||
void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
|
||||
void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
|
||||
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
|
||||
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
|
||||
void battleSpellCast(const BattleSpellCast *sc) override;
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
|
||||
|
@ -30,7 +30,7 @@ engineBase::engineBase()
|
||||
void engineBase::configure()
|
||||
{
|
||||
engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "Proportional");
|
||||
logAi->info(engine.toString());
|
||||
logAi->trace(engine.toString());
|
||||
}
|
||||
|
||||
void engineBase::addRule(const std::string & txt)
|
||||
|
@ -50,7 +50,7 @@ namespace Goals
|
||||
sightRadius = hero->getSightRadius();
|
||||
bestGoal = sptr(Goals::Invalid());
|
||||
bestValue = 0;
|
||||
ourPos = h->convertPosition(h->pos, false);
|
||||
ourPos = h->visitablePos();
|
||||
allowDeadEndCancellation = true;
|
||||
allowGatherArmy = gatherArmy;
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, c
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
{
|
||||
auto hpos = hero->getPosition(false);
|
||||
auto hpos = hero->visitablePos();
|
||||
auto initialNode =
|
||||
getOrCreateNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, NORMAL_CHAIN)
|
||||
.get();
|
||||
@ -211,7 +211,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
}
|
||||
}
|
||||
|
||||
if(hero->getPosition(false) == source.coord)
|
||||
if(hero->visitablePos() == source.coord)
|
||||
{
|
||||
calculateTownPortalTeleportations(source, neighbours);
|
||||
}
|
||||
|
@ -98,11 +98,13 @@ void VCAI::heroMoved(const TryMoveHero & details, bool verbose)
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
|
||||
validateObject(details.id); //enemy hero may have left visible area
|
||||
//enemy hero may have left visible area
|
||||
validateObject(details.id);
|
||||
auto hero = cb->getHero(details.id);
|
||||
|
||||
const int3 from = CGHeroInstance::convertPosition(details.start, false);
|
||||
const int3 to = CGHeroInstance::convertPosition(details.end, false);
|
||||
const int3 from = hero ? hero->convertToVisitablePos(details.start) : (details.start - int3(0,1,0));;
|
||||
const int3 to = hero ? hero->convertToVisitablePos(details.end) : (details.end - int3(0,1,0));
|
||||
|
||||
const CGObjectInstance * o1 = vstd::frontOrNull(cb->getVisitableObjs(from, verbose));
|
||||
const CGObjectInstance * o2 = vstd::frontOrNull(cb->getVisitableObjs(to, verbose));
|
||||
|
||||
@ -583,7 +585,7 @@ void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions)
|
||||
NET_EVENT_HANDLER;
|
||||
}
|
||||
|
||||
void VCAI::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
void VCAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
env = ENV;
|
||||
@ -608,7 +610,7 @@ void VCAI::yourTurn()
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
status.startedTurn();
|
||||
makingTurn = make_unique<boost::thread>(&VCAI::makeTurn, this);
|
||||
makingTurn = std::make_unique<boost::thread>(&VCAI::makeTurn, this);
|
||||
}
|
||||
|
||||
void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
|
||||
@ -756,7 +758,7 @@ void makePossibleUpgrades(const CArmedInstance * obj)
|
||||
if(const CStackInstance * s = obj->getStackPtr(SlotID(i)))
|
||||
{
|
||||
UpgradeInfo ui;
|
||||
cb->getUpgradeInfo(obj, SlotID(i), ui);
|
||||
cb->fillUpgradeInfo(obj, SlotID(i), ui);
|
||||
if(ui.oldID >= 0 && cb->getResourceAmount().canAfford(ui.cost[0] * s->count))
|
||||
{
|
||||
cb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
|
||||
@ -1813,7 +1815,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
{
|
||||
//FIXME: this assertion fails also if AI moves onto defeated guarded object
|
||||
assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object
|
||||
cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true));
|
||||
cb->moveHero(*h, h->convertFromVisitablePos(dst));
|
||||
afterMovementCheck(); // TODO: is it feasible to hero get killed there if game work properly?
|
||||
// If revisiting, teleport probing is never done, and so the entries into the list would remain unused and uncleared
|
||||
teleportChannelProbingList.clear();
|
||||
@ -1867,14 +1869,14 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
auto doMovement = [&](int3 dst, bool transit)
|
||||
{
|
||||
cb->moveHero(*h, CGHeroInstance::convertPosition(dst, true), transit);
|
||||
cb->moveHero(*h, h->convertFromVisitablePos(dst), transit);
|
||||
};
|
||||
|
||||
auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos)
|
||||
{
|
||||
destinationTeleport = exitId;
|
||||
if(exitPos.valid())
|
||||
destinationTeleportPos = CGHeroInstance::convertPosition(exitPos, true);
|
||||
destinationTeleportPos = h->convertFromVisitablePos(exitPos);
|
||||
cb->moveHero(*h, h->pos);
|
||||
destinationTeleport = ObjectInstanceID();
|
||||
destinationTeleportPos = int3(-1);
|
||||
@ -1883,7 +1885,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
|
||||
auto doChannelProbing = [&]() -> void
|
||||
{
|
||||
auto currentPos = CGHeroInstance::convertPosition(h->pos, false);
|
||||
auto currentPos = h->visitablePos();
|
||||
auto currentExit = getObj(currentPos, true)->id;
|
||||
|
||||
status.setChannelProbing(true);
|
||||
@ -1900,7 +1902,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
int3 currentCoord = path.nodes[i].coord;
|
||||
int3 nextCoord = path.nodes[i - 1].coord;
|
||||
|
||||
auto currentObject = getObj(currentCoord, currentCoord == CGHeroInstance::convertPosition(h->pos, false));
|
||||
auto currentObject = getObj(currentCoord, currentCoord == h->visitablePos());
|
||||
auto nextObjectTop = getObj(nextCoord, false);
|
||||
auto nextObject = getObj(nextCoord, true);
|
||||
auto destTeleportObj = getDestTeleportObj(currentObject, nextObjectTop, nextObject);
|
||||
|
@ -143,7 +143,7 @@ public:
|
||||
|
||||
std::string getBattleAIName() const override;
|
||||
|
||||
void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void yourTurn() override;
|
||||
|
||||
void heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID) override; //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id
|
||||
|
@ -335,11 +335,6 @@ int3 CCallback::getGuardingCreaturePosition(int3 tile)
|
||||
return gs->map->guardingCreaturePositions[tile.z][tile.x][tile.y];
|
||||
}
|
||||
|
||||
void CCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out)
|
||||
{
|
||||
gs->calculatePaths(hero, out);
|
||||
}
|
||||
|
||||
void CCallback::dig( const CGObjectInstance *hero )
|
||||
{
|
||||
DigWithHero dwh;
|
||||
@ -400,4 +395,4 @@ boost::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(
|
||||
const BattleStateInfoForRetreat & battleState)
|
||||
{
|
||||
return cl->playerint[getPlayerID().get()]->makeSurrenderRetreatDecision(battleState);
|
||||
}
|
||||
}
|
||||
|
@ -133,8 +133,6 @@ public:
|
||||
virtual int3 getGuardingCreaturePosition(int3 tile);
|
||||
virtual std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
|
||||
|
||||
virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);
|
||||
|
||||
//Set of metrhods that allows adding more interfaces for this player that'll receive game event call-ins.
|
||||
void registerBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
|
||||
void unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
|
||||
|
@ -8,4 +8,4 @@ sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf
|
||||
sudo apt-get install qtbase5-dev
|
||||
sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev
|
||||
# Optional dependencies
|
||||
sudo apt-get install libminizip-dev libfuzzylite-dev
|
||||
sudo apt-get install libminizip-dev libfuzzylite-dev qttools5-dev
|
||||
|
@ -1,5 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
# steps to upgrade MXE dependencies:
|
||||
# 1) Use Debian/Ubuntu system or install one (virtual machines will work too)
|
||||
# 2) update following script to include any new dependencies
|
||||
# You can also run it to upgrade existing ones, but don't expect much
|
||||
# MXE repository only provides ancient versions for the sake of "stability"
|
||||
# https://github.com/vcmi/vcmi-deps-mxe/blob/master/mirror-mxe.sh
|
||||
# 3) make release in vcmi-deps-mxe repository using resulting tar archive
|
||||
# 4) update paths to tar archive in this script
|
||||
|
||||
# Install nsis for installer creation
|
||||
sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security main'
|
||||
sudo apt-get install -qq nsis ninja-build libssl1.0.0
|
||||
|
127
CMakeLists.txt
127
CMakeLists.txt
@ -33,10 +33,6 @@ if(APPLE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE_IOS)
|
||||
set(BUILD_SINGLE_APP 1)
|
||||
endif()
|
||||
|
||||
############################################
|
||||
# User-provided options #
|
||||
############################################
|
||||
@ -52,6 +48,9 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
|
||||
option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF)
|
||||
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
|
||||
option(ENABLE_EDITOR "Enable compilation of map editor" ON)
|
||||
option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON)
|
||||
option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON)
|
||||
|
||||
if(APPLE_IOS)
|
||||
set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
|
||||
set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
|
||||
@ -63,7 +62,10 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||
endif(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||
option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
|
||||
option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON)
|
||||
option(ENABLE_STRICT_COMPILATION "Treat all compiler warnings as errors" OFF)
|
||||
option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON)
|
||||
option(ENABLE_SINGLE_APP_BUILD "Builds client and server as single executable" OFF)
|
||||
option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON)
|
||||
|
||||
# Used for Snap packages and also useful for debugging
|
||||
if(NOT APPLE_IOS)
|
||||
@ -74,11 +76,20 @@ endif()
|
||||
set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name")
|
||||
set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
|
||||
|
||||
if(APPLE_IOS AND NOT ENABLE_SINGLE_APP_BUILD)
|
||||
set(ENABLE_SINGLE_APP_BUILD ON)
|
||||
endif()
|
||||
|
||||
# ERM depends on LUA implicitly
|
||||
if(ENABLE_ERM AND NOT ENABLE_LUA)
|
||||
set(ENABLE_LUA ON)
|
||||
endif()
|
||||
|
||||
# We don't want to deploy assets into build directory for iOS build
|
||||
if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
|
||||
set(COPY_CONFIG_ON_BUILD OFF)
|
||||
endif()
|
||||
|
||||
############################################
|
||||
# Miscellaneous options #
|
||||
############################################
|
||||
@ -118,14 +129,22 @@ else()
|
||||
endif(ENABLE_GITVERSION)
|
||||
|
||||
# Precompiled header configuration
|
||||
if(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0 )
|
||||
set(ENABLE_PCH OFF) # broken
|
||||
endif()
|
||||
|
||||
if( ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||
set(ENABLE_PCH OFF) #not supported
|
||||
endif()
|
||||
|
||||
if(ENABLE_PCH)
|
||||
macro(enable_pch name)
|
||||
target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:<StdInc.h$<ANGLE-R>>)
|
||||
endmacro(enable_pch)
|
||||
else(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||
else()
|
||||
macro(enable_pch ignore)
|
||||
endmacro(enable_pch)
|
||||
endif(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||
endif()
|
||||
|
||||
############################################
|
||||
# Documentation section #
|
||||
@ -162,8 +181,8 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
|
||||
|
||||
if(BUILD_SINGLE_APP)
|
||||
add_compile_definitions(SINGLE_PROCESS_APP=1)
|
||||
if(ENABLE_SINGLE_APP_BUILD)
|
||||
add_definitions(-DSINGLE_PROCESS_APP=1)
|
||||
endif()
|
||||
|
||||
if(APPLE_IOS)
|
||||
@ -203,10 +222,18 @@ if(MINGW OR MSVC)
|
||||
# Suppress warnings
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
add_definitions(-D_SCL_SECURE_NO_WARNINGS)
|
||||
# 4250: 'class1' : inherits 'class2::member' via dominance
|
||||
# 4251: class 'xxx' needs to have dll-interface to be used by clients of class 'yyy'
|
||||
# 4275: non dll-interface class 'xxx' used as base for dll-interface class
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj /wd4250 /wd4251 /wd4275")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4250") # 4250: 'class1' : inherits 'class2::member' via dominance
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # 4251: class 'xxx' needs to have dll-interface to be used by clients of class 'yyy'
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244") # 4244: conversion from 'xxx' to 'yyy', possible loss of data
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") # 4267: conversion from 'xxx' to 'yyy', possible loss of data
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4275") # 4275: non dll-interface class 'xxx' used as base for dll-interface class
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss
|
||||
|
||||
if(ENABLE_STRICT_COMPILATION)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wx") # Treats all compiler warnings as errors
|
||||
endif()
|
||||
|
||||
if(ENABLE_MULTI_PROCESS_BUILDS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
@ -241,15 +268,30 @@ if(MINGW OR MSVC)
|
||||
endif(MINGW)
|
||||
endif(MINGW OR MSVC)
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR NOT WIN32) #so far all *nix compilers support such parameters
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpointer-arith -Wuninitialized")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing -Wno-switch -Wno-sign-compare -Wno-unused-local-typedefs")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-overloaded-virtual -Wno-type-limits -Wno-unknown-pragmas")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-varargs") # fuzzylite - Operation.h
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR NOT WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpointer-arith")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wuninitialized")
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-mismatched-tags -Wno-unknown-warning-option -Wno-missing-braces")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0 OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" )
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmismatched-tags")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter") # low chance of valid reports, a lot of emitted warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-switch") # large number of false-positives, disabled
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder") # large number of noise, low chance of any significant issues
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-compare") # low chance of any significant issues
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-varargs") # emitted in fuzzylite headers, disabled
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") # emitted only by ancient gcc 5.5 in MXE build, remove after upgrade
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-variable") # emitted only by ancient gcc 5.5 in MXE build, remove after upgrade
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-maybe-uninitialized") # emitted only by ancient gcc 5.5 in MXE build, remove after upgrade
|
||||
endif()
|
||||
|
||||
if(ENABLE_STRICT_COMPILATION)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=array-bounds") # false positives in boost::multiarray during release build, keep as warning-only
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
@ -270,7 +312,7 @@ if(NOT WIN32 AND NOT APPLE_IOS)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LUA)
|
||||
add_compile_definitions(SCRIPTING_ENABLED=1)
|
||||
add_definitions(-DSCRIPTING_ENABLED=1)
|
||||
endif()
|
||||
|
||||
############################################
|
||||
@ -312,12 +354,24 @@ find_package(SDL2_ttf REQUIRED)
|
||||
if(TARGET SDL2_ttf::SDL2_ttf)
|
||||
add_library(SDL2::TTF ALIAS SDL2_ttf::SDL2_ttf)
|
||||
endif()
|
||||
find_package(TBB REQUIRED)
|
||||
|
||||
if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
# Widgets finds its own dependencies (QtGui and QtCore).
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools)
|
||||
if(NOT Qt${QT_VERSION_MAJOR}LinguistTools_DIR)
|
||||
set(ENABLE_TRANSLATIONS OFF)
|
||||
endif()
|
||||
if(ENABLE_TRANSLATIONS)
|
||||
add_definitions(-DENABLE_QT_TRANSLATIONS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
find_package(TBB REQUIRED)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LUA)
|
||||
@ -385,6 +439,12 @@ else()
|
||||
set(BIN_DIR "." CACHE STRING "Where to install binaries")
|
||||
set(LIB_DIR "." CACHE STRING "Where to install main library")
|
||||
set(DATA_DIR "." CACHE STRING "Where to install data files")
|
||||
|
||||
# following constants only used for platforms using XDG (Linux, BSD, etc)
|
||||
add_definitions(-DM_DATA_DIR="${DATA_DIR}")
|
||||
add_definitions(-DM_BIN_DIR="${BIN_DIR}")
|
||||
add_definitions(-DM_LIB_DIR="${LIB_DIR}")
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN/")
|
||||
else()
|
||||
if(NOT BIN_DIR)
|
||||
@ -396,14 +456,14 @@ else()
|
||||
if(NOT DATA_DIR)
|
||||
set(DATA_DIR "${CMAKE_INSTALL_DATAROOTDIR}/vcmi" CACHE STRING "Where to install data files")
|
||||
endif()
|
||||
|
||||
# following constants only used for platforms using XDG (Linux, BSD, etc)
|
||||
add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
|
||||
add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
|
||||
add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
endif()
|
||||
|
||||
|
||||
# following constants only used for platforms using XDG (Linux, BSD, etc)
|
||||
add_definitions(-DM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/${DATA_DIR}")
|
||||
add_definitions(-DM_BIN_DIR="${CMAKE_INSTALL_PREFIX}/${BIN_DIR}")
|
||||
add_definitions(-DM_LIB_DIR="${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
endif()
|
||||
|
||||
# iOS has flat libs directory structure
|
||||
@ -424,13 +484,10 @@ if(APPLE_IOS)
|
||||
endif()
|
||||
|
||||
include(VCMI_lib)
|
||||
if(BUILD_SINGLE_APP)
|
||||
add_subdirectory(lib_client)
|
||||
add_subdirectory(lib)
|
||||
set(VCMI_LIB_TARGET vcmi)
|
||||
if(ENABLE_SINGLE_APP_BUILD)
|
||||
add_subdirectory(lib_server)
|
||||
set(VCMI_LIB_TARGET vcmi_lib_client)
|
||||
else()
|
||||
add_subdirectory(lib)
|
||||
set(VCMI_LIB_TARGET vcmi)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ERM)
|
||||
|
@ -24,6 +24,7 @@
|
||||
"PACKAGE_NAME_SUFFIX" : "$env{VCMI_PACKAGE_NAME_SUFFIX}",
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"ENABLE_TEST": "OFF",
|
||||
"ENABLE_STRICT_COMPILATION": "ON",
|
||||
"ENABLE_GITVERSION": "$env{VCMI_PACKAGE_GITVERSION}"
|
||||
}
|
||||
},
|
||||
|
94
Global.h
94
Global.h
@ -15,25 +15,6 @@
|
||||
// Fixed width bool data type is important for serialization
|
||||
static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__)
|
||||
#endif
|
||||
|
||||
#if !defined(__clang__) && defined(__GNUC__) && (GCC_VERSION < 470)
|
||||
# error VCMI requires at least gcc-4.7.2 for successful compilation or clang-3.1. Please update your compiler
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && (GCC_VERSION == 470 || GCC_VERSION == 471)
|
||||
# error This GCC version has buggy std::array::at version and should not be used. Please update to 4.7.2 or later
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
/* Suppress some compiler warnings */
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning (disable : 4800 ) /* disable conversion to bool warning -- I think it's intended in all places */
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
/* System detection. */
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
@ -83,6 +64,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
#endif
|
||||
|
||||
// Each compiler uses own way to supress fall through warning. Try to find it.
|
||||
// TODO: replace with c++17 [[fallthrough]]
|
||||
#ifdef __has_cpp_attribute
|
||||
# if __has_cpp_attribute(fallthrough)
|
||||
# define FALLTHROUGH [[fallthrough]];
|
||||
@ -101,9 +83,15 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
/* Commonly used C++, Boost headers */
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
#ifdef VCMI_WINDOWS
|
||||
# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - delete this line if something is missing.
|
||||
# define NOMINMAX // Exclude min/max macros from <Windows.h>. Use std::[min/max] from <algorithm> instead.
|
||||
# define _NO_W32_PSEUDO_MODIFIERS // Exclude more macros for compiling with MinGW on Linux.
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - delete this line if something is missing.
|
||||
# endif
|
||||
# ifndef NOMINMAX
|
||||
# define NOMINMAX // Exclude min/max macros from <Windows.h>. Use std::[min/max] from <algorithm> instead.
|
||||
# endif
|
||||
# ifndef _NO_W32_PSEUDO_MODIFIERS
|
||||
# define _NO_W32_PSEUDO_MODIFIERS // Exclude more macros for compiling with MinGW on Linux.
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef VCMI_ANDROID
|
||||
@ -122,10 +110,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
# define STRONG_INLINE inline
|
||||
#endif
|
||||
|
||||
#define TO_STRING_HELPER(x) #x
|
||||
#define TO_STRING(x) TO_STRING_HELPER(x)
|
||||
#define LINE_IN_FILE __FILE__ ":" TO_STRING(__LINE__)
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <cstdio>
|
||||
@ -233,7 +217,10 @@ typedef boost::lock_guard<boost::recursive_mutex> TLockGuardRec;
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
// Import + Export macro declarations
|
||||
#ifdef VCMI_WINDOWS
|
||||
# ifdef __GNUC__
|
||||
#ifdef VCMI_DLL_STATIC
|
||||
# define DLL_IMPORT
|
||||
# define DLL_EXPORT
|
||||
#elif defined(__GNUC__)
|
||||
# define DLL_IMPORT __attribute__((dllimport))
|
||||
# define DLL_EXPORT __attribute__((dllexport))
|
||||
# else
|
||||
@ -262,7 +249,8 @@ template<typename T, size_t N> char (&_ArrayCountObj(const T (&)[N]))[N];
|
||||
#define ARRAY_COUNT(arr) (sizeof(_ArrayCountObj(arr)))
|
||||
|
||||
// should be used for variables that becomes unused in release builds (e.g. only used for assert checks)
|
||||
#define UNUSED(VAR) ((void)VAR)
|
||||
// TODO: replace with c++17 [[maybe_unused]]
|
||||
#define MAYBE_UNUSED(VAR) ((void)VAR)
|
||||
|
||||
// old iOS SDKs compatibility
|
||||
#ifdef VCMI_IOS
|
||||
@ -503,36 +491,6 @@ namespace vstd
|
||||
ptr = nullptr;
|
||||
}
|
||||
|
||||
#if _MSC_VER >= 1800
|
||||
using std::make_unique;
|
||||
#else
|
||||
template<typename T>
|
||||
std::unique_ptr<T> make_unique()
|
||||
{
|
||||
return std::unique_ptr<T>(new T());
|
||||
}
|
||||
template<typename T, typename Arg1>
|
||||
std::unique_ptr<T> make_unique(Arg1 &&arg1)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1)));
|
||||
}
|
||||
template<typename T, typename Arg1, typename Arg2>
|
||||
std::unique_ptr<T> make_unique(Arg1 &&arg1, Arg2 &&arg2)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2)));
|
||||
}
|
||||
template<typename T, typename Arg1, typename Arg2, typename Arg3>
|
||||
std::unique_ptr<T> make_unique(Arg1 &&arg1, Arg2 &&arg2, Arg3 &&arg3)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3)));
|
||||
}
|
||||
template<typename T, typename Arg1, typename Arg2, typename Arg3, typename Arg4>
|
||||
std::unique_ptr<T> make_unique(Arg1 &&arg1, Arg2 &&arg2, Arg3 &&arg3, Arg4 &&arg4)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4)));
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename Container>
|
||||
typename Container::const_reference circularAt(const Container &r, size_t index)
|
||||
{
|
||||
@ -543,6 +501,12 @@ namespace vstd
|
||||
return *itr;
|
||||
}
|
||||
|
||||
template <typename Container, typename Item>
|
||||
void erase(Container &c, const Item &item)
|
||||
{
|
||||
c.erase(boost::remove(c, item), c.end());
|
||||
}
|
||||
|
||||
template<typename Range, typename Predicate>
|
||||
void erase_if(Range &vec, Predicate pred)
|
||||
{
|
||||
@ -704,12 +668,6 @@ namespace vstd
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Container, typename Pred>
|
||||
void erase(Container &c, Pred pred)
|
||||
{
|
||||
c.erase(boost::remove_if(c, pred), c.end());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void removeDuplicates(std::vector<T> &vec)
|
||||
{
|
||||
@ -760,10 +718,16 @@ namespace vstd
|
||||
return v;
|
||||
}
|
||||
|
||||
//c++20 feature
|
||||
template<typename Arithmetic, typename Floating>
|
||||
Arithmetic lerp(const Arithmetic & a, const Arithmetic & b, const Floating & f)
|
||||
{
|
||||
return a + (b - a) * f;
|
||||
}
|
||||
|
||||
using boost::math::round;
|
||||
}
|
||||
using vstd::operator-=;
|
||||
using vstd::make_unique;
|
||||
|
||||
#ifdef NO_STD_TOSTRING
|
||||
namespace std
|
||||
|
@ -1,7 +1,8 @@
|
||||
[![GitHub](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg)](https://github.com/vcmi/vcmi/actions/workflows/github.yml)
|
||||
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/github/vcmi/vcmi?branch=develop&svg=true)](https://ci.appveyor.com/project/vcmi/vcmi)
|
||||
[![Coverity Scan Build Status](https://scan.coverity.com/projects/vcmi/badge.svg)](https://scan.coverity.com/projects/vcmi)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.0.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.0.0)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.1.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.1.0)
|
||||
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
|
||||
# VCMI Project
|
||||
VCMI is work-in-progress attempt to recreate engine for Heroes III, giving it new and extended possibilities.
|
||||
|
||||
|
@ -20,7 +20,7 @@ namespace BitmapHandler
|
||||
{
|
||||
SDL_Surface * loadH3PCX(ui8 * data, size_t size);
|
||||
|
||||
SDL_Surface * loadBitmapFromDir(std::string path, std::string fname, bool setKey=true);
|
||||
SDL_Surface * loadBitmapFromDir(std::string path, std::string fname);
|
||||
}
|
||||
|
||||
bool isPCX(const ui8 *header)//check whether file can be PCX according to header
|
||||
@ -102,7 +102,7 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
|
||||
return ret;
|
||||
}
|
||||
|
||||
SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fname, bool setKey)
|
||||
SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fname)
|
||||
{
|
||||
if(!fname.size())
|
||||
{
|
||||
@ -121,14 +121,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
|
||||
if (isPCX(readFile.first.get()))
|
||||
{//H3-style PCX
|
||||
ret = loadH3PCX(readFile.first.get(), readFile.second);
|
||||
if (ret)
|
||||
{
|
||||
if(ret->format->BytesPerPixel == 1 && setKey)
|
||||
{
|
||||
CSDL_Ext::setColorKey(ret,ret->format->palette->colors[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!ret)
|
||||
{
|
||||
logGlobal->error("Failed to open %s as H3 PCX!", fname);
|
||||
return nullptr;
|
||||
@ -144,7 +137,8 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
|
||||
{
|
||||
if (ret->format->palette)
|
||||
{
|
||||
//set correct value for alpha\unused channel
|
||||
// set correct value for alpha\unused channel
|
||||
// NOTE: might be unnecessary with SDL2
|
||||
for (int i=0; i < ret->format->palette->ncolors; i++)
|
||||
ret->format->palette->colors[i].a = SDL_ALPHA_OPAQUE;
|
||||
}
|
||||
@ -161,7 +155,26 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
|
||||
// 1) Vampire mansion in Necropolis (not 1st color is transparent)
|
||||
// 2) Battle background when fighting on grass/dirt, topmost sky part (NO transparent color)
|
||||
// 3) New objects that may use 24-bit images for icons (e.g. witchking arts)
|
||||
if (ret->format->palette)
|
||||
// 4) special case - there are 2 .bmp images that have semi-transparency (CCELLGRD.BMP & CCELLSHD.BMP)
|
||||
if (ret->format->palette &&
|
||||
ret->format->palette->colors[0].r == 255 &&
|
||||
ret->format->palette->colors[0].g == 0 &&
|
||||
ret->format->palette->colors[0].b == 255 )
|
||||
{
|
||||
static SDL_Color shadow[3] =
|
||||
{
|
||||
{ 0, 0, 0, 0},// 100% - transparency
|
||||
{ 0, 0, 0, 32},// 75% - shadow border,
|
||||
{ 0, 0, 0, 128},// 50% - shadow body
|
||||
};
|
||||
|
||||
CSDL_Ext::setColorKey(ret, ret->format->palette->colors[0]);
|
||||
|
||||
ret->format->palette->colors[0] = shadow[0];
|
||||
ret->format->palette->colors[1] = shadow[1];
|
||||
ret->format->palette->colors[4] = shadow[2];
|
||||
}
|
||||
else if (ret->format->palette)
|
||||
{
|
||||
CSDL_Ext::setDefaultColorKeyPresize(ret);
|
||||
}
|
||||
@ -173,15 +186,16 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
|
||||
{
|
||||
CSDL_Ext::setDefaultColorKey(ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SDL_Surface * BitmapHandler::loadBitmap(std::string fname, bool setKey)
|
||||
SDL_Surface * BitmapHandler::loadBitmap(std::string fname)
|
||||
{
|
||||
SDL_Surface * bitmap = nullptr;
|
||||
|
||||
if (!(bitmap = loadBitmapFromDir("DATA/", fname, setKey)) &&
|
||||
!(bitmap = loadBitmapFromDir("SPRITES/", fname, setKey)))
|
||||
if (!(bitmap = loadBitmapFromDir("DATA/", fname)) &&
|
||||
!(bitmap = loadBitmapFromDir("SPRITES/", fname)))
|
||||
{
|
||||
logGlobal->error("Error: Failed to find file %s", fname);
|
||||
}
|
||||
|
@ -14,5 +14,5 @@ struct SDL_Surface;
|
||||
namespace BitmapHandler
|
||||
{
|
||||
//Load file from /DATA or /SPRITES
|
||||
SDL_Surface * loadBitmap(std::string fname, bool setKey=true);
|
||||
SDL_Surface * loadBitmap(std::string fname);
|
||||
}
|
||||
|
187
client/CMT.cpp
187
client/CMT.cpp
@ -468,10 +468,9 @@ int main(int argc, char * argv[])
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
pomtime.getDiff();
|
||||
CCS->curh = new CCursorHandler();
|
||||
graphics = new Graphics(); // should be before curh->init()
|
||||
graphics = new Graphics(); // should be before curh
|
||||
|
||||
CCS->curh->initCursor();
|
||||
CCS->curh = new CCursorHandler();
|
||||
logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
|
||||
pomtime.getDiff();
|
||||
|
||||
@ -585,6 +584,7 @@ void removeGUI()
|
||||
GH.curInt = nullptr;
|
||||
if(GH.topInt())
|
||||
GH.topInt()->deactivate();
|
||||
adventureInt = nullptr;
|
||||
GH.listInt.clear();
|
||||
GH.objsToBlit.clear();
|
||||
GH.statusbar = nullptr;
|
||||
@ -664,36 +664,7 @@ void processCommand(const std::string &message)
|
||||
// }
|
||||
else if(message=="convert txt")
|
||||
{
|
||||
std::cout << "Command accepted.\t";
|
||||
|
||||
const bfs::path outPath =
|
||||
VCMIDirs::get().userExtractedPath();
|
||||
|
||||
bfs::create_directories(outPath);
|
||||
|
||||
auto extractVector = [=](const std::vector<std::string> & source, const std::string & name)
|
||||
{
|
||||
JsonNode data(JsonNode::JsonType::DATA_VECTOR);
|
||||
size_t index = 0;
|
||||
for(auto & line : source)
|
||||
{
|
||||
JsonNode lineNode(JsonNode::JsonType::DATA_STRUCT);
|
||||
lineNode["text"].String() = line;
|
||||
lineNode["index"].Integer() = index++;
|
||||
data.Vector().push_back(lineNode);
|
||||
}
|
||||
|
||||
const bfs::path filePath = outPath / (name + ".json");
|
||||
bfs::ofstream file(filePath);
|
||||
file << data.toJson();
|
||||
};
|
||||
|
||||
extractVector(VLC->generaltexth->allTexts, "generalTexts");
|
||||
extractVector(VLC->generaltexth->jktexts, "jkTexts");
|
||||
extractVector(VLC->generaltexth->arraytxt, "arrayTexts");
|
||||
|
||||
std::cout << "\rExtracting done :)\n";
|
||||
std::cout << " Extracted files can be found in " << outPath << " directory\n";
|
||||
VLC->generaltexth->dumpAllTexts();
|
||||
}
|
||||
else if(message=="get config")
|
||||
{
|
||||
@ -878,7 +849,7 @@ void processCommand(const std::string &message)
|
||||
{
|
||||
std::string URI;
|
||||
readed >> URI;
|
||||
std::unique_ptr<CAnimation> anim = make_unique<CAnimation>(URI);
|
||||
std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
|
||||
anim->preload();
|
||||
anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
|
||||
}
|
||||
@ -1086,7 +1057,6 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
|
||||
if(!checkVideoMode(displayIndex, w, h))
|
||||
{
|
||||
logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1508,80 +1478,89 @@ static void mainLoop()
|
||||
}
|
||||
}
|
||||
|
||||
static void quitApplication()
|
||||
{
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
if(CSH->client)
|
||||
CSH->endGameplay();
|
||||
}
|
||||
|
||||
GH.listInt.clear();
|
||||
GH.objsToBlit.clear();
|
||||
|
||||
CMM.reset();
|
||||
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
// cleanup, mostly to remove false leaks from analyzer
|
||||
if(CCS)
|
||||
{
|
||||
CCS->musich->release();
|
||||
CCS->soundh->release();
|
||||
|
||||
vstd::clear_pointer(CCS);
|
||||
}
|
||||
CMessage::dispose();
|
||||
|
||||
vstd::clear_pointer(graphics);
|
||||
}
|
||||
|
||||
vstd::clear_pointer(VLC);
|
||||
|
||||
vstd::clear_pointer(console);// should be removed after everything else since used by logging
|
||||
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
if(settings["general"]["notifications"].Bool())
|
||||
{
|
||||
NotificationHandler::destroy();
|
||||
}
|
||||
|
||||
cleanupRenderer();
|
||||
|
||||
if(nullptr != mainRenderer)
|
||||
{
|
||||
SDL_DestroyRenderer(mainRenderer);
|
||||
mainRenderer = nullptr;
|
||||
}
|
||||
|
||||
if(nullptr != mainWindow)
|
||||
{
|
||||
SDL_DestroyWindow(mainWindow);
|
||||
mainWindow = nullptr;
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
if(logConfig != nullptr)
|
||||
{
|
||||
logConfig->deconfigure();
|
||||
delete logConfig;
|
||||
logConfig = nullptr;
|
||||
}
|
||||
|
||||
std::cout << "Ending...\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void handleQuit(bool ask)
|
||||
{
|
||||
auto quitApplication = []()
|
||||
{
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
if(CSH->client)
|
||||
CSH->endGameplay();
|
||||
}
|
||||
|
||||
GH.listInt.clear();
|
||||
GH.objsToBlit.clear();
|
||||
|
||||
CMM.reset();
|
||||
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
// cleanup, mostly to remove false leaks from analyzer
|
||||
if(CCS)
|
||||
{
|
||||
CCS->musich->release();
|
||||
CCS->soundh->release();
|
||||
|
||||
vstd::clear_pointer(CCS);
|
||||
}
|
||||
CMessage::dispose();
|
||||
|
||||
vstd::clear_pointer(graphics);
|
||||
}
|
||||
|
||||
vstd::clear_pointer(VLC);
|
||||
|
||||
vstd::clear_pointer(console);// should be removed after everything else since used by logging
|
||||
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
if(settings["general"]["notifications"].Bool())
|
||||
{
|
||||
NotificationHandler::destroy();
|
||||
}
|
||||
|
||||
cleanupRenderer();
|
||||
|
||||
if(nullptr != mainRenderer)
|
||||
{
|
||||
SDL_DestroyRenderer(mainRenderer);
|
||||
mainRenderer = nullptr;
|
||||
}
|
||||
|
||||
if(nullptr != mainWindow)
|
||||
{
|
||||
SDL_DestroyWindow(mainWindow);
|
||||
mainWindow = nullptr;
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
if(logConfig != nullptr)
|
||||
{
|
||||
logConfig->deconfigure();
|
||||
delete logConfig;
|
||||
logConfig = nullptr;
|
||||
}
|
||||
|
||||
std::cout << "Ending...\n";
|
||||
exit(0);
|
||||
};
|
||||
|
||||
if(CSH->client && LOCPLINT && ask)
|
||||
{
|
||||
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
|
||||
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], [](){
|
||||
// Workaround for assertion failure on exit:
|
||||
// handleQuit() is alway called during SDL event processing
|
||||
// during which, eventsM is kept locked
|
||||
// this leads to assertion failure if boost::mutex is in locked state
|
||||
eventsM.unlock();
|
||||
|
||||
quitApplication();
|
||||
}, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2,19 +2,31 @@ set(client_SRCS
|
||||
StdInc.cpp
|
||||
../CCallback.cpp
|
||||
|
||||
battle/CBattleAnimations.cpp
|
||||
battle/CBattleInterfaceClasses.cpp
|
||||
battle/CBattleInterface.cpp
|
||||
battle/CCreatureAnimation.cpp
|
||||
battle/BattleActionsController.cpp
|
||||
battle/BattleAnimationClasses.cpp
|
||||
battle/BattleEffectsController.cpp
|
||||
battle/BattleFieldController.cpp
|
||||
battle/BattleInterfaceClasses.cpp
|
||||
battle/BattleInterface.cpp
|
||||
battle/BattleObstacleController.cpp
|
||||
battle/BattleProjectileController.cpp
|
||||
battle/BattleRenderer.cpp
|
||||
battle/BattleSiegeController.cpp
|
||||
battle/BattleStacksController.cpp
|
||||
battle/BattleWindow.cpp
|
||||
battle/CreatureAnimation.cpp
|
||||
|
||||
gui/CAnimation.cpp
|
||||
gui/Canvas.cpp
|
||||
gui/CCursorHandler.cpp
|
||||
gui/CGuiHandler.cpp
|
||||
gui/CIntObject.cpp
|
||||
gui/ColorFilter.cpp
|
||||
gui/Fonts.cpp
|
||||
gui/Geometries.cpp
|
||||
gui/SDL_Extensions.cpp
|
||||
gui/NotificationHandler.cpp
|
||||
gui/InterfaceObjectConfigurable.cpp
|
||||
|
||||
widgets/AdventureMapClasses.cpp
|
||||
widgets/Buttons.cpp
|
||||
@ -75,14 +87,26 @@ set(client_SRCS
|
||||
set(client_HEADERS
|
||||
StdInc.h
|
||||
|
||||
battle/CBattleAnimations.h
|
||||
battle/CBattleInterfaceClasses.h
|
||||
battle/CBattleInterface.h
|
||||
battle/CCreatureAnimation.h
|
||||
battle/BattleActionsController.h
|
||||
battle/BattleAnimationClasses.h
|
||||
battle/BattleEffectsController.h
|
||||
battle/BattleFieldController.h
|
||||
battle/BattleInterfaceClasses.h
|
||||
battle/BattleInterface.h
|
||||
battle/BattleObstacleController.h
|
||||
battle/BattleProjectileController.h
|
||||
battle/BattleRenderer.h
|
||||
battle/BattleSiegeController.h
|
||||
battle/BattleStacksController.h
|
||||
battle/BattleWindow.h
|
||||
battle/CreatureAnimation.h
|
||||
battle/BattleConstants.h
|
||||
|
||||
gui/CAnimation.h
|
||||
gui/Canvas.h
|
||||
gui/CCursorHandler.h
|
||||
gui/CGuiHandler.h
|
||||
gui/ColorFilter.h
|
||||
gui/CIntObject.h
|
||||
gui/Fonts.h
|
||||
gui/Geometries.h
|
||||
@ -90,6 +114,7 @@ set(client_HEADERS
|
||||
gui/SDL_Extensions.h
|
||||
gui/SDL_Pixels.h
|
||||
gui/NotificationHandler.h
|
||||
gui/InterfaceObjectConfigurable.h
|
||||
|
||||
widgets/AdventureMapClasses.h
|
||||
widgets/Buttons.h
|
||||
@ -177,7 +202,10 @@ else()
|
||||
add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS} ${client_ICON})
|
||||
endif(ENABLE_DEBUG_CONSOLE)
|
||||
|
||||
add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI Nullkiller)
|
||||
add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI)
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
add_dependencies(vcmiclient Nullkiller)
|
||||
endif()
|
||||
if(APPLE_IOS)
|
||||
if(ENABLE_ERM)
|
||||
add_dependencies(vcmiclient vcmiERM)
|
||||
@ -243,7 +271,7 @@ elseif(APPLE_IOS)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
|
||||
endif()
|
||||
|
||||
if(BUILD_SINGLE_APP)
|
||||
if(ENABLE_SINGLE_APP_BUILD)
|
||||
target_link_libraries(vcmiclient PRIVATE vcmiserver)
|
||||
if(ENABLE_LAUNCHER)
|
||||
target_link_libraries(vcmiclient PRIVATE vcmilauncher)
|
||||
|
@ -71,7 +71,7 @@ void CMessage::init()
|
||||
{
|
||||
for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
|
||||
{
|
||||
dialogBorders[i] = make_unique<CAnimation>("DIALGBOX");
|
||||
dialogBorders[i] = std::make_unique<CAnimation>("DIALGBOX");
|
||||
dialogBorders[i]->preload();
|
||||
|
||||
for(int j=0; j < dialogBorders[i]->size(0); j++)
|
||||
|
@ -458,7 +458,7 @@ void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName,
|
||||
{
|
||||
try
|
||||
{
|
||||
queueNext(make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
|
||||
queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
|
@ -12,8 +12,11 @@
|
||||
#include <vcmi/Artifact.h>
|
||||
|
||||
#include "windows/CAdvmapInterface.h"
|
||||
#include "battle/CBattleInterface.h"
|
||||
#include "battle/CBattleInterfaceClasses.h"
|
||||
#include "battle/BattleInterface.h"
|
||||
#include "battle/BattleEffectsController.h"
|
||||
#include "battle/BattleFieldController.h"
|
||||
#include "battle/BattleInterfaceClasses.h"
|
||||
#include "battle/BattleWindow.h"
|
||||
#include "../CCallback.h"
|
||||
#include "windows/CCastleInterface.h"
|
||||
#include "gui/CCursorHandler.h"
|
||||
@ -29,7 +32,6 @@
|
||||
#include "windows/CTradeWindow.h"
|
||||
#include "windows/CSpellWindow.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "battle/CCreatureAnimation.h"
|
||||
#include "Graphics.h"
|
||||
#include "windows/GUIClasses.h"
|
||||
#include "../lib/CArtHandler.h"
|
||||
@ -55,6 +57,7 @@
|
||||
#include "../lib/CPlayerState.h"
|
||||
#include "../lib/GameConstants.h"
|
||||
#include "gui/CGuiHandler.h"
|
||||
#include "gui/CAnimation.h"
|
||||
#include "windows/InfoWindows.h"
|
||||
#include "../lib/UnlockGuard.h"
|
||||
#include "../lib/CPathfinder.h"
|
||||
@ -90,7 +93,7 @@ boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
|
||||
|
||||
CPlayerInterface * LOCPLINT;
|
||||
|
||||
CBattleInterface * CPlayerInterface::battleInt;
|
||||
std::shared_ptr<BattleInterface> CPlayerInterface::battleInt;
|
||||
|
||||
enum EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
|
||||
CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
|
||||
@ -139,14 +142,16 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
|
||||
|
||||
CPlayerInterface::~CPlayerInterface()
|
||||
{
|
||||
if(CCS->soundh) CCS->soundh->ambientStopAllChannels();
|
||||
if(CCS && CCS->soundh)
|
||||
CCS->soundh->ambientStopAllChannels();
|
||||
|
||||
logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
|
||||
delete showingDialog;
|
||||
delete cingconsole;
|
||||
if (LOCPLINT == this)
|
||||
LOCPLINT = nullptr;
|
||||
}
|
||||
void CPlayerInterface::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
|
||||
{
|
||||
cb = CB;
|
||||
env = ENV;
|
||||
@ -264,8 +269,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
|
||||
assert(adventureInt->terrain.currentPath->nodes.size() >= 2);
|
||||
std::vector<CGPathNode>::const_iterator nodesIt = adventureInt->terrain.currentPath->nodes.end() - 1;
|
||||
|
||||
if((nodesIt)->coord == CGHeroInstance::convertPosition(details.start, false)
|
||||
&& (nodesIt - 1)->coord == CGHeroInstance::convertPosition(details.end, false))
|
||||
if((nodesIt)->coord == hero->convertToVisitablePos(details.start)
|
||||
&& (nodesIt - 1)->coord == hero->convertToVisitablePos(details.end))
|
||||
{
|
||||
//path was between entrance and exit of teleport -> OK, erase node as usual
|
||||
removeLastNodeFromPath(hero);
|
||||
@ -689,7 +694,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
|
||||
if (settings["adventure"]["quickCombat"].Bool())
|
||||
{
|
||||
autofightingAI = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
|
||||
autofightingAI->init(env, cb);
|
||||
autofightingAI->initBattleInterface(env, cb);
|
||||
autofightingAI->battleStart(army1, army2, int3(0,0,0), hero1, hero2, side);
|
||||
isAutoFightOn = true;
|
||||
cb->registerBattleInterface(autofightingAI);
|
||||
@ -704,7 +709,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects)
|
||||
void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
@ -715,34 +720,14 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
|
||||
{
|
||||
case UnitChanges::EOperation::RESET_STATE:
|
||||
{
|
||||
const battle::Unit * unit = cb->battleGetUnitByID(info.id);
|
||||
const CStack * stack = cb->battleGetStackByID(info.id );
|
||||
|
||||
if(!unit)
|
||||
if(!stack)
|
||||
{
|
||||
logGlobal->error("Invalid unit ID %d", info.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto iter = battleInt->creAnims.find(info.id);
|
||||
|
||||
if(iter == battleInt->creAnims.end())
|
||||
{
|
||||
logGlobal->error("Unit %d have no animation", info.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto animation = iter->second;
|
||||
|
||||
if(unit->alive() && animation->isDead())
|
||||
animation->setType(CCreatureAnim::HOLDING);
|
||||
|
||||
if (unit->isClone())
|
||||
{
|
||||
std::unique_ptr<ColorShifterDeepBlue> shifter(new ColorShifterDeepBlue());
|
||||
animation->shiftColor(shifter.get());
|
||||
}
|
||||
|
||||
//TODO: handle more cases
|
||||
battleInt->stackReset(stack);
|
||||
}
|
||||
break;
|
||||
case UnitChanges::EOperation::REMOVE:
|
||||
@ -756,7 +741,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
|
||||
logGlobal->error("Invalid unit ID %d", info.id);
|
||||
continue;
|
||||
}
|
||||
battleInt->unitAdded(unit);
|
||||
battleInt->stackAdded(unit);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -764,8 +749,6 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
battleInt->displayCustomEffects(customEffects);
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
|
||||
@ -773,7 +756,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
bool needUpdate = false;
|
||||
std::vector<std::shared_ptr<const CObstacleInstance>> newObstacles;
|
||||
|
||||
for(auto & change : obstacles)
|
||||
{
|
||||
@ -781,19 +764,16 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
|
||||
{
|
||||
auto instance = cb->battleGetObstacleByID(change.id);
|
||||
if(instance)
|
||||
battleInt->obstaclePlaced(*instance);
|
||||
newObstacles.push_back(instance);
|
||||
else
|
||||
logNetwork->error("Invalid obstacle instance %d", change.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
needUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(needUpdate)
|
||||
//update accessible hexes
|
||||
battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
|
||||
if (!newObstacles.empty())
|
||||
battleInt->obstaclePlaced(newObstacles);
|
||||
|
||||
battleInt->fieldController->redrawBackgroundWithHexes();
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
|
||||
@ -858,36 +838,36 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
|
||||
autofightingAI.reset();
|
||||
}
|
||||
|
||||
CBattleInterface *b = battleInt;
|
||||
assert(battleInt);
|
||||
|
||||
if(!b)
|
||||
if(!battleInt)
|
||||
{
|
||||
return BattleAction::makeDefend(stack); // probably battle is finished already
|
||||
}
|
||||
|
||||
if(CBattleInterface::givenCommand.get())
|
||||
if(BattleInterface::givenCommand.get())
|
||||
{
|
||||
logGlobal->error("Command buffer must be clean! (we don't want to use old command)");
|
||||
vstd::clear_pointer(CBattleInterface::givenCommand.data);
|
||||
vstd::clear_pointer(BattleInterface::givenCommand.data);
|
||||
}
|
||||
|
||||
{
|
||||
boost::unique_lock<boost::recursive_mutex> un(*pim);
|
||||
b->stackActivated(stack);
|
||||
battleInt->stackActivated(stack);
|
||||
//Regeneration & mana drain go there
|
||||
}
|
||||
//wait till BattleInterface sets its command
|
||||
boost::unique_lock<boost::mutex> lock(CBattleInterface::givenCommand.mx);
|
||||
while(!CBattleInterface::givenCommand.data)
|
||||
boost::unique_lock<boost::mutex> lock(BattleInterface::givenCommand.mx);
|
||||
while(!BattleInterface::givenCommand.data)
|
||||
{
|
||||
CBattleInterface::givenCommand.cond.wait(lock);
|
||||
BattleInterface::givenCommand.cond.wait(lock);
|
||||
if (!battleInt) //battle ended while we were waiting for movement (eg. because of spell)
|
||||
throw boost::thread_interrupted(); //will shut the thread peacefully
|
||||
}
|
||||
|
||||
//tidy up
|
||||
BattleAction ret = *(CBattleInterface::givenCommand.data);
|
||||
vstd::clear_pointer(CBattleInterface::givenCommand.data);
|
||||
BattleAction ret = *(BattleInterface::givenCommand.data);
|
||||
vstd::clear_pointer(BattleInterface::givenCommand.data);
|
||||
|
||||
if(ret.actionType == EActionType::CANCEL)
|
||||
{
|
||||
@ -912,7 +892,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
|
||||
|
||||
if(!battleInt)
|
||||
{
|
||||
GH.pushIntT<CBattleResultWindow>(*br, *this);
|
||||
GH.pushIntT<BattleResultWindow>(*br, *this);
|
||||
// #1490 - during AI turn when quick combat is on, we need to display the message and wait for user to close it.
|
||||
// Otherwise NewTurn causes freeze.
|
||||
waitWhileDialog();
|
||||
@ -935,12 +915,12 @@ void CPlayerInterface::battleLogMessage(const std::vector<MetaString> & lines)
|
||||
battleInt->displayBattleLog(lines);
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
|
||||
void CPlayerInterface::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
battleInt->stackMoved(stack, dest, distance);
|
||||
battleInt->stackMoved(stack, dest, distance, teleport);
|
||||
}
|
||||
void CPlayerInterface::battleSpellCast( const BattleSpellCast *sc )
|
||||
{
|
||||
@ -962,9 +942,9 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
|
||||
//TODO why is this different (no return on LOPLINT != this) ?
|
||||
|
||||
RETURN_IF_QUICK_COMBAT;
|
||||
battleInt->battleTriggerEffect(bte);
|
||||
battleInt->effectsController->battleTriggerEffect(bte);
|
||||
}
|
||||
void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
|
||||
void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
@ -974,24 +954,25 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
|
||||
{
|
||||
const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false);
|
||||
const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false);
|
||||
if(elem.isEffect())
|
||||
{
|
||||
if(defender && !elem.isSecondary())
|
||||
battleInt->displayEffect(elem.effect, defender->getPosition());
|
||||
}
|
||||
if(elem.isSpell())
|
||||
{
|
||||
if(defender)
|
||||
battleInt->displaySpellEffect(elem.spellID, defender->getPosition());
|
||||
}
|
||||
//FIXME: why action is deleted during enchanter cast?
|
||||
bool remoteAttack = false;
|
||||
|
||||
if(LOCPLINT->curAction)
|
||||
remoteAttack |= LOCPLINT->curAction->actionType != EActionType::WALK_AND_ATTACK;
|
||||
assert(defender);
|
||||
|
||||
StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
|
||||
arg.push_back(to_put);
|
||||
StackAttackedInfo info;
|
||||
info.defender = defender;
|
||||
info.attacker = attacker;
|
||||
info.damageDealt = elem.damageAmount;
|
||||
info.amountKilled = elem.killedAmount;
|
||||
info.spellEffect = SpellID::NONE;
|
||||
info.indirectAttack = ranged;
|
||||
info.killed = elem.killed();
|
||||
info.rebirth = elem.willRebirth();
|
||||
info.cloneKilled = elem.cloneKilled();
|
||||
info.fireShield = elem.fireShield();
|
||||
|
||||
if (elem.isSpell())
|
||||
info.spellEffect = elem.spellID;
|
||||
|
||||
arg.push_back(info);
|
||||
}
|
||||
battleInt->stacksAreAttacked(arg);
|
||||
}
|
||||
@ -1002,96 +983,36 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||
|
||||
assert(curAction);
|
||||
|
||||
const CStack * attacker = cb->battleGetStackByID(ba->stackAttacking);
|
||||
StackAttackInfo info;
|
||||
info.attacker = cb->battleGetStackByID(ba->stackAttacking);
|
||||
info.defender = nullptr;
|
||||
info.indirectAttack = ba->shot();
|
||||
info.lucky = ba->lucky();
|
||||
info.unlucky = ba->unlucky();
|
||||
info.deathBlow = ba->deathBlow();
|
||||
info.lifeDrain = ba->lifeDrain();
|
||||
info.tile = ba->tile;
|
||||
info.spellEffect = SpellID::NONE;
|
||||
|
||||
if(!attacker)
|
||||
{
|
||||
logGlobal->error("Attacking stack not found");
|
||||
return;
|
||||
}
|
||||
if (ba->spellLike())
|
||||
info.spellEffect = ba->spellID;
|
||||
|
||||
if(ba->lucky()) //lucky hit
|
||||
for(auto & elem : ba->bsa)
|
||||
{
|
||||
battleInt->console->addText(attacker->formatGeneralMessage(-45));
|
||||
battleInt->displayEffect(18, attacker->getPosition());
|
||||
CCS->soundh->playSound(soundBase::GOODLUCK);
|
||||
}
|
||||
if(ba->unlucky()) //unlucky hit
|
||||
{
|
||||
battleInt->console->addText(attacker->formatGeneralMessage(-44));
|
||||
battleInt->displayEffect(48, attacker->getPosition());
|
||||
CCS->soundh->playSound(soundBase::BADLUCK);
|
||||
}
|
||||
if(ba->deathBlow())
|
||||
{
|
||||
battleInt->console->addText(attacker->formatGeneralMessage(365));
|
||||
for(auto & elem : ba->bsa)
|
||||
if(!elem.isSecondary())
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||
battleInt->displayEffect(73, attacked->getPosition());
|
||||
assert(info.defender == nullptr);
|
||||
info.defender = cb->battleGetStackByID(elem.stackAttacked);
|
||||
}
|
||||
CCS->soundh->playSound(soundBase::deathBlow);
|
||||
}
|
||||
|
||||
battleInt->displayCustomEffects(ba->customEffects);
|
||||
|
||||
battleInt->waitForAnims();
|
||||
|
||||
auto actionTarget = curAction->getTarget(cb.get());
|
||||
|
||||
if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot()))
|
||||
{
|
||||
logNetwork->error("Invalid current action: no destination.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(ba->shot())
|
||||
{
|
||||
for(auto & elem : ba->bsa)
|
||||
else
|
||||
{
|
||||
if(!elem.isSecondary()) //display projectile only for primary target
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||
battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
|
||||
}
|
||||
info.secondaryDefender.push_back(cb->battleGetStackByID(elem.stackAttacked));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto attackTarget = actionTarget.at(1).hexValue;
|
||||
assert(info.defender != nullptr);
|
||||
assert(info.attacker != nullptr);
|
||||
|
||||
//TODO: use information from BattleAttack but not curAction
|
||||
|
||||
int shift = 0;
|
||||
if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0)
|
||||
{
|
||||
int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition());
|
||||
int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition());
|
||||
|
||||
if(distp < distm)
|
||||
shift = 1;
|
||||
else
|
||||
shift = -1;
|
||||
}
|
||||
|
||||
if(!ba->bsa.empty())
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
|
||||
battleInt->stackAttacking(attacker, ba->counter() ? BattleHex(attackTarget + shift) : attackTarget, attacked, false);
|
||||
}
|
||||
}
|
||||
|
||||
//battleInt->waitForAnims(); //FIXME: freeze
|
||||
|
||||
if(ba->spellLike())
|
||||
{
|
||||
//TODO: use information from BattleAttack but not curAction
|
||||
|
||||
auto destination = actionTarget.at(0).hexValue;
|
||||
//display hit animation
|
||||
SpellID spellID = ba->spellID;
|
||||
battleInt->displaySpellHit(spellID, destination);
|
||||
}
|
||||
battleInt->stackAttacking(info);
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleGateStateChanged(const EGateState state)
|
||||
@ -1573,7 +1494,7 @@ void CPlayerInterface::newObject( const CGObjectInstance * obj )
|
||||
//we might have built a boat in shipyard in opened town screen
|
||||
if (obj->ID == Obj::BOAT
|
||||
&& LOCPLINT->castleInt
|
||||
&& obj->pos-obj->getVisitableOffset() == LOCPLINT->castleInt->town->bestLocation())
|
||||
&& obj->visitablePos() == LOCPLINT->castleInt->town->bestLocation())
|
||||
{
|
||||
CCS->soundh->playSound(soundBase::newBuilding);
|
||||
LOCPLINT->castleInt->addBuilding(BuildingID::SHIP);
|
||||
@ -1625,7 +1546,7 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
|
||||
GH.curInt = this;
|
||||
adventureInt->selection = nullptr;
|
||||
adventureInt->setPlayer(playerID);
|
||||
std::string msg = CGI->generaltexth->localizedTexts["adventureMap"]["playerAttacked"].String();
|
||||
std::string msg = CGI->generaltexth->translate("vcmi.adventureMap.playerAttacked");
|
||||
boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
|
||||
std::vector<std::shared_ptr<CComponent>> cmp;
|
||||
cmp.push_back(std::make_shared<CComponent>(CComponent::flag, playerID.getNum(), 0));
|
||||
@ -2008,7 +1929,7 @@ CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(h->getPosition(false) == path.startPos());
|
||||
assert(h->visitablePos() == path.startPos());
|
||||
//update the hero path in case of something has changed on map
|
||||
if (LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos()))
|
||||
return &path;
|
||||
@ -2112,7 +2033,7 @@ void CPlayerInterface::acceptTurn()
|
||||
void CPlayerInterface::tryDiggging(const CGHeroInstance * h)
|
||||
{
|
||||
int msgToShow = -1;
|
||||
const bool isBlocked = CGI->mh->hasObjectHole(h->getPosition(false)); // Don't dig in the pit.
|
||||
const bool isBlocked = CGI->mh->hasObjectHole(h->visitablePos()); // Don't dig in the pit.
|
||||
|
||||
const auto diggingStatus = isBlocked
|
||||
? EDiggingStatus::TILE_OCCUPIED
|
||||
@ -2409,7 +2330,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
int i = 1;
|
||||
auto getObj = [&](int3 coord, bool ignoreHero)
|
||||
{
|
||||
return cb->getTile(CGHeroInstance::convertPosition(coord,false))->topVisitableObj(ignoreHero);
|
||||
return cb->getTile(h->convertToVisitablePos(coord))->topVisitableObj(ignoreHero);
|
||||
};
|
||||
|
||||
auto isTeleportAction = [&](CGPathNode::ENodeAction action) -> bool
|
||||
@ -2448,7 +2369,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
};
|
||||
|
||||
{
|
||||
path.convert(0);
|
||||
for (auto & elem : path.nodes)
|
||||
elem.coord = h->convertFromVisitablePos(elem.coord);
|
||||
|
||||
TerrainId currentTerrain = Terrain::BORDER; // not init yet
|
||||
TerrainId newTerrain;
|
||||
int sh = -1;
|
||||
@ -2505,7 +2428,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
|
||||
sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
|
||||
#endif
|
||||
{
|
||||
newTerrain = cb->getTile(CGHeroInstance::convertPosition(currentCoord, false))->terType->id;
|
||||
newTerrain = cb->getTile(h->convertToVisitablePos(currentCoord))->terType->id;
|
||||
if(newTerrain != currentTerrain)
|
||||
{
|
||||
CCS->soundh->stopSound(sh);
|
||||
|
@ -40,9 +40,8 @@ class CButton;
|
||||
class CToggleGroup;
|
||||
class CAdvMapInt;
|
||||
class CCastleInterface;
|
||||
class CBattleInterface;
|
||||
class BattleInterface;
|
||||
class CComponent;
|
||||
class CCreatureAnimation;
|
||||
class CSelectableComponent;
|
||||
class CSlider;
|
||||
class CInGameConsole;
|
||||
@ -85,7 +84,7 @@ public:
|
||||
static const int SAVES_COUNT = 5;
|
||||
|
||||
CCastleInterface * castleInt; //nullptr if castle window isn't opened
|
||||
static CBattleInterface * battleInt; //nullptr if no battle
|
||||
static std::shared_ptr<BattleInterface> battleInt; //nullptr if no battle
|
||||
CInGameConsole * cingconsole;
|
||||
|
||||
std::shared_ptr<CCallback> cb; //to communicate with engine
|
||||
@ -194,14 +193,14 @@ public:
|
||||
void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
|
||||
void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
|
||||
void battleLogMessage(const std::vector<MetaString> & lines) override;
|
||||
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
|
||||
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
|
||||
void battleSpellCast(const BattleSpellCast *sc) override;
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
|
||||
void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override;
|
||||
void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
|
||||
void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects) override;
|
||||
void battleUnitsChanged(const std::vector<UnitChanges> & units) override;
|
||||
void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
|
||||
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||
void battleGateStateChanged(const EGateState state) override;
|
||||
@ -221,7 +220,7 @@ public:
|
||||
void openTownWindow(const CGTownInstance * town); //shows townscreen
|
||||
void openHeroWindow(const CGHeroInstance * hero); //shows hero window with given hero
|
||||
void updateInfo(const CGObjectInstance * specific);
|
||||
void init(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
|
||||
int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
|
||||
void activateForSpectator(); // TODO: spectator probably need own player interface class
|
||||
|
||||
|
@ -25,12 +25,15 @@
|
||||
#include "../lib/CAndroidVMHelper.h"
|
||||
#elif defined(VCMI_IOS)
|
||||
#include "ios/utils.h"
|
||||
#include "../server/CVCMIServer.h"
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#else
|
||||
#include "../lib/Interprocess.h"
|
||||
#endif
|
||||
|
||||
#ifdef SINGLE_PROCESS_APP
|
||||
#include "../server/CVCMIServer.h"
|
||||
#endif
|
||||
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/CGeneralTextHandler.h"
|
||||
#include "../lib/CThreadHelper.h"
|
||||
@ -129,7 +132,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
|
||||
{
|
||||
hostClientId = -1;
|
||||
state = EClientState::NONE;
|
||||
th = make_unique<CStopWatch>();
|
||||
th = std::make_unique<CStopWatch>();
|
||||
packsForLobbyScreen.clear();
|
||||
c.reset();
|
||||
si = std::make_shared<StartInfo>();
|
||||
@ -142,7 +145,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
|
||||
else
|
||||
myNames.push_back(settings["general"]["playerName"].String());
|
||||
|
||||
#if !defined(VCMI_ANDROID) && !defined(VCMI_IOS)
|
||||
#if !defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
|
||||
shm.reset();
|
||||
|
||||
if(!settings["session"]["disable-shm"].Bool())
|
||||
@ -173,7 +176,7 @@ void CServerHandler::startLocalServerAndConnect()
|
||||
|
||||
th->update();
|
||||
|
||||
auto errorMsg = CGI->generaltexth->localizedTexts["server"]["errors"]["existingProcess"].String();
|
||||
auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess");
|
||||
try
|
||||
{
|
||||
CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid);
|
||||
@ -715,7 +718,7 @@ void CServerHandler::restoreLastSession()
|
||||
saveSession->Bool() = false;
|
||||
};
|
||||
|
||||
CInfoWindow::showYesNoDialog(VLC->generaltexth->localizedTexts["server"]["confirmReconnect"].String(), {}, loadSession, cleanUpSession);
|
||||
CInfoWindow::showYesNoDialog(VLC->generaltexth->translate("vcmi.server.confirmReconnect"), {}, loadSession, cleanUpSession);
|
||||
}
|
||||
|
||||
void CServerHandler::debugStartTest(std::string filename, bool save)
|
||||
|
@ -30,8 +30,6 @@ template<typename T> class CApplier;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct SharedMemory;
|
||||
|
||||
class CClient;
|
||||
|
||||
class CBaseForLobbyApply;
|
||||
|
@ -42,7 +42,7 @@
|
||||
#include "mainmenu/CMainMenu.h"
|
||||
#include "mainmenu/CCampaignScreen.h"
|
||||
#include "lobby/CBonusSelection.h"
|
||||
#include "battle/CBattleInterface.h"
|
||||
#include "battle/BattleInterface.h"
|
||||
#include "../lib/CThreadHelper.h"
|
||||
#include "../lib/registerTypes/RegisterTypes.h"
|
||||
#include "gui/CGuiHandler.h"
|
||||
@ -380,20 +380,12 @@ void CClient::endGame()
|
||||
{
|
||||
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
|
||||
logNetwork->info("Ending current game!");
|
||||
if(GH.topInt())
|
||||
{
|
||||
GH.topInt()->deactivate();
|
||||
}
|
||||
GH.listInt.clear();
|
||||
GH.objsToBlit.clear();
|
||||
GH.statusbar = nullptr;
|
||||
logNetwork->info("Removed GUI.");
|
||||
removeGUI();
|
||||
|
||||
vstd::clear_pointer(const_cast<CGameInfo *>(CGI)->mh);
|
||||
vstd::clear_pointer(gs);
|
||||
|
||||
logNetwork->info("Deleted mapHandler and gameState.");
|
||||
LOCPLINT = nullptr;
|
||||
}
|
||||
|
||||
playerint.clear();
|
||||
@ -509,7 +501,7 @@ void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInte
|
||||
logGlobal->trace("\tInitializing the interface for player %s", color.getStr());
|
||||
auto cb = std::make_shared<CCallback>(gs, color, this);
|
||||
battleCallbacks[color] = cb;
|
||||
gameInterface->init(playerEnvironments.at(color), cb);
|
||||
gameInterface->initGameInterface(playerEnvironments.at(color), cb);
|
||||
|
||||
installNewBattleInterface(gameInterface, color, battlecb);
|
||||
}
|
||||
@ -525,7 +517,7 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
|
||||
logGlobal->trace("\tInitializing the battle interface for player %s", color.getStr());
|
||||
auto cbc = std::make_shared<CBattleCallback>(color, this);
|
||||
battleCallbacks[color] = cbc;
|
||||
battleInterface->init(playerEnvironments.at(color), cbc);
|
||||
battleInterface->initBattleInterface(playerEnvironments.at(color), cbc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,11 +586,10 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
Rect battleIntRect((screen->w - 800)/2, (screen->h - 600)/2, 800, 600);
|
||||
if(!!att || !!def)
|
||||
{
|
||||
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
|
||||
GH.pushIntT<CBattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def);
|
||||
CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def);
|
||||
}
|
||||
else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
|
||||
{
|
||||
@ -606,7 +597,7 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
|
||||
spectratorInt->cb->setBattle(info);
|
||||
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
|
||||
GH.pushIntT<CBattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def, spectratorInt);
|
||||
CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def, spectratorInt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -764,7 +755,7 @@ scripting::Pool * CClient::getContextPool() const
|
||||
|
||||
void CClient::reinitScripting()
|
||||
{
|
||||
clientEventBus = make_unique<events::EventBus>();
|
||||
clientEventBus = std::make_unique<events::EventBus>();
|
||||
#if SCRIPTING_ENABLED
|
||||
clientScripts.reset(new scripting::PoolImpl(this));
|
||||
#endif
|
||||
|
@ -18,9 +18,9 @@ CreatureCostBox::CreatureCostBox(Rect position, std::string titleText)
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
|
||||
type |= REDRAW_PARENT;
|
||||
pos = position + pos;
|
||||
pos = position + pos.topLeft();
|
||||
|
||||
title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, CENTER, Colors::WHITE, titleText);
|
||||
title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, titleText);
|
||||
}
|
||||
|
||||
void CreatureCostBox::set(TResources res)
|
||||
@ -39,7 +39,7 @@ void CreatureCostBox::createItems(TResources res)
|
||||
while(iter.valid())
|
||||
{
|
||||
ImagePtr image = std::make_shared<CAnimImage>("RESOURCE", iter->resType);
|
||||
LabelPtr text = std::make_shared<CLabel>(15, 43, FONT_SMALL, CENTER, Colors::WHITE, "0");
|
||||
LabelPtr text = std::make_shared<CLabel>(15, 43, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "0");
|
||||
|
||||
resources.insert(std::make_pair(iter->resType, std::make_pair(text, image)));
|
||||
iter++;
|
||||
|
@ -29,7 +29,6 @@
|
||||
#include "../lib/VCMI_Lib.h"
|
||||
#include "../CCallback.h"
|
||||
#include "../lib/CGeneralTextHandler.h"
|
||||
#include "CBitmapHandler.h"
|
||||
#include "../lib/CGameState.h"
|
||||
#include "../lib/JsonNode.h"
|
||||
#include "../lib/vcmi_endian.h"
|
||||
|
@ -30,7 +30,7 @@ struct SDL_Surface;
|
||||
struct SDL_Color;
|
||||
class CAnimation;
|
||||
|
||||
enum EFonts
|
||||
enum EFonts : int
|
||||
{
|
||||
FONT_BIG, FONT_CALLI, FONT_CREDITS, FONT_HIGH_SCORE, FONT_MEDIUM, FONT_SMALL, FONT_TIMES, FONT_TINY, FONT_VERD
|
||||
};
|
||||
|
@ -30,7 +30,7 @@
|
||||
#include "windows/GUIClasses.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "gui/SDL_Extensions.h"
|
||||
#include "battle/CBattleInterface.h"
|
||||
#include "battle/BattleInterface.h"
|
||||
#include "../lib/mapping/CCampaignHandler.h"
|
||||
#include "../lib/CGameState.h"
|
||||
#include "../lib/CStack.h"
|
||||
@ -272,15 +272,18 @@ void EraseArtifact::applyCl(CClient *cl)
|
||||
callInterfaceIfPresent(cl, al.owningPlayer(), &IGameEventsReceiver::artifactRemoved, al);
|
||||
}
|
||||
|
||||
void MoveArtifact::applyCl(CClient *cl)
|
||||
void MoveArtifact::applyCl(CClient * cl)
|
||||
{
|
||||
callInterfaceIfPresent(cl, src.owningPlayer(), &IGameEventsReceiver::artifactMoved, src, dst);
|
||||
callInterfaceIfPresent(cl, src.owningPlayer(), &IGameEventsReceiver::artifactPossibleAssembling, dst);
|
||||
if(src.owningPlayer() != dst.owningPlayer())
|
||||
auto moveArtifact = [this, cl](PlayerColor player) -> void
|
||||
{
|
||||
callInterfaceIfPresent(cl, dst.owningPlayer(), &IGameEventsReceiver::artifactMoved, src, dst);
|
||||
callInterfaceIfPresent(cl, dst.owningPlayer(), &IGameEventsReceiver::artifactPossibleAssembling, dst);
|
||||
}
|
||||
callInterfaceIfPresent(cl, player, &IGameEventsReceiver::artifactMoved, src, dst);
|
||||
if(askAssemble)
|
||||
callInterfaceIfPresent(cl, player, &IGameEventsReceiver::artifactPossibleAssembling, dst);
|
||||
};
|
||||
|
||||
moveArtifact(src.owningPlayer());
|
||||
if(src.owningPlayer() != dst.owningPlayer())
|
||||
moveArtifact(dst.owningPlayer());
|
||||
}
|
||||
|
||||
void BulkMoveArtifacts::applyCl(CClient * cl)
|
||||
@ -738,7 +741,7 @@ void BattleResult::applyFirstCl(CClient *cl)
|
||||
void BattleStackMoved::applyFirstCl(CClient *cl)
|
||||
{
|
||||
const CStack * movedStack = GS(cl)->curB->battleGetStackByID(stack);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance, teleporting);
|
||||
}
|
||||
|
||||
void BattleAttack::applyFirstCl(CClient *cl)
|
||||
@ -748,7 +751,7 @@ void BattleAttack::applyFirstCl(CClient *cl)
|
||||
|
||||
void BattleAttack::applyCl(CClient *cl)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa, shot());
|
||||
}
|
||||
|
||||
void StartAction::applyFirstCl(CClient *cl)
|
||||
@ -770,7 +773,7 @@ void SetStackEffect::applyCl(CClient *cl)
|
||||
|
||||
void StacksInjured::applyCl(CClient *cl)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks, false);
|
||||
}
|
||||
|
||||
void BattleResultsApplied::applyCl(CClient *cl)
|
||||
@ -782,7 +785,7 @@ void BattleResultsApplied::applyCl(CClient *cl)
|
||||
|
||||
void BattleUnitsChanged::applyCl(CClient * cl)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks);
|
||||
}
|
||||
|
||||
void BattleObstaclesChanged::applyCl(CClient *cl)
|
||||
|
791
client/battle/BattleActionsController.cpp
Normal file
791
client/battle/BattleActionsController.cpp
Normal file
@ -0,0 +1,791 @@
|
||||
/*
|
||||
* BattleActionsController.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 "BattleActionsController.h"
|
||||
|
||||
#include "BattleWindow.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CCursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/CIntObject.h"
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/Problem.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
|
||||
static std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
|
||||
{
|
||||
if (dmgRange.first != dmgRange.second)
|
||||
return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
|
||||
else
|
||||
return (boost::format("%d") % dmgRange.first).str();
|
||||
}
|
||||
|
||||
|
||||
BattleActionsController::BattleActionsController(BattleInterface & owner):
|
||||
owner(owner),
|
||||
creatureCasting(false),
|
||||
spellDestSelectMode(false),
|
||||
spellToCast(nullptr),
|
||||
currentSpell(nullptr)
|
||||
{
|
||||
currentAction = PossiblePlayerBattleAction::INVALID;
|
||||
selectedAction = PossiblePlayerBattleAction::INVALID;
|
||||
}
|
||||
|
||||
void BattleActionsController::endCastingSpell()
|
||||
{
|
||||
if(spellDestSelectMode)
|
||||
{
|
||||
spellToCast.reset();
|
||||
|
||||
currentSpell = nullptr;
|
||||
spellDestSelectMode = false;
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
|
||||
if(owner.stacksController->getActiveStack())
|
||||
{
|
||||
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); //restore actions after they were cleared
|
||||
owner.myTurn = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(owner.stacksController->getActiveStack())
|
||||
{
|
||||
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleActionsController::enterCreatureCastingMode()
|
||||
{
|
||||
//silently check for possible errors
|
||||
if (!owner.myTurn)
|
||||
return;
|
||||
|
||||
if (owner.tacticsMode)
|
||||
return;
|
||||
|
||||
//hero is casting a spell
|
||||
if (spellDestSelectMode)
|
||||
return;
|
||||
|
||||
if (!owner.stacksController->getActiveStack())
|
||||
return;
|
||||
|
||||
if (!owner.stacksController->activeStackSpellcaster())
|
||||
return;
|
||||
|
||||
//random spellcaster
|
||||
if (owner.stacksController->activeStackSpellToCast() == SpellID::NONE)
|
||||
return;
|
||||
|
||||
if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
|
||||
{
|
||||
const spells::Caster * caster = owner.stacksController->getActiveStack();
|
||||
const CSpell * spell = owner.stacksController->activeStackSpellToCast().toSpell();
|
||||
|
||||
spells::Target target;
|
||||
target.emplace_back();
|
||||
|
||||
spells::BattleCast cast(owner.curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
|
||||
|
||||
auto m = spell->battleMechanics(&cast);
|
||||
spells::detail::ProblemImpl ignored;
|
||||
|
||||
const bool isCastingPossible = m->canBeCastAt(target, ignored);
|
||||
|
||||
if (isCastingPossible)
|
||||
{
|
||||
owner.myTurn = false;
|
||||
owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner.stacksController->activeStackSpellToCast());
|
||||
owner.stacksController->setSelectedStack(nullptr);
|
||||
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
|
||||
|
||||
auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
|
||||
{
|
||||
return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
|
||||
(x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
|
||||
(x != PossiblePlayerBattleAction::OBSTACLE);
|
||||
};
|
||||
|
||||
vstd::erase_if(possibleActions, actionFilterPredicate);
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PossiblePlayerBattleAction> BattleActionsController::getPossibleActionsForStack(const CStack *stack) const
|
||||
{
|
||||
BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
|
||||
data.creatureSpellToCast = owner.stacksController->activeStackSpellToCast();
|
||||
data.tacticsMode = owner.tacticsMode;
|
||||
auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data);
|
||||
|
||||
return std::vector<PossiblePlayerBattleAction>(allActions);
|
||||
}
|
||||
|
||||
void BattleActionsController::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
|
||||
{
|
||||
if(owner.tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
|
||||
|
||||
auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
|
||||
{
|
||||
switch(item)
|
||||
{
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
case PossiblePlayerBattleAction::NO_LOCATION:
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
case PossiblePlayerBattleAction::OBSTACLE:
|
||||
if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
|
||||
return 1;
|
||||
else
|
||||
return 100;//bottom priority
|
||||
break;
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
return 2; break;
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
return 4; break;
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
return 5; break;
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
return 6; break;
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
return 7; break;
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
return 8; break;
|
||||
case PossiblePlayerBattleAction::CATAPULT:
|
||||
return 9; break;
|
||||
case PossiblePlayerBattleAction::HEAL:
|
||||
return 10; break;
|
||||
default:
|
||||
return 200; break;
|
||||
}
|
||||
};
|
||||
|
||||
auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
|
||||
{
|
||||
return assignPriority(lhs) > assignPriority(rhs);
|
||||
};
|
||||
|
||||
std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
|
||||
}
|
||||
|
||||
void BattleActionsController::castThisSpell(SpellID spellID)
|
||||
{
|
||||
spellToCast = std::make_shared<BattleAction>();
|
||||
spellToCast->actionType = EActionType::HERO_SPELL;
|
||||
spellToCast->actionSubtype = spellID; //spell number
|
||||
spellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
|
||||
spellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
||||
spellDestSelectMode = true;
|
||||
creatureCasting = false;
|
||||
|
||||
//choosing possible targets
|
||||
const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance;
|
||||
assert(castingHero); // code below assumes non-null hero
|
||||
currentSpell = spellID.toSpell();
|
||||
PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(currentSpell, castingHero, spells::Mode::HERO);
|
||||
|
||||
if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
|
||||
{
|
||||
spellToCast->aimToHex(BattleHex::INVALID);
|
||||
owner.curInt->cb->battleMakeAction(spellToCast.get());
|
||||
endCastingSpell();
|
||||
}
|
||||
else
|
||||
{
|
||||
possibleActions.clear();
|
||||
possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
|
||||
GH.fakeMouseMove();//update cursor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
|
||||
{
|
||||
if (!owner.myTurn) //we are not permit to do anything
|
||||
return;
|
||||
|
||||
// This function handles mouse move over hexes and l-clicking on them.
|
||||
// First we decide what happens if player clicks on this hex and set appropriately
|
||||
// consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
|
||||
//
|
||||
// Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
|
||||
|
||||
std::string newConsoleMsg;
|
||||
//used when hovering -> tooltip message and cursor to be set
|
||||
bool setCursor = true; //if we want to suppress setting cursor
|
||||
bool spellcastingCursor = false;
|
||||
auto cursorFrame = Cursor::Combat::POINTER;
|
||||
|
||||
//used when l-clicking -> action to be called upon the click
|
||||
std::function<void()> realizeAction;
|
||||
|
||||
//Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
|
||||
const CStack * shere = owner.curInt->cb->battleGetStackByPos(myNumber, true);
|
||||
if(!shere)
|
||||
shere = owner.curInt->cb->battleGetStackByPos(myNumber, false);
|
||||
|
||||
if(!owner.stacksController->getActiveStack())
|
||||
return;
|
||||
|
||||
bool ourStack = false;
|
||||
if (shere)
|
||||
ourStack = shere->owner == owner.curInt->playerID;
|
||||
|
||||
localActions.clear();
|
||||
illegalActions.clear();
|
||||
|
||||
reorderPossibleActionsPriority(owner.stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
|
||||
const bool forcedAction = possibleActions.size() == 1;
|
||||
|
||||
for (PossiblePlayerBattleAction action : possibleActions)
|
||||
{
|
||||
bool legalAction = false; //this action is legal and can be performed
|
||||
bool notLegal = false; //this action is not legal and should display message
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
if (shere && ourStack)
|
||||
legalAction = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::MOVE_TACTICS:
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
{
|
||||
if (!(shere && shere->alive())) //we can walk on dead stacks
|
||||
{
|
||||
if(canStackMoveHere(owner.stacksController->getActiveStack(), myNumber))
|
||||
legalAction = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
{
|
||||
if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), shere, myNumber))
|
||||
{
|
||||
if (owner.fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
|
||||
{
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
|
||||
|
||||
if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
|
||||
legalAction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
if(owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
if (myNumber > -1) //TODO: this should be checked for all actions
|
||||
{
|
||||
if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
|
||||
legalAction = true;
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
if(shere && isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
{
|
||||
if(shere && ourStack && shere != owner.stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
|
||||
{
|
||||
int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
|
||||
if(spellID > -1)
|
||||
{
|
||||
legalAction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::OBSTACLE:
|
||||
if(isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::TELEPORT:
|
||||
{
|
||||
//todo: move to mechanics
|
||||
ui8 skill = 0;
|
||||
if (creatureCasting)
|
||||
skill = owner.stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
else
|
||||
skill = owner.getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
|
||||
//TODO: explicitely save power, skill
|
||||
if (owner.curInt->cb->battleCanTeleportTo(owner.stacksController->getSelectedStack(), myNumber, skill))
|
||||
legalAction = true;
|
||||
else
|
||||
notLegal = true;
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
|
||||
if (shere && shere != owner.stacksController->getSelectedStack() && ourStack && shere->alive())
|
||||
legalAction = true;
|
||||
else
|
||||
notLegal = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
legalAction = true;
|
||||
if(!isCastingPossibleHere(owner.stacksController->getActiveStack(), shere, myNumber))
|
||||
{
|
||||
legalAction = false;
|
||||
notLegal = true;
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::CATAPULT:
|
||||
if (owner.siegeController && owner.siegeController->isAttackableByCatapult(myNumber))
|
||||
legalAction = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::HEAL:
|
||||
if (shere && ourStack && shere->canBeHealed())
|
||||
legalAction = true;
|
||||
break;
|
||||
}
|
||||
if (legalAction)
|
||||
localActions.push_back (action);
|
||||
else if (notLegal || forcedAction)
|
||||
illegalActions.push_back (action);
|
||||
}
|
||||
illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
|
||||
|
||||
if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
|
||||
currentAction = selectedAction;
|
||||
else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
|
||||
currentAction = localActions.front();
|
||||
else //no legal action possible
|
||||
{
|
||||
currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
|
||||
|
||||
if (vstd::contains(illegalActions, selectedAction))
|
||||
illegalAction = selectedAction;
|
||||
else if (illegalActions.size())
|
||||
illegalAction = illegalActions.front();
|
||||
else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
|
||||
{
|
||||
currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
|
||||
}
|
||||
else
|
||||
illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
|
||||
}
|
||||
|
||||
bool isCastingPossible = false;
|
||||
bool secondaryTarget = false;
|
||||
|
||||
if (currentAction > PossiblePlayerBattleAction::INVALID)
|
||||
{
|
||||
switch (currentAction) //display console message, realize selected action
|
||||
{
|
||||
case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
|
||||
realizeAction = [=](){ owner.stackActivated(shere); };
|
||||
break;
|
||||
case PossiblePlayerBattleAction::MOVE_TACTICS:
|
||||
case PossiblePlayerBattleAction::MOVE_STACK:
|
||||
if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
|
||||
{
|
||||
cursorFrame = Cursor::Combat::FLY;
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorFrame = Cursor::Combat::MOVE;
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
|
||||
}
|
||||
|
||||
realizeAction = [=]()
|
||||
{
|
||||
if(owner.stacksController->getActiveStack()->doubleWide())
|
||||
{
|
||||
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
|
||||
BattleHex shiftedDest = myNumber.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
|
||||
if(vstd::contains(acc, myNumber))
|
||||
owner.giveCommand(EActionType::WALK, myNumber);
|
||||
else if(vstd::contains(acc, shiftedDest))
|
||||
owner.giveCommand(EActionType::WALK, shiftedDest);
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.giveCommand(EActionType::WALK, myNumber);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor
|
||||
setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
|
||||
|
||||
bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
|
||||
|
||||
realizeAction = [=]()
|
||||
{
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
|
||||
if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
|
||||
{
|
||||
auto command = new BattleAction(BattleAction::makeMeleeAttack(owner.stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
|
||||
owner.sendCommand(command, owner.stacksController->getActiveStack());
|
||||
}
|
||||
};
|
||||
|
||||
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
|
||||
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
{
|
||||
if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), myNumber))
|
||||
cursorFrame = Cursor::Combat::SHOOT_PENALTY;
|
||||
else
|
||||
cursorFrame = Cursor::Combat::SHOOT;
|
||||
|
||||
realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);};
|
||||
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
|
||||
std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
|
||||
//printing - Shoot %s (%d shots left, %s damage)
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner.stacksController->getActiveStack()->shots.available() % estDmgText).str();
|
||||
}
|
||||
break;
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
|
||||
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->name % shere->getName()); //Cast %s on %s
|
||||
switch (currentSpell->id)
|
||||
{
|
||||
case SpellID::SACRIFICE:
|
||||
case SpellID::TELEPORT:
|
||||
owner.stacksController->setSelectedStack(shere); //remember first target
|
||||
secondaryTarget = true;
|
||||
break;
|
||||
}
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::ANY_LOCATION:
|
||||
currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
|
||||
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
|
||||
currentSpell = nullptr;
|
||||
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
|
||||
creatureCasting = true;
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::TELEPORT:
|
||||
newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
|
||||
cursorFrame = Cursor::Combat::TELEPORT;
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::OBSTACLE:
|
||||
newConsoleMsg = CGI->generaltexth->allTexts[550];
|
||||
//TODO: remove obstacle cursor
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
|
||||
cursorFrame = Cursor::Combat::SACRIFICE;
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
|
||||
isCastingPossible = true;
|
||||
break;
|
||||
case PossiblePlayerBattleAction::HEAL:
|
||||
cursorFrame = Cursor::Combat::HEAL;
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
|
||||
realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
|
||||
break;
|
||||
case PossiblePlayerBattleAction::CATAPULT:
|
||||
cursorFrame = Cursor::Combat::SHOOT_CATAPULT;
|
||||
realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };
|
||||
break;
|
||||
case PossiblePlayerBattleAction::CREATURE_INFO:
|
||||
{
|
||||
cursorFrame = Cursor::Combat::QUERY;
|
||||
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
|
||||
realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else //no possible valid action, display message
|
||||
{
|
||||
switch (illegalAction)
|
||||
{
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
|
||||
cursorFrame = Cursor::Combat::BLOCKED;
|
||||
newConsoleMsg = CGI->generaltexth->allTexts[23];
|
||||
break;
|
||||
case PossiblePlayerBattleAction::TELEPORT:
|
||||
cursorFrame = Cursor::Combat::BLOCKED;
|
||||
newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
|
||||
break;
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
|
||||
break;
|
||||
case PossiblePlayerBattleAction::FREE_LOCATION:
|
||||
cursorFrame = Cursor::Combat::BLOCKED;
|
||||
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here
|
||||
break;
|
||||
default:
|
||||
if (myNumber == -1)
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
else
|
||||
cursorFrame = Cursor::Combat::BLOCKED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCastingPossible) //common part
|
||||
{
|
||||
switch (currentAction) //don't use that with teleport / sacrifice
|
||||
{
|
||||
case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
|
||||
case PossiblePlayerBattleAction::SACRIFICE:
|
||||
break;
|
||||
default:
|
||||
spellcastingCursor = true;
|
||||
if (newConsoleMsg.empty() && currentSpell)
|
||||
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
|
||||
break;
|
||||
}
|
||||
|
||||
realizeAction = [=]()
|
||||
{
|
||||
if(secondaryTarget) //select that target now
|
||||
{
|
||||
|
||||
possibleActions.clear();
|
||||
switch (currentSpell->id.toEnum())
|
||||
{
|
||||
case SpellID::TELEPORT: //don't cast spell yet, only select target
|
||||
spellToCast->aimToUnit(shere);
|
||||
possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
|
||||
break;
|
||||
case SpellID::SACRIFICE:
|
||||
spellToCast->aimToHex(myNumber);
|
||||
possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (creatureCasting)
|
||||
{
|
||||
if (currentSpell)
|
||||
{
|
||||
owner.giveCommand(EActionType::MONSTER_SPELL, myNumber, owner.stacksController->activeStackSpellToCast());
|
||||
}
|
||||
else //unknown random spell
|
||||
{
|
||||
owner.giveCommand(EActionType::MONSTER_SPELL, myNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(currentSpell);
|
||||
switch (currentSpell->id.toEnum())
|
||||
{
|
||||
case SpellID::SACRIFICE:
|
||||
spellToCast->aimToUnit(shere);//victim
|
||||
break;
|
||||
default:
|
||||
spellToCast->aimToHex(myNumber);
|
||||
break;
|
||||
}
|
||||
owner.curInt->cb->battleMakeAction(spellToCast.get());
|
||||
endCastingSpell();
|
||||
}
|
||||
owner.stacksController->setSelectedStack(nullptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
if (eventType == CIntObject::MOVE)
|
||||
{
|
||||
if (setCursor)
|
||||
{
|
||||
if (spellcastingCursor)
|
||||
CCS->curh->set(Cursor::Spellcast::SPELL);
|
||||
else
|
||||
CCS->curh->set(cursorFrame);
|
||||
}
|
||||
|
||||
if (!currentConsoleMsg.empty())
|
||||
GH.statusbar->clearIfMatching(currentConsoleMsg);
|
||||
if (!newConsoleMsg.empty())
|
||||
GH.statusbar->write(newConsoleMsg);
|
||||
|
||||
currentConsoleMsg = newConsoleMsg;
|
||||
}
|
||||
if (eventType == CIntObject::LCLICK && realizeAction)
|
||||
{
|
||||
//opening creature window shouldn't affect myTurn...
|
||||
if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
|
||||
{
|
||||
owner.myTurn = false; //tends to crash with empty calls
|
||||
}
|
||||
realizeAction();
|
||||
if (!secondaryTarget) //do not replace teleport or sacrifice cursor
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
GH.statusbar->clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool BattleActionsController::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
|
||||
{
|
||||
creatureCasting = owner.stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
|
||||
|
||||
bool isCastingPossible = true;
|
||||
|
||||
int spellID = -1;
|
||||
if (creatureCasting)
|
||||
{
|
||||
if (owner.stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
|
||||
spellID = owner.stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
|
||||
}
|
||||
else //hero casting
|
||||
{
|
||||
spellID = spellToCast->actionSubtype;
|
||||
}
|
||||
|
||||
|
||||
currentSpell = nullptr;
|
||||
if (spellID >= 0)
|
||||
currentSpell = CGI->spellh->objects[spellID];
|
||||
|
||||
if (currentSpell)
|
||||
{
|
||||
const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(owner.curInt->cb->battleGetMyHero());
|
||||
if (caster == nullptr)
|
||||
{
|
||||
isCastingPossible = false;//just in case
|
||||
}
|
||||
else
|
||||
{
|
||||
const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
|
||||
|
||||
spells::Target target;
|
||||
target.emplace_back(myNumber);
|
||||
|
||||
spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
|
||||
|
||||
auto m = currentSpell->battleMechanics(&cast);
|
||||
spells::detail::ProblemImpl problem; //todo: display problem in status bar
|
||||
|
||||
isCastingPossible = m->canBeCastAt(target, problem);
|
||||
}
|
||||
}
|
||||
else
|
||||
isCastingPossible = false;
|
||||
if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
|
||||
isCastingPossible = false;
|
||||
|
||||
return isCastingPossible;
|
||||
}
|
||||
|
||||
bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
|
||||
{
|
||||
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove);
|
||||
BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
|
||||
|
||||
if (vstd::contains(acc, myNumber))
|
||||
return true;
|
||||
else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void BattleActionsController::activateStack()
|
||||
{
|
||||
const CStack * s = owner.stacksController->getActiveStack();
|
||||
if(s)
|
||||
{
|
||||
possibleActions = getPossibleActionsForStack(s);
|
||||
std::list<PossiblePlayerBattleAction> actionsToSelect;
|
||||
if(!possibleActions.empty())
|
||||
{
|
||||
switch(possibleActions.front())
|
||||
{
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
actionsToSelect.push_back(possibleActions.front());
|
||||
actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
actionsToSelect.push_back(possibleActions.front());
|
||||
actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK);
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
actionsToSelect.push_back(possibleActions.front());
|
||||
break;
|
||||
}
|
||||
}
|
||||
owner.windowObject->setAlternativeActions(actionsToSelect);
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleActionsController::spellcastingModeActive() const
|
||||
{
|
||||
return spellDestSelectMode;
|
||||
}
|
||||
|
||||
SpellID BattleActionsController::selectedSpell() const
|
||||
{
|
||||
if (!spellToCast)
|
||||
return SpellID::NONE;
|
||||
return SpellID(spellToCast->actionSubtype);
|
||||
}
|
||||
|
||||
const std::vector<PossiblePlayerBattleAction> & BattleActionsController::getPossibleActions() const
|
||||
{
|
||||
return possibleActions;
|
||||
}
|
||||
|
||||
void BattleActionsController::removePossibleAction(PossiblePlayerBattleAction action)
|
||||
{
|
||||
vstd::erase(possibleActions, action);
|
||||
}
|
||||
|
||||
void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction action)
|
||||
{
|
||||
possibleActions.insert(possibleActions.begin(), action);
|
||||
}
|
103
client/battle/BattleActionsController.h
Normal file
103
client/battle/BattleActionsController.h
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* BattleActionsController.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 "../../lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class BattleAction;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class BattleInterface;
|
||||
|
||||
enum class MouseHoveredHexContext
|
||||
{
|
||||
UNOCCUPIED_HEX,
|
||||
OCCUPIED_HEX
|
||||
};
|
||||
|
||||
/// Class that controls actions that can be performed by player, e.g. moving stacks, attacking, etc
|
||||
/// As well as all relevant feedback for these actions in user interface
|
||||
class BattleActionsController
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
/// all actions possible to call at the moment by player
|
||||
std::vector<PossiblePlayerBattleAction> possibleActions;
|
||||
|
||||
/// actions possible to take on hovered hex
|
||||
std::vector<PossiblePlayerBattleAction> localActions;
|
||||
|
||||
/// these actions display message in case of illegal target
|
||||
std::vector<PossiblePlayerBattleAction> illegalActions;
|
||||
|
||||
/// action that will be performed on l-click
|
||||
PossiblePlayerBattleAction currentAction;
|
||||
|
||||
/// last action chosen (and saved) by player
|
||||
PossiblePlayerBattleAction selectedAction;
|
||||
|
||||
/// if there are not possible actions to choose from, this action should be show as "illegal" in UI
|
||||
PossiblePlayerBattleAction illegalAction;
|
||||
|
||||
/// if true, stack currently aims to cats a spell
|
||||
bool creatureCasting;
|
||||
|
||||
/// if true, player is choosing destination for his spell - only for GUI / console
|
||||
bool spellDestSelectMode;
|
||||
|
||||
/// spell for which player is choosing destination
|
||||
std::shared_ptr<BattleAction> spellToCast;
|
||||
|
||||
/// spell for which player is choosing destination, pointer for convenience
|
||||
const CSpell *currentSpell;
|
||||
|
||||
/// cached message that was set by this class in status bar
|
||||
std::string currentConsoleMsg;
|
||||
|
||||
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
||||
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
|
||||
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
|
||||
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
||||
|
||||
public:
|
||||
BattleActionsController(BattleInterface & owner);
|
||||
|
||||
/// initialize list of potential actions for new active stack
|
||||
void activateStack();
|
||||
|
||||
/// initialize potential actions for spells that can be cast by active stack
|
||||
void enterCreatureCastingMode();
|
||||
|
||||
/// initialize potential actions for selected spell
|
||||
void castThisSpell(SpellID spellID);
|
||||
|
||||
/// ends casting spell (eg. when spell has been cast or canceled)
|
||||
void endCastingSpell();
|
||||
|
||||
/// update UI (e.g. status bar/cursor) according to new active hex
|
||||
void handleHex(BattleHex myNumber, int eventType);
|
||||
|
||||
/// returns currently selected spell or SpellID::NONE on error
|
||||
SpellID selectedSpell() const;
|
||||
|
||||
/// returns true if UI is currently in target selection mode
|
||||
bool spellcastingModeActive() const;
|
||||
|
||||
/// methods to work with array of possible actions, needed to control special creatures abilities
|
||||
const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
|
||||
void removePossibleAction(PossiblePlayerBattleAction);
|
||||
|
||||
/// inserts possible action in the beggining in order to prioritize it
|
||||
void pushFrontPossibleAction(PossiblePlayerBattleAction);
|
||||
|
||||
};
|
1121
client/battle/BattleAnimationClasses.cpp
Normal file
1121
client/battle/BattleAnimationClasses.cpp
Normal file
File diff suppressed because it is too large
Load Diff
367
client/battle/BattleAnimationClasses.h
Normal file
367
client/battle/BattleAnimationClasses.h
Normal file
@ -0,0 +1,367 @@
|
||||
/*
|
||||
* BattleAnimations.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 "../../lib/battle/BattleHex.h"
|
||||
#include "../gui/Geometries.h"
|
||||
#include "BattleConstants.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CStack;
|
||||
class CCreature;
|
||||
class CSpell;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct SDL_Color;
|
||||
class ColorFilter;
|
||||
class BattleHero;
|
||||
class CAnimation;
|
||||
class BattleInterface;
|
||||
class CreatureAnimation;
|
||||
struct StackAttackedInfo;
|
||||
struct Point;
|
||||
|
||||
/// Base class of battle animations
|
||||
class BattleAnimation
|
||||
{
|
||||
protected:
|
||||
BattleInterface & owner;
|
||||
bool initialized;
|
||||
|
||||
std::vector<BattleAnimation *> & pendingAnimations();
|
||||
std::shared_ptr<CreatureAnimation> stackAnimation(const CStack * stack) const;
|
||||
bool stackFacingRight(const CStack * stack);
|
||||
void setStackFacingRight(const CStack * stack, bool facingRight);
|
||||
|
||||
virtual bool init() = 0; //to be called - if returned false, call again until returns true
|
||||
|
||||
public:
|
||||
ui32 ID; //unique identifier
|
||||
|
||||
bool isInitialized();
|
||||
bool tryInitialize();
|
||||
virtual void nextFrame() {} //call every new frame
|
||||
virtual ~BattleAnimation();
|
||||
|
||||
BattleAnimation(BattleInterface & owner);
|
||||
};
|
||||
|
||||
/// Sub-class which is responsible for managing the battle stack animation.
|
||||
class BattleStackAnimation : public BattleAnimation
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<CreatureAnimation> myAnim; //animation for our stack, managed by BattleInterface
|
||||
const CStack * stack; //id of stack whose animation it is
|
||||
|
||||
BattleStackAnimation(BattleInterface & owner, const CStack * _stack);
|
||||
void rotateStack(BattleHex hex);
|
||||
};
|
||||
|
||||
class StackActionAnimation : public BattleStackAnimation
|
||||
{
|
||||
ECreatureAnimType nextGroup;
|
||||
ECreatureAnimType currGroup;
|
||||
std::string sound;
|
||||
public:
|
||||
void setNextGroup( ECreatureAnimType group );
|
||||
void setGroup( ECreatureAnimType group );
|
||||
void setSound( std::string sound );
|
||||
|
||||
ECreatureAnimType getGroup() const;
|
||||
|
||||
StackActionAnimation(BattleInterface & owner, const CStack * _stack);
|
||||
~StackActionAnimation();
|
||||
|
||||
bool init() override;
|
||||
};
|
||||
|
||||
/// Animation of a defending unit
|
||||
class DefenceAnimation : public StackActionAnimation
|
||||
{
|
||||
public:
|
||||
DefenceAnimation(BattleInterface & owner, const CStack * stack);
|
||||
};
|
||||
|
||||
/// Animation of a hit unit
|
||||
class HittedAnimation : public StackActionAnimation
|
||||
{
|
||||
public:
|
||||
HittedAnimation(BattleInterface & owner, const CStack * stack);
|
||||
};
|
||||
|
||||
/// Animation of a dying unit
|
||||
class DeathAnimation : public StackActionAnimation
|
||||
{
|
||||
public:
|
||||
DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged);
|
||||
};
|
||||
|
||||
/// Resurrects stack from dead state
|
||||
class ResurrectionAnimation : public StackActionAnimation
|
||||
{
|
||||
public:
|
||||
ResurrectionAnimation(BattleInterface & owner, const CStack * _stack);
|
||||
};
|
||||
|
||||
class ColorTransformAnimation : public BattleStackAnimation
|
||||
{
|
||||
std::vector<ColorFilter> steps;
|
||||
std::vector<float> timePoints;
|
||||
const CSpell * spell;
|
||||
|
||||
float totalProgress;
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
|
||||
public:
|
||||
ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell);
|
||||
};
|
||||
|
||||
/// Base class for all animations that play during stack movement
|
||||
class StackMoveAnimation : public BattleStackAnimation
|
||||
{
|
||||
public:
|
||||
BattleHex nextHex;
|
||||
BattleHex prevHex;
|
||||
|
||||
protected:
|
||||
StackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex prevHex, BattleHex nextHex);
|
||||
};
|
||||
|
||||
/// Move animation of a creature
|
||||
class MovementAnimation : public StackMoveAnimation
|
||||
{
|
||||
private:
|
||||
std::vector<BattleHex> destTiles; //full path, includes already passed hexes
|
||||
ui32 curentMoveIndex; // index of nextHex in destTiles
|
||||
|
||||
double begX, begY; // starting position
|
||||
double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
|
||||
|
||||
double timeToMove; // full length of movement animation
|
||||
double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
|
||||
|
||||
public:
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
|
||||
MovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
|
||||
~MovementAnimation();
|
||||
};
|
||||
|
||||
/// Move end animation of a creature
|
||||
class MovementEndAnimation : public StackMoveAnimation
|
||||
{
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
MovementEndAnimation(BattleInterface & owner, const CStack * _stack, BattleHex destTile);
|
||||
~MovementEndAnimation();
|
||||
};
|
||||
|
||||
/// Move start animation of a creature
|
||||
class MovementStartAnimation : public StackMoveAnimation
|
||||
{
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
MovementStartAnimation(BattleInterface & owner, const CStack * _stack);
|
||||
};
|
||||
|
||||
/// Class responsible for animation of stack chaning direction (left <-> right)
|
||||
class ReverseAnimation : public StackMoveAnimation
|
||||
{
|
||||
void setupSecondPart();
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
ReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest);
|
||||
};
|
||||
|
||||
/// This class is responsible for managing the battle attack animation
|
||||
class AttackAnimation : public StackActionAnimation
|
||||
{
|
||||
protected:
|
||||
BattleHex dest; //attacked hex
|
||||
const CStack *defendingStack;
|
||||
const CStack *attackingStack;
|
||||
int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
|
||||
|
||||
const CCreature * getCreature() const;
|
||||
ECreatureAnimType findValidGroup( const std::vector<ECreatureAnimType> candidates ) const;
|
||||
|
||||
public:
|
||||
AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
|
||||
};
|
||||
|
||||
/// Hand-to-hand attack
|
||||
class MeleeAttackAnimation : public AttackAnimation
|
||||
{
|
||||
ECreatureAnimType getUpwardsGroup(bool multiAttack) const;
|
||||
ECreatureAnimType getForwardGroup(bool multiAttack) const;
|
||||
ECreatureAnimType getDownwardsGroup(bool multiAttack) const;
|
||||
|
||||
ECreatureAnimType selectGroup(bool multiAttack);
|
||||
|
||||
public:
|
||||
MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack);
|
||||
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
|
||||
class RangedAttackAnimation : public AttackAnimation
|
||||
{
|
||||
void setAnimationGroup();
|
||||
void initializeProjectile();
|
||||
void emitProjectile();
|
||||
void emitExplosion();
|
||||
|
||||
protected:
|
||||
bool projectileEmitted;
|
||||
|
||||
virtual ECreatureAnimType getUpwardsGroup() const = 0;
|
||||
virtual ECreatureAnimType getForwardGroup() const = 0;
|
||||
virtual ECreatureAnimType getDownwardsGroup() const = 0;
|
||||
|
||||
virtual void createProjectile(const Point & from, const Point & dest) const = 0;
|
||||
virtual uint32_t getAttackClimaxFrame() const = 0;
|
||||
|
||||
public:
|
||||
RangedAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
|
||||
~RangedAttackAnimation();
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
/// Shooting attack
|
||||
class ShootingAnimation : public RangedAttackAnimation
|
||||
{
|
||||
ECreatureAnimType getUpwardsGroup() const override;
|
||||
ECreatureAnimType getForwardGroup() const override;
|
||||
ECreatureAnimType getDownwardsGroup() const override;
|
||||
|
||||
void createProjectile(const Point & from, const Point & dest) const override;
|
||||
uint32_t getAttackClimaxFrame() const override;
|
||||
|
||||
public:
|
||||
ShootingAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
|
||||
|
||||
};
|
||||
|
||||
/// Catapult attack
|
||||
class CatapultAnimation : public ShootingAnimation
|
||||
{
|
||||
private:
|
||||
bool explosionEmitted;
|
||||
int catapultDamage;
|
||||
|
||||
public:
|
||||
CatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);
|
||||
|
||||
void createProjectile(const Point & from, const Point & dest) const override;
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
class CastAnimation : public RangedAttackAnimation
|
||||
{
|
||||
const CSpell * spell;
|
||||
|
||||
ECreatureAnimType getUpwardsGroup() const override;
|
||||
ECreatureAnimType getForwardGroup() const override;
|
||||
ECreatureAnimType getDownwardsGroup() const override;
|
||||
|
||||
void createProjectile(const Point & from, const Point & dest) const override;
|
||||
uint32_t getAttackClimaxFrame() const override;
|
||||
|
||||
public:
|
||||
CastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
|
||||
};
|
||||
|
||||
class DummyAnimation : public BattleAnimation
|
||||
{
|
||||
private:
|
||||
int counter;
|
||||
int howMany;
|
||||
public:
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
|
||||
DummyAnimation(BattleInterface & owner, int howManyFrames);
|
||||
};
|
||||
|
||||
/// Class that plays effect at one or more positions along with (single) sound effect
|
||||
class EffectAnimation : public BattleAnimation
|
||||
{
|
||||
std::string soundName;
|
||||
bool effectFinished;
|
||||
int effectFlags;
|
||||
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
std::vector<Point> positions;
|
||||
std::vector<BattleHex> battlehexes;
|
||||
|
||||
bool alignToBottom() const;
|
||||
bool waitForSound() const;
|
||||
bool forceOnTop() const;
|
||||
bool screenFill() const;
|
||||
|
||||
void onEffectFinished();
|
||||
void clearEffect();
|
||||
void playEffect();
|
||||
|
||||
public:
|
||||
enum EEffectFlags
|
||||
{
|
||||
ALIGN_TO_BOTTOM = 1,
|
||||
FORCE_ON_TOP = 2,
|
||||
SCREEN_FILL = 4,
|
||||
};
|
||||
|
||||
/// Create animation with screen-wide effect
|
||||
EffectAnimation(BattleInterface & owner, std::string animationName, int effects = 0);
|
||||
|
||||
/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
|
||||
EffectAnimation(BattleInterface & owner, std::string animationName, Point pos , int effects = 0);
|
||||
EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<Point> pos , int effects = 0);
|
||||
|
||||
/// Create animation positioned at certain hex(es)
|
||||
EffectAnimation(BattleInterface & owner, std::string animationName, BattleHex hex , int effects = 0);
|
||||
EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<BattleHex> hex, int effects = 0);
|
||||
|
||||
EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, BattleHex hex, int effects = 0);
|
||||
~EffectAnimation();
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
class HeroCastAnimation : public BattleAnimation
|
||||
{
|
||||
std::shared_ptr<BattleHero> hero;
|
||||
const CStack * target;
|
||||
const CSpell * spell;
|
||||
BattleHex tile;
|
||||
bool projectileEmitted;
|
||||
|
||||
void initializeProjectile();
|
||||
void emitProjectile();
|
||||
void emitAnimationEvent();
|
||||
|
||||
public:
|
||||
HeroCastAnimation(BattleInterface & owner, std::shared_ptr<BattleHero> hero, BattleHex dest, const CStack * defender, const CSpell * spell);
|
||||
|
||||
void nextFrame() override;
|
||||
bool init() override;
|
||||
};
|
92
client/battle/BattleConstants.h
Normal file
92
client/battle/BattleConstants.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* BattleConstants.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
|
||||
|
||||
enum class EBattleEffect
|
||||
{
|
||||
// list of battle effects that have hardcoded triggers
|
||||
MAGIC_MIRROR = 3,
|
||||
FIRE_SHIELD = 11,
|
||||
FEAR = 15,
|
||||
GOOD_LUCK = 18,
|
||||
GOOD_MORALE = 20,
|
||||
BAD_MORALE = 30,
|
||||
BAD_LUCK = 48,
|
||||
RESURRECT = 50,
|
||||
DRAIN_LIFE = 52,
|
||||
POISON = 67,
|
||||
DEATH_BLOW = 73,
|
||||
REGENERATION = 74,
|
||||
MANA_DRAIN = 77,
|
||||
RESISTANCE = 78,
|
||||
|
||||
INVALID = -1,
|
||||
};
|
||||
|
||||
enum class EAnimationEvents {
|
||||
OPENING = 0, // battle opening sound is playing
|
||||
ACTION = 1, // there are any ongoing animations
|
||||
MOVEMENT = 2, // stacks are moving or turning around
|
||||
BEFORE_HIT = 3, // effects played before all attack/defence/hit animations
|
||||
ATTACK = 4, // attack and defence animations are playing
|
||||
HIT = 5, // hit & death animations are playing
|
||||
AFTER_HIT = 6, // after all hit & death animations are over
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum class EHeroAnimType
|
||||
{
|
||||
HOLDING = 0,
|
||||
IDLE = 1, // idling movement that happens from time to time
|
||||
DEFEAT = 2, // played when army loses stack or on friendly fire
|
||||
VICTORY = 3, // when enemy stack killed or huge damage is dealt
|
||||
CAST_SPELL = 4 // spellcasting
|
||||
};
|
||||
|
||||
enum class ECreatureAnimType
|
||||
{
|
||||
INVALID = -1,
|
||||
|
||||
MOVING = 0,
|
||||
MOUSEON = 1,
|
||||
HOLDING = 2, // base idling animation
|
||||
HITTED = 3, // base animation for when stack is taking damage
|
||||
DEFENCE = 4, // alternative animation for defending in melee if stack spent its action on defending
|
||||
DEATH = 5,
|
||||
DEATH_RANGED = 6, // Optional, alternative animation for when stack is killed by ranged attack
|
||||
TURN_L = 7,
|
||||
TURN_R = 8,
|
||||
//TURN_L2 = 9, //unused - identical to TURN_L
|
||||
//TURN_R2 = 10, //unused - identical to TURN_R
|
||||
ATTACK_UP = 11,
|
||||
ATTACK_FRONT = 12,
|
||||
ATTACK_DOWN = 13,
|
||||
SHOOT_UP = 14, // Shooters only
|
||||
SHOOT_FRONT = 15, // Shooters only
|
||||
SHOOT_DOWN = 16, // Shooters only
|
||||
SPECIAL_UP = 17, // If empty, fallback to SPECIAL_FRONT
|
||||
SPECIAL_FRONT = 18, // Used for any special moves - dragon breath, spellcasting, Pit Lord/Ogre Mage ability
|
||||
SPECIAL_DOWN = 19, // If empty, fallback to SPECIAL_FRONT
|
||||
MOVE_START = 20, // small animation to be played before MOVING
|
||||
MOVE_END = 21, // small animation to be played after MOVING
|
||||
|
||||
DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
|
||||
DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
|
||||
RESURRECTION = 24, // new group, used for animating resurrection, if empty - reversed "DEATH" animation will be copiend here
|
||||
FROZEN = 25, // new group, used when stack animation is paused (e.g. petrified). If empty - consist of first frame from HOLDING animation
|
||||
|
||||
CAST_UP = 30,
|
||||
CAST_FRONT = 31,
|
||||
CAST_DOWN = 32,
|
||||
|
||||
GROUP_ATTACK_UP = 40,
|
||||
GROUP_ATTACK_FRONT = 41,
|
||||
GROUP_ATTACK_DOWN = 42
|
||||
};
|
168
client/battle/BattleEffectsController.cpp
Normal file
168
client/battle/BattleEffectsController.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* BattleEffectsController.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 "BattleEffectsController.h"
|
||||
|
||||
#include "BattleAnimationClasses.h"
|
||||
#include "BattleWindow.h"
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/Canvas.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/filesystem/ResourceID.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/IGameEventsReceiver.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
|
||||
BattleEffectsController::BattleEffectsController(BattleInterface & owner):
|
||||
owner(owner)
|
||||
{
|
||||
loadColorMuxers();
|
||||
}
|
||||
|
||||
void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHex & destTile)
|
||||
{
|
||||
displayEffect(effect, "", destTile);
|
||||
}
|
||||
|
||||
void BattleEffectsController::displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile)
|
||||
{
|
||||
size_t effectID = static_cast<size_t>(effect);
|
||||
|
||||
std::string customAnim = graphics->battleACToDef[effectID][0];
|
||||
|
||||
CCS->soundh->playSound( soundFile );
|
||||
|
||||
owner.stacksController->addNewAnim(new EffectAnimation(owner, customAnim, destTile));
|
||||
}
|
||||
|
||||
void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
|
||||
{
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID);
|
||||
if(!stack)
|
||||
{
|
||||
logGlobal->error("Invalid stack ID %d", bte.stackID);
|
||||
return;
|
||||
}
|
||||
//don't show animation when no HP is regenerated
|
||||
switch(bte.effect)
|
||||
{
|
||||
case Bonus::HP_REGENERATION:
|
||||
displayEffect(EBattleEffect::REGENERATION, "REGENER", stack->getPosition());
|
||||
break;
|
||||
case Bonus::MANA_DRAIN:
|
||||
displayEffect(EBattleEffect::MANA_DRAIN, "MANADRAI", stack->getPosition());
|
||||
break;
|
||||
case Bonus::POISON:
|
||||
displayEffect(EBattleEffect::POISON, "POISON", stack->getPosition());
|
||||
break;
|
||||
case Bonus::FEAR:
|
||||
displayEffect(EBattleEffect::FEAR, "FEAR", stack->getPosition());
|
||||
break;
|
||||
case Bonus::MORALE:
|
||||
{
|
||||
std::string hlp = CGI->generaltexth->allTexts[33];
|
||||
boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
|
||||
displayEffect(EBattleEffect::GOOD_MORALE, "GOODMRLE", stack->getPosition());
|
||||
owner.appendBattleLog(hlp);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
}
|
||||
|
||||
void BattleEffectsController::startAction(const BattleAction* action)
|
||||
{
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
|
||||
|
||||
switch(action->actionType)
|
||||
{
|
||||
case EActionType::WAIT:
|
||||
owner.appendBattleLog(stack->formatGeneralMessage(136));
|
||||
break;
|
||||
case EActionType::BAD_MORALE:
|
||||
owner.appendBattleLog(stack->formatGeneralMessage(-34));
|
||||
displayEffect(EBattleEffect::BAD_MORALE, "BADMRLE", stack->getPosition());
|
||||
break;
|
||||
}
|
||||
|
||||
//displaying special abilities
|
||||
auto actionTarget = action->getTarget(owner.curInt->cb.get());
|
||||
switch(action->actionType)
|
||||
{
|
||||
case EActionType::STACK_HEAL:
|
||||
displayEffect(EBattleEffect::REGENERATION, "REGENER", actionTarget.at(0).hexValue);
|
||||
break;
|
||||
}
|
||||
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
}
|
||||
|
||||
void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer)
|
||||
{
|
||||
for (auto & elem : battleEffects)
|
||||
{
|
||||
renderer.insert( EBattleFieldLayer::EFFECTS, elem.tile, [&elem](BattleRenderer::RendererRef canvas)
|
||||
{
|
||||
int currentFrame = static_cast<int>(floor(elem.currentFrame));
|
||||
currentFrame %= elem.animation->size();
|
||||
|
||||
auto img = elem.animation->getImage(currentFrame);
|
||||
|
||||
canvas.draw(img, elem.pos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleEffectsController::loadColorMuxers()
|
||||
{
|
||||
const JsonNode config(ResourceID("config/battleEffects.json"));
|
||||
|
||||
for(auto & muxer : config["colorMuxers"].Struct())
|
||||
{
|
||||
ColorMuxerEffect effect;
|
||||
std::string identifier = muxer.first;
|
||||
|
||||
for (const JsonNode & entry : muxer.second.Vector() )
|
||||
{
|
||||
effect.timePoints.push_back(entry["time"].Float());
|
||||
effect.filters.push_back(ColorFilter::genFromJson(entry));
|
||||
}
|
||||
colorMuxerEffects[identifier] = effect;
|
||||
}
|
||||
}
|
||||
|
||||
const ColorMuxerEffect & BattleEffectsController::getMuxerEffect(const std::string & name)
|
||||
{
|
||||
static const ColorMuxerEffect emptyEffect;
|
||||
|
||||
if (colorMuxerEffects.count(name))
|
||||
return colorMuxerEffects[name];
|
||||
|
||||
logAnim->error("Failed to find color muxer effect named '%s'!", name);
|
||||
return emptyEffect;
|
||||
}
|
67
client/battle/BattleEffectsController.h
Normal file
67
client/battle/BattleEffectsController.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* BattleEffectsController.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 "../../lib/battle/BattleHex.h"
|
||||
#include "../gui/Geometries.h"
|
||||
#include "BattleConstants.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class BattleAction;
|
||||
struct BattleTriggerEffect;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct ColorMuxerEffect;
|
||||
class CAnimation;
|
||||
class Canvas;
|
||||
class BattleInterface;
|
||||
class BattleRenderer;
|
||||
class EffectAnimation;
|
||||
|
||||
/// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,...
|
||||
struct BattleEffect
|
||||
{
|
||||
Point pos; //position on the screen
|
||||
float currentFrame;
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
|
||||
BattleHex tile; //Indicates if effect which hex the effect is drawn on
|
||||
};
|
||||
|
||||
/// Controls rendering of effects in battle, e.g. from spells, abilities and various other actions like morale
|
||||
class BattleEffectsController
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
/// list of current effects that are being displayed on screen (spells & creature abilities)
|
||||
std::vector<BattleEffect> battleEffects;
|
||||
|
||||
std::map<std::string, ColorMuxerEffect> colorMuxerEffects;
|
||||
|
||||
void loadColorMuxers();
|
||||
public:
|
||||
const ColorMuxerEffect &getMuxerEffect(const std::string & name);
|
||||
|
||||
BattleEffectsController(BattleInterface & owner);
|
||||
|
||||
void startAction(const BattleAction* action);
|
||||
|
||||
//displays custom effect on the battlefield
|
||||
void displayEffect(EBattleEffect effect, const BattleHex & destTile);
|
||||
void displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile);
|
||||
|
||||
void battleTriggerEffect(const BattleTriggerEffect & bte);
|
||||
|
||||
void collectRenderableObjects(BattleRenderer & renderer);
|
||||
|
||||
friend class EffectAnimation;
|
||||
};
|
556
client/battle/BattleFieldController.cpp
Normal file
556
client/battle/BattleFieldController.cpp
Normal file
@ -0,0 +1,556 @@
|
||||
/*
|
||||
* BattleFieldController.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 "BattleFieldController.h"
|
||||
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleActionsController.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "BattleEffectsController.h"
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleObstacleController.h"
|
||||
#include "BattleProjectileController.h"
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../widgets/AdventureMapClasses.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/CCursorHandler.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/BattleFieldHandler.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
|
||||
BattleFieldController::BattleFieldController(BattleInterface & owner):
|
||||
owner(owner)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
strongInterest = true;
|
||||
|
||||
//preparing cells and hexes
|
||||
cellBorder = IImage::createFromFile("CCELLGRD.BMP");
|
||||
cellShade = IImage::createFromFile("CCELLSHD.BMP");
|
||||
|
||||
if(!owner.siegeController)
|
||||
{
|
||||
auto bfieldType = owner.curInt->cb->battleGetBattlefieldType();
|
||||
|
||||
if(bfieldType == BattleField::NONE)
|
||||
logGlobal->error("Invalid battlefield returned for current battle");
|
||||
else
|
||||
background = IImage::createFromFile(bfieldType.getInfo()->graphics);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string backgroundName = owner.siegeController->getBattleBackgroundName();
|
||||
background = IImage::createFromFile(backgroundName);
|
||||
}
|
||||
pos.w = background->width();
|
||||
pos.h = background->height();
|
||||
|
||||
//preparing graphic with cell borders
|
||||
cellBorders = std::make_unique<Canvas>(Point(background->width(), background->height()));
|
||||
|
||||
for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
|
||||
{
|
||||
if ( i % GameConstants::BFIELD_WIDTH == 0)
|
||||
continue;
|
||||
if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
|
||||
continue;
|
||||
|
||||
cellBorders->draw(cellBorder, hexPositionLocal(i).topLeft());
|
||||
}
|
||||
|
||||
backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
|
||||
|
||||
for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
|
||||
{
|
||||
auto hex = std::make_shared<ClickableHex>();
|
||||
hex->myNumber = h;
|
||||
hex->pos = hexPositionAbsolute(h);
|
||||
hex->myInterface = &owner;
|
||||
bfield.push_back(hex);
|
||||
}
|
||||
|
||||
auto accessibility = owner.curInt->cb->getAccesibility();
|
||||
for(int i = 0; i < accessibility.size(); i++)
|
||||
stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
|
||||
|
||||
addUsedEvents(MOVE);
|
||||
LOCPLINT->cingconsole->pos = this->pos;
|
||||
}
|
||||
|
||||
void BattleFieldController::createHeroes()
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
|
||||
// create heroes as part of our constructor for correct positioning inside battlefield
|
||||
if(owner.attackingHeroInstance)
|
||||
owner.attackingHero = std::make_shared<BattleHero>(owner, owner.attackingHeroInstance, false);
|
||||
|
||||
if(owner.defendingHeroInstance)
|
||||
owner.defendingHero = std::make_shared<BattleHero>(owner, owner.defendingHeroInstance, true);
|
||||
}
|
||||
|
||||
void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event)
|
||||
{
|
||||
BattleHex selectedHex = getHoveredHex();
|
||||
|
||||
owner.actionsController->handleHex(selectedHex, MOVE);
|
||||
}
|
||||
|
||||
|
||||
void BattleFieldController::renderBattlefield(Canvas & canvas)
|
||||
{
|
||||
Canvas clippedCanvas(canvas, pos);
|
||||
|
||||
showBackground(clippedCanvas);
|
||||
|
||||
BattleRenderer renderer(owner);
|
||||
|
||||
renderer.execute(clippedCanvas);
|
||||
|
||||
owner.projectilesController->showProjectiles(clippedCanvas);
|
||||
}
|
||||
|
||||
void BattleFieldController::showBackground(Canvas & canvas)
|
||||
{
|
||||
if (owner.stacksController->getActiveStack() != nullptr ) //&& creAnims[stacksController->getActiveStack()->ID]->isIdle() //show everything with range
|
||||
showBackgroundImageWithHexes(canvas);
|
||||
else
|
||||
showBackgroundImage(canvas);
|
||||
|
||||
showHighlightedHexes(canvas);
|
||||
|
||||
}
|
||||
|
||||
void BattleFieldController::showBackgroundImage(Canvas & canvas)
|
||||
{
|
||||
canvas.draw(background, Point(0, 0));
|
||||
|
||||
owner.obstacleController->showAbsoluteObstacles(canvas);
|
||||
if ( owner.siegeController )
|
||||
owner.siegeController->showAbsoluteObstacles(canvas);
|
||||
|
||||
if (settings["battle"]["cellBorders"].Bool())
|
||||
canvas.draw(*cellBorders, Point(0, 0));
|
||||
}
|
||||
|
||||
void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
|
||||
{
|
||||
canvas.draw(*backgroundWithHexes.get(), Point(0, 0));
|
||||
}
|
||||
|
||||
void BattleFieldController::redrawBackgroundWithHexes()
|
||||
{
|
||||
const CStack *activeStack = owner.stacksController->getActiveStack();
|
||||
std::vector<BattleHex> attackableHexes;
|
||||
if (activeStack)
|
||||
occupyableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
|
||||
|
||||
auto accessibility = owner.curInt->cb->getAccesibility();
|
||||
|
||||
for(int i = 0; i < accessibility.size(); i++)
|
||||
stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
|
||||
|
||||
//prepare background graphic with hexes and shaded hexes
|
||||
backgroundWithHexes->draw(background, Point(0,0));
|
||||
owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes);
|
||||
if ( owner.siegeController )
|
||||
owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes);
|
||||
|
||||
if (settings["battle"]["stackRange"].Bool())
|
||||
{
|
||||
std::vector<BattleHex> hexesToShade = occupyableHexes;
|
||||
hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
|
||||
for (BattleHex hex : hexesToShade)
|
||||
{
|
||||
backgroundWithHexes->draw(cellShade, hexPositionLocal(hex).topLeft());
|
||||
}
|
||||
}
|
||||
|
||||
if(settings["battle"]["cellBorders"].Bool())
|
||||
backgroundWithHexes->draw(*cellBorders, Point(0, 0));
|
||||
}
|
||||
|
||||
void BattleFieldController::showHighlightedHex(Canvas & canvas, BattleHex hex, bool darkBorder)
|
||||
{
|
||||
Point hexPos = hexPositionLocal(hex).topLeft();
|
||||
|
||||
canvas.draw(cellShade, hexPos);
|
||||
if(!darkBorder && settings["battle"]["cellBorders"].Bool())
|
||||
canvas.draw(cellBorder, hexPos);
|
||||
}
|
||||
|
||||
std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
|
||||
{
|
||||
std::set<BattleHex> result;
|
||||
|
||||
if ( !owner.stacksController->getActiveStack())
|
||||
return result;
|
||||
|
||||
if ( !settings["battle"]["stackRange"].Bool())
|
||||
return result;
|
||||
|
||||
auto hoveredHex = getHoveredHex();
|
||||
|
||||
std::set<BattleHex> set = owner.curInt->cb->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex, attackingHex);
|
||||
for(BattleHex hex : set)
|
||||
result.insert(hex);
|
||||
|
||||
// display the movement shadow of the stack at b (i.e. stack under mouse)
|
||||
const CStack * const shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, false);
|
||||
if(shere && shere != owner.stacksController->getActiveStack() && shere->alive())
|
||||
{
|
||||
std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
|
||||
for(BattleHex hex : v)
|
||||
result.insert(hex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
|
||||
{
|
||||
std::set<BattleHex> result;
|
||||
auto hoveredHex = getHoveredHex();
|
||||
|
||||
if(!settings["battle"]["mouseShadow"].Bool())
|
||||
return result;
|
||||
|
||||
const spells::Caster *caster = nullptr;
|
||||
const CSpell *spell = nullptr;
|
||||
|
||||
spells::Mode mode = spells::Mode::HERO;
|
||||
|
||||
if(owner.actionsController->spellcastingModeActive())//hero casts spell
|
||||
{
|
||||
spell = owner.actionsController->selectedSpell().toSpell();
|
||||
caster = owner.getActiveHero();
|
||||
}
|
||||
else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
|
||||
{
|
||||
spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
|
||||
caster = owner.stacksController->getActiveStack();
|
||||
mode = spells::Mode::CREATURE_ACTIVE;
|
||||
}
|
||||
|
||||
if(caster && spell) //when casting spell
|
||||
{
|
||||
// printing shaded hex(es)
|
||||
spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
|
||||
auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
|
||||
|
||||
for(BattleHex shadedHex : shaded)
|
||||
{
|
||||
if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
|
||||
result.insert(shadedHex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
|
||||
{
|
||||
const CStack * stack = owner.stacksController->getActiveStack();
|
||||
auto hoveredHex = getHoveredHex();
|
||||
|
||||
if (stack)
|
||||
{
|
||||
std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(stack, false, nullptr);
|
||||
|
||||
auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
|
||||
if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
|
||||
{
|
||||
if (isTileAttackable(hoveredHex))
|
||||
{
|
||||
BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
|
||||
|
||||
if (stack->doubleWide())
|
||||
return {attackFromHex, stack->occupiedHex(attackFromHex)};
|
||||
else
|
||||
return {attackFromHex};
|
||||
}
|
||||
}
|
||||
|
||||
if (vstd::contains(v,hoveredHex))
|
||||
{
|
||||
if (stack->doubleWide())
|
||||
return {hoveredHex, stack->occupiedHex(hoveredHex)};
|
||||
else
|
||||
return {hoveredHex};
|
||||
}
|
||||
if (stack->doubleWide())
|
||||
{
|
||||
for (auto const & hex : v)
|
||||
{
|
||||
if (stack->occupiedHex(hex) == hoveredHex)
|
||||
return { hoveredHex, hex };
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void BattleFieldController::showHighlightedHexes(Canvas & canvas)
|
||||
{
|
||||
std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
|
||||
std::set<BattleHex> hoveredSpell = getHighlightedHexesSpellRange();
|
||||
std::set<BattleHex> hoveredMove = getHighlightedHexesMovementTarget();
|
||||
|
||||
auto const & hoveredMouse = owner.actionsController->spellcastingModeActive() ? hoveredSpell : hoveredMove;
|
||||
|
||||
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
|
||||
{
|
||||
bool stack = hoveredStack.count(b);
|
||||
bool mouse = hoveredMouse.count(b);
|
||||
|
||||
if ( stack && mouse )
|
||||
{
|
||||
// area where enemy stack can move AND affected by mouse cursor - create darker highlight by blitting twice
|
||||
showHighlightedHex(canvas, b, true);
|
||||
showHighlightedHex(canvas, b, true);
|
||||
}
|
||||
if ( !stack && mouse )
|
||||
{
|
||||
showHighlightedHex(canvas, b, true);
|
||||
}
|
||||
if ( stack && !mouse )
|
||||
{
|
||||
showHighlightedHex(canvas, b, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect BattleFieldController::hexPositionLocal(BattleHex hex) const
|
||||
{
|
||||
int x = 14 + ((hex.getY())%2==0 ? 22 : 0) + 44*hex.getX();
|
||||
int y = 86 + 42 *hex.getY();
|
||||
int w = cellShade->width();
|
||||
int h = cellShade->height();
|
||||
return Rect(x, y, w, h);
|
||||
}
|
||||
|
||||
Rect BattleFieldController::hexPositionAbsolute(BattleHex hex) const
|
||||
{
|
||||
return hexPositionLocal(hex) + pos.topLeft();
|
||||
}
|
||||
|
||||
bool BattleFieldController::isPixelInHex(Point const & position)
|
||||
{
|
||||
return !cellShade->isTransparent(position);
|
||||
}
|
||||
|
||||
BattleHex BattleFieldController::getHoveredHex()
|
||||
{
|
||||
for ( auto const & hex : bfield)
|
||||
if (hex->hovered && hex->strictHovered)
|
||||
return hex->myNumber;
|
||||
|
||||
return BattleHex::INVALID;
|
||||
}
|
||||
|
||||
void BattleFieldController::setBattleCursor(BattleHex myNumber)
|
||||
{
|
||||
Point cursorPos = CCS->curh->position();
|
||||
|
||||
std::vector<Cursor::Combat> sectorCursor = {
|
||||
Cursor::Combat::HIT_SOUTHEAST,
|
||||
Cursor::Combat::HIT_SOUTHWEST,
|
||||
Cursor::Combat::HIT_WEST,
|
||||
Cursor::Combat::HIT_NORTHWEST,
|
||||
Cursor::Combat::HIT_NORTHEAST,
|
||||
Cursor::Combat::HIT_EAST,
|
||||
Cursor::Combat::HIT_SOUTH,
|
||||
Cursor::Combat::HIT_NORTH,
|
||||
};
|
||||
|
||||
auto direction = static_cast<size_t>(selectAttackDirection(myNumber, cursorPos));
|
||||
|
||||
assert(direction != -1);
|
||||
if (direction != -1)
|
||||
CCS->curh->set(sectorCursor[direction]);
|
||||
}
|
||||
|
||||
BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber, const Point & cursorPos)
|
||||
{
|
||||
const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
|
||||
auto neighbours = myNumber.allNeighbouringTiles();
|
||||
// 0 1
|
||||
// 5 x 2
|
||||
// 4 3
|
||||
|
||||
// if true - our current stack can move into this hex (and attack)
|
||||
std::array<bool, 8> attackAvailability;
|
||||
|
||||
if (doubleWide)
|
||||
{
|
||||
// For double-hexes we need to ensure that both hexes needed for this direction are occupyable:
|
||||
// | -0- | -1- | -2- | -3- | -4- | -5- | -6- | -7-
|
||||
// | o o - | - o o | - - | - - | - - | - - | o o | - -
|
||||
// | - x - | - x - | - x o o| - x - | - x - |o o x - | - x - | - x -
|
||||
// | - - | - - | - - | - o o | o o - | - - | - - | o o
|
||||
|
||||
for (size_t i : { 1, 2, 3})
|
||||
attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
|
||||
|
||||
for (size_t i : { 4, 5, 0})
|
||||
attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false));
|
||||
|
||||
attackAvailability[6] = vstd::contains(occupyableHexes, neighbours[0]) && vstd::contains(occupyableHexes, neighbours[1]);
|
||||
attackAvailability[7] = vstd::contains(occupyableHexes, neighbours[3]) && vstd::contains(occupyableHexes, neighbours[4]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < 6; ++i)
|
||||
attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]);
|
||||
|
||||
attackAvailability[6] = false;
|
||||
attackAvailability[7] = false;
|
||||
}
|
||||
|
||||
// Zero available tiles to attack from
|
||||
if ( vstd::find(attackAvailability, true) == attackAvailability.end())
|
||||
{
|
||||
logGlobal->error("Error: cannot find a hex to attack hex %d from!", myNumber);
|
||||
return BattleHex::NONE;
|
||||
}
|
||||
|
||||
// For each valid direction, select position to test against
|
||||
std::array<Point, 8> testPoint;
|
||||
|
||||
for (size_t i = 0; i < 6; ++i)
|
||||
if (attackAvailability[i])
|
||||
testPoint[i] = hexPositionAbsolute(neighbours[i]).center();
|
||||
|
||||
// For bottom/top directions select central point, but move it a bit away from true center to reduce zones allocated to them
|
||||
if (attackAvailability[6])
|
||||
testPoint[6] = (hexPositionAbsolute(neighbours[0]).center() + hexPositionAbsolute(neighbours[1]).center()) / 2 + Point(0, -5);
|
||||
|
||||
if (attackAvailability[7])
|
||||
testPoint[7] = (hexPositionAbsolute(neighbours[3]).center() + hexPositionAbsolute(neighbours[4]).center()) / 2 + Point(0, 5);
|
||||
|
||||
// Compute distance between tested position & cursor position and pick nearest
|
||||
std::array<int, 8> distance2;
|
||||
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
if (attackAvailability[i])
|
||||
distance2[i] = (testPoint[i].y - cursorPos.y)*(testPoint[i].y - cursorPos.y) + (testPoint[i].x - cursorPos.x)*(testPoint[i].x - cursorPos.x);
|
||||
|
||||
size_t nearest = -1;
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
if (attackAvailability[i] && (nearest == -1 || distance2[i] < distance2[nearest]) )
|
||||
nearest = i;
|
||||
|
||||
assert(nearest != -1);
|
||||
return BattleHex::EDir(nearest);
|
||||
}
|
||||
|
||||
BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
|
||||
{
|
||||
BattleHex::EDir direction = selectAttackDirection(attackTarget, CCS->curh->position());
|
||||
|
||||
const CStack * attacker = owner.stacksController->getActiveStack();
|
||||
|
||||
assert(direction != BattleHex::NONE);
|
||||
assert(attacker);
|
||||
|
||||
if (!attacker->doubleWide())
|
||||
{
|
||||
assert(direction != BattleHex::BOTTOM);
|
||||
assert(direction != BattleHex::TOP);
|
||||
return attackTarget.cloneInDirection(direction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to find position of right hex of double-hex creature (or left for defending side)
|
||||
// | TOP_LEFT |TOP_RIGHT | RIGHT |BOTTOM_RIGHT|BOTTOM_LEFT| LEFT | TOP |BOTTOM
|
||||
// | o o - | - o o | - - | - - | - - | - - | o o | - -
|
||||
// | - x - | - x - | - x o o| - x - | - x - |o o x - | - x - | - x -
|
||||
// | - - | - - | - - | - o o | o o - | - - | - - | o o
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case BattleHex::TOP_LEFT:
|
||||
case BattleHex::LEFT:
|
||||
case BattleHex::BOTTOM_LEFT:
|
||||
{
|
||||
if ( attacker->side == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(direction);
|
||||
else
|
||||
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::LEFT);
|
||||
}
|
||||
|
||||
case BattleHex::TOP_RIGHT:
|
||||
case BattleHex::RIGHT:
|
||||
case BattleHex::BOTTOM_RIGHT:
|
||||
{
|
||||
if ( attacker->side == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::RIGHT);
|
||||
else
|
||||
return attackTarget.cloneInDirection(direction);
|
||||
}
|
||||
|
||||
case BattleHex::TOP:
|
||||
{
|
||||
if ( attacker->side == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(BattleHex::TOP_RIGHT);
|
||||
else
|
||||
return attackTarget.cloneInDirection(BattleHex::TOP_LEFT);
|
||||
}
|
||||
|
||||
case BattleHex::BOTTOM:
|
||||
{
|
||||
if ( attacker->side == BattleSide::ATTACKER )
|
||||
return attackTarget.cloneInDirection(BattleHex::BOTTOM_RIGHT);
|
||||
else
|
||||
return attackTarget.cloneInDirection(BattleHex::BOTTOM_LEFT);
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
return attackTarget.cloneInDirection(BattleHex::LEFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleFieldController::isTileAttackable(const BattleHex & number) const
|
||||
{
|
||||
for (auto & elem : occupyableHexes)
|
||||
{
|
||||
if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const
|
||||
{
|
||||
return stackCountOutsideHexes[number];
|
||||
}
|
||||
|
||||
void BattleFieldController::showAll(SDL_Surface * to)
|
||||
{
|
||||
show(to);
|
||||
}
|
||||
|
||||
void BattleFieldController::show(SDL_Surface * to)
|
||||
{
|
||||
owner.stacksController->update();
|
||||
owner.obstacleController->update();
|
||||
|
||||
Canvas canvas(to);
|
||||
|
||||
renderBattlefield(canvas);
|
||||
}
|
101
client/battle/BattleFieldController.h
Normal file
101
client/battle/BattleFieldController.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* BattleFieldController.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 "../../lib/battle/BattleHex.h"
|
||||
#include "../gui/CIntObject.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CStack;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct Rect;
|
||||
struct Point;
|
||||
|
||||
class ClickableHex;
|
||||
class BattleHero;
|
||||
class Canvas;
|
||||
class IImage;
|
||||
class BattleInterface;
|
||||
|
||||
/// Handles battlefield grid as well as rendering of background layer of battle interface
|
||||
class BattleFieldController : public CIntObject
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
std::shared_ptr<IImage> background;
|
||||
std::shared_ptr<IImage> cellBorder;
|
||||
std::shared_ptr<IImage> cellShade;
|
||||
|
||||
/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
|
||||
std::unique_ptr<Canvas> backgroundWithHexes;
|
||||
|
||||
/// Canvas that contains cell borders of all tiles in the battlefield
|
||||
std::unique_ptr<Canvas> cellBorders;
|
||||
|
||||
/// hex from which the stack would perform attack with current cursor
|
||||
BattleHex attackingHex;
|
||||
|
||||
/// hexes to which currently active stack can move
|
||||
std::vector<BattleHex> occupyableHexes;
|
||||
|
||||
/// hexes that when in front of a unit cause it's amount box to move back
|
||||
std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
|
||||
|
||||
std::vector<std::shared_ptr<ClickableHex>> bfield;
|
||||
|
||||
void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder);
|
||||
|
||||
std::set<BattleHex> getHighlightedHexesStackRange();
|
||||
std::set<BattleHex> getHighlightedHexesSpellRange();
|
||||
std::set<BattleHex> getHighlightedHexesMovementTarget();
|
||||
|
||||
void showBackground(Canvas & canvas);
|
||||
void showBackgroundImage(Canvas & canvas);
|
||||
void showBackgroundImageWithHexes(Canvas & canvas);
|
||||
void showHighlightedHexes(Canvas & canvas);
|
||||
|
||||
BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point);
|
||||
|
||||
void mouseMoved(const SDL_MouseMotionEvent &event) override;
|
||||
void showAll(SDL_Surface * to) override;
|
||||
void show(SDL_Surface * to) override;
|
||||
|
||||
public:
|
||||
BattleFieldController(BattleInterface & owner);
|
||||
|
||||
void createHeroes();
|
||||
|
||||
void redrawBackgroundWithHexes();
|
||||
void renderBattlefield(Canvas & canvas);
|
||||
|
||||
/// Returns position of hex relative to owner (BattleInterface)
|
||||
Rect hexPositionLocal(BattleHex hex) const;
|
||||
|
||||
/// Returns position of hex relative to game window
|
||||
Rect hexPositionAbsolute(BattleHex hex) const;
|
||||
|
||||
/// Checks whether selected pixel is transparent, uses local coordinates of a hex
|
||||
bool isPixelInHex(Point const & position);
|
||||
|
||||
/// Returns ID of currently hovered hex or BattleHex::INVALID if none
|
||||
BattleHex getHoveredHex();
|
||||
|
||||
/// returns true if selected tile can be attacked in melee by current stack
|
||||
bool isTileAttackable(const BattleHex & number) const;
|
||||
|
||||
/// returns true if stack should render its stack count image in default position - outside own hex
|
||||
bool stackCountOutsideHex(const BattleHex & number) const;
|
||||
|
||||
void setBattleCursor(BattleHex myNumber);
|
||||
BattleHex fromWhichHexAttack(BattleHex myNumber);
|
||||
};
|
768
client/battle/BattleInterface.cpp
Normal file
768
client/battle/BattleInterface.cpp
Normal file
@ -0,0 +1,768 @@
|
||||
/*
|
||||
* BattleInterface.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 "BattleInterface.h"
|
||||
|
||||
#include "BattleAnimationClasses.h"
|
||||
#include "BattleActionsController.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "CreatureAnimation.h"
|
||||
#include "BattleProjectileController.h"
|
||||
#include "BattleEffectsController.h"
|
||||
#include "BattleObstacleController.h"
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleWindow.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMessage.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/CCursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../windows/CAdvmapInterface.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/CondSh.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
|
||||
CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
|
||||
|
||||
BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
|
||||
const CGHeroInstance *hero1, const CGHeroInstance *hero2,
|
||||
std::shared_ptr<CPlayerInterface> att,
|
||||
std::shared_ptr<CPlayerInterface> defen,
|
||||
std::shared_ptr<CPlayerInterface> spectatorInt)
|
||||
: attackingHeroInstance(hero1)
|
||||
, defendingHeroInstance(hero2)
|
||||
, attackerInt(att)
|
||||
, defenderInt(defen)
|
||||
, curInt(att)
|
||||
, myTurn(false)
|
||||
, moveSoundHander(-1)
|
||||
{
|
||||
for ( auto & event : animationEvents)
|
||||
event.setn(false);
|
||||
|
||||
if(spectatorInt)
|
||||
{
|
||||
curInt = spectatorInt;
|
||||
}
|
||||
else if(!curInt)
|
||||
{
|
||||
//May happen when we are defending during network MP game -> attacker interface is just not present
|
||||
curInt = defenderInt;
|
||||
}
|
||||
|
||||
givenCommand.setn(nullptr);
|
||||
|
||||
//hot-seat -> check tactics for both players (defender may be local human)
|
||||
if(attackerInt && attackerInt->cb->battleGetTacticDist())
|
||||
tacticianInterface = attackerInt;
|
||||
else if(defenderInt && defenderInt->cb->battleGetTacticDist())
|
||||
tacticianInterface = defenderInt;
|
||||
|
||||
//if we found interface of player with tactics, then enter tactics mode
|
||||
tacticsMode = static_cast<bool>(tacticianInterface);
|
||||
|
||||
//initializing armies
|
||||
this->army1 = army1;
|
||||
this->army2 = army2;
|
||||
|
||||
const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
|
||||
if(town && town->hasFort())
|
||||
siegeController.reset(new BattleSiegeController(*this, town));
|
||||
|
||||
windowObject = std::make_shared<BattleWindow>(*this);
|
||||
projectilesController.reset(new BattleProjectileController(*this));
|
||||
stacksController.reset( new BattleStacksController(*this));
|
||||
actionsController.reset( new BattleActionsController(*this));
|
||||
effectsController.reset(new BattleEffectsController(*this));
|
||||
obstacleController.reset(new BattleObstacleController(*this));
|
||||
|
||||
CCS->musich->stopMusic();
|
||||
setAnimationCondition(EAnimationEvents::OPENING, true);
|
||||
battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
|
||||
auto onIntroPlayed = [this]()
|
||||
{
|
||||
if(LOCPLINT->battleInt)
|
||||
{
|
||||
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
|
||||
onIntroSoundPlayed();
|
||||
}
|
||||
};
|
||||
|
||||
GH.pushInt(windowObject);
|
||||
windowObject->blockUI(true);
|
||||
windowObject->updateQueue();
|
||||
|
||||
if (battleIntroSoundChannel != -1)
|
||||
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
||||
else
|
||||
onIntroSoundPlayed();
|
||||
}
|
||||
|
||||
void BattleInterface::onIntroSoundPlayed()
|
||||
{
|
||||
setAnimationCondition(EAnimationEvents::OPENING, false);
|
||||
CCS->musich->playMusicFromSet("battle", true, true);
|
||||
if(tacticsMode)
|
||||
tacticNextStack(nullptr);
|
||||
activateStack();
|
||||
battleIntroSoundChannel = -1;
|
||||
}
|
||||
|
||||
BattleInterface::~BattleInterface()
|
||||
{
|
||||
CPlayerInterface::battleInt = nullptr;
|
||||
givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
|
||||
|
||||
if (adventureInt && adventureInt->selection)
|
||||
{
|
||||
//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
|
||||
const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
|
||||
CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
|
||||
}
|
||||
|
||||
// may happen if user decided to close game while in battle
|
||||
if (getAnimationCondition(EAnimationEvents::ACTION) == true)
|
||||
logGlobal->error("Shutting down BattleInterface during animation playback!");
|
||||
setAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
}
|
||||
|
||||
void BattleInterface::setPrintCellBorders(bool set)
|
||||
{
|
||||
Settings cellBorders = settings.write["battle"]["cellBorders"];
|
||||
cellBorders->Bool() = set;
|
||||
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
GH.totalRedraw();
|
||||
}
|
||||
|
||||
void BattleInterface::setPrintStackRange(bool set)
|
||||
{
|
||||
Settings stackRange = settings.write["battle"]["stackRange"];
|
||||
stackRange->Bool() = set;
|
||||
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
GH.totalRedraw();
|
||||
}
|
||||
|
||||
void BattleInterface::setPrintMouseShadow(bool set)
|
||||
{
|
||||
Settings shadow = settings.write["battle"]["mouseShadow"];
|
||||
shadow->Bool() = set;
|
||||
}
|
||||
|
||||
void BattleInterface::stackReset(const CStack * stack)
|
||||
{
|
||||
stacksController->stackReset(stack);
|
||||
}
|
||||
|
||||
void BattleInterface::stackAdded(const CStack * stack)
|
||||
{
|
||||
stacksController->stackAdded(stack, false);
|
||||
}
|
||||
|
||||
void BattleInterface::stackRemoved(uint32_t stackID)
|
||||
{
|
||||
stacksController->stackRemoved(stackID);
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
windowObject->updateQueue();
|
||||
}
|
||||
|
||||
void BattleInterface::stackActivated(const CStack *stack)
|
||||
{
|
||||
stacksController->stackActivated(stack);
|
||||
}
|
||||
|
||||
void BattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport)
|
||||
{
|
||||
if (teleport)
|
||||
stacksController->stackTeleported(stack, destHex, distance);
|
||||
else
|
||||
stacksController->stackMoved(stack, destHex, distance);
|
||||
}
|
||||
|
||||
void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
|
||||
{
|
||||
stacksController->stacksAreAttacked(attackedInfos);
|
||||
|
||||
std::array<int, 2> killedBySide = {0, 0};
|
||||
|
||||
int targets = 0;
|
||||
for(const StackAttackedInfo & attackedInfo : attackedInfos)
|
||||
{
|
||||
++targets;
|
||||
|
||||
ui8 side = attackedInfo.defender->side;
|
||||
killedBySide.at(side) += attackedInfo.amountKilled;
|
||||
}
|
||||
|
||||
for(ui8 side = 0; side < 2; side++)
|
||||
{
|
||||
if(killedBySide.at(side) > killedBySide.at(1-side))
|
||||
setHeroAnimation(side, EHeroAnimType::DEFEAT);
|
||||
else if(killedBySide.at(side) < killedBySide.at(1-side))
|
||||
setHeroAnimation(side, EHeroAnimType::VICTORY);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo )
|
||||
{
|
||||
stacksController->stackAttacking(attackInfo);
|
||||
}
|
||||
|
||||
void BattleInterface::newRoundFirst( int round )
|
||||
{
|
||||
waitForAnimationCondition(EAnimationEvents::OPENING, false);
|
||||
}
|
||||
|
||||
void BattleInterface::newRound(int number)
|
||||
{
|
||||
console->addText(CGI->generaltexth->allTexts[412]);
|
||||
}
|
||||
|
||||
void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
|
||||
{
|
||||
const CStack * actor = nullptr;
|
||||
if(action != EActionType::HERO_SPELL && action != EActionType::RETREAT && action != EActionType::SURRENDER)
|
||||
{
|
||||
actor = stacksController->getActiveStack();
|
||||
}
|
||||
|
||||
auto side = curInt->cb->playerToSide(curInt->playerID);
|
||||
if(!side)
|
||||
{
|
||||
logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
|
||||
return;
|
||||
}
|
||||
|
||||
auto ba = new BattleAction(); //is deleted in CPlayerInterface::stacksController->getActiveStack()()
|
||||
ba->side = side.get();
|
||||
ba->actionType = action;
|
||||
ba->aimToHex(tile);
|
||||
ba->actionSubtype = additional;
|
||||
|
||||
sendCommand(ba, actor);
|
||||
}
|
||||
|
||||
void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
|
||||
{
|
||||
command->stackNumber = actor ? actor->unitId() : ((command->side == BattleSide::ATTACKER) ? -1 : -2);
|
||||
|
||||
if(!tacticsMode)
|
||||
{
|
||||
logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
|
||||
myTurn = false;
|
||||
stacksController->setActiveStack(nullptr);
|
||||
givenCommand.setn(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
curInt->cb->battleMakeTacticAction(command);
|
||||
vstd::clear_pointer(command);
|
||||
stacksController->setActiveStack(nullptr);
|
||||
//next stack will be activated when action ends
|
||||
}
|
||||
}
|
||||
|
||||
const CGHeroInstance * BattleInterface::getActiveHero()
|
||||
{
|
||||
const CStack *attacker = stacksController->getActiveStack();
|
||||
if(!attacker)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(attacker->side == BattleSide::ATTACKER)
|
||||
{
|
||||
return attackingHeroInstance;
|
||||
}
|
||||
|
||||
return defendingHeroInstance;
|
||||
}
|
||||
|
||||
void BattleInterface::stackIsCatapulting(const CatapultAttack & ca)
|
||||
{
|
||||
if (siegeController)
|
||||
siegeController->stackIsCatapulting(ca);
|
||||
}
|
||||
|
||||
void BattleInterface::gateStateChanged(const EGateState state)
|
||||
{
|
||||
if (siegeController)
|
||||
siegeController->gateStateChanged(state);
|
||||
}
|
||||
|
||||
void BattleInterface::battleFinished(const BattleResult& br)
|
||||
{
|
||||
assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
stacksController->setActiveStack(nullptr);
|
||||
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
curInt->waitWhileDialog();
|
||||
|
||||
if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
|
||||
{
|
||||
windowObject->close();
|
||||
return;
|
||||
}
|
||||
|
||||
GH.pushInt(std::make_shared<BattleResultWindow>(br, *(this->curInt)));
|
||||
curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
|
||||
CPlayerInterface::battleInt = nullptr;
|
||||
}
|
||||
|
||||
void BattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
{
|
||||
windowObject->blockUI(true);
|
||||
|
||||
const SpellID spellID = sc->spellID;
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
auto targetedTile = sc->tile;
|
||||
|
||||
assert(spell);
|
||||
if(!spell)
|
||||
return;
|
||||
|
||||
const std::string & castSoundPath = spell->getCastSound();
|
||||
|
||||
if (!castSoundPath.empty())
|
||||
{
|
||||
auto group = spell->animationInfo.projectile.empty() ?
|
||||
EAnimationEvents::HIT:
|
||||
EAnimationEvents::BEFORE_HIT;//FIXME: recheck whether this should be on projectile spawning
|
||||
|
||||
executeOnAnimationCondition(group, true, [=]() {
|
||||
CCS->soundh->playSound(castSoundPath);
|
||||
});
|
||||
}
|
||||
|
||||
if ( sc->activeCast )
|
||||
{
|
||||
const CStack * casterStack = curInt->cb->battleGetStackByID(sc->casterStack);
|
||||
|
||||
if(casterStack != nullptr )
|
||||
{
|
||||
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
|
||||
{
|
||||
stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
|
||||
displaySpellCast(spell, casterStack->getPosition());
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hero = sc->side ? defendingHero : attackingHero;
|
||||
assert(hero);
|
||||
|
||||
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
|
||||
{
|
||||
stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
displaySpellHit(spell, targetedTile);
|
||||
});
|
||||
|
||||
//queuing affect animation
|
||||
for(auto & elem : sc->affectedCres)
|
||||
{
|
||||
auto stack = curInt->cb->battleGetStackByID(elem, false);
|
||||
assert(stack);
|
||||
if(stack)
|
||||
{
|
||||
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
displaySpellEffect(spell, stack->getPosition());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & elem : sc->reflectedCres)
|
||||
{
|
||||
auto stack = curInt->cb->battleGetStackByID(elem, false);
|
||||
assert(stack);
|
||||
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
effectsController->displayEffect(EBattleEffect::MAGIC_MIRROR, stack->getPosition());
|
||||
});
|
||||
}
|
||||
|
||||
if (!sc->resistedCres.empty())
|
||||
{
|
||||
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
CCS->soundh->playSound("MAGICRES");
|
||||
});
|
||||
}
|
||||
|
||||
for(auto & elem : sc->resistedCres)
|
||||
{
|
||||
auto stack = curInt->cb->battleGetStackByID(elem, false);
|
||||
assert(stack);
|
||||
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
effectsController->displayEffect(EBattleEffect::RESISTANCE, stack->getPosition());
|
||||
});
|
||||
}
|
||||
|
||||
//mana absorption
|
||||
if (sc->manaGained > 0)
|
||||
{
|
||||
Point leftHero = Point(15, 30);
|
||||
Point rightHero = Point(755, 30);
|
||||
bool side = sc->side;
|
||||
|
||||
executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
|
||||
{
|
||||
if(stacksController->getActiveStack() != nullptr)
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
}
|
||||
|
||||
void BattleInterface::setHeroAnimation(ui8 side, EHeroAnimType phase)
|
||||
{
|
||||
if(side == BattleSide::ATTACKER)
|
||||
{
|
||||
if(attackingHero)
|
||||
attackingHero->setPhase(phase);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(defendingHero)
|
||||
defendingHero->setPhase(phase);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
|
||||
{
|
||||
for(const auto & line : battleLog)
|
||||
{
|
||||
std::string formatted = line.toString();
|
||||
boost::algorithm::trim(formatted);
|
||||
appendBattleLog(formatted);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleInterface::displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit)
|
||||
{
|
||||
for(const CSpell::TAnimation & animation : q)
|
||||
{
|
||||
if(animation.pause > 0)
|
||||
stacksController->addNewAnim(new DummyAnimation(*this, animation.pause));
|
||||
|
||||
if (!animation.effectName.empty())
|
||||
{
|
||||
const CStack * destStack = getCurrentPlayerInterface()->cb->battleGetStackByPos(destinationTile, false);
|
||||
|
||||
if (destStack)
|
||||
stacksController->addNewAnim(new ColorTransformAnimation(*this, destStack, animation.effectName, spell ));
|
||||
}
|
||||
|
||||
if(!animation.resourceName.empty())
|
||||
{
|
||||
int flags = 0;
|
||||
|
||||
if (isHit)
|
||||
flags |= EffectAnimation::FORCE_ON_TOP;
|
||||
|
||||
if (animation.verticalPosition == VerticalPosition::BOTTOM)
|
||||
flags |= EffectAnimation::ALIGN_TO_BOTTOM;
|
||||
|
||||
if (!destinationTile.isValid())
|
||||
flags |= EffectAnimation::SCREEN_FILL;
|
||||
|
||||
if (!destinationTile.isValid())
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags));
|
||||
else
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleInterface::displaySpellCast(const CSpell * spell, BattleHex destinationTile)
|
||||
{
|
||||
if(spell)
|
||||
displaySpellAnimationQueue(spell, spell->animationInfo.cast, destinationTile, false);
|
||||
}
|
||||
|
||||
void BattleInterface::displaySpellEffect(const CSpell * spell, BattleHex destinationTile)
|
||||
{
|
||||
if(spell)
|
||||
displaySpellAnimationQueue(spell, spell->animationInfo.affect, destinationTile, false);
|
||||
}
|
||||
|
||||
void BattleInterface::displaySpellHit(const CSpell * spell, BattleHex destinationTile)
|
||||
{
|
||||
if(spell)
|
||||
displaySpellAnimationQueue(spell, spell->animationInfo.hit, destinationTile, true);
|
||||
}
|
||||
|
||||
void BattleInterface::setAnimSpeed(int set)
|
||||
{
|
||||
Settings speed = settings.write["battle"]["animationSpeed"];
|
||||
speed->Float() = float(set) / 100;
|
||||
}
|
||||
|
||||
int BattleInterface::getAnimSpeed() const
|
||||
{
|
||||
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
|
||||
return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
|
||||
|
||||
return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
|
||||
}
|
||||
|
||||
CPlayerInterface *BattleInterface::getCurrentPlayerInterface() const
|
||||
{
|
||||
return curInt.get();
|
||||
}
|
||||
|
||||
void BattleInterface::trySetActivePlayer( PlayerColor player )
|
||||
{
|
||||
if ( attackerInt && attackerInt->playerID == player )
|
||||
curInt = attackerInt;
|
||||
|
||||
if ( defenderInt && defenderInt->playerID == player )
|
||||
curInt = defenderInt;
|
||||
}
|
||||
|
||||
void BattleInterface::activateStack()
|
||||
{
|
||||
stacksController->activateStack();
|
||||
|
||||
const CStack * s = stacksController->getActiveStack();
|
||||
if(!s)
|
||||
return;
|
||||
|
||||
myTurn = true;
|
||||
windowObject->updateQueue();
|
||||
windowObject->blockUI(false);
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
actionsController->activateStack();
|
||||
GH.fakeMouseMove();
|
||||
}
|
||||
|
||||
void BattleInterface::endAction(const BattleAction* action)
|
||||
{
|
||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||
|
||||
stacksController->endAction(action);
|
||||
windowObject->updateQueue();
|
||||
|
||||
//stack ended movement in tactics phase -> select the next one
|
||||
if (tacticsMode)
|
||||
tacticNextStack(stack);
|
||||
|
||||
//we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
|
||||
if(action->actionType == EActionType::HERO_SPELL)
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
}
|
||||
|
||||
void BattleInterface::appendBattleLog(const std::string & newEntry)
|
||||
{
|
||||
console->addText(newEntry);
|
||||
}
|
||||
|
||||
void BattleInterface::startAction(const BattleAction* action)
|
||||
{
|
||||
if(action->actionType == EActionType::END_TACTIC_PHASE)
|
||||
{
|
||||
windowObject->tacticPhaseEnded();
|
||||
return;
|
||||
}
|
||||
|
||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||
|
||||
if (stack)
|
||||
{
|
||||
windowObject->updateQueue();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(action->actionType == EActionType::HERO_SPELL); //only cast spell is valid action without acting stack number
|
||||
}
|
||||
|
||||
stacksController->startAction(action);
|
||||
|
||||
if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
|
||||
return;
|
||||
|
||||
if (!stack)
|
||||
{
|
||||
logGlobal->error("Something wrong with stackNumber in actionStarted. Stack number: %d", action->stackNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
effectsController->startAction(action);
|
||||
}
|
||||
|
||||
void BattleInterface::tacticPhaseEnd()
|
||||
{
|
||||
stacksController->setActiveStack(nullptr);
|
||||
tacticsMode = false;
|
||||
}
|
||||
|
||||
static bool immobile(const CStack *s)
|
||||
{
|
||||
return !s->Speed(0, true); //should bound stacks be immobile?
|
||||
}
|
||||
|
||||
void BattleInterface::tacticNextStack(const CStack * current)
|
||||
{
|
||||
if (!current)
|
||||
current = stacksController->getActiveStack();
|
||||
|
||||
//no switching stacks when the current one is moving
|
||||
assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
|
||||
vstd::erase_if (stacksOfMine, &immobile);
|
||||
if (stacksOfMine.empty())
|
||||
{
|
||||
tacticPhaseEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = vstd::find(stacksOfMine, current);
|
||||
if (it != stacksOfMine.end() && ++it != stacksOfMine.end())
|
||||
stackActivated(*it);
|
||||
else
|
||||
stackActivated(stacksOfMine.front());
|
||||
|
||||
}
|
||||
|
||||
void BattleInterface::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi)
|
||||
{
|
||||
obstacleController->obstaclePlaced(oi);
|
||||
}
|
||||
|
||||
const CGHeroInstance *BattleInterface::currentHero() const
|
||||
{
|
||||
if (attackingHeroInstance && attackingHeroInstance->tempOwner == curInt->playerID)
|
||||
return attackingHeroInstance;
|
||||
|
||||
if (defendingHeroInstance && defendingHeroInstance->tempOwner == curInt->playerID)
|
||||
return defendingHeroInstance;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InfoAboutHero BattleInterface::enemyHero() const
|
||||
{
|
||||
InfoAboutHero ret;
|
||||
if (attackingHeroInstance->tempOwner == curInt->playerID)
|
||||
curInt->cb->getHeroInfo(defendingHeroInstance, ret);
|
||||
else
|
||||
curInt->cb->getHeroInfo(attackingHeroInstance, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BattleInterface::requestAutofightingAIToTakeAction()
|
||||
{
|
||||
assert(curInt->isAutoFightOn);
|
||||
|
||||
boost::thread aiThread([&]()
|
||||
{
|
||||
auto ba = std::make_unique<BattleAction>(curInt->autofightingAI->activeStack(stacksController->getActiveStack()));
|
||||
|
||||
if(curInt->cb->battleIsFinished())
|
||||
{
|
||||
return; // battle finished with spellcast
|
||||
}
|
||||
|
||||
if (curInt->isAutoFightOn)
|
||||
{
|
||||
if (tacticsMode)
|
||||
{
|
||||
// Always end tactics mode. Player interface is blocked currently, so it's not possible that
|
||||
// the AI can take any action except end tactics phase (AI actions won't be triggered)
|
||||
//TODO implement the possibility that the AI will be triggered for further actions
|
||||
//TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
|
||||
stacksController->setActiveStack(nullptr);
|
||||
tacticsMode = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
givenCommand.setn(ba.release());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
|
||||
activateStack();
|
||||
}
|
||||
});
|
||||
|
||||
aiThread.detach();
|
||||
}
|
||||
|
||||
void BattleInterface::castThisSpell(SpellID spellID)
|
||||
{
|
||||
actionsController->castThisSpell(spellID);
|
||||
}
|
||||
|
||||
void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state)
|
||||
{
|
||||
logAnim->debug("setAnimationCondition: %d -> %s", static_cast<int>(event), state ? "ON" : "OFF");
|
||||
|
||||
size_t index = static_cast<size_t>(event);
|
||||
animationEvents[index].setn(state);
|
||||
|
||||
decltype(awaitingEvents) executingEvents;
|
||||
|
||||
for (auto it = awaitingEvents.begin(); it != awaitingEvents.end();)
|
||||
{
|
||||
if (it->event == event && it->eventState == state)
|
||||
{
|
||||
executingEvents.push_back(*it);
|
||||
it = awaitingEvents.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto const & event : executingEvents)
|
||||
event.action();
|
||||
}
|
||||
|
||||
bool BattleInterface::getAnimationCondition( EAnimationEvents event)
|
||||
{
|
||||
size_t index = static_cast<size_t>(event);
|
||||
return animationEvents[index].get();
|
||||
}
|
||||
|
||||
void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state)
|
||||
{
|
||||
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
|
||||
size_t index = static_cast<size_t>(event);
|
||||
animationEvents[index].waitUntil(state);
|
||||
}
|
||||
|
||||
void BattleInterface::executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action)
|
||||
{
|
||||
awaitingEvents.push_back({action, event, state});
|
||||
}
|
226
client/battle/BattleInterface.h
Normal file
226
client/battle/BattleInterface.h
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* BattleInterface.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 "BattleConstants.h"
|
||||
#include "../gui/CIntObject.h"
|
||||
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
|
||||
#include "../../lib/CondSh.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CCreatureSet;
|
||||
class CGHeroInstance;
|
||||
class CStack;
|
||||
struct BattleResult;
|
||||
struct BattleSpellCast;
|
||||
struct CObstacleInstance;
|
||||
struct SetStackEffect;
|
||||
class BattleAction;
|
||||
class CGTownInstance;
|
||||
struct CatapultAttack;
|
||||
struct BattleTriggerEffect;
|
||||
struct BattleHex;
|
||||
struct InfoAboutHero;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class BattleHero;
|
||||
class Canvas;
|
||||
class BattleResultWindow;
|
||||
class StackQueue;
|
||||
class CPlayerInterface;
|
||||
class ClickableHex;
|
||||
class CAnimation;
|
||||
struct BattleEffect;
|
||||
class IImage;
|
||||
class StackQueue;
|
||||
|
||||
class BattleProjectileController;
|
||||
class BattleSiegeController;
|
||||
class BattleObstacleController;
|
||||
class BattleFieldController;
|
||||
class BattleRenderer;
|
||||
class BattleWindow;
|
||||
class BattleStacksController;
|
||||
class BattleActionsController;
|
||||
class BattleEffectsController;
|
||||
class BattleConsole;
|
||||
|
||||
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
|
||||
struct StackAttackedInfo
|
||||
{
|
||||
const CStack *defender;
|
||||
const CStack *attacker;
|
||||
|
||||
int64_t damageDealt;
|
||||
uint32_t amountKilled;
|
||||
SpellID spellEffect;
|
||||
|
||||
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
|
||||
bool killed; //if true, stack has been killed
|
||||
bool rebirth; //if true, play rebirth animation after all
|
||||
bool cloneKilled;
|
||||
bool fireShield;
|
||||
};
|
||||
|
||||
struct StackAttackInfo
|
||||
{
|
||||
const CStack *attacker;
|
||||
const CStack *defender;
|
||||
std::vector< const CStack *> secondaryDefender;
|
||||
|
||||
SpellID spellEffect;
|
||||
BattleHex tile;
|
||||
|
||||
bool indirectAttack;
|
||||
bool lucky;
|
||||
bool unlucky;
|
||||
bool deathBlow;
|
||||
bool lifeDrain;
|
||||
};
|
||||
|
||||
/// Main class for battles, responsible for relaying information from server to various battle entities
|
||||
class BattleInterface
|
||||
{
|
||||
using AwaitingAnimationAction = std::function<void()>;
|
||||
|
||||
struct AwaitingAnimationEvents {
|
||||
AwaitingAnimationAction action;
|
||||
EAnimationEvents event;
|
||||
bool eventState;
|
||||
};
|
||||
|
||||
/// Conditional variables that are set depending on ongoing animations on the battlefield
|
||||
std::array< CondSh<bool>, static_cast<size_t>(EAnimationEvents::COUNT)> animationEvents;
|
||||
|
||||
/// List of events that are waiting to be triggered
|
||||
std::vector<AwaitingAnimationEvents> awaitingEvents;
|
||||
|
||||
/// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
|
||||
std::shared_ptr<CPlayerInterface> tacticianInterface;
|
||||
|
||||
/// attacker interface, not null if attacker is human in our vcmiclient
|
||||
std::shared_ptr<CPlayerInterface> attackerInt;
|
||||
|
||||
/// defender interface, not null if attacker is human in our vcmiclient
|
||||
std::shared_ptr<CPlayerInterface> defenderInt;
|
||||
|
||||
void onIntroSoundPlayed();
|
||||
public:
|
||||
/// copy of initial armies (for result window)
|
||||
const CCreatureSet *army1;
|
||||
const CCreatureSet *army2;
|
||||
|
||||
/// ID of channel on which battle opening sound is playing, or -1 if none
|
||||
int battleIntroSoundChannel;
|
||||
|
||||
std::shared_ptr<BattleWindow> windowObject;
|
||||
std::shared_ptr<BattleConsole> console;
|
||||
|
||||
/// currently active player interface
|
||||
std::shared_ptr<CPlayerInterface> curInt;
|
||||
|
||||
const CGHeroInstance *attackingHeroInstance;
|
||||
const CGHeroInstance *defendingHeroInstance;
|
||||
|
||||
bool tacticsMode;
|
||||
|
||||
std::unique_ptr<BattleProjectileController> projectilesController;
|
||||
std::unique_ptr<BattleSiegeController> siegeController;
|
||||
std::unique_ptr<BattleObstacleController> obstacleController;
|
||||
std::unique_ptr<BattleFieldController> fieldController;
|
||||
std::unique_ptr<BattleStacksController> stacksController;
|
||||
std::unique_ptr<BattleActionsController> actionsController;
|
||||
std::unique_ptr<BattleEffectsController> effectsController;
|
||||
|
||||
std::shared_ptr<BattleHero> attackingHero;
|
||||
std::shared_ptr<BattleHero> defendingHero;
|
||||
|
||||
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
|
||||
|
||||
bool myTurn; //if true, interface is active (commands can be ordered)
|
||||
int moveSoundHander; // sound handler used when moving a unit
|
||||
|
||||
BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
|
||||
~BattleInterface();
|
||||
|
||||
void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
|
||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||
void requestAutofightingAIToTakeAction();
|
||||
|
||||
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
|
||||
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
|
||||
|
||||
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
|
||||
|
||||
void showInterface(SDL_Surface * to);
|
||||
|
||||
void setHeroAnimation(ui8 side, EHeroAnimType phase);
|
||||
|
||||
void executeSpellCast(); //called when a hero casts a spell
|
||||
|
||||
void appendBattleLog(const std::string & newEntry);
|
||||
|
||||
void setPrintCellBorders(bool set); //if true, cell borders will be printed
|
||||
void setPrintStackRange(bool set); //if true,range of active stack will be printed
|
||||
void setPrintMouseShadow(bool set); //if true, hex under mouse will be shaded
|
||||
void setAnimSpeed(int set); //speed of animation; range 1..100
|
||||
int getAnimSpeed() const; //speed of animation; range 1..100
|
||||
CPlayerInterface *getCurrentPlayerInterface() const;
|
||||
|
||||
void tacticNextStack(const CStack *current);
|
||||
void tacticPhaseEnd();
|
||||
|
||||
/// sets condition to targeted state and executes any awaiting actions
|
||||
void setAnimationCondition( EAnimationEvents event, bool state);
|
||||
|
||||
/// returns current state of condition
|
||||
bool getAnimationCondition( EAnimationEvents event);
|
||||
|
||||
/// locks execution until selected condition reached targeted state
|
||||
void waitForAnimationCondition( EAnimationEvents event, bool state);
|
||||
|
||||
/// adds action that will be executed one selected condition reached targeted state
|
||||
void executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action);
|
||||
|
||||
//call-ins
|
||||
void startAction(const BattleAction* action);
|
||||
void stackReset(const CStack * stack);
|
||||
void stackAdded(const CStack * stack); //new stack appeared on battlefield
|
||||
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
|
||||
void stackActivated(const CStack *stack); //active stack has been changed
|
||||
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport); //stack with id number moved to destHex
|
||||
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
|
||||
void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
|
||||
void newRoundFirst( int round );
|
||||
void newRound(int number); //caled when round is ended; number is the number of round
|
||||
void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
|
||||
void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
|
||||
void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
|
||||
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
|
||||
|
||||
void displayBattleLog(const std::vector<MetaString> & battleLog);
|
||||
|
||||
void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit);
|
||||
void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation
|
||||
void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
|
||||
void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
|
||||
|
||||
void endAction(const BattleAction* action);
|
||||
|
||||
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
|
||||
|
||||
void gateStateChanged(const EGateState state);
|
||||
|
||||
const CGHeroInstance *currentHero() const;
|
||||
InfoAboutHero enemyHero() const;
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* CBattleInterfaceClasses.cpp, part of VCMI engine
|
||||
* BattleInterfaceClasses.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
@ -8,11 +8,16 @@
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CBattleInterfaceClasses.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
|
||||
#include "CBattleInterface.h"
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleActionsController.h"
|
||||
#include "BattleRenderer.h"
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleWindow.h"
|
||||
|
||||
#include "../CBitmapHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMessage.h"
|
||||
#include "../CMusicHandler.h"
|
||||
@ -20,10 +25,12 @@
|
||||
#include "../CVideoHandler.h"
|
||||
#include "../Graphics.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/CCursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/SDL_Extensions.h"
|
||||
#include "../widgets/AdventureMapClasses.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../windows/CCreatureWindow.h"
|
||||
#include "../windows/CSpellWindow.h"
|
||||
@ -35,249 +42,340 @@
|
||||
#include "../../lib/CGameState.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CTownHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/CondSh.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
||||
void CBattleConsole::showAll(SDL_Surface * to)
|
||||
void BattleConsole::showAll(SDL_Surface * to)
|
||||
{
|
||||
Point textPos(pos.x + pos.w/2, pos.y + 17);
|
||||
CIntObject::showAll(to);
|
||||
|
||||
if(ingcAlter.size())
|
||||
Point line1 (pos.x + pos.w/2, pos.y + 8);
|
||||
Point line2 (pos.x + pos.w/2, pos.y + 24);
|
||||
|
||||
auto visibleText = getVisibleText();
|
||||
|
||||
if(visibleText.size() > 0)
|
||||
graphics->fonts[FONT_SMALL]->renderTextCenter(to, visibleText[0], Colors::WHITE, line1);
|
||||
|
||||
if(visibleText.size() > 1)
|
||||
graphics->fonts[FONT_SMALL]->renderTextCenter(to, visibleText[1], Colors::WHITE, line2);
|
||||
}
|
||||
|
||||
std::vector<std::string> BattleConsole::getVisibleText()
|
||||
{
|
||||
// high priority texts that hide battle log entries
|
||||
for (auto const & text : {consoleText, hoverText} )
|
||||
{
|
||||
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(ingcAlter, pos.w, FONT_SMALL), Colors::WHITE, textPos);
|
||||
if (text.empty())
|
||||
continue;
|
||||
|
||||
auto result = CMessage::breakText(text, pos.w, FONT_SMALL);
|
||||
|
||||
if(result.size() > 2)
|
||||
result.resize(2);
|
||||
return result;
|
||||
}
|
||||
else if(alterTxt.size())
|
||||
|
||||
// log is small enough to fit entirely - display it as such
|
||||
if (logEntries.size() < 3)
|
||||
return logEntries;
|
||||
|
||||
return { logEntries[scrollPosition - 1], logEntries[scrollPosition] };
|
||||
}
|
||||
|
||||
std::vector<std::string> BattleConsole::splitText(const std::string &text)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
std::vector<std::string> output;
|
||||
|
||||
boost::split(lines, text, boost::is_any_of("\n"));
|
||||
|
||||
for (auto const & line : lines)
|
||||
{
|
||||
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(alterTxt, pos.w, FONT_SMALL), Colors::WHITE, textPos);
|
||||
}
|
||||
else if(texts.size())
|
||||
{
|
||||
if(texts.size()==1)
|
||||
if (graphics->fonts[FONT_SMALL]->getStringWidth(text) < pos.w)
|
||||
{
|
||||
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[0], pos.w, FONT_SMALL), Colors::WHITE, textPos);
|
||||
output.push_back(line);
|
||||
}
|
||||
else
|
||||
{
|
||||
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[lastShown - 1], pos.w, FONT_SMALL), Colors::WHITE, textPos);
|
||||
textPos.y += 16;
|
||||
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[lastShown], pos.w, FONT_SMALL), Colors::WHITE, textPos);
|
||||
std::vector<std::string> substrings = CMessage::breakText(line, pos.w, FONT_SMALL);
|
||||
output.insert(output.end(), substrings.begin(), substrings.end());
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
bool CBattleConsole::addText(const std::string & text)
|
||||
bool BattleConsole::addText(const std::string & text)
|
||||
{
|
||||
logGlobal->trace("CBattleConsole message: %s", text);
|
||||
if(text.size()>70)
|
||||
return false; //text too long!
|
||||
int firstInToken = 0;
|
||||
for(size_t i = 0; i < text.size(); ++i) //tokenize
|
||||
{
|
||||
if(text[i] == 10)
|
||||
{
|
||||
texts.push_back( text.substr(firstInToken, i-firstInToken) );
|
||||
firstInToken = (int)i+1;
|
||||
}
|
||||
}
|
||||
|
||||
texts.push_back( text.substr(firstInToken, text.size()) );
|
||||
lastShown = (int)texts.size()-1;
|
||||
auto newLines = splitText(text);
|
||||
|
||||
logEntries.insert(logEntries.end(), newLines.begin(), newLines.end());
|
||||
scrollPosition = (int)logEntries.size()-1;
|
||||
redraw();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CBattleConsole::alterText(const std::string &text)
|
||||
void BattleConsole::scrollUp(ui32 by)
|
||||
{
|
||||
//char buf[500];
|
||||
//sprintf(buf, text.c_str());
|
||||
//alterTxt = buf;
|
||||
alterTxt = text;
|
||||
if(scrollPosition > static_cast<int>(by))
|
||||
scrollPosition -= by;
|
||||
redraw();
|
||||
}
|
||||
|
||||
void CBattleConsole::eraseText(ui32 pos)
|
||||
void BattleConsole::scrollDown(ui32 by)
|
||||
{
|
||||
if(pos < texts.size())
|
||||
if(scrollPosition + by < logEntries.size())
|
||||
scrollPosition += by;
|
||||
redraw();
|
||||
}
|
||||
|
||||
BattleConsole::BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size)
|
||||
: scrollPosition(-1)
|
||||
, enteringText(false)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
pos += objectPos;
|
||||
pos.w = size.x;
|
||||
pos.h = size.y;
|
||||
|
||||
background = std::make_shared<CPicture>(backgroundSource->getSurface(), Rect(imagePos, size), 0, 0 );
|
||||
}
|
||||
|
||||
void BattleConsole::deactivate()
|
||||
{
|
||||
if (enteringText)
|
||||
LOCPLINT->cingconsole->endEnteringText(false);
|
||||
|
||||
CIntObject::deactivate();
|
||||
}
|
||||
|
||||
void BattleConsole::setEnteringMode(bool on)
|
||||
{
|
||||
consoleText.clear();
|
||||
|
||||
if (on)
|
||||
{
|
||||
texts.erase(texts.begin() + pos);
|
||||
if(lastShown == texts.size())
|
||||
--lastShown;
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleConsole::changeTextAt(const std::string & text, ui32 pos)
|
||||
{
|
||||
if(pos >= texts.size()) //no such pos
|
||||
return;
|
||||
texts[pos] = text;
|
||||
}
|
||||
|
||||
void CBattleConsole::scrollUp(ui32 by)
|
||||
{
|
||||
if(lastShown > static_cast<int>(by))
|
||||
lastShown -= by;
|
||||
}
|
||||
|
||||
void CBattleConsole::scrollDown(ui32 by)
|
||||
{
|
||||
if(lastShown + by < texts.size())
|
||||
lastShown += by;
|
||||
}
|
||||
|
||||
CBattleConsole::CBattleConsole() : lastShown(-1), alterTxt(""), whoSetAlter(0)
|
||||
{}
|
||||
|
||||
void CBattleHero::show(SDL_Surface * to)
|
||||
{
|
||||
auto flagFrame = flagAnimation->getImage(flagAnim, 0, true);
|
||||
|
||||
if(!flagFrame)
|
||||
return;
|
||||
|
||||
//animation of flag
|
||||
SDL_Rect temp_rect;
|
||||
if(flip)
|
||||
{
|
||||
temp_rect = genRect(
|
||||
flagFrame->height(),
|
||||
flagFrame->width(),
|
||||
pos.x + 61,
|
||||
pos.y + 39);
|
||||
|
||||
assert(enteringText == false);
|
||||
CSDL_Ext::startTextInput(&pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
temp_rect = genRect(
|
||||
flagFrame->height(),
|
||||
flagFrame->width(),
|
||||
pos.x + 72,
|
||||
pos.y + 39);
|
||||
assert(enteringText == true);
|
||||
CSDL_Ext::stopTextInput();
|
||||
}
|
||||
enteringText = on;
|
||||
redraw();
|
||||
}
|
||||
|
||||
flagFrame->draw(screen, &temp_rect, nullptr); //FIXME: why screen?
|
||||
void BattleConsole::setEnteredText(const std::string & text)
|
||||
{
|
||||
assert(enteringText == true);
|
||||
consoleText = text;
|
||||
redraw();
|
||||
}
|
||||
|
||||
//animation of hero
|
||||
SDL_Rect rect = pos;
|
||||
void BattleConsole::write(const std::string & Text)
|
||||
{
|
||||
hoverText = Text;
|
||||
redraw();
|
||||
}
|
||||
|
||||
auto heroFrame = animation->getImage(currentFrame, phase, true);
|
||||
if(!heroFrame)
|
||||
return;
|
||||
void BattleConsole::clearIfMatching(const std::string & Text)
|
||||
{
|
||||
if (hoverText == Text)
|
||||
clear();
|
||||
}
|
||||
|
||||
heroFrame->draw(to, &rect, nullptr);
|
||||
void BattleConsole::clear()
|
||||
{
|
||||
write({});
|
||||
}
|
||||
|
||||
if(++animCount >= 4)
|
||||
const CGHeroInstance * BattleHero::instance()
|
||||
{
|
||||
return hero;
|
||||
}
|
||||
|
||||
void BattleHero::render(Canvas & canvas)
|
||||
{
|
||||
size_t groupIndex = static_cast<size_t>(phase);
|
||||
|
||||
auto flagFrame = flagAnimation->getImage(flagCurrentFrame, 0, true);
|
||||
auto heroFrame = animation->getImage(currentFrame, groupIndex, true);
|
||||
|
||||
Point heroPosition = pos.center() - parent->pos.topLeft() - heroFrame->dimensions() / 2;
|
||||
Point flagPosition = pos.center() - parent->pos.topLeft() - flagFrame->dimensions() / 2;
|
||||
|
||||
if(defender)
|
||||
flagPosition += Point(-4, -41);
|
||||
else
|
||||
flagPosition += Point(4, -41);
|
||||
|
||||
canvas.draw(flagFrame, flagPosition);
|
||||
canvas.draw(heroFrame, heroPosition);
|
||||
|
||||
flagCurrentFrame += currentSpeed;
|
||||
currentFrame += currentSpeed;
|
||||
|
||||
if(flagCurrentFrame >= flagAnimation->size(0))
|
||||
flagCurrentFrame -= flagAnimation->size(0);
|
||||
|
||||
if(currentFrame >= animation->size(groupIndex))
|
||||
{
|
||||
animCount = 0;
|
||||
if(++flagAnim >= flagAnimation->size(0))
|
||||
flagAnim = 0;
|
||||
|
||||
if(++currentFrame >= lastFrame)
|
||||
switchToNextPhase();
|
||||
currentFrame -= animation->size(groupIndex);
|
||||
switchToNextPhase();
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleHero::setPhase(int newPhase)
|
||||
void BattleHero::pause()
|
||||
{
|
||||
currentSpeed = 0.f;
|
||||
}
|
||||
|
||||
void BattleHero::play()
|
||||
{
|
||||
//FIXME: un-hardcode speed
|
||||
currentSpeed = 0.25f;
|
||||
}
|
||||
|
||||
float BattleHero::getFrame() const
|
||||
{
|
||||
return currentFrame;
|
||||
}
|
||||
|
||||
void BattleHero::collectRenderableObjects(BattleRenderer & renderer)
|
||||
{
|
||||
auto hex = defender ? BattleHex(GameConstants::BFIELD_WIDTH-1) : BattleHex(0);
|
||||
|
||||
renderer.insert(EBattleFieldLayer::HEROES, hex, [this](BattleRenderer::RendererRef canvas)
|
||||
{
|
||||
render(canvas);
|
||||
});
|
||||
}
|
||||
|
||||
void BattleHero::onPhaseFinished(const std::function<void()> & callback)
|
||||
{
|
||||
phaseFinishedCallback = callback;
|
||||
}
|
||||
|
||||
void BattleHero::setPhase(EHeroAnimType newPhase)
|
||||
{
|
||||
nextPhase = newPhase;
|
||||
switchToNextPhase(); //immediately switch to next phase and then restore idling phase
|
||||
nextPhase = 0;
|
||||
nextPhase = EHeroAnimType::HOLDING;
|
||||
}
|
||||
|
||||
void CBattleHero::hover(bool on)
|
||||
void BattleHero::hover(bool on)
|
||||
{
|
||||
//TODO: Make lines below work properly
|
||||
//TODO: BROKEN CODE
|
||||
if (on)
|
||||
CCS->curh->changeGraphic(ECursor::COMBAT, 5);
|
||||
CCS->curh->set(Cursor::Combat::HERO);
|
||||
else
|
||||
CCS->curh->changeGraphic(ECursor::COMBAT, 0);
|
||||
CCS->curh->set(Cursor::Combat::POINTER);
|
||||
}
|
||||
|
||||
void CBattleHero::clickLeft(tribool down, bool previousState)
|
||||
void BattleHero::clickLeft(tribool down, bool previousState)
|
||||
{
|
||||
if(myOwner->spellDestSelectMode) //we are casting a spell
|
||||
if(owner.actionsController->spellcastingModeActive()) //we are casting a spell
|
||||
return;
|
||||
|
||||
if(boost::logic::indeterminate(down))
|
||||
return;
|
||||
|
||||
if(!myHero || down || !myOwner->myTurn)
|
||||
if(!hero || down || !owner.myTurn)
|
||||
return;
|
||||
|
||||
if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
|
||||
if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
|
||||
{
|
||||
for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
|
||||
{
|
||||
if(myOwner->bfield[it]->hovered && myOwner->bfield[it]->strictHovered)
|
||||
return;
|
||||
}
|
||||
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
|
||||
BattleHex hoveredHex = owner.fieldController->getHoveredHex();
|
||||
//do nothing when any hex is hovered - hero's animation overlaps battlefield
|
||||
if ( hoveredHex != BattleHex::INVALID )
|
||||
return;
|
||||
|
||||
GH.pushIntT<CSpellWindow>(myHero, myOwner->getCurrentPlayerInterface());
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
|
||||
GH.pushIntT<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleHero::clickRight(tribool down, bool previousState)
|
||||
void BattleHero::clickRight(tribool down, bool previousState)
|
||||
{
|
||||
if(boost::logic::indeterminate(down))
|
||||
return;
|
||||
|
||||
Point windowPosition;
|
||||
windowPosition.x = (!flip) ? myOwner->pos.topLeft().x + 1 : myOwner->pos.topRight().x - 79;
|
||||
windowPosition.y = myOwner->pos.y + 135;
|
||||
windowPosition.x = (!defender) ? owner.fieldController->pos.topLeft().x + 1 : owner.fieldController->pos.topRight().x - 79;
|
||||
windowPosition.y = owner.fieldController->pos.y + 135;
|
||||
|
||||
InfoAboutHero targetHero;
|
||||
if(down && (myOwner->myTurn || settings["session"]["spectate"].Bool()))
|
||||
if(down && (owner.myTurn || settings["session"]["spectate"].Bool()))
|
||||
{
|
||||
auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance;
|
||||
auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance;
|
||||
targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
|
||||
GH.pushIntT<CHeroInfoWindow>(targetHero, &windowPosition);
|
||||
GH.pushIntT<HeroInfoWindow>(targetHero, &windowPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleHero::switchToNextPhase()
|
||||
void BattleHero::switchToNextPhase()
|
||||
{
|
||||
if(phase != nextPhase)
|
||||
{
|
||||
phase = nextPhase;
|
||||
phase = nextPhase;
|
||||
currentFrame = 0.f;
|
||||
|
||||
firstFrame = 0;
|
||||
|
||||
lastFrame = static_cast<int>(animation->size(phase));
|
||||
}
|
||||
|
||||
currentFrame = firstFrame;
|
||||
auto copy = phaseFinishedCallback;
|
||||
phaseFinishedCallback.clear();
|
||||
copy();
|
||||
}
|
||||
|
||||
CBattleHero::CBattleHero(const std::string & animationPath, bool flipG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner):
|
||||
flip(flipG),
|
||||
myHero(hero),
|
||||
myOwner(owner),
|
||||
phase(1),
|
||||
nextPhase(0),
|
||||
flagAnim(0),
|
||||
animCount(0)
|
||||
BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender):
|
||||
defender(defender),
|
||||
hero(hero),
|
||||
owner(owner),
|
||||
phase(EHeroAnimType::HOLDING),
|
||||
nextPhase(EHeroAnimType::HOLDING),
|
||||
currentSpeed(0.f),
|
||||
currentFrame(0.f),
|
||||
flagCurrentFrame(0.f)
|
||||
{
|
||||
std::string animationPath;
|
||||
|
||||
if(!hero->type->battleImage.empty())
|
||||
animationPath = hero->type->battleImage;
|
||||
else
|
||||
if(hero->sex)
|
||||
animationPath = hero->type->heroClass->imageBattleFemale;
|
||||
else
|
||||
animationPath = hero->type->heroClass->imageBattleMale;
|
||||
|
||||
animation = std::make_shared<CAnimation>(animationPath);
|
||||
animation->preload();
|
||||
if(flipG)
|
||||
|
||||
pos.w = 64;
|
||||
pos.h = 136;
|
||||
pos.x = owner.fieldController->pos.x + (defender ? (owner.fieldController->pos.w - pos.w) : 0);
|
||||
pos.y = owner.fieldController->pos.y;
|
||||
|
||||
if(defender)
|
||||
animation->verticalFlip();
|
||||
|
||||
if(flip)
|
||||
if(defender)
|
||||
flagAnimation = std::make_shared<CAnimation>("CMFLAGR");
|
||||
else
|
||||
flagAnimation = std::make_shared<CAnimation>("CMFLAGL");
|
||||
|
||||
flagAnimation->preload();
|
||||
flagAnimation->playerColored(player);
|
||||
flagAnimation->playerColored(hero->tempOwner);
|
||||
|
||||
addUsedEvents(LCLICK | RCLICK | HOVER);
|
||||
|
||||
switchToNextPhase();
|
||||
play();
|
||||
}
|
||||
|
||||
CBattleHero::~CBattleHero() = default;
|
||||
|
||||
CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero & hero, Point * position)
|
||||
HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
|
||||
: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, "CHRPOP")
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
@ -297,49 +395,46 @@ CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero & hero, Point * position)
|
||||
icons.push_back(std::make_shared<CAnimImage>("PortraitsLarge", hero.portrait, 0, 10, 6));
|
||||
|
||||
//primary stats
|
||||
labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 87, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[381] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 99, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[382] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 111, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[383] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 87, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[381] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 99, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[382] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 111, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[383] + ":"));
|
||||
|
||||
labels.push_back(std::make_shared<CLabel>(69, 87, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(attack)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 99, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(defense)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 111, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(power)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 123, EFonts::FONT_TINY, EAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(knowledge)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 87, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(attack)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 99, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(defense)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 111, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(power)));
|
||||
labels.push_back(std::make_shared<CLabel>(69, 123, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(knowledge)));
|
||||
|
||||
//morale+luck
|
||||
labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
|
||||
labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
|
||||
|
||||
icons.push_back(std::make_shared<CAnimImage>("IMRL22", morale + 3, 0, 47, 131));
|
||||
icons.push_back(std::make_shared<CAnimImage>("ILCK22", luck + 3, 0, 47, 143));
|
||||
|
||||
//spell points
|
||||
labels.push_back(std::make_shared<CLabel>(39, 174, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[387]));
|
||||
labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
|
||||
labels.push_back(std::make_shared<CLabel>(39, 174, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[387]));
|
||||
labels.push_back(std::make_shared<CLabel>(39, 186, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)));
|
||||
}
|
||||
|
||||
CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInterface *owner)
|
||||
BattleOptionsWindow::BattleOptionsWindow(BattleInterface & owner):
|
||||
CWindowObject(PLAYER_COLORED, "comopbck.bmp")
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
pos = position;
|
||||
|
||||
background = std::make_shared<CPicture>("comopbck.bmp");
|
||||
background->colorize(owner->getCurrentPlayerInterface()->playerID);
|
||||
|
||||
auto viewGrid = std::make_shared<CToggleButton>(Point(25, 56), "sysopchk.def", CGI->generaltexth->zelp[427], [=](bool on){owner->setPrintCellBorders(on);} );
|
||||
auto viewGrid = std::make_shared<CToggleButton>(Point(25, 56), "sysopchk.def", CGI->generaltexth->zelp[427], [&](bool on){owner.setPrintCellBorders(on);} );
|
||||
viewGrid->setSelected(settings["battle"]["cellBorders"].Bool());
|
||||
toggles.push_back(viewGrid);
|
||||
|
||||
auto movementShadow = std::make_shared<CToggleButton>(Point(25, 89), "sysopchk.def", CGI->generaltexth->zelp[428], [=](bool on){owner->setPrintStackRange(on);});
|
||||
auto movementShadow = std::make_shared<CToggleButton>(Point(25, 89), "sysopchk.def", CGI->generaltexth->zelp[428], [&](bool on){owner.setPrintStackRange(on);});
|
||||
movementShadow->setSelected(settings["battle"]["stackRange"].Bool());
|
||||
toggles.push_back(movementShadow);
|
||||
|
||||
auto mouseShadow = std::make_shared<CToggleButton>(Point(25, 122), "sysopchk.def", CGI->generaltexth->zelp[429], [=](bool on){owner->setPrintMouseShadow(on);});
|
||||
auto mouseShadow = std::make_shared<CToggleButton>(Point(25, 122), "sysopchk.def", CGI->generaltexth->zelp[429], [&](bool on){owner.setPrintMouseShadow(on);});
|
||||
mouseShadow->setSelected(settings["battle"]["mouseShadow"].Bool());
|
||||
toggles.push_back(mouseShadow);
|
||||
|
||||
animSpeeds = std::make_shared<CToggleGroup>([=](int value){ owner->setAnimSpeed(value);});
|
||||
animSpeeds = std::make_shared<CToggleGroup>([&](int value){ owner.setAnimSpeed(value);});
|
||||
|
||||
std::shared_ptr<CToggleButton> toggle;
|
||||
toggle = std::make_shared<CToggleButton>(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422]);
|
||||
@ -351,7 +446,7 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt
|
||||
toggle = std::make_shared<CToggleButton>(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424]);
|
||||
animSpeeds->addToggle(100, toggle);
|
||||
|
||||
animSpeeds->setSelected(owner->getAnimSpeed());
|
||||
animSpeeds->setSelected(owner.getAnimSpeed());
|
||||
|
||||
setToDefault = std::make_shared<CButton>(Point(246, 359), "codefaul.def", CGI->generaltexth->zelp[393], [&](){ bDefaultf(); });
|
||||
setToDefault->setImageOrder(1, 0, 2, 3);
|
||||
@ -359,42 +454,42 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt
|
||||
exit->setImageOrder(1, 0, 2, 3);
|
||||
|
||||
//creating labels
|
||||
labels.push_back(std::make_shared<CLabel>(242, 32, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[392]));//window title
|
||||
labels.push_back(std::make_shared<CLabel>(122, 214, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[393]));//animation speed
|
||||
labels.push_back(std::make_shared<CLabel>(122, 293, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[394]));//music volume
|
||||
labels.push_back(std::make_shared<CLabel>(122, 359, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[395]));//effects' volume
|
||||
labels.push_back(std::make_shared<CLabel>(353, 66, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[396]));//auto - combat options
|
||||
labels.push_back(std::make_shared<CLabel>(353, 265, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[397]));//creature info
|
||||
labels.push_back(std::make_shared<CLabel>(242, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[392]));//window title
|
||||
labels.push_back(std::make_shared<CLabel>(122, 214, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[393]));//animation speed
|
||||
labels.push_back(std::make_shared<CLabel>(122, 293, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[394]));//music volume
|
||||
labels.push_back(std::make_shared<CLabel>(122, 359, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[395]));//effects' volume
|
||||
labels.push_back(std::make_shared<CLabel>(353, 66, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[396]));//auto - combat options
|
||||
labels.push_back(std::make_shared<CLabel>(353, 265, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[397]));//creature info
|
||||
|
||||
//auto - combat options
|
||||
labels.push_back(std::make_shared<CLabel>(283, 86, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[398]));//creatures
|
||||
labels.push_back(std::make_shared<CLabel>(283, 116, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[399]));//spells
|
||||
labels.push_back(std::make_shared<CLabel>(283, 146, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[400]));//catapult
|
||||
labels.push_back(std::make_shared<CLabel>(283, 176, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[151]));//ballista
|
||||
labels.push_back(std::make_shared<CLabel>(283, 206, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[401]));//first aid tent
|
||||
labels.push_back(std::make_shared<CLabel>(283, 86, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[398]));//creatures
|
||||
labels.push_back(std::make_shared<CLabel>(283, 116, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[399]));//spells
|
||||
labels.push_back(std::make_shared<CLabel>(283, 146, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[400]));//catapult
|
||||
labels.push_back(std::make_shared<CLabel>(283, 176, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[151]));//ballista
|
||||
labels.push_back(std::make_shared<CLabel>(283, 206, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[401]));//first aid tent
|
||||
|
||||
//creature info
|
||||
labels.push_back(std::make_shared<CLabel>(283, 285, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[402]));//all stats
|
||||
labels.push_back(std::make_shared<CLabel>(283, 315, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[403]));//spells only
|
||||
labels.push_back(std::make_shared<CLabel>(283, 285, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[402]));//all stats
|
||||
labels.push_back(std::make_shared<CLabel>(283, 315, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[403]));//spells only
|
||||
|
||||
//general options
|
||||
labels.push_back(std::make_shared<CLabel>(61, 57, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[404]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 90, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[405]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 123, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[406]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 156, FONT_MEDIUM, TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[407]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 57, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[404]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 90, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[405]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 123, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[406]));
|
||||
labels.push_back(std::make_shared<CLabel>(61, 156, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[407]));
|
||||
}
|
||||
|
||||
void CBattleOptionsWindow::bDefaultf()
|
||||
void BattleOptionsWindow::bDefaultf()
|
||||
{
|
||||
//TODO: implement
|
||||
}
|
||||
|
||||
void CBattleOptionsWindow::bExitf()
|
||||
void BattleOptionsWindow::bExitf()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterface & _owner)
|
||||
BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner)
|
||||
: owner(_owner)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
@ -408,25 +503,25 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
|
||||
|
||||
if(br.winner == 0) //attacker won
|
||||
{
|
||||
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
|
||||
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
|
||||
}
|
||||
else
|
||||
{
|
||||
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
|
||||
labels.push_back(std::make_shared<CLabel>(59, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
|
||||
}
|
||||
|
||||
if(br.winner == 1)
|
||||
{
|
||||
labels.push_back(std::make_shared<CLabel>(412, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
|
||||
labels.push_back(std::make_shared<CLabel>(412, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[410]));
|
||||
}
|
||||
else
|
||||
{
|
||||
labels.push_back(std::make_shared<CLabel>(408, 124, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
|
||||
labels.push_back(std::make_shared<CLabel>(408, 124, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[411]));
|
||||
}
|
||||
|
||||
labels.push_back(std::make_shared<CLabel>(232, 302, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[407]));
|
||||
labels.push_back(std::make_shared<CLabel>(232, 332, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[408]));
|
||||
labels.push_back(std::make_shared<CLabel>(232, 428, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[409]));
|
||||
labels.push_back(std::make_shared<CLabel>(232, 302, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[407]));
|
||||
labels.push_back(std::make_shared<CLabel>(232, 332, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[408]));
|
||||
labels.push_back(std::make_shared<CLabel>(232, 428, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[409]));
|
||||
|
||||
std::string sideNames[2] = {"N/A", "N/A"};
|
||||
|
||||
@ -462,15 +557,15 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
|
||||
}
|
||||
|
||||
//printing attacker and defender's names
|
||||
labels.push_back(std::make_shared<CLabel>(89, 37, FONT_SMALL, TOPLEFT, Colors::WHITE, sideNames[0]));
|
||||
labels.push_back(std::make_shared<CLabel>(381, 53, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, sideNames[1]));
|
||||
labels.push_back(std::make_shared<CLabel>(89, 37, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, sideNames[0]));
|
||||
labels.push_back(std::make_shared<CLabel>(381, 53, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, sideNames[1]));
|
||||
|
||||
//printing casualties
|
||||
for(int step = 0; step < 2; ++step)
|
||||
{
|
||||
if(br.casualties[step].size()==0)
|
||||
{
|
||||
labels.push_back(std::make_shared<CLabel>(235, 360 + 97 * step, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[523]));
|
||||
labels.push_back(std::make_shared<CLabel>(235, 360 + 97 * step, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[523]));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -485,7 +580,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
|
||||
icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", creature->getIconIndex(), 0, xPos, yPos));
|
||||
std::ostringstream amount;
|
||||
amount<<elem.second;
|
||||
labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, CENTER, Colors::WHITE, amount.str()));
|
||||
labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, amount.str()));
|
||||
xPos += 42;
|
||||
}
|
||||
}
|
||||
@ -522,7 +617,7 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
|
||||
boost::algorithm::replace_first(str, "%d", boost::lexical_cast<std::string>(br.exp[weAreAttacker ? 0 : 1]));
|
||||
}
|
||||
|
||||
description = std::make_shared<CTextBox>(str, Rect(69, 203, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE);
|
||||
description = std::make_shared<CTextBox>(str, Rect(69, 203, 330, 68), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
||||
}
|
||||
else // we lose
|
||||
{
|
||||
@ -550,31 +645,29 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult & br, CPlayerInterfa
|
||||
CCS->musich->playMusic(musicName, false, true);
|
||||
CCS->videoh->open(videoName);
|
||||
|
||||
labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text]));
|
||||
labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text]));
|
||||
}
|
||||
}
|
||||
|
||||
CBattleResultWindow::~CBattleResultWindow() = default;
|
||||
|
||||
void CBattleResultWindow::activate()
|
||||
void BattleResultWindow::activate()
|
||||
{
|
||||
owner.showingDialog->set(true);
|
||||
CIntObject::activate();
|
||||
}
|
||||
|
||||
void CBattleResultWindow::show(SDL_Surface * to)
|
||||
void BattleResultWindow::show(SDL_Surface * to)
|
||||
{
|
||||
CIntObject::show(to);
|
||||
CCS->videoh->update(pos.x + 107, pos.y + 70, screen, true, false);
|
||||
}
|
||||
|
||||
void CBattleResultWindow::bExitf()
|
||||
void BattleResultWindow::bExitf()
|
||||
{
|
||||
CPlayerInterface &intTmp = owner; //copy reference because "this" will be destructed soon
|
||||
|
||||
close();
|
||||
|
||||
if(dynamic_cast<CBattleInterface*>(GH.topInt().get()))
|
||||
if(dynamic_cast<BattleWindow*>(GH.topInt().get()))
|
||||
GH.popInts(1); //pop battle interface if present
|
||||
|
||||
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
|
||||
@ -583,121 +676,56 @@ void CBattleResultWindow::bExitf()
|
||||
CCS->videoh->close();
|
||||
}
|
||||
|
||||
Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBattleInterface * cbi)
|
||||
{
|
||||
assert(cbi);
|
||||
|
||||
Point ret(-500, -500); //returned value
|
||||
if(stack && stack->initialPosition < 0) //creatures in turrets
|
||||
{
|
||||
switch(stack->initialPosition)
|
||||
{
|
||||
case -2: //keep
|
||||
ret = cbi->siegeH->town->town->clientInfo.siegePositions[18];
|
||||
break;
|
||||
case -3: //lower turret
|
||||
ret = cbi->siegeH->town->town->clientInfo.siegePositions[19];
|
||||
break;
|
||||
case -4: //upper turret
|
||||
ret = cbi->siegeH->town->town->clientInfo.siegePositions[20];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static const Point basePos(-190, -139); // position of creature in topleft corner
|
||||
static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left
|
||||
|
||||
ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX();
|
||||
ret.y = basePos.y + 42 * hexNum.getY();
|
||||
|
||||
if (stack)
|
||||
{
|
||||
if(cbi->creDir[stack->ID])
|
||||
ret.x += imageShiftX;
|
||||
else
|
||||
ret.x -= imageShiftX;
|
||||
|
||||
//shifting position for double - hex creatures
|
||||
if(stack->doubleWide())
|
||||
{
|
||||
if(stack->side == BattleSide::ATTACKER)
|
||||
{
|
||||
if(cbi->creDir[stack->ID])
|
||||
ret.x -= 44;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!cbi->creDir[stack->ID])
|
||||
ret.x += 44;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//returning
|
||||
return ret + CPlayerInterface::battleInt->pos;
|
||||
}
|
||||
|
||||
void CClickableHex::hover(bool on)
|
||||
void ClickableHex::hover(bool on)
|
||||
{
|
||||
hovered = on;
|
||||
//Hoverable::hover(on);
|
||||
if(!on && setAlterText)
|
||||
{
|
||||
myInterface->console->alterTxt = std::string();
|
||||
GH.statusbar->clear();
|
||||
setAlterText = false;
|
||||
}
|
||||
}
|
||||
|
||||
CClickableHex::CClickableHex() : setAlterText(false), myNumber(-1), accessible(true), strictHovered(false), myInterface(nullptr)
|
||||
ClickableHex::ClickableHex() : setAlterText(false), myNumber(-1), strictHovered(false), myInterface(nullptr)
|
||||
{
|
||||
addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
|
||||
}
|
||||
|
||||
void CClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
||||
void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
|
||||
{
|
||||
if(myInterface->cellShade)
|
||||
{
|
||||
if(CSDL_Ext::SDL_GetPixel(myInterface->cellShade, sEvent.x-pos.x, sEvent.y-pos.y) == 0) //hovered pixel is outside hex
|
||||
{
|
||||
strictHovered = false;
|
||||
}
|
||||
else //hovered pixel is inside hex
|
||||
{
|
||||
strictHovered = true;
|
||||
}
|
||||
}
|
||||
strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y));
|
||||
|
||||
if(hovered && strictHovered) //print attacked creature to console
|
||||
{
|
||||
const CStack * attackedStack = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber);
|
||||
if(myInterface->console->alterTxt.size() == 0 &&attackedStack != nullptr &&
|
||||
if( attackedStack != nullptr &&
|
||||
attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
|
||||
attackedStack->alive())
|
||||
{
|
||||
MetaString text;
|
||||
text.addTxt(MetaString::GENERAL_TXT, 220);
|
||||
attackedStack->addNameReplacement(text);
|
||||
myInterface->console->alterTxt = text.toString();
|
||||
GH.statusbar->write(text.toString());
|
||||
setAlterText = true;
|
||||
}
|
||||
}
|
||||
else if(setAlterText)
|
||||
{
|
||||
myInterface->console->alterTxt = std::string();
|
||||
GH.statusbar->clear();
|
||||
setAlterText = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CClickableHex::clickLeft(tribool down, bool previousState)
|
||||
void ClickableHex::clickLeft(tribool down, bool previousState)
|
||||
{
|
||||
if(!down && hovered && strictHovered) //we've been really clicked!
|
||||
{
|
||||
myInterface->hexLclicked(myNumber);
|
||||
myInterface->actionsController->handleHex(myNumber, LCLICK);
|
||||
}
|
||||
}
|
||||
|
||||
void CClickableHex::clickRight(tribool down, bool previousState)
|
||||
void ClickableHex::clickRight(tribool down, bool previousState)
|
||||
{
|
||||
const CStack * myst = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber); //stack info
|
||||
if(hovered && strictHovered && myst!=nullptr)
|
||||
@ -710,17 +738,17 @@ void CClickableHex::clickRight(tribool down, bool previousState)
|
||||
}
|
||||
}
|
||||
|
||||
CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
|
||||
StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
|
||||
: embedded(Embedded),
|
||||
owner(_owner)
|
||||
owner(owner)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
if(embedded)
|
||||
{
|
||||
pos.w = QUEUE_SIZE * 37;
|
||||
pos.h = 46;
|
||||
pos.x = screen->w/2 - pos.w/2;
|
||||
pos.y = (screen->h - 600)/2 + 10;
|
||||
pos.w = QUEUE_SIZE * 41;
|
||||
pos.h = 49;
|
||||
pos.x += parent->pos.w/2 - pos.w/2;
|
||||
pos.y += 10;
|
||||
|
||||
icons = std::make_shared<CAnimation>("CPRSMALL");
|
||||
stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
|
||||
@ -729,6 +757,8 @@ CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
|
||||
{
|
||||
pos.w = 800;
|
||||
pos.h = 85;
|
||||
pos.x += 0;
|
||||
pos.y -= pos.h;
|
||||
|
||||
background = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
@ -743,17 +773,22 @@ CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
|
||||
for (int i = 0; i < stackBoxes.size(); i++)
|
||||
{
|
||||
stackBoxes[i] = std::make_shared<StackBox>(this);
|
||||
stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80) * i, 0));
|
||||
stackBoxes[i]->moveBy(Point(1 + (embedded ? 41 : 80) * i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
CStackQueue::~CStackQueue() = default;
|
||||
void StackQueue::show(SDL_Surface * to)
|
||||
{
|
||||
if (embedded)
|
||||
showAll(to);
|
||||
CIntObject::show(to);
|
||||
}
|
||||
|
||||
void CStackQueue::update()
|
||||
void StackQueue::update()
|
||||
{
|
||||
std::vector<battle::Units> queueData;
|
||||
|
||||
owner->getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
|
||||
owner.getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
|
||||
|
||||
size_t boxIndex = 0;
|
||||
|
||||
@ -767,12 +802,12 @@ void CStackQueue::update()
|
||||
stackBoxes[boxIndex]->setUnit(nullptr);
|
||||
}
|
||||
|
||||
int32_t CStackQueue::getSiegeShooterIconID()
|
||||
int32_t StackQueue::getSiegeShooterIconID()
|
||||
{
|
||||
return owner->siegeH->town->town->faction->index;
|
||||
return owner.siegeController->getSiegedTown()->town->faction->index;
|
||||
}
|
||||
|
||||
CStackQueue::StackBox::StackBox(CStackQueue * owner):
|
||||
StackQueue::StackBox::StackBox(StackQueue * owner):
|
||||
owner(owner)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
@ -784,12 +819,12 @@ CStackQueue::StackBox::StackBox(CStackQueue * owner):
|
||||
if(owner->embedded)
|
||||
{
|
||||
icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 5, 2);
|
||||
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 7, FONT_SMALL, CENTER, Colors::WHITE);
|
||||
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 7, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 9, 1);
|
||||
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, CENTER, Colors::WHITE);
|
||||
amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
|
||||
|
||||
int icon_x = pos.w - 17;
|
||||
int icon_y = pos.h - 18;
|
||||
@ -799,7 +834,7 @@ CStackQueue::StackBox::StackBox(CStackQueue * owner):
|
||||
}
|
||||
}
|
||||
|
||||
void CStackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
|
||||
void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
|
||||
{
|
||||
if(unit)
|
||||
{
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* CBattleInterfaceClasses.h, part of VCMI engine
|
||||
* BattleInterfaceClasses.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
@ -9,7 +9,9 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "BattleConstants.h"
|
||||
#include "../gui/CIntObject.h"
|
||||
#include "../../lib/FunctionList.h"
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
#include "../windows/CWindowObject.h"
|
||||
|
||||
@ -26,8 +28,9 @@ class Unit;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class Canvas;
|
||||
struct SDL_Surface;
|
||||
class CBattleInterface;
|
||||
class BattleInterface;
|
||||
class CPicture;
|
||||
class CFilledTexture;
|
||||
class CButton;
|
||||
@ -37,82 +40,121 @@ class CLabel;
|
||||
class CTextBox;
|
||||
class CAnimImage;
|
||||
class CPlayerInterface;
|
||||
class BattleRenderer;
|
||||
|
||||
/// Class which shows the console at the bottom of the battle screen and manages the text of the console
|
||||
class CBattleConsole : public CIntObject
|
||||
class BattleConsole : public CIntObject, public IStatusBar
|
||||
{
|
||||
private:
|
||||
std::vector< std::string > texts; //a place where texts are stored
|
||||
int lastShown; //last shown line of text
|
||||
std::shared_ptr<CPicture> background;
|
||||
|
||||
/// List of all texts added during battle, essentially - log of entire battle
|
||||
std::vector< std::string > logEntries;
|
||||
|
||||
/// Current scrolling position, to allow showing older entries via scroll buttons
|
||||
int scrollPosition;
|
||||
|
||||
/// current hover text set on mouse move, takes priority over log entries
|
||||
std::string hoverText;
|
||||
|
||||
/// current text entered via in-game console, takes priority over both log entries and hover text
|
||||
std::string consoleText;
|
||||
|
||||
/// if true then we are currently entering console text
|
||||
bool enteringText;
|
||||
|
||||
/// splits text into individual strings for battle log
|
||||
std::vector<std::string> splitText(const std::string &text);
|
||||
|
||||
/// select line(s) that will be visible in UI
|
||||
std::vector<std::string> getVisibleText();
|
||||
public:
|
||||
std::string alterTxt; //if it's not empty, this text is displayed
|
||||
std::string ingcAlter; //alternative text set by in-game console - very important!
|
||||
int whoSetAlter; //who set alter text; 0 - battle interface or none, 1 - button
|
||||
CBattleConsole();
|
||||
void showAll(SDL_Surface * to = 0) override;
|
||||
BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size);
|
||||
|
||||
void showAll(SDL_Surface * to) override;
|
||||
void deactivate() override;
|
||||
|
||||
bool addText(const std::string &text); //adds text at the last position; returns false if failed (e.g. text longer than 70 characters)
|
||||
void alterText(const std::string &text); //place string at alterTxt
|
||||
void eraseText(ui32 pos); //erases added text at position pos
|
||||
void changeTextAt(const std::string &text, ui32 pos); //if we have more than pos texts, pos-th is changed to given one
|
||||
void scrollUp(ui32 by = 1); //scrolls console up by 'by' positions
|
||||
void scrollDown(ui32 by = 1); //scrolls console up by 'by' positions
|
||||
|
||||
// IStatusBar interface
|
||||
void write(const std::string & Text) override;
|
||||
void clearIfMatching(const std::string & Text) override;
|
||||
void clear() override;
|
||||
void setEnteringMode(bool on) override;
|
||||
void setEnteredText(const std::string & text) override;
|
||||
};
|
||||
|
||||
/// Hero battle animation
|
||||
class CBattleHero : public CIntObject
|
||||
class BattleHero : public CIntObject
|
||||
{
|
||||
void switchToNextPhase();
|
||||
public:
|
||||
bool flip; //false if it's attacking hero, true otherwise
|
||||
bool defender;
|
||||
|
||||
CFunctionList<void()> phaseFinishedCallback;
|
||||
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
std::shared_ptr<CAnimation> flagAnimation;
|
||||
|
||||
const CGHeroInstance * myHero; //this animation's hero instance
|
||||
const CBattleInterface * myOwner; //battle interface to which this animation is assigned
|
||||
int phase; //stage of animation
|
||||
int nextPhase; //stage of animation to be set after current phase is fully displayed
|
||||
int currentFrame, firstFrame, lastFrame; //frame of animation
|
||||
const CGHeroInstance * hero; //this animation's hero instance
|
||||
const BattleInterface & owner; //battle interface to which this animation is assigned
|
||||
|
||||
EHeroAnimType phase; //stage of animation
|
||||
EHeroAnimType nextPhase; //stage of animation to be set after current phase is fully displayed
|
||||
|
||||
float currentSpeed;
|
||||
float currentFrame; //frame of animation
|
||||
float flagCurrentFrame;
|
||||
|
||||
void switchToNextPhase();
|
||||
|
||||
void render(Canvas & canvas); //prints next frame of animation to to
|
||||
public:
|
||||
const CGHeroInstance * instance();
|
||||
|
||||
void setPhase(EHeroAnimType newPhase); //sets phase of hero animation
|
||||
|
||||
void collectRenderableObjects(BattleRenderer & renderer);
|
||||
|
||||
float getFrame() const;
|
||||
void onPhaseFinished(const std::function<void()> &);
|
||||
|
||||
void pause();
|
||||
void play();
|
||||
|
||||
size_t flagAnim;
|
||||
ui8 animCount; //for flag animation
|
||||
void show(SDL_Surface * to) override; //prints next frame of animation to to
|
||||
void setPhase(int newPhase); //sets phase of hero animation
|
||||
void hover(bool on) override;
|
||||
void clickLeft(tribool down, bool previousState) override; //call-in
|
||||
void clickRight(tribool down, bool previousState) override; //call-in
|
||||
CBattleHero(const std::string & animationPath, bool filpG, PlayerColor player, const CGHeroInstance * hero, const CBattleInterface * owner);
|
||||
~CBattleHero();
|
||||
BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender);
|
||||
};
|
||||
|
||||
class CHeroInfoWindow : public CWindowObject
|
||||
class HeroInfoWindow : public CWindowObject
|
||||
{
|
||||
private:
|
||||
std::vector<std::shared_ptr<CLabel>> labels;
|
||||
std::vector<std::shared_ptr<CAnimImage>> icons;
|
||||
public:
|
||||
CHeroInfoWindow(const InfoAboutHero & hero, Point * position);
|
||||
HeroInfoWindow(const InfoAboutHero & hero, Point * position);
|
||||
};
|
||||
|
||||
/// Class which manages the battle options window
|
||||
class CBattleOptionsWindow : public WindowBase
|
||||
class BattleOptionsWindow : public CWindowObject
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<CPicture> background;
|
||||
std::shared_ptr<CButton> setToDefault;
|
||||
std::shared_ptr<CButton> exit;
|
||||
std::shared_ptr<CToggleGroup> animSpeeds;
|
||||
std::vector<std::shared_ptr<CLabel>> labels;
|
||||
std::vector<std::shared_ptr<CToggleButton>> toggles;
|
||||
public:
|
||||
CBattleOptionsWindow(const SDL_Rect & position, CBattleInterface * owner);
|
||||
BattleOptionsWindow(BattleInterface & owner);
|
||||
|
||||
void bDefaultf(); //default button callback
|
||||
void bExitf(); //exit button callback
|
||||
};
|
||||
|
||||
/// Class which is responsible for showing the battle result window
|
||||
class CBattleResultWindow : public WindowBase
|
||||
class BattleResultWindow : public WindowBase
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<CPicture> background;
|
||||
@ -122,8 +164,7 @@ private:
|
||||
std::shared_ptr<CTextBox> description;
|
||||
CPlayerInterface & owner;
|
||||
public:
|
||||
CBattleResultWindow(const BattleResult & br, CPlayerInterface & _owner);
|
||||
~CBattleResultWindow();
|
||||
BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner);
|
||||
|
||||
void bExitf(); //exit button callback
|
||||
|
||||
@ -132,32 +173,29 @@ public:
|
||||
};
|
||||
|
||||
/// Class which stands for a single hex field on a battlefield
|
||||
class CClickableHex : public CIntObject
|
||||
class ClickableHex : public CIntObject
|
||||
{
|
||||
private:
|
||||
bool setAlterText; //if true, this hex has set alternative text in console and will clean it
|
||||
public:
|
||||
ui32 myNumber; //number of hex in commonly used format
|
||||
bool accessible; //if true, this hex is accessible for units
|
||||
//CStack * ourStack;
|
||||
bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering)
|
||||
CBattleInterface * myInterface; //interface that owns me
|
||||
static Point getXYUnitAnim(BattleHex hexNum, const CStack * creature, CBattleInterface * cbi); //returns (x, y) of left top corner of animation
|
||||
BattleInterface * myInterface; //interface that owns me
|
||||
|
||||
//for user interactions
|
||||
void hover (bool on) override;
|
||||
void mouseMoved (const SDL_MouseMotionEvent &sEvent) override;
|
||||
void clickLeft(tribool down, bool previousState) override;
|
||||
void clickRight(tribool down, bool previousState) override;
|
||||
CClickableHex();
|
||||
ClickableHex();
|
||||
};
|
||||
|
||||
/// Shows the stack queue
|
||||
class CStackQueue : public CIntObject
|
||||
class StackQueue : public CIntObject
|
||||
{
|
||||
class StackBox : public CIntObject
|
||||
{
|
||||
CStackQueue * owner;
|
||||
StackQueue * owner;
|
||||
public:
|
||||
std::shared_ptr<CPicture> background;
|
||||
std::shared_ptr<CAnimImage> icon;
|
||||
@ -165,13 +203,13 @@ class CStackQueue : public CIntObject
|
||||
std::shared_ptr<CAnimImage> stateIcon;
|
||||
|
||||
void setUnit(const battle::Unit * unit, size_t turn = 0);
|
||||
StackBox(CStackQueue * owner);
|
||||
StackBox(StackQueue * owner);
|
||||
};
|
||||
|
||||
static const int QUEUE_SIZE = 10;
|
||||
std::shared_ptr<CFilledTexture> background;
|
||||
std::vector<std::shared_ptr<StackBox>> stackBoxes;
|
||||
CBattleInterface * owner;
|
||||
BattleInterface & owner;
|
||||
|
||||
std::shared_ptr<CAnimation> icons;
|
||||
std::shared_ptr<CAnimation> stateIcons;
|
||||
@ -180,7 +218,8 @@ class CStackQueue : public CIntObject
|
||||
public:
|
||||
const bool embedded;
|
||||
|
||||
CStackQueue(bool Embedded, CBattleInterface * _owner);
|
||||
~CStackQueue();
|
||||
StackQueue(bool Embedded, BattleInterface & owner);
|
||||
void update();
|
||||
|
||||
void show(SDL_Surface * to) override;
|
||||
};
|
178
client/battle/BattleObstacleController.cpp
Normal file
178
client/battle/BattleObstacleController.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* BattleObstacleController.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 "BattleObstacleController.h"
|
||||
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleAnimationClasses.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleRenderer.h"
|
||||
#include "CreatureAnimation.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/ObstacleHandler.h"
|
||||
|
||||
BattleObstacleController::BattleObstacleController(BattleInterface & owner):
|
||||
owner(owner),
|
||||
timePassed(0.f)
|
||||
{
|
||||
auto obst = owner.curInt->cb->battleGetAllObstacles();
|
||||
for(auto & elem : obst)
|
||||
{
|
||||
if ( elem->obstacleType == CObstacleInstance::MOAT )
|
||||
continue; // handled by siege controller;
|
||||
loadObstacleImage(*elem);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
|
||||
{
|
||||
std::string animationName;
|
||||
|
||||
if (auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&oi))
|
||||
{
|
||||
animationName = spellObstacle->animation;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( oi.obstacleType == CObstacleInstance::USUAL || oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE);
|
||||
animationName = oi.getInfo().animation;
|
||||
}
|
||||
|
||||
if (animationsCache.count(animationName) == 0)
|
||||
{
|
||||
if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
{
|
||||
// obstacle uses single bitmap image for animations
|
||||
auto animation = std::make_shared<CAnimation>();
|
||||
animation->setCustom(animationName, 0, 0);
|
||||
animationsCache[animationName] = animation;
|
||||
animation->preload();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto animation = std::make_shared<CAnimation>(animationName);
|
||||
animationsCache[animationName] = animation;
|
||||
animation->preload();
|
||||
}
|
||||
}
|
||||
obstacleAnimations[oi.uniqueID] = animationsCache[animationName];
|
||||
}
|
||||
|
||||
void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & obstacles)
|
||||
{
|
||||
for (auto const & oi : obstacles)
|
||||
{
|
||||
auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(oi.get());
|
||||
|
||||
if (!spellObstacle)
|
||||
{
|
||||
logGlobal->error("I don't know how to animate appearing obstacle of type %d", (int)oi->obstacleType);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto animation = std::make_shared<CAnimation>(spellObstacle->appearAnimation);
|
||||
animation->preload();
|
||||
|
||||
auto first = animation->getImage(0, 0);
|
||||
if(!first)
|
||||
continue;
|
||||
|
||||
//we assume here that effect graphics have the same size as the usual obstacle image
|
||||
// -> if we know how to blit obstacle, let's blit the effect in the same place
|
||||
Point whereTo = getObstaclePosition(first, *oi);
|
||||
CCS->soundh->playSound( spellObstacle->appearSound );
|
||||
owner.stacksController->addNewAnim(new EffectAnimation(owner, spellObstacle->appearAnimation, whereTo, oi->pos));
|
||||
|
||||
//so when multiple obstacles are added, they show up one after another
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
loadObstacleImage(*spellObstacle);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas)
|
||||
{
|
||||
//Blit absolute obstacles
|
||||
for(auto & oi : owner.curInt->cb->battleGetAllObstacles())
|
||||
{
|
||||
if(oi->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
{
|
||||
auto img = getObstacleImage(*oi);
|
||||
if(img)
|
||||
canvas.draw(img, Point(oi->getInfo().width, oi->getInfo().height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleObstacleController::collectRenderableObjects(BattleRenderer & renderer)
|
||||
{
|
||||
for (auto obstacle : owner.curInt->cb->battleGetAllObstacles())
|
||||
{
|
||||
if (obstacle->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
continue;
|
||||
|
||||
if (obstacle->obstacleType == CObstacleInstance::MOAT)
|
||||
continue;
|
||||
|
||||
renderer.insert(EBattleFieldLayer::OBSTACLES, obstacle->pos, [this, obstacle]( BattleRenderer::RendererRef canvas ){
|
||||
auto img = getObstacleImage(*obstacle);
|
||||
if(img)
|
||||
{
|
||||
Point p = getObstaclePosition(img, *obstacle);
|
||||
canvas.draw(img, p);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleObstacleController::update()
|
||||
{
|
||||
timePassed += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
|
||||
}
|
||||
|
||||
std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstacleInstance & oi)
|
||||
{
|
||||
int framesCount = timePassed * AnimationControls::getObstaclesSpeed();
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
|
||||
// obstacle is not loaded yet, don't show anything
|
||||
if (obstacleAnimations.count(oi.uniqueID) == 0)
|
||||
return nullptr;
|
||||
|
||||
animation = obstacleAnimations[oi.uniqueID];
|
||||
assert(animation);
|
||||
|
||||
if(animation)
|
||||
{
|
||||
int frameIndex = framesCount % animation->size(0);
|
||||
return animation->getImage(frameIndex, 0);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Point BattleObstacleController::getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle)
|
||||
{
|
||||
int offset = obstacle.getAnimationYOffset(image->height());
|
||||
|
||||
Rect r = owner.fieldController->hexPositionLocal(obstacle.pos);
|
||||
r.y += 42 - image->height() + offset;
|
||||
|
||||
return r.topLeft();
|
||||
}
|
60
client/battle/BattleObstacleController.h
Normal file
60
client/battle/BattleObstacleController.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* BattleObstacleController.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
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct BattleHex;
|
||||
struct CObstacleInstance;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class IImage;
|
||||
class Canvas;
|
||||
class CAnimation;
|
||||
class BattleInterface;
|
||||
class BattleRenderer;
|
||||
struct Point;
|
||||
|
||||
/// Controls all currently active projectiles on the battlefield
|
||||
/// (with exception of moat, which is apparently handled by siege controller)
|
||||
class BattleObstacleController
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
/// total time, in seconds, since start of battle. Used for animating obstacles
|
||||
float timePassed;
|
||||
|
||||
/// cached animations of all obstacles in current battle
|
||||
std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
|
||||
|
||||
/// list of all obstacles that are currently being rendered
|
||||
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
|
||||
|
||||
void loadObstacleImage(const CObstacleInstance & oi);
|
||||
|
||||
std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
|
||||
Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
|
||||
|
||||
public:
|
||||
BattleObstacleController(BattleInterface & owner);
|
||||
|
||||
/// called every frame
|
||||
void update();
|
||||
|
||||
/// call-in from network pack, add newly placed obstacles with any required animations
|
||||
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & oi);
|
||||
|
||||
/// renders all "absolute" obstacles
|
||||
void showAbsoluteObstacles(Canvas & canvas);
|
||||
|
||||
/// adds all non-"absolute" visible obstacles for rendering
|
||||
void collectRenderableObjects(BattleRenderer & renderer);
|
||||
};
|
370
client/battle/BattleProjectileController.cpp
Normal file
370
client/battle/BattleProjectileController.cpp
Normal file
@ -0,0 +1,370 @@
|
||||
/*
|
||||
* BattleProjectileController.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 "BattleProjectileController.h"
|
||||
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "CreatureAnimation.h"
|
||||
|
||||
#include "../gui/Geometries.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
||||
static double calculateCatapultParabolaY(const Point & from, const Point & dest, int x)
|
||||
{
|
||||
double facA = 0.005; // seems to be constant
|
||||
|
||||
// system of 2 linear equations, solutions of which are missing coefficients
|
||||
// for quadratic equation a*x*x + b*x + c
|
||||
double eq[2][3] = {
|
||||
{ static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x },
|
||||
{ static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x }
|
||||
};
|
||||
|
||||
// solve system via determinants
|
||||
double det = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1];
|
||||
double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1];
|
||||
double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2];
|
||||
|
||||
double facB = detB / det;
|
||||
double facC = detC / det;
|
||||
|
||||
return facA *pow(x, 2.0) + facB *x + facC;
|
||||
}
|
||||
|
||||
void ProjectileMissile::show(Canvas & canvas)
|
||||
{
|
||||
size_t group = reverse ? 1 : 0;
|
||||
auto image = animation->getImage(frameNum, group, true);
|
||||
|
||||
if(image)
|
||||
{
|
||||
Point pos {
|
||||
vstd::lerp(from.x, dest.x, progress) - image->width() / 2,
|
||||
vstd::lerp(from.y, dest.y, progress) - image->height() / 2,
|
||||
};
|
||||
|
||||
canvas.draw(image, pos);
|
||||
}
|
||||
|
||||
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
|
||||
progress += timePassed * speed;
|
||||
}
|
||||
|
||||
void ProjectileAnimatedMissile::show(Canvas & canvas)
|
||||
{
|
||||
ProjectileMissile::show(canvas);
|
||||
frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
|
||||
size_t animationSize = animation->size(reverse ? 1 : 0);
|
||||
while (frameProgress > animationSize)
|
||||
frameProgress -= animationSize;
|
||||
|
||||
frameNum = std::floor(frameProgress);
|
||||
}
|
||||
|
||||
void ProjectileCatapult::show(Canvas & canvas)
|
||||
{
|
||||
auto image = animation->getImage(frameNum, 0, true);
|
||||
|
||||
if(image)
|
||||
{
|
||||
int posX = vstd::lerp(from.x, dest.x, progress);
|
||||
int posY = calculateCatapultParabolaY(from, dest, posX);
|
||||
Point pos(posX, posY);
|
||||
|
||||
canvas.draw(image, pos);
|
||||
|
||||
frameNum = (frameNum + 1) % animation->size(0);
|
||||
}
|
||||
|
||||
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
|
||||
progress += timePassed * speed;
|
||||
}
|
||||
|
||||
void ProjectileRay::show(Canvas & canvas)
|
||||
{
|
||||
Point curr {
|
||||
vstd::lerp(from.x, dest.x, progress),
|
||||
vstd::lerp(from.y, dest.y, progress),
|
||||
};
|
||||
|
||||
Point length = curr - from;
|
||||
|
||||
//select axis to draw ray on, we want angle to be less than 45 degrees so individual sub-rays won't overlap each other
|
||||
|
||||
if (std::abs(length.x) > std::abs(length.y)) // draw in horizontal axis
|
||||
{
|
||||
int y1 = from.y - rayConfig.size() / 2;
|
||||
int y2 = curr.y - rayConfig.size() / 2;
|
||||
|
||||
int x1 = from.x;
|
||||
int x2 = curr.x;
|
||||
|
||||
for (size_t i = 0; i < rayConfig.size(); ++i)
|
||||
{
|
||||
auto ray = rayConfig[i];
|
||||
SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1};
|
||||
SDL_Color endColor { ray.r2, ray.g2, ray.b2, ray.a2};
|
||||
|
||||
canvas.drawLine(Point(x1, y1 + i), Point(x2, y2+i), beginColor, endColor);
|
||||
}
|
||||
}
|
||||
else // draw in vertical axis
|
||||
{
|
||||
int x1 = from.x - rayConfig.size() / 2;
|
||||
int x2 = curr.x - rayConfig.size() / 2;
|
||||
|
||||
int y1 = from.y;
|
||||
int y2 = curr.y;
|
||||
|
||||
for (size_t i = 0; i < rayConfig.size(); ++i)
|
||||
{
|
||||
auto ray = rayConfig[i];
|
||||
SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1};
|
||||
SDL_Color endColor { ray.r2, ray.g2, ray.b2, ray.a2};
|
||||
|
||||
canvas.drawLine(Point(x1 + i, y1), Point(x2 + i, y2), beginColor, endColor);
|
||||
}
|
||||
}
|
||||
|
||||
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
|
||||
progress += timePassed * speed;
|
||||
}
|
||||
|
||||
BattleProjectileController::BattleProjectileController(BattleInterface & owner):
|
||||
owner(owner)
|
||||
{}
|
||||
|
||||
const CCreature & BattleProjectileController::getShooter(const CStack * stack) const
|
||||
{
|
||||
const CCreature * creature = stack->getCreature();
|
||||
|
||||
if(creature->idNumber == CreatureID::ARROW_TOWERS)
|
||||
creature = owner.siegeController->getTurretCreature();
|
||||
|
||||
if(creature->animation.missleFrameAngles.empty())
|
||||
{
|
||||
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->nameSing);
|
||||
creature = CGI->creh->objects[CreatureID::ARCHER];
|
||||
}
|
||||
|
||||
return *creature;
|
||||
}
|
||||
|
||||
bool BattleProjectileController::stackUsesRayProjectile(const CStack * stack) const
|
||||
{
|
||||
return !getShooter(stack).animation.projectileRay.empty();
|
||||
}
|
||||
|
||||
bool BattleProjectileController::stackUsesMissileProjectile(const CStack * stack) const
|
||||
{
|
||||
return !getShooter(stack).animation.projectileImageName.empty();
|
||||
}
|
||||
|
||||
void BattleProjectileController::initStackProjectile(const CStack * stack)
|
||||
{
|
||||
if (!stackUsesMissileProjectile(stack))
|
||||
return;
|
||||
|
||||
const CCreature & creature = getShooter(stack);
|
||||
projectilesCache[creature.animation.projectileImageName] = createProjectileImage(creature.animation.projectileImageName);
|
||||
}
|
||||
|
||||
std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const std::string & path )
|
||||
{
|
||||
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(path);
|
||||
projectile->preload();
|
||||
|
||||
if(projectile->size(1) != 0)
|
||||
logAnim->error("Expected empty group 1 in stack projectile");
|
||||
else
|
||||
projectile->createFlippedGroup(0, 1);
|
||||
|
||||
return projectile;
|
||||
}
|
||||
|
||||
std::shared_ptr<CAnimation> BattleProjectileController::getProjectileImage(const CStack * stack)
|
||||
{
|
||||
const CCreature & creature = getShooter(stack);
|
||||
std::string imageName = creature.animation.projectileImageName;
|
||||
|
||||
if (!projectilesCache.count(imageName))
|
||||
initStackProjectile(stack);
|
||||
|
||||
return projectilesCache[imageName];
|
||||
}
|
||||
|
||||
void BattleProjectileController::emitStackProjectile(const CStack * stack)
|
||||
{
|
||||
int stackID = stack ? stack->ID : -1;
|
||||
|
||||
for (auto projectile : projectiles)
|
||||
{
|
||||
if ( !projectile->playing && projectile->shooterID == stackID)
|
||||
{
|
||||
projectile->playing = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleProjectileController::showProjectiles(Canvas & canvas)
|
||||
{
|
||||
for ( auto projectile: projectiles)
|
||||
{
|
||||
if ( projectile->playing )
|
||||
projectile->show(canvas);
|
||||
}
|
||||
|
||||
vstd::erase_if(projectiles, [&](const std::shared_ptr<ProjectileBase> & projectile){
|
||||
return projectile->progress > 1.0f;
|
||||
});
|
||||
}
|
||||
|
||||
bool BattleProjectileController::hasActiveProjectile(const CStack * stack, bool emittedOnly) const
|
||||
{
|
||||
int stackID = stack ? stack->ID : -1;
|
||||
|
||||
for(auto const & instance : projectiles)
|
||||
{
|
||||
if(instance->shooterID == stackID && (instance->playing || !emittedOnly))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float BattleProjectileController::computeProjectileFlightTime( Point from, Point dest, double animSpeed)
|
||||
{
|
||||
float distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y);
|
||||
float distance = sqrt(distanceSquared);
|
||||
|
||||
assert(distance > 1.f);
|
||||
|
||||
return animSpeed / std::max( 1.f, distance);
|
||||
}
|
||||
|
||||
int BattleProjectileController::computeProjectileFrameID( Point from, Point dest, const CStack * stack)
|
||||
{
|
||||
const CCreature & creature = getShooter(stack);
|
||||
|
||||
auto & angles = creature.animation.missleFrameAngles;
|
||||
auto animation = getProjectileImage(stack);
|
||||
|
||||
// only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used
|
||||
size_t maxFrame = std::min<size_t>(angles.size(), animation->size(0));
|
||||
|
||||
assert(maxFrame > 0);
|
||||
double projectileAngle = -atan2(dest.y - from.y, std::abs(dest.x - from.x));
|
||||
|
||||
// values in angles array indicate position from which this frame was rendered, in degrees.
|
||||
// possible range is 90 ... -90, where projectile for +90 will be used for shooting upwards, +0 for shots towards right and -90 for downwards shots
|
||||
// find frame that has closest angle to one that we need for this shot
|
||||
int bestID = 0;
|
||||
double bestDiff = fabs( angles[0] / 180 * M_PI - projectileAngle );
|
||||
|
||||
for (int i=1; i<maxFrame; i++)
|
||||
{
|
||||
double currentDiff = fabs( angles[i] / 180 * M_PI - projectileAngle );
|
||||
if (currentDiff < bestDiff)
|
||||
{
|
||||
bestID = i;
|
||||
bestDiff = currentDiff;
|
||||
}
|
||||
}
|
||||
return bestID;
|
||||
}
|
||||
|
||||
void BattleProjectileController::createCatapultProjectile(const CStack * shooter, Point from, Point dest)
|
||||
{
|
||||
auto catapultProjectile = new ProjectileCatapult();
|
||||
|
||||
catapultProjectile->animation = getProjectileImage(shooter);
|
||||
catapultProjectile->frameNum = 0;
|
||||
catapultProjectile->progress = 0;
|
||||
catapultProjectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
|
||||
catapultProjectile->from = from;
|
||||
catapultProjectile->dest = dest;
|
||||
catapultProjectile->shooterID = shooter->ID;
|
||||
catapultProjectile->playing = false;
|
||||
|
||||
projectiles.push_back(std::shared_ptr<ProjectileBase>(catapultProjectile));
|
||||
}
|
||||
|
||||
void BattleProjectileController::createProjectile(const CStack * shooter, Point from, Point dest)
|
||||
{
|
||||
const CCreature & shooterInfo = getShooter(shooter);
|
||||
|
||||
std::shared_ptr<ProjectileBase> projectile;
|
||||
if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter))
|
||||
{
|
||||
logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo.nameSing);
|
||||
}
|
||||
|
||||
if (stackUsesRayProjectile(shooter))
|
||||
{
|
||||
auto rayProjectile = new ProjectileRay();
|
||||
projectile.reset(rayProjectile);
|
||||
|
||||
rayProjectile->rayConfig = shooterInfo.animation.projectileRay;
|
||||
}
|
||||
else if (stackUsesMissileProjectile(shooter))
|
||||
{
|
||||
auto missileProjectile = new ProjectileMissile();
|
||||
projectile.reset(missileProjectile);
|
||||
|
||||
missileProjectile->animation = getProjectileImage(shooter);
|
||||
missileProjectile->reverse = !owner.stacksController->facingRight(shooter);
|
||||
missileProjectile->frameNum = computeProjectileFrameID(from, dest, shooter);
|
||||
}
|
||||
|
||||
projectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
|
||||
projectile->from = from;
|
||||
projectile->dest = dest;
|
||||
projectile->shooterID = shooter->ID;
|
||||
projectile->progress = 0;
|
||||
projectile->playing = false;
|
||||
|
||||
projectiles.push_back(projectile);
|
||||
}
|
||||
|
||||
void BattleProjectileController::createSpellProjectile(const CStack * shooter, Point from, Point dest, const CSpell * spell)
|
||||
{
|
||||
double projectileAngle = std::abs(atan2(dest.x - from.x, dest.y - from.y));
|
||||
std::string animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
|
||||
|
||||
assert(!animToDisplay.empty());
|
||||
|
||||
if(!animToDisplay.empty())
|
||||
{
|
||||
auto projectile = new ProjectileAnimatedMissile();
|
||||
|
||||
projectile->animation = createProjectileImage(animToDisplay);
|
||||
projectile->frameProgress = 0;
|
||||
projectile->frameNum = 0;
|
||||
projectile->reverse = from.x > dest.x;
|
||||
projectile->from = from;
|
||||
projectile->dest = dest;
|
||||
projectile->shooterID = shooter ? shooter->ID : -1;
|
||||
projectile->progress = 0;
|
||||
projectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
|
||||
projectile->playing = false;
|
||||
|
||||
projectiles.push_back(std::shared_ptr<ProjectileBase>(projectile));
|
||||
}
|
||||
}
|
118
client/battle/BattleProjectileController.h
Normal file
118
client/battle/BattleProjectileController.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* BattleSiegeController.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 "../../lib/CCreatureHandler.h"
|
||||
#include "../gui/Geometries.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CStack;
|
||||
class CSpell;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct Point;
|
||||
class CAnimation;
|
||||
class Canvas;
|
||||
class BattleInterface;
|
||||
|
||||
/// Base class for projectiles
|
||||
struct ProjectileBase
|
||||
{
|
||||
virtual ~ProjectileBase() = default;
|
||||
virtual void show(Canvas & canvas) = 0;
|
||||
|
||||
Point from; // initial position on the screen
|
||||
Point dest; // target position on the screen
|
||||
|
||||
float progress; // current position of projectile on from->dest line
|
||||
float speed; // how much progress is gained per second
|
||||
int shooterID; // ID of shooter stack
|
||||
bool playing; // if set to true, projectile animation is playing, e.g. flying to target
|
||||
};
|
||||
|
||||
/// Projectile for most shooters - render pre-selected frame moving in straight line from origin to destination
|
||||
struct ProjectileMissile : ProjectileBase
|
||||
{
|
||||
void show(Canvas & canvas) override;
|
||||
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
int frameNum; // frame to display from projectile animation
|
||||
bool reverse; // if true, projectile will be flipped by vertical axis
|
||||
};
|
||||
|
||||
/// Projectile for spell - render animation moving in straight line from origin to destination
|
||||
struct ProjectileAnimatedMissile : ProjectileMissile
|
||||
{
|
||||
void show(Canvas & canvas) override;
|
||||
float frameProgress;
|
||||
};
|
||||
|
||||
/// Projectile for catapult - render spinning projectile moving at parabolic trajectory to its destination
|
||||
struct ProjectileCatapult : ProjectileBase
|
||||
{
|
||||
void show(Canvas & canvas) override;
|
||||
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
int frameNum; // frame to display from projectile animation
|
||||
};
|
||||
|
||||
/// Projectile for mages/evil eye - render ray expanding from origin position to destination
|
||||
struct ProjectileRay : ProjectileBase
|
||||
{
|
||||
void show(Canvas & canvas) override;
|
||||
|
||||
std::vector<CCreature::CreatureAnimation::RayColor> rayConfig;
|
||||
};
|
||||
|
||||
/// Class that manages all ongoing projectiles in the game
|
||||
/// ... even though in H3 only 1 projectile can be on screen at any point of time
|
||||
class BattleProjectileController
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
/// all projectiles loaded during current battle
|
||||
std::map<std::string, std::shared_ptr<CAnimation>> projectilesCache;
|
||||
|
||||
/// projectiles currently flying on battlefield
|
||||
std::vector<std::shared_ptr<ProjectileBase>> projectiles;
|
||||
|
||||
std::shared_ptr<CAnimation> getProjectileImage(const CStack * stack);
|
||||
std::shared_ptr<CAnimation> createProjectileImage(const std::string & path );
|
||||
void initStackProjectile(const CStack * stack);
|
||||
|
||||
bool stackUsesRayProjectile(const CStack * stack) const;
|
||||
bool stackUsesMissileProjectile(const CStack * stack) const;
|
||||
|
||||
void showProjectile(Canvas & canvas, std::shared_ptr<ProjectileBase> projectile);
|
||||
|
||||
const CCreature & getShooter(const CStack * stack) const;
|
||||
|
||||
int computeProjectileFrameID( Point from, Point dest, const CStack * stack);
|
||||
float computeProjectileFlightTime( Point from, Point dest, double speed);
|
||||
|
||||
public:
|
||||
BattleProjectileController(BattleInterface & owner);
|
||||
|
||||
/// renders all currently active projectiles
|
||||
void showProjectiles(Canvas & canvas);
|
||||
|
||||
/// returns true if stack has projectile that is yet to hit target
|
||||
bool hasActiveProjectile(const CStack * stack, bool emittedOnly) const;
|
||||
|
||||
/// starts rendering previously created projectile
|
||||
void emitStackProjectile(const CStack * stack);
|
||||
|
||||
/// creates (but not emits!) projectile and initializes it based on parameters
|
||||
void createProjectile(const CStack * shooter, Point from, Point dest);
|
||||
void createSpellProjectile(const CStack * shooter, Point from, Point dest, const CSpell * spell);
|
||||
void createCatapultProjectile(const CStack * shooter, Point from, Point dest);
|
||||
};
|
76
client/battle/BattleRenderer.cpp
Normal file
76
client/battle/BattleRenderer.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* BattleFieldController.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 "BattleRenderer.h"
|
||||
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "BattleEffectsController.h"
|
||||
#include "BattleWindow.h"
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleObstacleController.h"
|
||||
|
||||
void BattleRenderer::collectObjects()
|
||||
{
|
||||
owner.effectsController->collectRenderableObjects(*this);
|
||||
owner.obstacleController->collectRenderableObjects(*this);
|
||||
owner.stacksController->collectRenderableObjects(*this);
|
||||
if (owner.siegeController)
|
||||
owner.siegeController->collectRenderableObjects(*this);
|
||||
if (owner.defendingHero)
|
||||
owner.defendingHero->collectRenderableObjects(*this);
|
||||
if (owner.attackingHero)
|
||||
owner.attackingHero->collectRenderableObjects(*this);
|
||||
}
|
||||
|
||||
void BattleRenderer::sortObjects()
|
||||
{
|
||||
auto getRow = [](const RenderableInstance & object) -> int
|
||||
{
|
||||
if (object.tile.isValid())
|
||||
return object.tile.getY();
|
||||
|
||||
if ( object.tile == BattleHex::HEX_BEFORE_ALL )
|
||||
return -1;
|
||||
|
||||
assert( object.tile == BattleHex::HEX_AFTER_ALL || object.tile == BattleHex::INVALID);
|
||||
return GameConstants::BFIELD_HEIGHT;
|
||||
};
|
||||
|
||||
std::stable_sort(objects.begin(), objects.end(), [&](const RenderableInstance & left, const RenderableInstance & right){
|
||||
if ( getRow(left) != getRow(right))
|
||||
return getRow(left) < getRow(right);
|
||||
return left.layer < right.layer;
|
||||
});
|
||||
}
|
||||
|
||||
void BattleRenderer::renderObjects(BattleRenderer::RendererRef targetCanvas)
|
||||
{
|
||||
for (auto const & object : objects)
|
||||
object.functor(targetCanvas);
|
||||
}
|
||||
|
||||
BattleRenderer::BattleRenderer(BattleInterface & owner):
|
||||
owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
void BattleRenderer::insert(EBattleFieldLayer layer, BattleHex tile, BattleRenderer::RenderFunctor functor)
|
||||
{
|
||||
objects.push_back({functor, layer, tile});
|
||||
}
|
||||
|
||||
void BattleRenderer::execute(BattleRenderer::RendererRef targetCanvas)
|
||||
{
|
||||
collectObjects();
|
||||
sortObjects();
|
||||
renderObjects(targetCanvas);
|
||||
}
|
54
client/battle/BattleRenderer.h
Normal file
54
client/battle/BattleRenderer.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* BattleFieldController.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 "../../lib/battle/BattleHex.h"
|
||||
|
||||
class Canvas;
|
||||
class BattleInterface;
|
||||
|
||||
enum class EBattleFieldLayer {
|
||||
// confirmed ordering requirements:
|
||||
OBSTACLES = 0,
|
||||
CORPSES = 0,
|
||||
WALLS = 1,
|
||||
HEROES = 2,
|
||||
STACKS = 2, // after corpses, obstacles, walls
|
||||
BATTLEMENTS = 3, // after stacks
|
||||
STACK_AMOUNTS = 3, // after stacks, obstacles, corpses
|
||||
EFFECTS = 4, // after obstacles, battlements
|
||||
};
|
||||
|
||||
class BattleRenderer
|
||||
{
|
||||
public:
|
||||
using RendererRef = Canvas &;
|
||||
using RenderFunctor = std::function<void(RendererRef)>;
|
||||
|
||||
private:
|
||||
BattleInterface & owner;
|
||||
|
||||
struct RenderableInstance
|
||||
{
|
||||
RenderFunctor functor;
|
||||
EBattleFieldLayer layer;
|
||||
BattleHex tile;
|
||||
};
|
||||
std::vector<RenderableInstance> objects;
|
||||
|
||||
void collectObjects();
|
||||
void sortObjects();
|
||||
void renderObjects(RendererRef targetCanvas);
|
||||
public:
|
||||
BattleRenderer(BattleInterface & owner);
|
||||
|
||||
void insert(EBattleFieldLayer layer, BattleHex tile, RenderFunctor functor);
|
||||
void execute(RendererRef targetCanvas);
|
||||
};
|
371
client/battle/BattleSiegeController.cpp
Normal file
371
client/battle/BattleSiegeController.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
* BattleSiegeController.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 "BattleSiegeController.h"
|
||||
|
||||
#include "BattleAnimationClasses.h"
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleRenderer.h"
|
||||
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/Canvas.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/NetPacks.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
||||
std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const
|
||||
{
|
||||
auto getImageIndex = [&]() -> int
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case EWallState::INTACT :
|
||||
return 1;
|
||||
case EWallState::DAMAGED :
|
||||
// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
|
||||
if(what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
|
||||
return 1;
|
||||
else
|
||||
return 2;
|
||||
case EWallState::DESTROYED :
|
||||
if (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
|
||||
return 2;
|
||||
else
|
||||
return 3;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
const std::string & prefix = town->town->clientInfo.siegePrefix;
|
||||
std::string addit = boost::lexical_cast<std::string>(getImageIndex());
|
||||
|
||||
switch(what)
|
||||
{
|
||||
case EWallVisual::BACKGROUND_WALL:
|
||||
{
|
||||
switch(town->town->faction->index)
|
||||
{
|
||||
case ETownType::RAMPART:
|
||||
case ETownType::NECROPOLIS:
|
||||
case ETownType::DUNGEON:
|
||||
case ETownType::STRONGHOLD:
|
||||
return prefix + "TPW1.BMP";
|
||||
default:
|
||||
return prefix + "TPWL.BMP";
|
||||
}
|
||||
}
|
||||
case EWallVisual::KEEP:
|
||||
return prefix + "MAN" + addit + ".BMP";
|
||||
case EWallVisual::BOTTOM_TOWER:
|
||||
return prefix + "TW1" + addit + ".BMP";
|
||||
case EWallVisual::BOTTOM_WALL:
|
||||
return prefix + "WA1" + addit + ".BMP";
|
||||
case EWallVisual::WALL_BELLOW_GATE:
|
||||
return prefix + "WA3" + addit + ".BMP";
|
||||
case EWallVisual::WALL_OVER_GATE:
|
||||
return prefix + "WA4" + addit + ".BMP";
|
||||
case EWallVisual::UPPER_WALL:
|
||||
return prefix + "WA6" + addit + ".BMP";
|
||||
case EWallVisual::UPPER_TOWER:
|
||||
return prefix + "TW2" + addit + ".BMP";
|
||||
case EWallVisual::GATE:
|
||||
return prefix + "DRW" + addit + ".BMP";
|
||||
case EWallVisual::GATE_ARCH:
|
||||
return prefix + "ARCH.BMP";
|
||||
case EWallVisual::BOTTOM_STATIC_WALL:
|
||||
return prefix + "WA2.BMP";
|
||||
case EWallVisual::UPPER_STATIC_WALL:
|
||||
return prefix + "WA5.BMP";
|
||||
case EWallVisual::MOAT:
|
||||
return prefix + "MOAT.BMP";
|
||||
case EWallVisual::MOAT_BANK:
|
||||
return prefix + "MLIP.BMP";
|
||||
case EWallVisual::KEEP_BATTLEMENT:
|
||||
return prefix + "MANC.BMP";
|
||||
case EWallVisual::BOTTOM_BATTLEMENT:
|
||||
return prefix + "TW1C.BMP";
|
||||
case EWallVisual::UPPER_BATTLEMENT:
|
||||
return prefix + "TW2C.BMP";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what)
|
||||
{
|
||||
auto & ci = town->town->clientInfo;
|
||||
auto const & pos = ci.siegePositions[what];
|
||||
|
||||
if ( wallPieceImages[what])
|
||||
canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));
|
||||
}
|
||||
|
||||
std::string BattleSiegeController::getBattleBackgroundName() const
|
||||
{
|
||||
const std::string & prefix = town->town->clientInfo.siegePrefix;
|
||||
return prefix + "BACK.BMP";
|
||||
}
|
||||
|
||||
bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
|
||||
{
|
||||
//FIXME: use this instead of buildings test?
|
||||
//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();
|
||||
|
||||
switch (what)
|
||||
{
|
||||
case EWallVisual::MOAT: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER;
|
||||
case EWallVisual::MOAT_BANK: return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS;
|
||||
case EWallVisual::KEEP_BATTLEMENT: return town->hasBuilt(BuildingID::CITADEL) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED;
|
||||
case EWallVisual::UPPER_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED;
|
||||
case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual what) const
|
||||
{
|
||||
static const std::array<BattleHex, 18> wallsPositions = {
|
||||
BattleHex::INVALID, // BACKGROUND, // handled separately
|
||||
BattleHex::HEX_BEFORE_ALL, // BACKGROUND_WALL,
|
||||
135, // KEEP,
|
||||
BattleHex::HEX_AFTER_ALL, // BOTTOM_TOWER,
|
||||
182, // BOTTOM_WALL,
|
||||
130, // WALL_BELLOW_GATE,
|
||||
62, // WALL_OVER_GATE,
|
||||
12, // UPPER_WALL,
|
||||
BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER,
|
||||
BattleHex::HEX_BEFORE_ALL, // GATE, // 94
|
||||
112, // GATE_ARCH,
|
||||
165, // BOTTOM_STATIC_WALL,
|
||||
45, // UPPER_STATIC_WALL,
|
||||
BattleHex::INVALID, // MOAT, // printed as absolute obstacle
|
||||
BattleHex::INVALID, // MOAT_BANK, // printed as absolute obstacle
|
||||
135, // KEEP_BATTLEMENT,
|
||||
BattleHex::HEX_AFTER_ALL, // BOTTOM_BATTLEMENT,
|
||||
BattleHex::HEX_BEFORE_ALL, // UPPER_BATTLEMENT,
|
||||
};
|
||||
|
||||
return wallsPositions[what];
|
||||
}
|
||||
|
||||
BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTownInstance *siegeTown):
|
||||
owner(owner),
|
||||
town(siegeTown)
|
||||
{
|
||||
assert(owner.fieldController.get() == nullptr); // must be created after this
|
||||
|
||||
for (int g = 0; g < wallPieceImages.size(); ++g)
|
||||
{
|
||||
if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state
|
||||
continue;
|
||||
|
||||
if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
|
||||
continue;
|
||||
|
||||
wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::INTACT));
|
||||
}
|
||||
}
|
||||
|
||||
const CCreature *BattleSiegeController::getTurretCreature() const
|
||||
{
|
||||
return CGI->creh->objects[town->town->clientInfo.siegeShooter];
|
||||
}
|
||||
|
||||
Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const
|
||||
{
|
||||
// Turret positions are read out of the config/wall_pos.txt
|
||||
int posID = 0;
|
||||
switch (position)
|
||||
{
|
||||
case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature
|
||||
posID = EWallVisual::CREATURE_KEEP;
|
||||
break;
|
||||
case BattleHex::CASTLE_BOTTOM_TOWER: // bottom creature
|
||||
posID = EWallVisual::CREATURE_BOTTOM_TOWER;
|
||||
break;
|
||||
case BattleHex::CASTLE_UPPER_TOWER: // upper creature
|
||||
posID = EWallVisual::CREATURE_UPPER_TOWER;
|
||||
break;
|
||||
}
|
||||
|
||||
if (posID != 0)
|
||||
{
|
||||
return {
|
||||
town->town->clientInfo.siegePositions[posID].x,
|
||||
town->town->clientInfo.siegePositions[posID].y
|
||||
};
|
||||
}
|
||||
|
||||
assert(0);
|
||||
return Point(0,0);
|
||||
}
|
||||
|
||||
void BattleSiegeController::gateStateChanged(const EGateState state)
|
||||
{
|
||||
auto oldState = owner.curInt->cb->battleGetGateState();
|
||||
bool playSound = false;
|
||||
auto stateId = EWallState::NONE;
|
||||
switch(state)
|
||||
{
|
||||
case EGateState::CLOSED:
|
||||
if (oldState != EGateState::BLOCKED)
|
||||
playSound = true;
|
||||
break;
|
||||
case EGateState::BLOCKED:
|
||||
if (oldState != EGateState::CLOSED)
|
||||
playSound = true;
|
||||
break;
|
||||
case EGateState::OPENED:
|
||||
playSound = true;
|
||||
stateId = EWallState::DAMAGED;
|
||||
break;
|
||||
case EGateState::DESTROYED:
|
||||
stateId = EWallState::DESTROYED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)
|
||||
wallPieceImages[EWallVisual::GATE] = nullptr;
|
||||
|
||||
if (stateId != EWallState::NONE)
|
||||
wallPieceImages[EWallVisual::GATE] = IImage::createFromFile(getWallPieceImageName(EWallVisual::GATE, stateId));
|
||||
|
||||
if (playSound)
|
||||
CCS->soundh->playSound(soundBase::DRAWBRG);
|
||||
}
|
||||
|
||||
void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas)
|
||||
{
|
||||
if (getWallPieceExistance(EWallVisual::MOAT))
|
||||
showWallPiece(canvas, EWallVisual::MOAT);
|
||||
|
||||
if (getWallPieceExistance(EWallVisual::MOAT_BANK))
|
||||
showWallPiece(canvas, EWallVisual::MOAT_BANK);
|
||||
}
|
||||
|
||||
BattleHex BattleSiegeController::getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const
|
||||
{
|
||||
switch(wallPiece)
|
||||
{
|
||||
case EWallVisual::KEEP_BATTLEMENT: return BattleHex::CASTLE_CENTRAL_TOWER;
|
||||
case EWallVisual::BOTTOM_BATTLEMENT: return BattleHex::CASTLE_BOTTOM_TOWER;
|
||||
case EWallVisual::UPPER_BATTLEMENT: return BattleHex::CASTLE_UPPER_TOWER;
|
||||
}
|
||||
assert(0);
|
||||
return BattleHex::INVALID;
|
||||
}
|
||||
|
||||
const CStack * BattleSiegeController::getTurretStack(EWallVisual::EWallVisual wallPiece) const
|
||||
{
|
||||
for (auto & stack : owner.curInt->cb->battleGetAllStacks(true))
|
||||
{
|
||||
if ( stack->initialPosition == getTurretBattleHex(wallPiece))
|
||||
return stack;
|
||||
}
|
||||
assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer)
|
||||
{
|
||||
for (int i = EWallVisual::WALL_FIRST; i <= EWallVisual::WALL_LAST; ++i)
|
||||
{
|
||||
auto wallPiece = EWallVisual::EWallVisual(i);
|
||||
|
||||
if ( !getWallPieceExistance(wallPiece))
|
||||
continue;
|
||||
|
||||
if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)
|
||||
continue;
|
||||
|
||||
if (wallPiece == EWallVisual::KEEP_BATTLEMENT ||
|
||||
wallPiece == EWallVisual::BOTTOM_BATTLEMENT ||
|
||||
wallPiece == EWallVisual::UPPER_BATTLEMENT)
|
||||
{
|
||||
renderer.insert( EBattleFieldLayer::STACKS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
|
||||
owner.stacksController->showStack(canvas, getTurretStack(wallPiece));
|
||||
});
|
||||
renderer.insert( EBattleFieldLayer::BATTLEMENTS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
|
||||
showWallPiece(canvas, wallPiece);
|
||||
});
|
||||
}
|
||||
renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
|
||||
showWallPiece(canvas, wallPiece);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
|
||||
{
|
||||
if (owner.tacticsMode)
|
||||
return false;
|
||||
|
||||
auto wallPart = owner.curInt->cb->battleHexToWallPart(hex);
|
||||
if (!owner.curInt->cb->isWallPartPotentiallyAttackable(wallPart))
|
||||
return false;
|
||||
|
||||
auto state = owner.curInt->cb->battleGetWallState(static_cast<int>(wallPart));
|
||||
return state != EWallState::DESTROYED && state != EWallState::NONE;
|
||||
}
|
||||
|
||||
void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
|
||||
{
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
if (ca.attacker != -1)
|
||||
{
|
||||
const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker);
|
||||
for (auto attackInfo : ca.attackedParts)
|
||||
{
|
||||
owner.stacksController->addNewAnim(new CatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<Point> positions;
|
||||
|
||||
//no attacker stack, assume spell-related (earthquake) - only hit animation
|
||||
for (auto attackInfo : ca.attackedParts)
|
||||
positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
|
||||
|
||||
CCS->soundh->playSound( "WALLHIT" );
|
||||
owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions));
|
||||
}
|
||||
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.setAnimationCondition(EAnimationEvents::HIT, false);
|
||||
|
||||
for (auto attackInfo : ca.attackedParts)
|
||||
{
|
||||
int wallId = attackInfo.attackedPart + EWallVisual::DESTRUCTIBLE_FIRST;
|
||||
//gate state changing handled separately
|
||||
if (wallId == EWallVisual::GATE)
|
||||
continue;
|
||||
|
||||
auto wallState = EWallState::EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart));
|
||||
|
||||
wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
|
||||
}
|
||||
}
|
||||
|
||||
const CGTownInstance *BattleSiegeController::getSiegedTown() const
|
||||
{
|
||||
return town;
|
||||
}
|
110
client/battle/BattleSiegeController.h
Normal file
110
client/battle/BattleSiegeController.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* BattleObstacleController.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 "../../lib/GameConstants.h"
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct CatapultAttack;
|
||||
class CCreature;
|
||||
class CStack;
|
||||
class CGTownInstance;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct Point;
|
||||
class Canvas;
|
||||
class BattleInterface;
|
||||
class BattleRenderer;
|
||||
class IImage;
|
||||
|
||||
namespace EWallVisual
|
||||
{
|
||||
enum EWallVisual
|
||||
{
|
||||
BACKGROUND,
|
||||
BACKGROUND_WALL,
|
||||
|
||||
KEEP,
|
||||
BOTTOM_TOWER,
|
||||
BOTTOM_WALL,
|
||||
WALL_BELLOW_GATE,
|
||||
WALL_OVER_GATE,
|
||||
UPPER_WALL,
|
||||
UPPER_TOWER,
|
||||
GATE,
|
||||
|
||||
GATE_ARCH,
|
||||
BOTTOM_STATIC_WALL,
|
||||
UPPER_STATIC_WALL,
|
||||
MOAT,
|
||||
MOAT_BANK,
|
||||
KEEP_BATTLEMENT,
|
||||
BOTTOM_BATTLEMENT,
|
||||
UPPER_BATTLEMENT,
|
||||
|
||||
CREATURE_KEEP,
|
||||
CREATURE_BOTTOM_TOWER,
|
||||
CREATURE_UPPER_TOWER,
|
||||
|
||||
WALL_FIRST = BACKGROUND_WALL,
|
||||
WALL_LAST = UPPER_BATTLEMENT,
|
||||
|
||||
// these entries are mapped to EWallPart enum
|
||||
DESTRUCTIBLE_FIRST = KEEP,
|
||||
DESTRUCTIBLE_LAST = GATE,
|
||||
};
|
||||
}
|
||||
|
||||
class BattleSiegeController
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
/// besieged town
|
||||
const CGTownInstance *town;
|
||||
|
||||
/// sections of castle walls, in their currently visible state
|
||||
std::array<std::shared_ptr<IImage>, EWallVisual::WALL_LAST + 1> wallPieceImages;
|
||||
|
||||
/// return URI for image for a wall piece
|
||||
std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const;
|
||||
|
||||
/// returns BattleHex to which chosen wall piece is bound
|
||||
BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
|
||||
|
||||
/// returns true if chosen wall piece should be present in current battle
|
||||
bool getWallPieceExistance(EWallVisual::EWallVisual what) const;
|
||||
|
||||
void showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what);
|
||||
|
||||
BattleHex getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const;
|
||||
const CStack * getTurretStack(EWallVisual::EWallVisual wallPiece) const;
|
||||
|
||||
public:
|
||||
BattleSiegeController(BattleInterface & owner, const CGTownInstance *siegeTown);
|
||||
|
||||
/// call-ins from server
|
||||
void gateStateChanged(const EGateState state);
|
||||
void stackIsCatapulting(const CatapultAttack & ca);
|
||||
|
||||
/// call-ins from other battle controllers
|
||||
void showAbsoluteObstacles(Canvas & canvas);
|
||||
void collectRenderableObjects(BattleRenderer & renderer);
|
||||
|
||||
/// queries from other battle controllers
|
||||
bool isAttackableByCatapult(BattleHex hex) const;
|
||||
std::string getBattleBackgroundName() const;
|
||||
const CCreature *getTurretCreature() const;
|
||||
Point getTurretCreaturePosition( BattleHex position ) const;
|
||||
|
||||
const CGTownInstance *getSiegedTown() const;
|
||||
};
|
941
client/battle/BattleStacksController.cpp
Normal file
941
client/battle/BattleStacksController.cpp
Normal file
@ -0,0 +1,941 @@
|
||||
/*
|
||||
* BattleStacksController.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 "BattleStacksController.h"
|
||||
|
||||
#include "BattleSiegeController.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleActionsController.h"
|
||||
#include "BattleAnimationClasses.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleEffectsController.h"
|
||||
#include "BattleProjectileController.h"
|
||||
#include "BattleWindow.h"
|
||||
#include "BattleRenderer.h"
|
||||
#include "CreatureAnimation.h"
|
||||
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
#include "../../lib/CGameState.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CondSh.h"
|
||||
|
||||
static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnimation> anim)
|
||||
{
|
||||
std::shared_ptr<CreatureAnimation> animation = anim.lock();
|
||||
if(!animation)
|
||||
return;
|
||||
|
||||
if (!stack->isFrozen() && animation->getType() == ECreatureAnimType::FROZEN)
|
||||
animation->setType(ECreatureAnimType::HOLDING);
|
||||
|
||||
if (animation->isIdle())
|
||||
{
|
||||
const CCreature *creature = stack->getCreature();
|
||||
|
||||
if (stack->isFrozen())
|
||||
animation->setType(ECreatureAnimType::FROZEN);
|
||||
else
|
||||
if (animation->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
|
||||
{
|
||||
if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets *10)
|
||||
animation->playOnce(ECreatureAnimType::MOUSEON);
|
||||
else
|
||||
animation->setType(ECreatureAnimType::HOLDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
animation->setType(ECreatureAnimType::HOLDING);
|
||||
}
|
||||
}
|
||||
// always reset callback
|
||||
animation->onAnimationReset += std::bind(&onAnimationFinished, stack, anim);
|
||||
}
|
||||
|
||||
BattleStacksController::BattleStacksController(BattleInterface & owner):
|
||||
owner(owner),
|
||||
activeStack(nullptr),
|
||||
stackToActivate(nullptr),
|
||||
selectedStack(nullptr),
|
||||
stackCanCastSpell(false),
|
||||
creatureSpellToCast(uint32_t(-1)),
|
||||
animIDhelper(0)
|
||||
{
|
||||
//preparing graphics for displaying amounts of creatures
|
||||
amountNormal = IImage::createFromFile("CMNUMWIN.BMP");
|
||||
amountPositive = IImage::createFromFile("CMNUMWIN.BMP");
|
||||
amountNegative = IImage::createFromFile("CMNUMWIN.BMP");
|
||||
amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
|
||||
|
||||
static const auto shifterNormal = ColorFilter::genRangeShifter( 0,0,0, 0.6, 0.2, 1.0 );
|
||||
static const auto shifterPositive = ColorFilter::genRangeShifter( 0,0,0, 0.2, 1.0, 0.2 );
|
||||
static const auto shifterNegative = ColorFilter::genRangeShifter( 0,0,0, 1.0, 0.2, 0.2 );
|
||||
static const auto shifterNeutral = ColorFilter::genRangeShifter( 0,0,0, 1.0, 1.0, 0.2 );
|
||||
|
||||
amountNormal->adjustPalette(shifterNormal);
|
||||
amountPositive->adjustPalette(shifterPositive);
|
||||
amountNegative->adjustPalette(shifterNegative);
|
||||
amountEffNeutral->adjustPalette(shifterNeutral);
|
||||
|
||||
//Restore border color {255, 231, 132, 255} to its original state
|
||||
amountNormal->resetPalette(26);
|
||||
amountPositive->resetPalette(26);
|
||||
amountNegative->resetPalette(26);
|
||||
amountEffNeutral->resetPalette(26);
|
||||
|
||||
std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
|
||||
for(const CStack * s : stacks)
|
||||
{
|
||||
stackAdded(s, true);
|
||||
}
|
||||
}
|
||||
|
||||
BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack) const
|
||||
{
|
||||
if ( !stackAnimation.at(stack->ID)->isMoving())
|
||||
return stack->getPosition();
|
||||
|
||||
if (stack->hasBonusOfType(Bonus::FLYING) && stackAnimation.at(stack->ID)->getType() == ECreatureAnimType::MOVING )
|
||||
return BattleHex::HEX_AFTER_ALL;
|
||||
|
||||
for (auto & anim : currentAnimations)
|
||||
{
|
||||
// certainly ugly workaround but fixes quite annoying bug
|
||||
// stack position will be updated only *after* movement is finished
|
||||
// before this - stack is always at its initial position. Thus we need to find
|
||||
// its current position. Which can be found only in this class
|
||||
if (StackMoveAnimation *move = dynamic_cast<StackMoveAnimation*>(anim))
|
||||
{
|
||||
if (move->stack == stack)
|
||||
return std::max(move->prevHex, move->nextHex);
|
||||
}
|
||||
}
|
||||
return stack->getPosition();
|
||||
}
|
||||
|
||||
void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
|
||||
{
|
||||
auto stacks = owner.curInt->cb->battleGetAllStacks(false);
|
||||
|
||||
for (auto stack : stacks)
|
||||
{
|
||||
if (stackAnimation.find(stack->ID) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
|
||||
continue;
|
||||
|
||||
//FIXME: hack to ignore ghost stacks
|
||||
if ((stackAnimation[stack->ID]->getType() == ECreatureAnimType::DEAD || stackAnimation[stack->ID]->getType() == ECreatureAnimType::HOLDING) && stack->isGhost())
|
||||
continue;
|
||||
|
||||
auto layer = stackAnimation[stack->ID]->isDead() ? EBattleFieldLayer::CORPSES : EBattleFieldLayer::STACKS;
|
||||
auto location = getStackCurrentPosition(stack);
|
||||
|
||||
renderer.insert(layer, location, [this, stack]( BattleRenderer::RendererRef renderer ){
|
||||
showStack(renderer, stack);
|
||||
});
|
||||
|
||||
if (stackNeedsAmountBox(stack))
|
||||
{
|
||||
renderer.insert(EBattleFieldLayer::STACK_AMOUNTS, location, [this, stack]( BattleRenderer::RendererRef renderer ){
|
||||
showStackAmountBox(renderer, stack);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleStacksController::stackReset(const CStack * stack)
|
||||
{
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
//reset orientation?
|
||||
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
|
||||
|
||||
auto iter = stackAnimation.find(stack->ID);
|
||||
|
||||
if(iter == stackAnimation.end())
|
||||
{
|
||||
logGlobal->error("Unit %d have no animation", stack->ID);
|
||||
return;
|
||||
}
|
||||
|
||||
auto animation = iter->second;
|
||||
|
||||
if(stack->alive() && animation->isDeadOrDying())
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
|
||||
{
|
||||
addNewAnim(new ResurrectionAnimation(owner, stack));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleStacksController::stackAdded(const CStack * stack, bool instant)
|
||||
{
|
||||
// Tower shooters have only their upper half visible
|
||||
static const int turretCreatureAnimationHeight = 225;
|
||||
|
||||
stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
|
||||
|
||||
Point coords = getStackPositionAtHex(stack->getPosition(), stack);
|
||||
|
||||
if(stack->initialPosition < 0) //turret
|
||||
{
|
||||
assert(owner.siegeController);
|
||||
|
||||
const CCreature *turretCreature = owner.siegeController->getTurretCreature();
|
||||
|
||||
stackAnimation[stack->ID] = AnimationControls::getAnimation(turretCreature);
|
||||
stackAnimation[stack->ID]->pos.h = turretCreatureAnimationHeight;
|
||||
|
||||
coords = owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
|
||||
stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]);
|
||||
stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight();
|
||||
}
|
||||
stackAnimation[stack->ID]->pos.x = coords.x;
|
||||
stackAnimation[stack->ID]->pos.y = coords.y;
|
||||
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
|
||||
stackAnimation[stack->ID]->setType(ECreatureAnimType::HOLDING);
|
||||
|
||||
if (!instant)
|
||||
{
|
||||
// immediately make stack transparent, giving correct shifter time to start
|
||||
auto shifterFade = ColorFilter::genAlphaShifter(0);
|
||||
setStackColorFilter(shifterFade, stack, nullptr, true);
|
||||
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
|
||||
{
|
||||
addNewAnim(new ColorTransformAnimation(owner, stack, "summonFadeIn", nullptr));
|
||||
if (stack->isClone())
|
||||
addNewAnim(new ColorTransformAnimation(owner, stack, "cloning", SpellID(SpellID::CLONE).toSpell() ));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BattleStacksController::setActiveStack(const CStack *stack)
|
||||
{
|
||||
if (activeStack) // update UI
|
||||
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder());
|
||||
|
||||
activeStack = stack;
|
||||
|
||||
if (activeStack) // update UI
|
||||
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
|
||||
|
||||
owner.windowObject->blockUI(activeStack == nullptr);
|
||||
}
|
||||
|
||||
bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
|
||||
{
|
||||
BattleHex currentActionTarget;
|
||||
if(owner.curInt->curAction)
|
||||
{
|
||||
auto target = owner.curInt->curAction->getTarget(owner.curInt->cb.get());
|
||||
if(!target.empty())
|
||||
currentActionTarget = target.at(0).hexValue;
|
||||
}
|
||||
|
||||
//do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
|
||||
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1)
|
||||
return false;
|
||||
|
||||
if(!stack->alive())
|
||||
return false;
|
||||
|
||||
//hide box when target is going to die anyway - do not display "0 creatures"
|
||||
if(stack->getCount() == 0)
|
||||
return false;
|
||||
|
||||
// if stack has any ongoing animation - hide the box
|
||||
for(auto anim : currentAnimations)
|
||||
{
|
||||
auto stackAnimation = dynamic_cast<BattleStackAnimation*>(anim);
|
||||
if(stackAnimation && (stackAnimation->stack->ID == stack->ID))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<IImage> BattleStacksController::getStackAmountBox(const CStack * stack)
|
||||
{
|
||||
std::vector<si32> activeSpells = stack->activeSpells();
|
||||
|
||||
if ( activeSpells.empty())
|
||||
return amountNormal;
|
||||
|
||||
int effectsPositivness = 0;
|
||||
|
||||
for ( auto const & spellID : activeSpells)
|
||||
effectsPositivness += CGI->spellh->objects.at(spellID)->positiveness;
|
||||
|
||||
if (effectsPositivness > 0)
|
||||
return amountPositive;
|
||||
|
||||
if (effectsPositivness < 0)
|
||||
return amountNegative;
|
||||
|
||||
return amountEffNeutral;
|
||||
}
|
||||
|
||||
void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack * stack)
|
||||
{
|
||||
//blitting amount background box
|
||||
auto amountBG = getStackAmountBox(stack);
|
||||
|
||||
const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1;
|
||||
const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
|
||||
const BattleHex nextPos = stack->getPosition() + sideShift;
|
||||
const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
|
||||
const bool moveInside = !edge && !owner.fieldController->stackCountOutsideHex(nextPos);
|
||||
|
||||
int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
|
||||
(stack->doubleWide() ? 44 : 0) * sideShift +
|
||||
(moveInside ? amountBG->width() + 10 : 0) * reverseSideShift;
|
||||
int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15);
|
||||
|
||||
canvas.draw(amountBG, stackAnimation[stack->ID]->pos.topLeft() + Point(xAdd, yAdd));
|
||||
|
||||
//blitting amount
|
||||
Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd);
|
||||
|
||||
canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, makeNumberShort(stack->getCount()));
|
||||
}
|
||||
|
||||
void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
|
||||
{
|
||||
ColorFilter fullFilter = ColorFilter::genEmptyShifter();
|
||||
for (auto const & filter : stackFilterEffects)
|
||||
{
|
||||
if (filter.target == stack)
|
||||
fullFilter = ColorFilter::genCombined(fullFilter, filter.effect);
|
||||
}
|
||||
|
||||
bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
|
||||
|
||||
if (stackHasProjectile)
|
||||
stackAnimation[stack->ID]->pause();
|
||||
else
|
||||
stackAnimation[stack->ID]->play();
|
||||
|
||||
stackAnimation[stack->ID]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
|
||||
stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
|
||||
}
|
||||
|
||||
void BattleStacksController::update()
|
||||
{
|
||||
updateHoveredStacks();
|
||||
updateBattleAnimations();
|
||||
}
|
||||
|
||||
void BattleStacksController::initializeBattleAnimations()
|
||||
{
|
||||
auto copiedVector = currentAnimations;
|
||||
for (auto & elem : copiedVector)
|
||||
if (elem && !elem->isInitialized())
|
||||
elem->tryInitialize();
|
||||
}
|
||||
|
||||
void BattleStacksController::stepFrameBattleAnimations()
|
||||
{
|
||||
// operate on copy - to prevent potential iterator invalidation due to push_back's
|
||||
// FIXME? : remove remaining calls to addNewAnim from BattleAnimation::nextFrame (only Catapult explosion at the time of writing)
|
||||
auto copiedVector = currentAnimations;
|
||||
for (auto & elem : copiedVector)
|
||||
if (elem && elem->isInitialized())
|
||||
elem->nextFrame();
|
||||
}
|
||||
|
||||
void BattleStacksController::updateBattleAnimations()
|
||||
{
|
||||
bool hadAnimations = !currentAnimations.empty();
|
||||
initializeBattleAnimations();
|
||||
stepFrameBattleAnimations();
|
||||
vstd::erase(currentAnimations, nullptr);
|
||||
|
||||
if (hadAnimations && currentAnimations.empty())
|
||||
owner.setAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
initializeBattleAnimations();
|
||||
}
|
||||
|
||||
void BattleStacksController::addNewAnim(BattleAnimation *anim)
|
||||
{
|
||||
currentAnimations.push_back(anim);
|
||||
owner.setAnimationCondition(EAnimationEvents::ACTION, true);
|
||||
}
|
||||
|
||||
void BattleStacksController::stackRemoved(uint32_t stackID)
|
||||
{
|
||||
if (getActiveStack() && getActiveStack()->ID == stackID)
|
||||
{
|
||||
BattleAction *action = new BattleAction();
|
||||
action->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
||||
action->actionType = EActionType::CANCEL;
|
||||
action->stackNumber = getActiveStack()->ID;
|
||||
owner.givenCommand.setn(action);
|
||||
setActiveStack(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
// remove any potentially erased petrification effect
|
||||
removeExpiredColorFilters();
|
||||
});
|
||||
|
||||
for(auto & attackedInfo : attackedInfos)
|
||||
{
|
||||
if (!attackedInfo.attacker)
|
||||
continue;
|
||||
|
||||
// In H3, attacked stack will not reverse on ranged attack
|
||||
if (attackedInfo.indirectAttack)
|
||||
continue;
|
||||
|
||||
// Another type of indirect attack - dragon breath
|
||||
if (!CStack::isMeleeAttackPossible(attackedInfo.attacker, attackedInfo.defender))
|
||||
continue;
|
||||
|
||||
// defender need to face in direction opposited to out attacker
|
||||
bool needsReverse = shouldAttackFacingRight(attackedInfo.attacker, attackedInfo.defender) == facingRight(attackedInfo.defender);
|
||||
|
||||
// FIXME: this check is better, however not usable since stacksAreAttacked is called after net pack is applyed - petrification is already removed
|
||||
// if (needsReverse && !attackedInfo.defender->isFrozen())
|
||||
if (needsReverse && stackAnimation[attackedInfo.defender->ID]->getType() != ECreatureAnimType::FROZEN)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]()
|
||||
{
|
||||
addNewAnim(new ReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & attackedInfo : attackedInfos)
|
||||
{
|
||||
bool useDeathAnim = attackedInfo.killed;
|
||||
bool useDefenceAnim = attackedInfo.defender->defendingAnim && !attackedInfo.indirectAttack && !attackedInfo.killed;
|
||||
|
||||
EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents::ATTACK : EAnimationEvents::HIT;
|
||||
|
||||
owner.executeOnAnimationCondition(usedEvent, true, [=]()
|
||||
{
|
||||
if (useDeathAnim)
|
||||
addNewAnim(new DeathAnimation(owner, attackedInfo.defender, attackedInfo.indirectAttack));
|
||||
else if(useDefenceAnim)
|
||||
addNewAnim(new DefenceAnimation(owner, attackedInfo.defender));
|
||||
else
|
||||
addNewAnim(new HittedAnimation(owner, attackedInfo.defender));
|
||||
|
||||
if (attackedInfo.fireShield)
|
||||
owner.effectsController->displayEffect(EBattleEffect::FIRE_SHIELD, "FIRESHIE", attackedInfo.attacker->getPosition());
|
||||
|
||||
if (attackedInfo.spellEffect != SpellID::NONE)
|
||||
{
|
||||
auto spell = attackedInfo.spellEffect.toSpell();
|
||||
if (!spell->getCastSound().empty())
|
||||
CCS->soundh->playSound(spell->getCastSound());
|
||||
|
||||
|
||||
owner.displaySpellEffect(spell, attackedInfo.defender->getPosition());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto & attackedInfo : attackedInfos)
|
||||
{
|
||||
if (attackedInfo.rebirth)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
|
||||
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition());
|
||||
addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender));
|
||||
});
|
||||
}
|
||||
|
||||
if (attackedInfo.killed && attackedInfo.defender->summoned)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
|
||||
addNewAnim(new ColorTransformAnimation(owner, attackedInfo.defender, "summonFadeOut", nullptr));
|
||||
stackRemoved(attackedInfo.defender->ID);
|
||||
});
|
||||
}
|
||||
}
|
||||
executeAttackAnimations();
|
||||
}
|
||||
|
||||
void BattleStacksController::stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance)
|
||||
{
|
||||
assert(destHex.size() > 0);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
|
||||
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeOut", nullptr) );
|
||||
});
|
||||
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
|
||||
stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack));
|
||||
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) );
|
||||
});
|
||||
|
||||
// animations will be executed by spell
|
||||
}
|
||||
|
||||
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
|
||||
{
|
||||
assert(destHex.size() > 0);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
bool stackTeleports = stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1));
|
||||
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
|
||||
|
||||
auto enqueMoveEnd = [&](){
|
||||
addNewAnim(new MovementEndAnimation(owner, stack, destHex.back()));
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, [&](){
|
||||
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
|
||||
});
|
||||
};
|
||||
|
||||
auto enqueMove = [&](){
|
||||
if (!stackTeleports)
|
||||
{
|
||||
addNewAnim(new MovementAnimation(owner, stack, destHex, distance));
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveEnd);
|
||||
}
|
||||
else
|
||||
enqueMoveEnd();
|
||||
};
|
||||
|
||||
auto enqueMoveStart = [&](){
|
||||
addNewAnim(new MovementStartAnimation(owner, stack));
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMove);
|
||||
};
|
||||
|
||||
if(shouldRotate(stack, stack->getPosition(), destHex[0]))
|
||||
{
|
||||
addNewAnim(new ReverseAnimation(owner, stack, stack->getPosition()));
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveStart);
|
||||
}
|
||||
else
|
||||
enqueMoveStart();
|
||||
|
||||
owner.waitForAnimationCondition(EAnimationEvents::MOVEMENT, false);
|
||||
}
|
||||
|
||||
bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, const CStack * defender)
|
||||
{
|
||||
bool mustReverse = owner.curInt->cb->isToReverse(
|
||||
attacker->getPosition(),
|
||||
attacker,
|
||||
defender);
|
||||
|
||||
if (attacker->side == BattleSide::ATTACKER)
|
||||
return !mustReverse;
|
||||
else
|
||||
return mustReverse;
|
||||
}
|
||||
|
||||
void BattleStacksController::stackAttacking( const StackAttackInfo & info )
|
||||
{
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
auto attacker = info.attacker;
|
||||
auto defender = info.defender;
|
||||
auto tile = info.tile;
|
||||
auto spellEffect = info.spellEffect;
|
||||
auto multiAttack = !info.secondaryDefender.empty();
|
||||
bool needsReverse = false;
|
||||
|
||||
if (info.indirectAttack)
|
||||
{
|
||||
needsReverse = shouldRotate(attacker, attacker->position, info.tile);
|
||||
}
|
||||
else
|
||||
{
|
||||
needsReverse = shouldAttackFacingRight(attacker, defender) != facingRight(attacker);
|
||||
}
|
||||
|
||||
if (needsReverse)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]()
|
||||
{
|
||||
addNewAnim(new ReverseAnimation(owner, attacker, attacker->getPosition()));
|
||||
});
|
||||
}
|
||||
|
||||
if(info.lucky)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
|
||||
owner.appendBattleLog(info.attacker->formatGeneralMessage(-45));
|
||||
owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition());
|
||||
});
|
||||
}
|
||||
|
||||
if(info.unlucky)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
|
||||
owner.appendBattleLog(info.attacker->formatGeneralMessage(-44));
|
||||
owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition());
|
||||
});
|
||||
}
|
||||
|
||||
if(info.deathBlow)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
|
||||
owner.appendBattleLog(info.attacker->formatGeneralMessage(365));
|
||||
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition());
|
||||
});
|
||||
|
||||
for(auto elem : info.secondaryDefender)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
|
||||
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, elem->getPosition());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]()
|
||||
{
|
||||
if (info.indirectAttack)
|
||||
{
|
||||
addNewAnim(new ShootingAnimation(owner, attacker, tile, defender));
|
||||
}
|
||||
else
|
||||
{
|
||||
addNewAnim(new MeleeAttackAnimation(owner, attacker, tile, defender, multiAttack));
|
||||
}
|
||||
});
|
||||
|
||||
if (info.spellEffect != SpellID::NONE)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
|
||||
{
|
||||
owner.displaySpellHit(spellEffect.toSpell(), tile);
|
||||
});
|
||||
}
|
||||
|
||||
if (info.lifeDrain)
|
||||
{
|
||||
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=]()
|
||||
{
|
||||
owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, "DRAINLIF", attacker->getPosition());
|
||||
});
|
||||
}
|
||||
|
||||
//return, animation playback will be handled by stacksAreAttacked
|
||||
}
|
||||
|
||||
void BattleStacksController::executeAttackAnimations()
|
||||
{
|
||||
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
|
||||
|
||||
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, true);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, false);
|
||||
|
||||
owner.setAnimationCondition(EAnimationEvents::ATTACK, true);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.setAnimationCondition(EAnimationEvents::ATTACK, false);
|
||||
|
||||
// Note that HIT event can also be emitted by attack animation
|
||||
owner.setAnimationCondition(EAnimationEvents::HIT, true);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.setAnimationCondition(EAnimationEvents::HIT, false);
|
||||
|
||||
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, true);
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, false);
|
||||
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
}
|
||||
|
||||
bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const
|
||||
{
|
||||
Point begPosition = getStackPositionAtHex(oldPos,stack);
|
||||
Point endPosition = getStackPositionAtHex(nextHex, stack);
|
||||
|
||||
if((begPosition.x > endPosition.x) && facingRight(stack))
|
||||
return true;
|
||||
else if((begPosition.x < endPosition.x) && !facingRight(stack))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BattleStacksController::endAction(const BattleAction* action)
|
||||
{
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
|
||||
//check if we should reverse stacks
|
||||
TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
|
||||
|
||||
for (const CStack *s : stacks)
|
||||
{
|
||||
bool shouldFaceRight = s && s->side == BattleSide::ATTACKER;
|
||||
|
||||
if (s && facingRight(s) != shouldFaceRight && s->alive() && stackAnimation[s->ID]->isIdle())
|
||||
{
|
||||
addNewAnim(new ReverseAnimation(owner, s, s->getPosition()));
|
||||
}
|
||||
}
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
|
||||
//Ensure that all animation flags were reset
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::OPENING) == false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::MOVEMENT) == false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::ATTACK) == false);
|
||||
assert(owner.getAnimationCondition(EAnimationEvents::HIT) == false);
|
||||
|
||||
owner.windowObject->blockUI(activeStack == nullptr);
|
||||
removeExpiredColorFilters();
|
||||
}
|
||||
|
||||
void BattleStacksController::startAction(const BattleAction* action)
|
||||
{
|
||||
removeExpiredColorFilters();
|
||||
}
|
||||
|
||||
void BattleStacksController::stackActivated(const CStack *stack)
|
||||
{
|
||||
stackToActivate = stack;
|
||||
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
|
||||
owner.activateStack();
|
||||
}
|
||||
|
||||
void BattleStacksController::activateStack()
|
||||
{
|
||||
if ( !currentAnimations.empty())
|
||||
return;
|
||||
|
||||
if ( !stackToActivate)
|
||||
return;
|
||||
|
||||
owner.trySetActivePlayer(stackToActivate->owner);
|
||||
|
||||
setActiveStack(stackToActivate);
|
||||
stackToActivate = nullptr;
|
||||
|
||||
const CStack * s = getActiveStack();
|
||||
if(!s)
|
||||
return;
|
||||
|
||||
//set casting flag to true if creature can use it to not check it every time
|
||||
const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
|
||||
const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
|
||||
if(s->canCast() && (spellcaster || randomSpellcaster))
|
||||
{
|
||||
stackCanCastSpell = true;
|
||||
if(randomSpellcaster)
|
||||
creatureSpellToCast = -1; //spell will be set later on cast
|
||||
else
|
||||
creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
|
||||
//TODO: what if creature can cast BOTH random genie spell and aimed spell?
|
||||
//TODO: faerie dragon type spell should be selected by server
|
||||
}
|
||||
else
|
||||
{
|
||||
stackCanCastSpell = false;
|
||||
creatureSpellToCast = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void BattleStacksController::setSelectedStack(const CStack *stack)
|
||||
{
|
||||
selectedStack = stack;
|
||||
}
|
||||
|
||||
const CStack* BattleStacksController::getSelectedStack() const
|
||||
{
|
||||
return selectedStack;
|
||||
}
|
||||
|
||||
const CStack* BattleStacksController::getActiveStack() const
|
||||
{
|
||||
return activeStack;
|
||||
}
|
||||
|
||||
bool BattleStacksController::facingRight(const CStack * stack) const
|
||||
{
|
||||
return stackFacingRight.at(stack->ID);
|
||||
}
|
||||
|
||||
bool BattleStacksController::activeStackSpellcaster()
|
||||
{
|
||||
return stackCanCastSpell;
|
||||
}
|
||||
|
||||
SpellID BattleStacksController::activeStackSpellToCast()
|
||||
{
|
||||
if (!stackCanCastSpell)
|
||||
return SpellID::NONE;
|
||||
return SpellID(creatureSpellToCast);
|
||||
}
|
||||
|
||||
Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CStack * stack) const
|
||||
{
|
||||
Point ret(-500, -500); //returned value
|
||||
if(stack && stack->initialPosition < 0) //creatures in turrets
|
||||
return owner.siegeController->getTurretCreaturePosition(stack->initialPosition);
|
||||
|
||||
static const Point basePos(-190, -139); // position of creature in topleft corner
|
||||
static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left
|
||||
|
||||
ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX();
|
||||
ret.y = basePos.y + 42 * hexNum.getY();
|
||||
|
||||
if (stack)
|
||||
{
|
||||
if(facingRight(stack))
|
||||
ret.x += imageShiftX;
|
||||
else
|
||||
ret.x -= imageShiftX;
|
||||
|
||||
//shifting position for double - hex creatures
|
||||
if(stack->doubleWide())
|
||||
{
|
||||
if(stack->side == BattleSide::ATTACKER)
|
||||
{
|
||||
if(facingRight(stack))
|
||||
ret.x -= 44;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!facingRight(stack))
|
||||
ret.x += 44;
|
||||
}
|
||||
}
|
||||
}
|
||||
//returning
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BattleStacksController::setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell * source, bool persistent)
|
||||
{
|
||||
for (auto & filter : stackFilterEffects)
|
||||
{
|
||||
if (filter.target == target && filter.source == source)
|
||||
{
|
||||
filter.effect = effect;
|
||||
filter.persistent = persistent;
|
||||
return;
|
||||
}
|
||||
}
|
||||
stackFilterEffects.push_back({ effect, target, source, persistent });
|
||||
}
|
||||
|
||||
void BattleStacksController::removeExpiredColorFilters()
|
||||
{
|
||||
vstd::erase_if(stackFilterEffects, [&](const BattleStackFilterEffect & filter)
|
||||
{
|
||||
if (!filter.persistent)
|
||||
{
|
||||
if (filter.source && !filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id), Selector::all))
|
||||
return true;
|
||||
if (filter.effect == ColorFilter::genEmptyShifter())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void BattleStacksController::updateHoveredStacks()
|
||||
{
|
||||
auto newStacks = selectHoveredStacks();
|
||||
|
||||
for (auto const * stack : mouseHoveredStacks)
|
||||
{
|
||||
if (vstd::contains(newStacks, stack))
|
||||
continue;
|
||||
|
||||
if (stack == activeStack)
|
||||
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getGoldBorder());
|
||||
else
|
||||
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getNoBorder());
|
||||
}
|
||||
|
||||
for (auto const * stack : newStacks)
|
||||
{
|
||||
if (vstd::contains(mouseHoveredStacks, stack))
|
||||
continue;
|
||||
|
||||
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder());
|
||||
if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
|
||||
stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON);
|
||||
|
||||
}
|
||||
|
||||
mouseHoveredStacks = newStacks;
|
||||
}
|
||||
|
||||
std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
|
||||
{
|
||||
// only allow during our turn - do not try to highlight creatures while they are in the middle of actions
|
||||
if (!activeStack)
|
||||
return {};
|
||||
|
||||
if(owner.getAnimationCondition(EAnimationEvents::ACTION) == true)
|
||||
return {};
|
||||
|
||||
auto hoveredHex = owner.fieldController->getHoveredHex();
|
||||
|
||||
if (!hoveredHex.isValid())
|
||||
return {};
|
||||
|
||||
const spells::Caster *caster = nullptr;
|
||||
const CSpell *spell = nullptr;
|
||||
|
||||
spells::Mode mode = spells::Mode::HERO;
|
||||
|
||||
if(owner.actionsController->spellcastingModeActive())//hero casts spell
|
||||
{
|
||||
spell = owner.actionsController->selectedSpell().toSpell();
|
||||
caster = owner.getActiveHero();
|
||||
}
|
||||
else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
|
||||
{
|
||||
spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
|
||||
caster = owner.stacksController->getActiveStack();
|
||||
mode = spells::Mode::CREATURE_ACTIVE;
|
||||
}
|
||||
|
||||
if(caster && spell) //when casting spell
|
||||
{
|
||||
spells::Target target;
|
||||
target.emplace_back(hoveredHex);
|
||||
|
||||
spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
|
||||
auto mechanics = spell->battleMechanics(&event);
|
||||
return mechanics->getAffectedStacks(target);
|
||||
}
|
||||
|
||||
if(hoveredHex.isValid())
|
||||
{
|
||||
const CStack * const stack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
|
||||
|
||||
if (stack)
|
||||
return {stack};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
154
client/battle/BattleStacksController.h
Normal file
154
client/battle/BattleStacksController.h
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* BattleStacksController.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 "../gui/Geometries.h"
|
||||
#include "../gui/ColorFilter.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct BattleHex;
|
||||
class BattleAction;
|
||||
class CStack;
|
||||
class CSpell;
|
||||
class SpellID;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct StackAttackedInfo;
|
||||
struct StackAttackInfo;
|
||||
|
||||
class ColorFilter;
|
||||
class Canvas;
|
||||
class BattleInterface;
|
||||
class BattleAnimation;
|
||||
class CreatureAnimation;
|
||||
class BattleAnimation;
|
||||
class BattleRenderer;
|
||||
class IImage;
|
||||
|
||||
struct BattleStackFilterEffect
|
||||
{
|
||||
ColorFilter effect;
|
||||
const CStack * target;
|
||||
const CSpell * source;
|
||||
bool persistent;
|
||||
};
|
||||
|
||||
/// Class responsible for handling stacks in battle
|
||||
/// Handles ordering of stacks animation
|
||||
/// As well as rendering of stacks, their amount boxes
|
||||
/// And any other effect applied to stacks
|
||||
class BattleStacksController
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
std::shared_ptr<IImage> amountNormal;
|
||||
std::shared_ptr<IImage> amountNegative;
|
||||
std::shared_ptr<IImage> amountPositive;
|
||||
std::shared_ptr<IImage> amountEffNeutral;
|
||||
|
||||
/// currently displayed animations <anim, initialized>
|
||||
std::vector<BattleAnimation *> currentAnimations;
|
||||
|
||||
/// currently active color effects on stacks, in order of their addition (which corresponds to their apply order)
|
||||
std::vector<BattleStackFilterEffect> stackFilterEffects;
|
||||
|
||||
/// animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
|
||||
std::map<int32_t, std::shared_ptr<CreatureAnimation>> stackAnimation;
|
||||
|
||||
/// <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
|
||||
std::map<int, bool> stackFacingRight;
|
||||
|
||||
/// currently active stack; nullptr - no one
|
||||
const CStack *activeStack;
|
||||
|
||||
/// stacks below mouse pointer (multiple stacks possible while spellcasting), used for border animation
|
||||
std::vector<const CStack *> mouseHoveredStacks;
|
||||
|
||||
///when animation is playing, we should wait till the end to make the next stack active; nullptr of none
|
||||
const CStack *stackToActivate;
|
||||
|
||||
/// stack that was selected for multi-target spells - Teleport / Sacrifice
|
||||
const CStack *selectedStack;
|
||||
|
||||
/// if true, active stack could possibly cast some target spell
|
||||
bool stackCanCastSpell;
|
||||
si32 creatureSpellToCast;
|
||||
|
||||
/// for giving IDs for animations
|
||||
ui32 animIDhelper;
|
||||
|
||||
bool stackNeedsAmountBox(const CStack * stack) const;
|
||||
void showStackAmountBox(Canvas & canvas, const CStack * stack);
|
||||
BattleHex getStackCurrentPosition(const CStack * stack) const;
|
||||
|
||||
std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
|
||||
|
||||
void executeAttackAnimations();
|
||||
void removeExpiredColorFilters();
|
||||
|
||||
void initializeBattleAnimations();
|
||||
void stepFrameBattleAnimations();
|
||||
|
||||
void updateBattleAnimations();
|
||||
void updateHoveredStacks();
|
||||
|
||||
std::vector<const CStack *> selectHoveredStacks();
|
||||
|
||||
bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender);
|
||||
|
||||
public:
|
||||
BattleStacksController(BattleInterface & owner);
|
||||
|
||||
bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const;
|
||||
bool facingRight(const CStack * stack) const;
|
||||
|
||||
void stackReset(const CStack * stack);
|
||||
void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield
|
||||
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
|
||||
void stackActivated(const CStack *stack); //active stack has been changed
|
||||
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
|
||||
void stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
|
||||
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
|
||||
void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
|
||||
|
||||
void startAction(const BattleAction* action);
|
||||
void endAction(const BattleAction* action);
|
||||
|
||||
bool activeStackSpellcaster();
|
||||
SpellID activeStackSpellToCast();
|
||||
|
||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||
|
||||
void setActiveStack(const CStack *stack);
|
||||
void setSelectedStack(const CStack *stack);
|
||||
|
||||
void showAliveStack(Canvas & canvas, const CStack * stack);
|
||||
void showStack(Canvas & canvas, const CStack * stack);
|
||||
|
||||
void collectRenderableObjects(BattleRenderer & renderer);
|
||||
|
||||
/// Adds new color filter effect targeting stack
|
||||
/// Effect will last as long as stack is affected by specified spell (unless effect is persistent)
|
||||
/// If effect from same (target, source) already exists, it will be updated
|
||||
void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent);
|
||||
void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims
|
||||
|
||||
const CStack* getActiveStack() const;
|
||||
const CStack* getSelectedStack() const;
|
||||
|
||||
void update();
|
||||
|
||||
/// returns position of animation needed to place stack in specific hex
|
||||
Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const;
|
||||
|
||||
friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
|
||||
};
|
572
client/battle/BattleWindow.cpp
Normal file
572
client/battle/BattleWindow.cpp
Normal file
@ -0,0 +1,572 @@
|
||||
/*
|
||||
* BattleWindow.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 "BattleWindow.h"
|
||||
|
||||
#include "BattleInterface.h"
|
||||
#include "BattleInterfaceClasses.h"
|
||||
#include "BattleFieldController.h"
|
||||
#include "BattleStacksController.h"
|
||||
#include "BattleActionsController.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CMessage.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../CMusicHandler.h"
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/CCursorHandler.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../windows/CSpellWindow.h"
|
||||
#include "../widgets/AdventureMapClasses.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/Images.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/filesystem/ResourceID.h"
|
||||
|
||||
BattleWindow::BattleWindow(BattleInterface & owner):
|
||||
owner(owner)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
pos.w = 800;
|
||||
pos.h = 600;
|
||||
pos = center();
|
||||
|
||||
REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
|
||||
|
||||
const JsonNode config(ResourceID("config/widgets/BattleWindow.json"));
|
||||
|
||||
addCallback("options", std::bind(&BattleWindow::bOptionsf, this));
|
||||
addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this));
|
||||
addCallback("flee", std::bind(&BattleWindow::bFleef, this));
|
||||
addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this));
|
||||
addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this));
|
||||
addCallback("wait", std::bind(&BattleWindow::bWaitf, this));
|
||||
addCallback("defence", std::bind(&BattleWindow::bDefencef, this));
|
||||
addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this));
|
||||
addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this));
|
||||
addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this));
|
||||
addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this));
|
||||
addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this));
|
||||
|
||||
build(config);
|
||||
|
||||
console = widget<BattleConsole>("console");
|
||||
|
||||
GH.statusbar = console;
|
||||
owner.console = console;
|
||||
|
||||
owner.fieldController.reset( new BattleFieldController(owner));
|
||||
owner.fieldController->createHeroes();
|
||||
|
||||
//create stack queue and adjust our own position
|
||||
bool embedQueue;
|
||||
std::string queueSize = settings["battle"]["queueSize"].String();
|
||||
|
||||
if(queueSize == "auto")
|
||||
embedQueue = screen->h < 700;
|
||||
else
|
||||
embedQueue = screen->h < 700 || queueSize == "small";
|
||||
|
||||
queue = std::make_shared<StackQueue>(embedQueue, owner);
|
||||
if(!embedQueue && settings["battle"]["showQueue"].Bool())
|
||||
{
|
||||
//re-center, taking into account stack queue position
|
||||
pos.y -= queue->pos.h;
|
||||
pos.h += queue->pos.h;
|
||||
pos = center();
|
||||
}
|
||||
|
||||
if ( owner.tacticsMode )
|
||||
tacticPhaseStarted();
|
||||
else
|
||||
tacticPhaseEnded();
|
||||
|
||||
addUsedEvents(RCLICK | KEYBOARD);
|
||||
}
|
||||
|
||||
BattleWindow::~BattleWindow()
|
||||
{
|
||||
CPlayerInterface::battleInt = nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
|
||||
{
|
||||
auto rect = readRect(config["rect"]);
|
||||
auto offset = readPosition(config["imagePosition"]);
|
||||
auto background = widget<CPicture>("menuBattle");
|
||||
return std::make_shared<BattleConsole>(background, rect.topLeft(), offset, rect.dimensions() );
|
||||
}
|
||||
|
||||
void BattleWindow::hideQueue()
|
||||
{
|
||||
Settings showQueue = settings.write["battle"]["showQueue"];
|
||||
showQueue->Bool() = false;
|
||||
|
||||
queue->disable();
|
||||
|
||||
if (!queue->embedded)
|
||||
{
|
||||
//re-center, taking into account stack queue position
|
||||
pos.y += queue->pos.h;
|
||||
pos.h -= queue->pos.h;
|
||||
pos = center();
|
||||
GH.totalRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::showQueue()
|
||||
{
|
||||
Settings showQueue = settings.write["battle"]["showQueue"];
|
||||
showQueue->Bool() = true;
|
||||
|
||||
queue->enable();
|
||||
|
||||
if (!queue->embedded)
|
||||
{
|
||||
//re-center, taking into account stack queue position
|
||||
pos.y -= queue->pos.h;
|
||||
pos.h += queue->pos.h;
|
||||
pos = center();
|
||||
GH.totalRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::updateQueue()
|
||||
{
|
||||
queue->update();
|
||||
}
|
||||
|
||||
void BattleWindow::activate()
|
||||
{
|
||||
GH.statusbar = console;
|
||||
CIntObject::activate();
|
||||
LOCPLINT->cingconsole->activate();
|
||||
}
|
||||
|
||||
void BattleWindow::deactivate()
|
||||
{
|
||||
CIntObject::deactivate();
|
||||
LOCPLINT->cingconsole->deactivate();
|
||||
}
|
||||
|
||||
void BattleWindow::keyPressed(const SDL_KeyboardEvent & key)
|
||||
{
|
||||
if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
|
||||
{
|
||||
if(settings["battle"]["showQueue"].Bool()) //hide queue
|
||||
hideQueue();
|
||||
else
|
||||
showQueue();
|
||||
|
||||
}
|
||||
else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
|
||||
{
|
||||
owner.actionsController->enterCreatureCastingMode();
|
||||
}
|
||||
else if(key.keysym.sym == SDLK_ESCAPE)
|
||||
{
|
||||
if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true)
|
||||
CCS->soundh->stopSound(owner.battleIntroSoundChannel);
|
||||
else
|
||||
owner.actionsController->endCastingSpell();
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::clickRight(tribool down, bool previousState)
|
||||
{
|
||||
if (!down)
|
||||
owner.actionsController->endCastingSpell();
|
||||
}
|
||||
|
||||
void BattleWindow::tacticPhaseStarted()
|
||||
{
|
||||
auto menuBattle = widget<CIntObject>("menuBattle");
|
||||
auto console = widget<CIntObject>("console");
|
||||
auto menuTactics = widget<CIntObject>("menuTactics");
|
||||
auto tacticNext = widget<CIntObject>("tacticNext");
|
||||
auto tacticEnd = widget<CIntObject>("tacticEnd");
|
||||
|
||||
menuBattle->disable();
|
||||
console->disable();
|
||||
|
||||
menuTactics->enable();
|
||||
tacticNext->enable();
|
||||
tacticEnd->enable();
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
void BattleWindow::tacticPhaseEnded()
|
||||
{
|
||||
auto menuBattle = widget<CIntObject>("menuBattle");
|
||||
auto console = widget<CIntObject>("console");
|
||||
auto menuTactics = widget<CIntObject>("menuTactics");
|
||||
auto tacticNext = widget<CIntObject>("tacticNext");
|
||||
auto tacticEnd = widget<CIntObject>("tacticEnd");
|
||||
|
||||
menuBattle->enable();
|
||||
console->enable();
|
||||
|
||||
menuTactics->disable();
|
||||
tacticNext->disable();
|
||||
tacticEnd->disable();
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
void BattleWindow::bOptionsf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
|
||||
GH.pushIntT<BattleOptionsWindow>(owner);
|
||||
}
|
||||
|
||||
void BattleWindow::bSurrenderf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
int cost = owner.curInt->cb->battleGetSurrenderCost();
|
||||
if(cost >= 0)
|
||||
{
|
||||
std::string enemyHeroName = owner.curInt->cb->battleGetEnemyHero().name;
|
||||
if(enemyHeroName.empty())
|
||||
{
|
||||
logGlobal->warn("Surrender performed without enemy hero, should not happen!");
|
||||
enemyHeroName = "#ENEMY#";
|
||||
}
|
||||
|
||||
std::string surrenderMessage = boost::str(boost::format(CGI->generaltexth->allTexts[32]) % enemyHeroName % cost); //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
|
||||
owner.curInt->showYesNoDialog(surrenderMessage, [this](){ reallySurrender(); }, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::bFleef()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
if ( owner.curInt->cb->battleCanFlee() )
|
||||
{
|
||||
CFunctionList<void()> ony = std::bind(&BattleWindow::reallyFlee,this);
|
||||
owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::shared_ptr<CComponent>> comps;
|
||||
std::string heroName;
|
||||
//calculating fleeing hero's name
|
||||
if (owner.attackingHeroInstance)
|
||||
if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
|
||||
heroName = owner.attackingHeroInstance->name;
|
||||
if (owner.defendingHeroInstance)
|
||||
if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
|
||||
heroName = owner.defendingHeroInstance->name;
|
||||
//calculating text
|
||||
auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present. %s can not retreat!
|
||||
|
||||
//printing message
|
||||
owner.curInt->showInfoDialog(boost::to_string(txt), comps);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::reallyFlee()
|
||||
{
|
||||
owner.giveCommand(EActionType::RETREAT);
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
}
|
||||
|
||||
void BattleWindow::reallySurrender()
|
||||
{
|
||||
if (owner.curInt->cb->getResourceAmount(Res::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
|
||||
{
|
||||
owner.curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.giveCommand(EActionType::SURRENDER);
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
|
||||
{
|
||||
auto w = widget<CButton>("alternativeAction");
|
||||
if(!w)
|
||||
return;
|
||||
|
||||
std::string iconName = variables["actionIconDefault"].String();
|
||||
switch(action)
|
||||
{
|
||||
case PossiblePlayerBattleAction::ATTACK:
|
||||
iconName = variables["actionIconAttack"].String();
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::SHOOT:
|
||||
iconName = variables["actionIconShoot"].String();
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
|
||||
iconName = variables["actionIconSpell"].String();
|
||||
break;
|
||||
|
||||
//TODO: figure out purpose of this icon
|
||||
//case PossiblePlayerBattleAction::???:
|
||||
//iconName = variables["actionIconWalk"].String();
|
||||
//break;
|
||||
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
|
||||
iconName = variables["actionIconReturn"].String();
|
||||
break;
|
||||
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
iconName = variables["actionIconNoReturn"].String();
|
||||
break;
|
||||
}
|
||||
|
||||
auto anim = std::make_shared<CAnimation>(iconName);
|
||||
w->setImage(anim, false);
|
||||
}
|
||||
|
||||
void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
|
||||
{
|
||||
alternativeActions = actions;
|
||||
defaultAction = PossiblePlayerBattleAction::INVALID;
|
||||
if(alternativeActions.size() > 1)
|
||||
defaultAction = alternativeActions.back();
|
||||
if(!alternativeActions.empty())
|
||||
showAlternativeActionIcon(alternativeActions.front());
|
||||
else
|
||||
showAlternativeActionIcon(defaultAction);
|
||||
}
|
||||
|
||||
void BattleWindow::bAutofightf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
//Stop auto-fight mode
|
||||
if(owner.curInt->isAutoFightOn)
|
||||
{
|
||||
assert(owner.curInt->autofightingAI);
|
||||
owner.curInt->isAutoFightOn = false;
|
||||
logGlobal->trace("Stopping the autofight...");
|
||||
}
|
||||
else if(!owner.curInt->autofightingAI)
|
||||
{
|
||||
owner.curInt->isAutoFightOn = true;
|
||||
blockUI(true);
|
||||
|
||||
auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
|
||||
ai->initBattleInterface(owner.curInt->env, owner.curInt->cb);
|
||||
ai->battleStart(owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.curInt->cb->battleGetMySide());
|
||||
owner.curInt->autofightingAI = ai;
|
||||
owner.curInt->cb->registerBattleInterface(ai);
|
||||
|
||||
owner.requestAutofightingAIToTakeAction();
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::bSpellf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
if (!owner.myTurn)
|
||||
return;
|
||||
|
||||
auto myHero = owner.currentHero();
|
||||
if(!myHero)
|
||||
return;
|
||||
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
|
||||
ESpellCastProblem::ESpellCastProblem spellCastProblem = owner.curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
|
||||
|
||||
if(spellCastProblem == ESpellCastProblem::OK)
|
||||
{
|
||||
GH.pushIntT<CSpellWindow>(myHero, owner.curInt.get());
|
||||
}
|
||||
else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
|
||||
{
|
||||
//TODO: move to spell mechanics, add more information to spell cast problem
|
||||
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
|
||||
auto blockingBonus = owner.currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
|
||||
if (!blockingBonus)
|
||||
return;
|
||||
|
||||
if (blockingBonus->source == Bonus::ARTIFACT)
|
||||
{
|
||||
const auto artID = ArtifactID(blockingBonus->sid);
|
||||
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
|
||||
//TODO check who *really* is source of bonus
|
||||
std::string heroName = myHero->hasArt(artID) ? myHero->name : owner.enemyHero().name;
|
||||
|
||||
//%s wields the %s, an ancient artifact which creates a p dead to all magic.
|
||||
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
|
||||
% heroName % CGI->artifacts()->getByIndex(artID)->getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::bSwitchActionf()
|
||||
{
|
||||
if(alternativeActions.empty())
|
||||
return;
|
||||
|
||||
if(alternativeActions.front() == defaultAction)
|
||||
{
|
||||
alternativeActions.push_back(alternativeActions.front());
|
||||
alternativeActions.pop_front();
|
||||
}
|
||||
|
||||
auto actions = owner.actionsController->getPossibleActions();
|
||||
if(!actions.empty() && actions.front() == alternativeActions.front())
|
||||
{
|
||||
owner.actionsController->removePossibleAction(alternativeActions.front());
|
||||
showAlternativeActionIcon(defaultAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.actionsController->pushFrontPossibleAction(alternativeActions.front());
|
||||
showAlternativeActionIcon(alternativeActions.front());
|
||||
}
|
||||
|
||||
alternativeActions.push_back(alternativeActions.front());
|
||||
alternativeActions.pop_front();
|
||||
}
|
||||
|
||||
void BattleWindow::bWaitf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
if (owner.stacksController->getActiveStack() != nullptr)
|
||||
owner.giveCommand(EActionType::WAIT);
|
||||
}
|
||||
|
||||
void BattleWindow::bDefencef()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
if (owner.stacksController->getActiveStack() != nullptr)
|
||||
owner.giveCommand(EActionType::DEFEND);
|
||||
}
|
||||
|
||||
void BattleWindow::bConsoleUpf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
console->scrollUp();
|
||||
}
|
||||
|
||||
void BattleWindow::bConsoleDownf()
|
||||
{
|
||||
if (owner.actionsController->spellcastingModeActive())
|
||||
return;
|
||||
|
||||
console->scrollDown();
|
||||
}
|
||||
|
||||
void BattleWindow::bTacticNextStack()
|
||||
{
|
||||
owner.tacticNextStack(nullptr);
|
||||
}
|
||||
|
||||
void BattleWindow::bTacticPhaseEnd()
|
||||
{
|
||||
owner.tacticPhaseEnd();
|
||||
}
|
||||
|
||||
void BattleWindow::blockUI(bool on)
|
||||
{
|
||||
bool canCastSpells = false;
|
||||
auto hero = owner.curInt->cb->battleGetMyHero();
|
||||
|
||||
if(hero)
|
||||
{
|
||||
ESpellCastProblem::ESpellCastProblem spellcastingProblem = owner.curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO);
|
||||
|
||||
//if magic is blocked, we leave button active, so the message can be displayed after button click
|
||||
canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
|
||||
}
|
||||
|
||||
bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
|
||||
|
||||
if(auto w = widget<CButton>("options"))
|
||||
w->block(on);
|
||||
if(auto w = widget<CButton>("flee"))
|
||||
w->block(on || !owner.curInt->cb->battleCanFlee());
|
||||
if(auto w = widget<CButton>("surrender"))
|
||||
w->block(on || owner.curInt->cb->battleGetSurrenderCost() < 0);
|
||||
if(auto w = widget<CButton>("cast"))
|
||||
w->block(on || owner.tacticsMode || !canCastSpells);
|
||||
if(auto w = widget<CButton>("wait"))
|
||||
w->block(on || owner.tacticsMode || !canWait);
|
||||
if(auto w = widget<CButton>("defence"))
|
||||
w->block(on || owner.tacticsMode);
|
||||
if(auto w = widget<CButton>("alternativeAction"))
|
||||
w->block(on || owner.tacticsMode);
|
||||
|
||||
// block only if during enemy turn and auto-fight is off
|
||||
// otherwise - crash on accessing non-exisiting active stack
|
||||
if(auto w = widget<CButton>("options"))
|
||||
w->block(!owner.curInt->isAutoFightOn && !owner.stacksController->getActiveStack());
|
||||
|
||||
auto btactEnd = widget<CButton>("tacticEnd");
|
||||
auto btactNext = widget<CButton>("tacticNext");
|
||||
if(owner.tacticsMode && btactEnd && btactNext)
|
||||
{
|
||||
btactNext->block(on);
|
||||
btactEnd->block(on);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto bConsoleUp = widget<CButton>("consoleUp");
|
||||
auto bConsoleDown = widget<CButton>("consoleDown");
|
||||
if(bConsoleUp && bConsoleDown)
|
||||
{
|
||||
bConsoleUp->block(on);
|
||||
bConsoleDown->block(on);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BattleWindow::showAll(SDL_Surface *to)
|
||||
{
|
||||
CIntObject::showAll(to);
|
||||
|
||||
if (screen->w != 800 || screen->h !=600)
|
||||
CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
|
||||
}
|
||||
|
||||
void BattleWindow::show(SDL_Surface *to)
|
||||
{
|
||||
CIntObject::show(to);
|
||||
LOCPLINT->cingconsole->show(to);
|
||||
}
|
||||
|
||||
void BattleWindow::close()
|
||||
{
|
||||
if(GH.topInt().get() != this)
|
||||
logGlobal->error("Only top interface must be closed");
|
||||
GH.popInts(1);
|
||||
}
|
94
client/battle/BattleWindow.h
Normal file
94
client/battle/BattleWindow.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* BattleWindow.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 "../gui/CIntObject.h"
|
||||
#include "../gui/InterfaceObjectConfigurable.h"
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class CStack;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CButton;
|
||||
class BattleInterface;
|
||||
class BattleConsole;
|
||||
class BattleRenderer;
|
||||
class StackQueue;
|
||||
|
||||
/// GUI object that handles functionality of panel at the bottom of combat screen
|
||||
class BattleWindow : public InterfaceObjectConfigurable
|
||||
{
|
||||
BattleInterface & owner;
|
||||
|
||||
std::shared_ptr<StackQueue> queue;
|
||||
std::shared_ptr<BattleConsole> console;
|
||||
|
||||
/// button press handling functions
|
||||
void bOptionsf();
|
||||
void bSurrenderf();
|
||||
void bFleef();
|
||||
void bAutofightf();
|
||||
void bSpellf();
|
||||
void bWaitf();
|
||||
void bSwitchActionf();
|
||||
void bDefencef();
|
||||
void bConsoleUpf();
|
||||
void bConsoleDownf();
|
||||
void bTacticNextStack();
|
||||
void bTacticPhaseEnd();
|
||||
|
||||
/// functions for handling actions after they were confirmed by popup window
|
||||
void reallyFlee();
|
||||
void reallySurrender();
|
||||
|
||||
/// management of alternative actions
|
||||
std::list<PossiblePlayerBattleAction> alternativeActions;
|
||||
PossiblePlayerBattleAction defaultAction;
|
||||
void showAlternativeActionIcon(PossiblePlayerBattleAction);
|
||||
|
||||
/// Toggle StackQueue visibility
|
||||
void hideQueue();
|
||||
void showQueue();
|
||||
|
||||
std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
|
||||
|
||||
public:
|
||||
BattleWindow(BattleInterface & owner );
|
||||
~BattleWindow();
|
||||
|
||||
/// Closes window once battle finished
|
||||
void close();
|
||||
|
||||
/// block all UI elements when player is not allowed to act, e.g. during enemy turn
|
||||
void blockUI(bool on);
|
||||
|
||||
/// Refresh queue after turn order changes
|
||||
void updateQueue();
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void keyPressed(const SDL_KeyboardEvent & key) override;
|
||||
void clickRight(tribool down, bool previousState) override;
|
||||
void show(SDL_Surface *to) override;
|
||||
void showAll(SDL_Surface *to) override;
|
||||
|
||||
/// Toggle UI to displaying tactics phase
|
||||
void tacticPhaseStarted();
|
||||
|
||||
/// Toggle UI to displaying battle log in place of tactics UI
|
||||
void tacticPhaseEnded();
|
||||
|
||||
/// Set possible alternative options. If more than 1 - the last will be considered as default option
|
||||
void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
|
||||
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,262 +0,0 @@
|
||||
/*
|
||||
* CBattleAnimations.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 "../../lib/battle/BattleHex.h"
|
||||
#include "../widgets/Images.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CStack;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CBattleInterface;
|
||||
class CCreatureAnimation;
|
||||
struct CatapultProjectileInfo;
|
||||
struct StackAttackedInfo;
|
||||
|
||||
/// Base class of battle animations
|
||||
class CBattleAnimation
|
||||
{
|
||||
protected:
|
||||
CBattleInterface * owner;
|
||||
public:
|
||||
virtual bool init() = 0; //to be called - if returned false, call again until returns true
|
||||
virtual void nextFrame() {} //call every new frame
|
||||
virtual void endAnim(); //to be called mostly internally; in this class it removes animation from pendingAnims list
|
||||
virtual ~CBattleAnimation();
|
||||
|
||||
bool isEarliest(bool perStackConcurrency); //determines if this animation is earliest of all
|
||||
|
||||
ui32 ID; //unique identifier
|
||||
CBattleAnimation(CBattleInterface * _owner);
|
||||
};
|
||||
|
||||
/// Sub-class which is responsible for managing the battle stack animation.
|
||||
class CBattleStackAnimation : public CBattleAnimation
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<CCreatureAnimation> myAnim; //animation for our stack, managed by CBattleInterface
|
||||
const CStack * stack; //id of stack whose animation it is
|
||||
|
||||
CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack);
|
||||
|
||||
void shiftColor(const ColorShifter * shifter);
|
||||
};
|
||||
|
||||
/// This class is responsible for managing the battle attack animation
|
||||
class CAttackAnimation : public CBattleStackAnimation
|
||||
{
|
||||
bool soundPlayed;
|
||||
|
||||
protected:
|
||||
BattleHex dest; //attacked hex
|
||||
bool shooting;
|
||||
CCreatureAnim::EAnimType group; //if shooting is true, print this animation group
|
||||
const CStack *attackedStack;
|
||||
const CStack *attackingStack;
|
||||
int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
|
||||
public:
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
bool checkInitialConditions();
|
||||
|
||||
CAttackAnimation(CBattleInterface *_owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
|
||||
};
|
||||
|
||||
/// Animation of a defending unit
|
||||
class CDefenceAnimation : public CBattleStackAnimation
|
||||
{
|
||||
CCreatureAnim::EAnimType getMyAnimType();
|
||||
std::string getMySound();
|
||||
|
||||
void startAnimation();
|
||||
|
||||
const CStack * attacker; //attacking stack
|
||||
bool rangedAttack; //if true, stack has been attacked by shooting
|
||||
bool killed; //if true, stack has been killed
|
||||
|
||||
float timeToWait; // for how long this animation should be paused
|
||||
public:
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
|
||||
CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner);
|
||||
virtual ~CDefenceAnimation(){};
|
||||
};
|
||||
|
||||
class CDummyAnimation : public CBattleAnimation
|
||||
{
|
||||
private:
|
||||
int counter;
|
||||
int howMany;
|
||||
public:
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
|
||||
CDummyAnimation(CBattleInterface * _owner, int howManyFrames);
|
||||
virtual ~CDummyAnimation(){}
|
||||
};
|
||||
|
||||
/// Hand-to-hand attack
|
||||
class CMeleeAttackAnimation : public CAttackAnimation
|
||||
{
|
||||
public:
|
||||
bool init() override;
|
||||
void endAnim() override;
|
||||
|
||||
CMeleeAttackAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked);
|
||||
virtual ~CMeleeAttackAnimation(){};
|
||||
};
|
||||
|
||||
/// Move animation of a creature
|
||||
class CMovementAnimation : public CBattleStackAnimation
|
||||
{
|
||||
private:
|
||||
std::vector<BattleHex> destTiles; //full path, includes already passed hexes
|
||||
ui32 curentMoveIndex; // index of nextHex in destTiles
|
||||
|
||||
BattleHex oldPos; //position of stack before move
|
||||
|
||||
double begX, begY; // starting position
|
||||
double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
|
||||
|
||||
double timeToMove; // full length of movement animation
|
||||
double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
|
||||
|
||||
public:
|
||||
BattleHex nextHex; // next hex, to which creature move right now
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
|
||||
CMovementAnimation(CBattleInterface *_owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
|
||||
virtual ~CMovementAnimation(){};
|
||||
};
|
||||
|
||||
/// Move end animation of a creature
|
||||
class CMovementEndAnimation : public CBattleStackAnimation
|
||||
{
|
||||
private:
|
||||
BattleHex destinationTile;
|
||||
public:
|
||||
bool init() override;
|
||||
void endAnim() override;
|
||||
|
||||
CMovementEndAnimation(CBattleInterface * _owner, const CStack * _stack, BattleHex destTile);
|
||||
virtual ~CMovementEndAnimation(){};
|
||||
};
|
||||
|
||||
/// Move start animation of a creature
|
||||
class CMovementStartAnimation : public CBattleStackAnimation
|
||||
{
|
||||
public:
|
||||
bool init() override;
|
||||
void endAnim() override;
|
||||
|
||||
CMovementStartAnimation(CBattleInterface * _owner, const CStack * _stack);
|
||||
virtual ~CMovementStartAnimation(){};
|
||||
};
|
||||
|
||||
/// Class responsible for animation of stack chaning direction (left <-> right)
|
||||
class CReverseAnimation : public CBattleStackAnimation
|
||||
{
|
||||
BattleHex hex;
|
||||
public:
|
||||
bool priority; //true - high, false - low
|
||||
bool init() override;
|
||||
|
||||
static void rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex);
|
||||
|
||||
void setupSecondPart();
|
||||
void endAnim() override;
|
||||
|
||||
CReverseAnimation(CBattleInterface * _owner, const CStack * stack, BattleHex dest, bool _priority);
|
||||
virtual ~CReverseAnimation(){};
|
||||
};
|
||||
|
||||
/// Small struct which contains information about the position and the velocity of a projectile
|
||||
struct ProjectileInfo
|
||||
{
|
||||
double x0, y0; //initial position on the screen
|
||||
double x, y; //position on the screen
|
||||
double dx, dy; //change in position in one step
|
||||
int step, lastStep; //to know when finish showing this projectile
|
||||
int creID; //ID of creature that shot this projectile
|
||||
int stackID; //ID of stack
|
||||
int frameNum; //frame to display form projectile animation
|
||||
//bool spin; //if true, frameNum will be increased
|
||||
int animStartDelay; //frame of shooter animation when projectile should appear
|
||||
bool shotDone; // actual shot already done, projectile is flying
|
||||
bool reverse; //if true, projectile will be flipped by vertical asix
|
||||
std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
|
||||
};
|
||||
|
||||
class CRangedAttackAnimation : public CAttackAnimation
|
||||
{
|
||||
public:
|
||||
CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
/// Shooting attack
|
||||
class CShootingAnimation : public CRangedAttackAnimation
|
||||
{
|
||||
private:
|
||||
int catapultDamage;
|
||||
public:
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
|
||||
//last two params only for catapult attacks
|
||||
CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest,
|
||||
const CStack * _attacked, bool _catapult = false, int _catapultDmg = 0);
|
||||
virtual ~CShootingAnimation(){};
|
||||
};
|
||||
|
||||
class CCastAnimation : public CRangedAttackAnimation
|
||||
{
|
||||
public:
|
||||
CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/// This class manages effect animation
|
||||
class CEffectAnimation : public CBattleAnimation
|
||||
{
|
||||
private:
|
||||
BattleHex destTile;
|
||||
std::shared_ptr<CAnimation> customAnim;
|
||||
int x, y, dx, dy;
|
||||
bool Vflip;
|
||||
bool alignToBottom;
|
||||
public:
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
void endAnim() override;
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx = 0, int _dy = 0);
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
|
||||
virtual ~CEffectAnimation(){};
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,414 +0,0 @@
|
||||
/*
|
||||
* CBattleInterface.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 <vcmi/spells/Magic.h>
|
||||
|
||||
#include "../../lib/ConstTransitivePtr.h" //may be redundant
|
||||
#include "../../lib/GameConstants.h"
|
||||
|
||||
#include "CBattleAnimations.h"
|
||||
|
||||
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CCreatureSet;
|
||||
class CGHeroInstance;
|
||||
class CStack;
|
||||
struct BattleResult;
|
||||
struct BattleSpellCast;
|
||||
struct CObstacleInstance;
|
||||
template <typename T> struct CondSh;
|
||||
struct SetStackEffect;
|
||||
class BattleAction;
|
||||
class CGTownInstance;
|
||||
struct CatapultAttack;
|
||||
struct BattleTriggerEffect;
|
||||
struct BattleHex;
|
||||
struct InfoAboutHero;
|
||||
class CBattleGameInterface;
|
||||
struct CustomEffectInfo;
|
||||
class CSpell;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CLabel;
|
||||
class CCallback;
|
||||
class CButton;
|
||||
class CToggleButton;
|
||||
class CToggleGroup;
|
||||
struct CatapultProjectileInfo;
|
||||
class CBattleAnimation;
|
||||
class CBattleHero;
|
||||
class CBattleConsole;
|
||||
class CBattleResultWindow;
|
||||
class CStackQueue;
|
||||
class CPlayerInterface;
|
||||
class CCreatureAnimation;
|
||||
struct ProjectileInfo;
|
||||
class CClickableHex;
|
||||
class CAnimation;
|
||||
class IImage;
|
||||
class CStackQueue;
|
||||
|
||||
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
|
||||
struct StackAttackedInfo
|
||||
{
|
||||
const CStack *defender; //attacked stack
|
||||
int64_t dmg; //damage dealt
|
||||
unsigned int amountKilled; //how many creatures in stack has been killed
|
||||
const CStack *attacker; //attacking stack
|
||||
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
|
||||
bool killed; //if true, stack has been killed
|
||||
bool rebirth; //if true, play rebirth animation after all
|
||||
bool cloneKilled;
|
||||
};
|
||||
|
||||
/// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,...
|
||||
struct BattleEffect
|
||||
{
|
||||
int x, y; //position on the screen
|
||||
float currentFrame;
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
|
||||
BattleHex position; //Indicates if effect which hex the effect is drawn on
|
||||
};
|
||||
|
||||
struct BattleObjectsByHex
|
||||
{
|
||||
typedef std::vector<int> TWallList;
|
||||
typedef std::vector<const CStack *> TStackList;
|
||||
typedef std::vector<const BattleEffect *> TEffectList;
|
||||
typedef std::vector<std::shared_ptr<const CObstacleInstance>> TObstacleList;
|
||||
|
||||
struct HexData
|
||||
{
|
||||
TWallList walls;
|
||||
TStackList dead;
|
||||
TStackList alive;
|
||||
TEffectList effects;
|
||||
TObstacleList obstacles;
|
||||
};
|
||||
|
||||
HexData beforeAll;
|
||||
HexData afterAll;
|
||||
std::array<HexData, GameConstants::BFIELD_SIZE> hex;
|
||||
};
|
||||
|
||||
/// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon
|
||||
struct CatapultProjectileInfo
|
||||
{
|
||||
CatapultProjectileInfo(Point from, Point dest);
|
||||
|
||||
double facA, facB, facC;
|
||||
|
||||
double calculateY(double x);
|
||||
};
|
||||
|
||||
enum class MouseHoveredHexContext
|
||||
{
|
||||
UNOCCUPIED_HEX,
|
||||
OCCUPIED_HEX
|
||||
};
|
||||
|
||||
/// Big class which handles the overall battle interface actions and it is also responsible for
|
||||
/// drawing everything correctly.
|
||||
class CBattleInterface : public WindowBase
|
||||
{
|
||||
private:
|
||||
SDL_Surface *background, *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral, *cellBorders, *backgroundWithHexes;
|
||||
|
||||
std::shared_ptr<CButton> bOptions;
|
||||
std::shared_ptr<CButton> bSurrender;
|
||||
std::shared_ptr<CButton> bFlee;
|
||||
std::shared_ptr<CButton> bAutofight;
|
||||
std::shared_ptr<CButton> bSpell;
|
||||
std::shared_ptr<CButton> bWait;
|
||||
std::shared_ptr<CButton> bDefence;
|
||||
std::shared_ptr<CButton> bConsoleUp;
|
||||
std::shared_ptr<CButton> bConsoleDown;
|
||||
std::shared_ptr<CButton> btactNext;
|
||||
std::shared_ptr<CButton> btactEnd;
|
||||
|
||||
std::shared_ptr<CBattleConsole> console;
|
||||
std::shared_ptr<CBattleHero> attackingHero;
|
||||
std::shared_ptr<CBattleHero> defendingHero;
|
||||
std::shared_ptr<CStackQueue> queue;
|
||||
|
||||
const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
|
||||
const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
|
||||
std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
|
||||
|
||||
std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
|
||||
std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay;
|
||||
|
||||
std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
|
||||
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
|
||||
|
||||
std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
|
||||
ui8 animCount;
|
||||
const CStack *activeStack; //number of active stack; nullptr - no one
|
||||
const CStack *mouseHoveredStack; // stack below mouse pointer, used for border animation
|
||||
const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
|
||||
const CStack *selectedStack; //for Teleport / Sacrifice
|
||||
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
|
||||
std::vector<BattleHex> occupyableHexes, //hexes available for active stack
|
||||
attackableHexes; //hexes attackable by active stack
|
||||
std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes; // hexes that when in front of a unit cause it's amount box to move back
|
||||
BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago
|
||||
BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon)
|
||||
int attackingHex; //hex from which the stack would perform attack with current cursor
|
||||
|
||||
std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
|
||||
bool tacticsMode;
|
||||
bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
|
||||
bool creatureCasting; //if true, stack currently aims to cats a spell
|
||||
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
|
||||
std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
|
||||
const CSpell *sp; //spell pointer for convenience
|
||||
si32 creatureSpellToCast;
|
||||
std::vector<PossiblePlayerBattleAction> possibleActions; //all actions possible to call at the moment by player
|
||||
std::vector<PossiblePlayerBattleAction> localActions; //actions possible to take on hovered hex
|
||||
std::vector<PossiblePlayerBattleAction> illegalActions; //these actions display message in case of illegal target
|
||||
PossiblePlayerBattleAction currentAction; //action that will be performed on l-click
|
||||
PossiblePlayerBattleAction selectedAction; //last action chosen (and saved) by player
|
||||
PossiblePlayerBattleAction illegalAction; //most likely action that can't be performed here
|
||||
bool battleActionsStarted; //used for delaying battle actions until intro sound stops
|
||||
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
|
||||
|
||||
void setActiveStack(const CStack *stack);
|
||||
void setHoveredStack(const CStack *stack);
|
||||
|
||||
void requestAutofightingAIToTakeAction();
|
||||
|
||||
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn
|
||||
void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled)
|
||||
void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
|
||||
|
||||
//force active stack to cast a spell if possible
|
||||
void enterCreatureCastingMode();
|
||||
|
||||
std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
|
||||
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
|
||||
void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
|
||||
|
||||
bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles
|
||||
bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
|
||||
|
||||
std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
|
||||
|
||||
/// Class which is responsible for drawing the wall of a siege during battle
|
||||
class SiegeHelper
|
||||
{
|
||||
private:
|
||||
SDL_Surface* walls[18];
|
||||
const CBattleInterface *owner;
|
||||
public:
|
||||
const CGTownInstance *town; //besieged town
|
||||
|
||||
SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface *_owner);
|
||||
~SiegeHelper();
|
||||
|
||||
std::string getSiegeName(ui16 what) const;
|
||||
std::string getSiegeName(ui16 what, int state) const; // state uses EWallState enum
|
||||
|
||||
void printPartOfWall(SDL_Surface *to, int what);
|
||||
|
||||
enum EWallVisual
|
||||
{
|
||||
BACKGROUND = 0,
|
||||
BACKGROUND_WALL = 1,
|
||||
KEEP,
|
||||
BOTTOM_TOWER,
|
||||
BOTTOM_WALL,
|
||||
WALL_BELLOW_GATE,
|
||||
WALL_OVER_GATE,
|
||||
UPPER_WALL,
|
||||
UPPER_TOWER,
|
||||
GATE,
|
||||
GATE_ARCH,
|
||||
BOTTOM_STATIC_WALL,
|
||||
UPPER_STATIC_WALL,
|
||||
MOAT,
|
||||
BACKGROUND_MOAT,
|
||||
KEEP_BATTLEMENT,
|
||||
BOTTOM_BATTLEMENT,
|
||||
UPPER_BATTLEMENT
|
||||
};
|
||||
|
||||
friend class CBattleInterface;
|
||||
} *siegeH;
|
||||
|
||||
std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
|
||||
std::shared_ptr<CPlayerInterface> curInt; //current player interface
|
||||
const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
|
||||
|
||||
/** Methods for displaying battle screen */
|
||||
void showBackground(SDL_Surface *to);
|
||||
|
||||
void showBackgroundImage(SDL_Surface *to);
|
||||
void showAbsoluteObstacles(SDL_Surface *to);
|
||||
void showHighlightedHexes(SDL_Surface *to);
|
||||
void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder = false);
|
||||
void showInterface(SDL_Surface *to);
|
||||
|
||||
void showBattlefieldObjects(SDL_Surface *to);
|
||||
|
||||
void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
|
||||
void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
|
||||
void showObstacles(SDL_Surface *to, std::vector<std::shared_ptr<const CObstacleInstance>> &obstacles);
|
||||
void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
|
||||
|
||||
void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
|
||||
void showProjectiles(SDL_Surface *to);
|
||||
|
||||
BattleObjectsByHex sortObjectsByHex();
|
||||
void updateBattleAnimations();
|
||||
|
||||
std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
|
||||
|
||||
Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
|
||||
|
||||
void redrawBackgroundWithHexes(const CStack *activeStack);
|
||||
/** End of battle screen blitting methods */
|
||||
|
||||
void setHeroAnimation(ui8 side, int phase);
|
||||
public:
|
||||
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
|
||||
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
|
||||
|
||||
std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized>
|
||||
void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
|
||||
ui32 animIDhelper; //for giving IDs for animations
|
||||
|
||||
|
||||
CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
|
||||
virtual ~CBattleInterface();
|
||||
|
||||
//std::vector<TimeInterested*> timeinterested; //animation handling
|
||||
void setPrintCellBorders(bool set); //if true, cell borders will be printed
|
||||
void setPrintStackRange(bool set); //if true,range of active stack will be printed
|
||||
void setPrintMouseShadow(bool set); //if true, hex under mouse will be shaded
|
||||
void setAnimSpeed(int set); //speed of animation; range 1..100
|
||||
int getAnimSpeed() const; //speed of animation; range 1..100
|
||||
CPlayerInterface *getCurrentPlayerInterface() const;
|
||||
bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
|
||||
|
||||
std::vector<std::shared_ptr<CClickableHex>> bfield; //11 lines, 17 hexes on each
|
||||
SDL_Surface *cellBorder, *cellShade;
|
||||
|
||||
bool myTurn; //if true, interface is active (commands can be ordered)
|
||||
|
||||
bool moveStarted; //if true, the creature that is already moving is going to make its first step
|
||||
int moveSoundHander; // sound handler used when moving a unit
|
||||
|
||||
const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
|
||||
|
||||
// block all UI elements, e.g. during enemy turn
|
||||
// unlike activate/deactivate this method will correctly grey-out all elements
|
||||
void blockUI(bool on);
|
||||
|
||||
//button handle funcs:
|
||||
void bOptionsf();
|
||||
void bSurrenderf();
|
||||
void bFleef();
|
||||
void reallyFlee(); //performs fleeing without asking player
|
||||
void reallySurrender(); //performs surrendering without asking player
|
||||
void bAutofightf();
|
||||
void bSpellf();
|
||||
void bWaitf();
|
||||
void bDefencef();
|
||||
void bConsoleUpf();
|
||||
void bConsoleDownf();
|
||||
void bTacticNextStack(const CStack *current = nullptr);
|
||||
void bEndTacticPhase();
|
||||
//end of button handle funcs
|
||||
//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void keyPressed(const SDL_KeyboardEvent & key) override;
|
||||
void mouseMoved(const SDL_MouseMotionEvent &sEvent) override;
|
||||
void clickRight(tribool down, bool previousState) override;
|
||||
|
||||
void show(SDL_Surface *to) override;
|
||||
void showAll(SDL_Surface *to) override;
|
||||
|
||||
//call-ins
|
||||
void startAction(const BattleAction* action);
|
||||
void unitAdded(const CStack * stack); //new stack appeared on battlefield
|
||||
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
|
||||
void stackActivated(const CStack *stack); //active stack has been changed
|
||||
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
|
||||
void waitForAnims();
|
||||
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
|
||||
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
|
||||
void newRoundFirst( int round );
|
||||
void newRound(int number); //caled when round is ended; number is the number of round
|
||||
void hexLclicked(int whichOne); //hex only call-in
|
||||
void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
|
||||
void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
|
||||
void displayBattleFinished(); //displays battle result
|
||||
void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
|
||||
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
|
||||
|
||||
void displayBattleLog(const std::vector<MetaString> & battleLog);
|
||||
void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
|
||||
|
||||
void displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield
|
||||
|
||||
void displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile);
|
||||
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
|
||||
void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
|
||||
void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
|
||||
|
||||
void battleTriggerEffect(const BattleTriggerEffect & bte);
|
||||
void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
|
||||
void endAction(const BattleAction* action);
|
||||
void hideQueue();
|
||||
void showQueue();
|
||||
|
||||
Rect hexPosition(BattleHex hex) const;
|
||||
|
||||
void handleHex(BattleHex myNumber, int eventType);
|
||||
bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
|
||||
bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
|
||||
|
||||
BattleHex fromWhichHexAttack(BattleHex myNumber);
|
||||
void obstaclePlaced(const CObstacleInstance & oi);
|
||||
|
||||
void gateStateChanged(const EGateState state);
|
||||
|
||||
void initStackProjectile(const CStack * stack);
|
||||
|
||||
const CGHeroInstance *currentHero() const;
|
||||
InfoAboutHero enemyHero() const;
|
||||
|
||||
friend class CPlayerInterface;
|
||||
friend class CButton;
|
||||
friend class CInGameConsole;
|
||||
friend class CStackQueue;
|
||||
friend class CBattleResultWindow;
|
||||
friend class CBattleHero;
|
||||
friend class CEffectAnimation;
|
||||
friend class CBattleStackAnimation;
|
||||
friend class CReverseAnimation;
|
||||
friend class CDefenceAnimation;
|
||||
friend class CMovementAnimation;
|
||||
friend class CMovementStartAnimation;
|
||||
friend class CAttackAnimation;
|
||||
friend class CMeleeAttackAnimation;
|
||||
friend class CShootingAnimation;
|
||||
friend class CCastAnimation;
|
||||
friend class CClickableHex;
|
||||
};
|
@ -1,365 +0,0 @@
|
||||
/*
|
||||
* CCreatureAnimation.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 "CCreatureAnimation.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
|
||||
#include "../gui/SDL_Extensions.h"
|
||||
|
||||
static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
|
||||
static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
|
||||
static const SDL_Color creatureNoBorder = { 0, 0, 0, 0 };
|
||||
|
||||
SDL_Color AnimationControls::getBlueBorder()
|
||||
{
|
||||
return creatureBlueBorder;
|
||||
}
|
||||
|
||||
SDL_Color AnimationControls::getGoldBorder()
|
||||
{
|
||||
return creatureGoldBorder;
|
||||
}
|
||||
|
||||
SDL_Color AnimationControls::getNoBorder()
|
||||
{
|
||||
return creatureNoBorder;
|
||||
}
|
||||
|
||||
std::shared_ptr<CCreatureAnimation> AnimationControls::getAnimation(const CCreature * creature)
|
||||
{
|
||||
auto func = std::bind(&AnimationControls::getCreatureAnimationSpeed, creature, _1, _2);
|
||||
return std::make_shared<CCreatureAnimation>(creature->animDefName, func);
|
||||
}
|
||||
|
||||
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t group)
|
||||
{
|
||||
CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group);
|
||||
|
||||
assert(creature->animation.walkAnimationTime != 0);
|
||||
assert(creature->animation.attackAnimationTime != 0);
|
||||
assert(anim->framesInGroup(type) != 0);
|
||||
|
||||
// possible new fields for creature format:
|
||||
//split "Attack time" into "Shoot Time" and "Cast Time"
|
||||
|
||||
// a lot of arbitrary multipliers, mostly to make animation speed closer to H3
|
||||
const float baseSpeed = 0.1f;
|
||||
const float speedMult = static_cast<float>(settings["battle"]["animationSpeed"].Float());
|
||||
const float speed = baseSpeed / speedMult;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CCreatureAnim::MOVING:
|
||||
return static_cast<float>(speed * 2 * creature->animation.walkAnimationTime / anim->framesInGroup(type));
|
||||
|
||||
case CCreatureAnim::MOUSEON:
|
||||
return baseSpeed;
|
||||
case CCreatureAnim::HOLDING:
|
||||
return static_cast<float>(baseSpeed * creature->animation.idleAnimationTime / anim->framesInGroup(type));
|
||||
|
||||
case CCreatureAnim::SHOOT_UP:
|
||||
case CCreatureAnim::SHOOT_FRONT:
|
||||
case CCreatureAnim::SHOOT_DOWN:
|
||||
case CCreatureAnim::CAST_UP:
|
||||
case CCreatureAnim::CAST_FRONT:
|
||||
case CCreatureAnim::CAST_DOWN:
|
||||
case CCreatureAnim::VCMI_CAST_DOWN:
|
||||
case CCreatureAnim::VCMI_CAST_FRONT:
|
||||
case CCreatureAnim::VCMI_CAST_UP:
|
||||
return static_cast<float>(speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type));
|
||||
|
||||
// as strange as it looks like "attackAnimationTime" does not affects melee attacks
|
||||
// necessary because length of these animations must be same for all creatures for synchronization
|
||||
case CCreatureAnim::ATTACK_UP:
|
||||
case CCreatureAnim::ATTACK_FRONT:
|
||||
case CCreatureAnim::ATTACK_DOWN:
|
||||
case CCreatureAnim::HITTED:
|
||||
case CCreatureAnim::DEFENCE:
|
||||
case CCreatureAnim::DEATH:
|
||||
case CCreatureAnim::DEATH_RANGED:
|
||||
case CCreatureAnim::VCMI_2HEX_DOWN:
|
||||
case CCreatureAnim::VCMI_2HEX_FRONT:
|
||||
case CCreatureAnim::VCMI_2HEX_UP:
|
||||
return speed * 3 / anim->framesInGroup(type);
|
||||
|
||||
case CCreatureAnim::TURN_L:
|
||||
case CCreatureAnim::TURN_R:
|
||||
return speed / 3;
|
||||
|
||||
case CCreatureAnim::MOVE_START:
|
||||
case CCreatureAnim::MOVE_END:
|
||||
return speed / 3;
|
||||
|
||||
case CCreatureAnim::DEAD:
|
||||
case CCreatureAnim::DEAD_RANGED:
|
||||
return speed;
|
||||
|
||||
default:
|
||||
return speed;
|
||||
}
|
||||
}
|
||||
|
||||
float AnimationControls::getProjectileSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 100);
|
||||
}
|
||||
|
||||
float AnimationControls::getSpellEffectSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
|
||||
}
|
||||
|
||||
float AnimationControls::getMovementDuration(const CCreature * creature)
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime);
|
||||
}
|
||||
|
||||
float AnimationControls::getFlightDistance(const CCreature * creature)
|
||||
{
|
||||
return static_cast<float>(creature->animation.flightAnimationDistance * 200);
|
||||
}
|
||||
|
||||
CCreatureAnim::EAnimType CCreatureAnimation::getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
void CCreatureAnimation::setType(CCreatureAnim::EAnimType type)
|
||||
{
|
||||
this->type = type;
|
||||
currentFrame = 0;
|
||||
once = false;
|
||||
|
||||
play();
|
||||
}
|
||||
|
||||
void CCreatureAnimation::shiftColor(const ColorShifter* shifter)
|
||||
{
|
||||
if(forward)
|
||||
forward->shiftColor(shifter);
|
||||
|
||||
if(reverse)
|
||||
reverse->shiftColor(shifter);
|
||||
}
|
||||
|
||||
CCreatureAnimation::CCreatureAnimation(const std::string & name_, TSpeedController controller)
|
||||
: name(name_),
|
||||
speed(0.1f),
|
||||
currentFrame(0),
|
||||
elapsedTime(0),
|
||||
type(CCreatureAnim::HOLDING),
|
||||
border(CSDL_Ext::makeColor(0, 0, 0, 0)),
|
||||
speedController(controller),
|
||||
once(false)
|
||||
{
|
||||
forward = std::make_shared<CAnimation>(name_);
|
||||
reverse = std::make_shared<CAnimation>(name_);
|
||||
|
||||
//todo: optimize
|
||||
forward->preload();
|
||||
reverse->preload();
|
||||
|
||||
// if necessary, add one frame into vcmi-only group DEAD
|
||||
if(forward->size(CCreatureAnim::DEAD) == 0)
|
||||
{
|
||||
forward->duplicateImage(CCreatureAnim::DEATH, forward->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
|
||||
reverse->duplicateImage(CCreatureAnim::DEATH, reverse->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
|
||||
}
|
||||
|
||||
if(forward->size(CCreatureAnim::DEAD_RANGED) == 0 && forward->size(CCreatureAnim::DEATH_RANGED) != 0)
|
||||
{
|
||||
forward->duplicateImage(CCreatureAnim::DEATH_RANGED, forward->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED);
|
||||
reverse->duplicateImage(CCreatureAnim::DEATH_RANGED, reverse->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED);
|
||||
}
|
||||
|
||||
//TODO: get dimensions form CAnimation
|
||||
auto first = forward->getImage(0, type, true);
|
||||
|
||||
if(!first)
|
||||
{
|
||||
fullWidth = 0;
|
||||
fullHeight = 0;
|
||||
return;
|
||||
}
|
||||
fullWidth = first->width();
|
||||
fullHeight = first->height();
|
||||
|
||||
reverse->verticalFlip();
|
||||
|
||||
play();
|
||||
}
|
||||
|
||||
void CCreatureAnimation::endAnimation()
|
||||
{
|
||||
once = false;
|
||||
auto copy = onAnimationReset;
|
||||
onAnimationReset.clear();
|
||||
copy();
|
||||
}
|
||||
|
||||
bool CCreatureAnimation::incrementFrame(float timePassed)
|
||||
{
|
||||
elapsedTime += timePassed;
|
||||
currentFrame += timePassed * speed;
|
||||
const auto framesNumber = framesInGroup(type);
|
||||
|
||||
if(framesNumber <= 0)
|
||||
{
|
||||
endAnimation();
|
||||
}
|
||||
else if(currentFrame >= float(framesNumber))
|
||||
{
|
||||
// just in case of extremely low fps (or insanely high speed)
|
||||
while(currentFrame >= float(framesNumber))
|
||||
currentFrame -= framesNumber;
|
||||
|
||||
if(once)
|
||||
setType(CCreatureAnim::HOLDING);
|
||||
|
||||
endAnimation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CCreatureAnimation::setBorderColor(SDL_Color palette)
|
||||
{
|
||||
border = palette;
|
||||
}
|
||||
|
||||
int CCreatureAnimation::getWidth() const
|
||||
{
|
||||
return fullWidth;
|
||||
}
|
||||
|
||||
int CCreatureAnimation::getHeight() const
|
||||
{
|
||||
return fullHeight;
|
||||
}
|
||||
|
||||
float CCreatureAnimation::getCurrentFrame() const
|
||||
{
|
||||
return currentFrame;
|
||||
}
|
||||
|
||||
void CCreatureAnimation::playOnce( CCreatureAnim::EAnimType type )
|
||||
{
|
||||
setType(type);
|
||||
once = true;
|
||||
}
|
||||
|
||||
inline int getBorderStrength(float time)
|
||||
{
|
||||
float borderStrength = fabs(vstd::round(time) - time) * 2; // generate value in range 0-1
|
||||
|
||||
return static_cast<int>(borderStrength * 155 + 100); // scale to 0-255
|
||||
}
|
||||
|
||||
static SDL_Color genShadow(ui8 alpha)
|
||||
{
|
||||
return CSDL_Ext::makeColor(0, 0, 0, alpha);
|
||||
}
|
||||
|
||||
static SDL_Color genBorderColor(ui8 alpha, const SDL_Color & base)
|
||||
{
|
||||
return CSDL_Ext::makeColor(base.r, base.g, base.b, ui8(base.a * alpha / 256));
|
||||
}
|
||||
|
||||
static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2)
|
||||
{
|
||||
return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256;
|
||||
}
|
||||
|
||||
static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
|
||||
{
|
||||
return CSDL_Ext::makeColor(
|
||||
mixChannels(over.r, base.r, over.a, base.a),
|
||||
mixChannels(over.g, base.g, over.a, base.a),
|
||||
mixChannels(over.b, base.b, over.a, base.a),
|
||||
ui8(over.a + base.a * (255 - over.a) / 256)
|
||||
);
|
||||
}
|
||||
|
||||
void CCreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
|
||||
{
|
||||
target[0] = genBorderColor(getBorderStrength(elapsedTime), border);
|
||||
target[1] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
target[2] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
}
|
||||
|
||||
void CCreatureAnimation::nextFrame(SDL_Surface * dest, bool attacker)
|
||||
{
|
||||
size_t frame = static_cast<size_t>(floor(currentFrame));
|
||||
|
||||
std::shared_ptr<IImage> image;
|
||||
|
||||
if(attacker)
|
||||
image = forward->getImage(frame, type);
|
||||
else
|
||||
image = reverse->getImage(frame, type);
|
||||
|
||||
if(image)
|
||||
{
|
||||
IImage::BorderPallete borderPallete;
|
||||
genBorderPalette(borderPallete);
|
||||
|
||||
image->setBorderPallete(borderPallete);
|
||||
|
||||
image->draw(dest, pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
|
||||
{
|
||||
return static_cast<int>(forward->size(group));
|
||||
}
|
||||
|
||||
bool CCreatureAnimation::isDead() const
|
||||
{
|
||||
return getType() == CCreatureAnim::DEAD
|
||||
|| getType() == CCreatureAnim::DEATH
|
||||
|| getType() == CCreatureAnim::DEAD_RANGED
|
||||
|| getType() == CCreatureAnim::DEATH_RANGED;
|
||||
}
|
||||
|
||||
bool CCreatureAnimation::isIdle() const
|
||||
{
|
||||
return getType() == CCreatureAnim::HOLDING
|
||||
|| getType() == CCreatureAnim::MOUSEON;
|
||||
}
|
||||
|
||||
bool CCreatureAnimation::isMoving() const
|
||||
{
|
||||
return getType() == CCreatureAnim::MOVE_START
|
||||
|| getType() == CCreatureAnim::MOVING
|
||||
|| getType() == CCreatureAnim::MOVE_END;
|
||||
}
|
||||
|
||||
bool CCreatureAnimation::isShooting() const
|
||||
{
|
||||
return getType() == CCreatureAnim::SHOOT_UP
|
||||
|| getType() == CCreatureAnim::SHOOT_FRONT
|
||||
|| getType() == CCreatureAnim::SHOOT_DOWN;
|
||||
}
|
||||
|
||||
void CCreatureAnimation::pause()
|
||||
{
|
||||
speed = 0;
|
||||
}
|
||||
|
||||
void CCreatureAnimation::play()
|
||||
{
|
||||
//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
|
||||
speed = 0;
|
||||
if(speedController(this, type) != 0)
|
||||
speed = 1 / speedController(this, type);
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* CCreatureAnimation.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 "../../lib/FunctionList.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
|
||||
class CIntObject;
|
||||
class CCreatureAnimation;
|
||||
|
||||
/// Namespace for some common controls of animations
|
||||
namespace AnimationControls
|
||||
{
|
||||
/// get SDL_Color for creature selection borders
|
||||
SDL_Color getBlueBorder();
|
||||
SDL_Color getGoldBorder();
|
||||
SDL_Color getNoBorder();
|
||||
|
||||
/// creates animation object with preset speed control
|
||||
std::shared_ptr<CCreatureAnimation> getAnimation(const CCreature * creature);
|
||||
|
||||
/// returns animation speed of specific group, taking in mind game setting (in frames per second)
|
||||
float getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t groupID);
|
||||
|
||||
/// returns how far projectile should move each frame
|
||||
/// TODO: make it time-based
|
||||
float getProjectileSpeed();
|
||||
|
||||
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
|
||||
float getSpellEffectSpeed();
|
||||
|
||||
/// returns duration of full movement animation, in seconds. Needed to move animation on screen
|
||||
float getMovementDuration(const CCreature * creature);
|
||||
|
||||
/// Returns distance on which flying creatures should during one animation loop
|
||||
float getFlightDistance(const CCreature * creature);
|
||||
}
|
||||
|
||||
/// Class which manages animations of creatures/units inside battles
|
||||
/// TODO: split into constant image container and class that does *control* of animation
|
||||
class CCreatureAnimation : public CIntObject
|
||||
{
|
||||
public:
|
||||
typedef std::function<float(CCreatureAnimation *, size_t)> TSpeedController;
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::shared_ptr<CAnimation> forward;
|
||||
std::shared_ptr<CAnimation> reverse;
|
||||
|
||||
int fullWidth;
|
||||
int fullHeight;
|
||||
|
||||
// speed of animation, measure in frames per second
|
||||
float speed;
|
||||
|
||||
// currently displayed frame. Float to allow H3-style animations where frames
|
||||
// don't display for integer number of frames
|
||||
float currentFrame;
|
||||
// cumulative, real-time duration of animation. Used for effects like selection border
|
||||
float elapsedTime;
|
||||
CCreatureAnim::EAnimType type; //type of animation being displayed
|
||||
|
||||
// border color, disabled if alpha = 0
|
||||
SDL_Color border;
|
||||
|
||||
TSpeedController speedController;
|
||||
|
||||
bool once; // animation will be played once and the reset to idling
|
||||
|
||||
void endAnimation();
|
||||
|
||||
|
||||
void genBorderPalette(IImage::BorderPallete & target);
|
||||
public:
|
||||
|
||||
// function(s) that will be called when animation ends, after reset to 1st frame
|
||||
// NOTE that these function will be fired only once
|
||||
CFunctionList<void()> onAnimationReset;
|
||||
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
|
||||
/// Constructor
|
||||
/// name - path to .def file, relative to SPRITES/ directory
|
||||
/// controller - function that will return for how long *each* frame
|
||||
/// in specified group of animation should be played, measured in seconds
|
||||
CCreatureAnimation(const std::string & name_, TSpeedController speedController);
|
||||
|
||||
void setType(CCreatureAnim::EAnimType type); //sets type of animation and cleares framecount
|
||||
CCreatureAnim::EAnimType getType() const; //returns type of animation
|
||||
|
||||
void nextFrame(SDL_Surface * dest, bool attacker);
|
||||
|
||||
// should be called every frame, return true when animation was reset to beginning
|
||||
bool incrementFrame(float timePassed);
|
||||
void setBorderColor(SDL_Color palette);
|
||||
|
||||
// tint color effect
|
||||
void shiftColor(const ColorShifter * shifter);
|
||||
|
||||
float getCurrentFrame() const; // Gets the current frame ID relative to frame group.
|
||||
|
||||
void playOnce(CCreatureAnim::EAnimType type); //plays once given stage of animation, then resets to 2
|
||||
|
||||
int framesInGroup(CCreatureAnim::EAnimType group) const;
|
||||
|
||||
void pause();
|
||||
void play();
|
||||
|
||||
//helpers. TODO: move them somewhere else
|
||||
bool isDead() const;
|
||||
bool isIdle() const;
|
||||
bool isMoving() const;
|
||||
bool isShooting() const;
|
||||
};
|
412
client/battle/CreatureAnimation.cpp
Normal file
412
client/battle/CreatureAnimation.cpp
Normal file
@ -0,0 +1,412 @@
|
||||
/*
|
||||
* CCreatureAnimation.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 "CreatureAnimation.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
|
||||
#include "../gui/Canvas.h"
|
||||
#include "../gui/ColorFilter.h"
|
||||
|
||||
static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
|
||||
static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
|
||||
static const SDL_Color creatureNoBorder = { 0, 0, 0, 0 };
|
||||
|
||||
static SDL_Color genShadow(ui8 alpha)
|
||||
{
|
||||
return CSDL_Ext::makeColor(0, 0, 0, alpha);
|
||||
}
|
||||
|
||||
SDL_Color AnimationControls::getBlueBorder()
|
||||
{
|
||||
return creatureBlueBorder;
|
||||
}
|
||||
|
||||
SDL_Color AnimationControls::getGoldBorder()
|
||||
{
|
||||
return creatureGoldBorder;
|
||||
}
|
||||
|
||||
SDL_Color AnimationControls::getNoBorder()
|
||||
{
|
||||
return creatureNoBorder;
|
||||
}
|
||||
|
||||
std::shared_ptr<CreatureAnimation> AnimationControls::getAnimation(const CCreature * creature)
|
||||
{
|
||||
auto func = std::bind(&AnimationControls::getCreatureAnimationSpeed, creature, _1, _2);
|
||||
return std::make_shared<CreatureAnimation>(creature->animDefName, func);
|
||||
}
|
||||
|
||||
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType type)
|
||||
{
|
||||
assert(creature->animation.walkAnimationTime != 0);
|
||||
assert(creature->animation.attackAnimationTime != 0);
|
||||
assert(anim->framesInGroup(type) != 0);
|
||||
|
||||
// possible new fields for creature format:
|
||||
//split "Attack time" into "Shoot Time" and "Cast Time"
|
||||
|
||||
// a lot of arbitrary multipliers, mostly to make animation speed closer to H3
|
||||
const float baseSpeed = 0.1f;
|
||||
const float speedMult = static_cast<float>(settings["battle"]["animationSpeed"].Float());
|
||||
const float speed = baseSpeed / speedMult;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ECreatureAnimType::MOVING:
|
||||
return static_cast<float>(speed * 2 * creature->animation.walkAnimationTime / anim->framesInGroup(type));
|
||||
|
||||
case ECreatureAnimType::MOUSEON:
|
||||
return baseSpeed;
|
||||
case ECreatureAnimType::HOLDING:
|
||||
return static_cast<float>(baseSpeed * creature->animation.idleAnimationTime / anim->framesInGroup(type));
|
||||
|
||||
case ECreatureAnimType::SHOOT_UP:
|
||||
case ECreatureAnimType::SHOOT_FRONT:
|
||||
case ECreatureAnimType::SHOOT_DOWN:
|
||||
case ECreatureAnimType::SPECIAL_UP:
|
||||
case ECreatureAnimType::SPECIAL_FRONT:
|
||||
case ECreatureAnimType::SPECIAL_DOWN:
|
||||
case ECreatureAnimType::CAST_DOWN:
|
||||
case ECreatureAnimType::CAST_FRONT:
|
||||
case ECreatureAnimType::CAST_UP:
|
||||
return static_cast<float>(speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type));
|
||||
|
||||
// as strange as it looks like "attackAnimationTime" does not affects melee attacks
|
||||
// necessary because length of these animations must be same for all creatures for synchronization
|
||||
case ECreatureAnimType::ATTACK_UP:
|
||||
case ECreatureAnimType::ATTACK_FRONT:
|
||||
case ECreatureAnimType::ATTACK_DOWN:
|
||||
case ECreatureAnimType::HITTED:
|
||||
case ECreatureAnimType::DEFENCE:
|
||||
case ECreatureAnimType::DEATH:
|
||||
case ECreatureAnimType::DEATH_RANGED:
|
||||
case ECreatureAnimType::RESURRECTION:
|
||||
case ECreatureAnimType::GROUP_ATTACK_DOWN:
|
||||
case ECreatureAnimType::GROUP_ATTACK_FRONT:
|
||||
case ECreatureAnimType::GROUP_ATTACK_UP:
|
||||
return speed * 3 / anim->framesInGroup(type);
|
||||
|
||||
case ECreatureAnimType::TURN_L:
|
||||
case ECreatureAnimType::TURN_R:
|
||||
return speed / 3;
|
||||
|
||||
case ECreatureAnimType::MOVE_START:
|
||||
case ECreatureAnimType::MOVE_END:
|
||||
return speed / 3;
|
||||
|
||||
case ECreatureAnimType::DEAD:
|
||||
case ECreatureAnimType::DEAD_RANGED:
|
||||
return speed;
|
||||
|
||||
default:
|
||||
return speed;
|
||||
}
|
||||
}
|
||||
|
||||
float AnimationControls::getProjectileSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4000);
|
||||
}
|
||||
|
||||
float AnimationControls::getCatapultSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 1000);
|
||||
}
|
||||
|
||||
float AnimationControls::getSpellEffectSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
|
||||
}
|
||||
|
||||
float AnimationControls::getMovementDuration(const CCreature * creature)
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime);
|
||||
}
|
||||
|
||||
float AnimationControls::getFlightDistance(const CCreature * creature)
|
||||
{
|
||||
return static_cast<float>(creature->animation.flightAnimationDistance * 200);
|
||||
}
|
||||
|
||||
float AnimationControls::getFadeInDuration()
|
||||
{
|
||||
return 1.0f / settings["battle"]["animationSpeed"].Float();
|
||||
}
|
||||
|
||||
float AnimationControls::getObstaclesSpeed()
|
||||
{
|
||||
return 10.0;// does not seems to be affected by animaiton speed settings
|
||||
}
|
||||
|
||||
ECreatureAnimType CreatureAnimation::getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
void CreatureAnimation::setType(ECreatureAnimType type)
|
||||
{
|
||||
this->type = type;
|
||||
currentFrame = 0;
|
||||
once = false;
|
||||
|
||||
play();
|
||||
}
|
||||
|
||||
CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
|
||||
: name(name_),
|
||||
speed(0.1f),
|
||||
shadowAlpha(128),
|
||||
currentFrame(0),
|
||||
elapsedTime(0),
|
||||
type(ECreatureAnimType::HOLDING),
|
||||
border(CSDL_Ext::makeColor(0, 0, 0, 0)),
|
||||
speedController(controller),
|
||||
once(false)
|
||||
{
|
||||
forward = std::make_shared<CAnimation>(name_);
|
||||
reverse = std::make_shared<CAnimation>(name_);
|
||||
|
||||
//todo: optimize
|
||||
forward->preload();
|
||||
reverse->preload();
|
||||
|
||||
// if necessary, add one frame into vcmi-only group DEAD
|
||||
if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)
|
||||
{
|
||||
forward->duplicateImage(size_t(ECreatureAnimType::DEATH), forward->size(size_t(ECreatureAnimType::DEATH))-1, size_t(ECreatureAnimType::DEAD));
|
||||
reverse->duplicateImage(size_t(ECreatureAnimType::DEATH), reverse->size(size_t(ECreatureAnimType::DEATH))-1, size_t(ECreatureAnimType::DEAD));
|
||||
}
|
||||
|
||||
if(forward->size(size_t(ECreatureAnimType::DEAD_RANGED)) == 0 && forward->size(size_t(ECreatureAnimType::DEATH_RANGED)) != 0)
|
||||
{
|
||||
forward->duplicateImage(size_t(ECreatureAnimType::DEATH_RANGED), forward->size(size_t(ECreatureAnimType::DEATH_RANGED))-1, size_t(ECreatureAnimType::DEAD_RANGED));
|
||||
reverse->duplicateImage(size_t(ECreatureAnimType::DEATH_RANGED), reverse->size(size_t(ECreatureAnimType::DEATH_RANGED))-1, size_t(ECreatureAnimType::DEAD_RANGED));
|
||||
}
|
||||
|
||||
if(forward->size(size_t(ECreatureAnimType::FROZEN)) == 0)
|
||||
{
|
||||
forward->duplicateImage(size_t(ECreatureAnimType::HOLDING), 0, size_t(ECreatureAnimType::FROZEN));
|
||||
reverse->duplicateImage(size_t(ECreatureAnimType::HOLDING), 0, size_t(ECreatureAnimType::FROZEN));
|
||||
}
|
||||
|
||||
if(forward->size(size_t(ECreatureAnimType::RESURRECTION)) == 0)
|
||||
{
|
||||
for (size_t i = 0; i < forward->size(size_t(ECreatureAnimType::DEATH)); ++i)
|
||||
{
|
||||
size_t current = forward->size(size_t(ECreatureAnimType::DEATH)) - 1 - i;
|
||||
|
||||
forward->duplicateImage(size_t(ECreatureAnimType::DEATH), current, size_t(ECreatureAnimType::RESURRECTION));
|
||||
reverse->duplicateImage(size_t(ECreatureAnimType::DEATH), current, size_t(ECreatureAnimType::RESURRECTION));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: get dimensions form CAnimation
|
||||
auto first = forward->getImage(0, size_t(type), true);
|
||||
|
||||
if(!first)
|
||||
{
|
||||
fullWidth = 0;
|
||||
fullHeight = 0;
|
||||
return;
|
||||
}
|
||||
fullWidth = first->width();
|
||||
fullHeight = first->height();
|
||||
|
||||
reverse->verticalFlip();
|
||||
|
||||
play();
|
||||
}
|
||||
|
||||
void CreatureAnimation::endAnimation()
|
||||
{
|
||||
once = false;
|
||||
auto copy = onAnimationReset;
|
||||
onAnimationReset.clear();
|
||||
copy();
|
||||
}
|
||||
|
||||
bool CreatureAnimation::incrementFrame(float timePassed)
|
||||
{
|
||||
elapsedTime += timePassed;
|
||||
currentFrame += timePassed * speed;
|
||||
const auto framesNumber = framesInGroup(type);
|
||||
|
||||
if(framesNumber <= 0)
|
||||
{
|
||||
endAnimation();
|
||||
}
|
||||
else if(currentFrame >= float(framesNumber))
|
||||
{
|
||||
// just in case of extremely low fps (or insanely high speed)
|
||||
while(currentFrame >= float(framesNumber))
|
||||
currentFrame -= framesNumber;
|
||||
|
||||
if(once)
|
||||
setType(ECreatureAnimType::HOLDING);
|
||||
|
||||
endAnimation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CreatureAnimation::setBorderColor(SDL_Color palette)
|
||||
{
|
||||
border = palette;
|
||||
}
|
||||
|
||||
int CreatureAnimation::getWidth() const
|
||||
{
|
||||
return fullWidth;
|
||||
}
|
||||
|
||||
int CreatureAnimation::getHeight() const
|
||||
{
|
||||
return fullHeight;
|
||||
}
|
||||
|
||||
float CreatureAnimation::getCurrentFrame() const
|
||||
{
|
||||
return currentFrame;
|
||||
}
|
||||
|
||||
void CreatureAnimation::playOnce( ECreatureAnimType type )
|
||||
{
|
||||
setType(type);
|
||||
once = true;
|
||||
}
|
||||
|
||||
inline int getBorderStrength(float time)
|
||||
{
|
||||
float borderStrength = fabs(vstd::round(time) - time) * 2; // generate value in range 0-1
|
||||
|
||||
return static_cast<int>(borderStrength * 155 + 100); // scale to 0-255
|
||||
}
|
||||
|
||||
static SDL_Color genBorderColor(ui8 alpha, const SDL_Color & base)
|
||||
{
|
||||
return CSDL_Ext::makeColor(base.r, base.g, base.b, ui8(base.a * alpha / 256));
|
||||
}
|
||||
|
||||
static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2)
|
||||
{
|
||||
return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256;
|
||||
}
|
||||
|
||||
static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
|
||||
{
|
||||
return CSDL_Ext::makeColor(
|
||||
mixChannels(over.r, base.r, over.a, base.a),
|
||||
mixChannels(over.g, base.g, over.a, base.a),
|
||||
mixChannels(over.b, base.b, over.a, base.a),
|
||||
ui8(over.a + base.a * (255 - over.a) / 256)
|
||||
);
|
||||
}
|
||||
|
||||
void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target)
|
||||
{
|
||||
target[0] = genShadow(shadowAlpha / 2);
|
||||
target[1] = genShadow(shadowAlpha / 2);
|
||||
target[2] = genShadow(shadowAlpha);
|
||||
target[3] = genShadow(shadowAlpha);
|
||||
target[4] = genBorderColor(getBorderStrength(elapsedTime), border);
|
||||
target[5] = addColors(genShadow(shadowAlpha), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
target[6] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
|
||||
}
|
||||
|
||||
void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight)
|
||||
{
|
||||
SDL_Color shadowTest = shifter.shiftColor(genShadow(128));
|
||||
shadowAlpha = shadowTest.a;
|
||||
|
||||
size_t frame = static_cast<size_t>(floor(currentFrame));
|
||||
|
||||
std::shared_ptr<IImage> image;
|
||||
|
||||
if(facingRight)
|
||||
image = forward->getImage(frame, size_t(type));
|
||||
else
|
||||
image = reverse->getImage(frame, size_t(type));
|
||||
|
||||
if(image)
|
||||
{
|
||||
IImage::SpecialPalette SpecialPalette;
|
||||
genSpecialPalette(SpecialPalette);
|
||||
|
||||
image->setSpecialPallete(SpecialPalette);
|
||||
image->adjustPalette(shifter);
|
||||
|
||||
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int CreatureAnimation::framesInGroup(ECreatureAnimType group) const
|
||||
{
|
||||
return static_cast<int>(forward->size(size_t(group)));
|
||||
}
|
||||
|
||||
bool CreatureAnimation::isDead() const
|
||||
{
|
||||
return getType() == ECreatureAnimType::DEAD
|
||||
|| getType() == ECreatureAnimType::DEAD_RANGED;
|
||||
}
|
||||
|
||||
bool CreatureAnimation::isDying() const
|
||||
{
|
||||
return getType() == ECreatureAnimType::DEATH
|
||||
|| getType() == ECreatureAnimType::DEATH_RANGED;
|
||||
}
|
||||
|
||||
bool CreatureAnimation::isDeadOrDying() const
|
||||
{
|
||||
return getType() == ECreatureAnimType::DEAD
|
||||
|| getType() == ECreatureAnimType::DEATH
|
||||
|| getType() == ECreatureAnimType::DEAD_RANGED
|
||||
|| getType() == ECreatureAnimType::DEATH_RANGED;
|
||||
}
|
||||
|
||||
bool CreatureAnimation::isIdle() const
|
||||
{
|
||||
return getType() == ECreatureAnimType::HOLDING
|
||||
|| getType() == ECreatureAnimType::MOUSEON;
|
||||
}
|
||||
|
||||
bool CreatureAnimation::isMoving() const
|
||||
{
|
||||
return getType() == ECreatureAnimType::MOVE_START
|
||||
|| getType() == ECreatureAnimType::MOVING
|
||||
|| getType() == ECreatureAnimType::MOVE_END
|
||||
|| getType() == ECreatureAnimType::TURN_L
|
||||
|| getType() == ECreatureAnimType::TURN_R;
|
||||
}
|
||||
|
||||
bool CreatureAnimation::isShooting() const
|
||||
{
|
||||
return getType() == ECreatureAnimType::SHOOT_UP
|
||||
|| getType() == ECreatureAnimType::SHOOT_FRONT
|
||||
|| getType() == ECreatureAnimType::SHOOT_DOWN;
|
||||
}
|
||||
|
||||
void CreatureAnimation::pause()
|
||||
{
|
||||
speed = 0;
|
||||
}
|
||||
|
||||
void CreatureAnimation::play()
|
||||
{
|
||||
//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
|
||||
speed = 0;
|
||||
if(speedController(this, type) != 0)
|
||||
speed = 1 / speedController(this, type);
|
||||
}
|
149
client/battle/CreatureAnimation.h
Normal file
149
client/battle/CreatureAnimation.h
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* CCreatureAnimation.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 "../../lib/FunctionList.h"
|
||||
#include "../widgets/Images.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
|
||||
class CIntObject;
|
||||
class CreatureAnimation;
|
||||
class Canvas;
|
||||
|
||||
/// Namespace for some common controls of animations
|
||||
namespace AnimationControls
|
||||
{
|
||||
/// get SDL_Color for creature selection borders
|
||||
SDL_Color getBlueBorder();
|
||||
SDL_Color getGoldBorder();
|
||||
SDL_Color getNoBorder();
|
||||
|
||||
/// creates animation object with preset speed control
|
||||
std::shared_ptr<CreatureAnimation> getAnimation(const CCreature * creature);
|
||||
|
||||
/// returns animation speed of specific group, taking in mind game setting (in frames per second)
|
||||
float getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType groupID);
|
||||
|
||||
/// returns how far projectile should move per second
|
||||
float getProjectileSpeed();
|
||||
|
||||
/// returns speed of catapult projectile, in pixels per second (horizontal axis only)
|
||||
float getCatapultSpeed();
|
||||
|
||||
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
|
||||
float getSpellEffectSpeed();
|
||||
|
||||
/// returns duration of full movement animation, in seconds. Needed to move animation on screen
|
||||
float getMovementDuration(const CCreature * creature);
|
||||
|
||||
/// Returns distance on which flying creatures should during one animation loop
|
||||
float getFlightDistance(const CCreature * creature);
|
||||
|
||||
/// Returns total time for full fade-in effect on newly summoned creatures, in seconds
|
||||
float getFadeInDuration();
|
||||
|
||||
/// Returns animation speed for obstacles, in frames per second
|
||||
float getObstaclesSpeed();
|
||||
}
|
||||
|
||||
/// Class which manages animations of creatures/units inside battles
|
||||
/// TODO: split into constant image container and class that does *control* of animation
|
||||
class CreatureAnimation : public CIntObject
|
||||
{
|
||||
public:
|
||||
typedef std::function<float(CreatureAnimation *, ECreatureAnimType)> TSpeedController;
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
|
||||
/// animation for rendering stack in default orientation - facing right
|
||||
std::shared_ptr<CAnimation> forward;
|
||||
|
||||
/// animation that has all its frames flipped for rendering stack facing left
|
||||
std::shared_ptr<CAnimation> reverse;
|
||||
|
||||
int fullWidth;
|
||||
int fullHeight;
|
||||
|
||||
/// speed of animation, measure in frames per second
|
||||
float speed;
|
||||
|
||||
/// currently displayed frame. Float to allow H3-style animations where frames
|
||||
/// don't display for integer number of frames
|
||||
float currentFrame;
|
||||
|
||||
/// cumulative, real-time duration of animation. Used for effects like selection border
|
||||
float elapsedTime;
|
||||
|
||||
///type of animation being displayed
|
||||
ECreatureAnimType type;
|
||||
|
||||
/// current value of shadow transparency
|
||||
uint8_t shadowAlpha;
|
||||
|
||||
/// border color, disabled if alpha = 0
|
||||
SDL_Color border;
|
||||
|
||||
TSpeedController speedController;
|
||||
|
||||
/// animation will be played once and the reset to idling
|
||||
bool once;
|
||||
|
||||
void endAnimation();
|
||||
|
||||
void genSpecialPalette(IImage::SpecialPalette & target);
|
||||
public:
|
||||
|
||||
/// function(s) that will be called when animation ends, after reset to 1st frame
|
||||
/// NOTE that these functions will be fired only once
|
||||
CFunctionList<void()> onAnimationReset;
|
||||
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
|
||||
/// Constructor
|
||||
/// name - path to .def file, relative to SPRITES/ directory
|
||||
/// controller - function that will return for how long *each* frame
|
||||
/// in specified group of animation should be played, measured in seconds
|
||||
CreatureAnimation(const std::string & name_, TSpeedController speedController);
|
||||
|
||||
/// sets type of animation and resets framecount
|
||||
void setType(ECreatureAnimType type);
|
||||
|
||||
/// returns currently rendered type of animation
|
||||
ECreatureAnimType getType() const;
|
||||
|
||||
void nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight);
|
||||
|
||||
/// should be called every frame, return true when animation was reset to beginning
|
||||
bool incrementFrame(float timePassed);
|
||||
|
||||
void setBorderColor(SDL_Color palette);
|
||||
|
||||
/// Gets the current frame ID within current group.
|
||||
float getCurrentFrame() const;
|
||||
|
||||
/// plays once given type of animation, then resets to idle
|
||||
void playOnce(ECreatureAnimType type);
|
||||
|
||||
/// returns number of frames in selected animation type
|
||||
int framesInGroup(ECreatureAnimType group) const;
|
||||
|
||||
void pause();
|
||||
void play();
|
||||
|
||||
/// helpers to classify current type of animation
|
||||
bool isDead() const;
|
||||
bool isDying() const;
|
||||
bool isDeadOrDying() const;
|
||||
bool isIdle() const;
|
||||
bool isMoving() const;
|
||||
bool isShooting() const;
|
||||
};
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "SDL_Extensions.h"
|
||||
#include "SDL_Pixels.h"
|
||||
#include "ColorFilter.h"
|
||||
|
||||
#include "../CBitmapHandler.h"
|
||||
#include "../Graphics.h"
|
||||
@ -33,6 +34,7 @@ class CDefFile
|
||||
{
|
||||
private:
|
||||
|
||||
PACKED_STRUCT_BEGIN
|
||||
struct SSpriteDef
|
||||
{
|
||||
ui32 size;
|
||||
@ -43,7 +45,7 @@ private:
|
||||
ui32 height;
|
||||
si32 leftMargin;
|
||||
si32 topMargin;
|
||||
} PACKED_STRUCT;
|
||||
} PACKED_STRUCT_END;
|
||||
//offset[group][frame] - offset of frame data in file
|
||||
std::map<size_t, std::vector <size_t> > offset;
|
||||
|
||||
@ -92,23 +94,24 @@ public:
|
||||
// Keep the original palette, in order to do color switching operation
|
||||
void savePalette();
|
||||
|
||||
void draw(SDL_Surface * where, int posX=0, int posY=0, Rect *src=nullptr, ui8 alpha=255) const override;
|
||||
void draw(SDL_Surface * where, SDL_Rect * dest, SDL_Rect * src, ui8 alpha=255) const override;
|
||||
void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override;
|
||||
void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const override;
|
||||
std::shared_ptr<IImage> scaleFast(float scale) const override;
|
||||
void exportBitmap(const boost::filesystem::path & path) const override;
|
||||
void playerColored(PlayerColor player) override;
|
||||
void setFlagColor(PlayerColor player) override;
|
||||
int width() const override;
|
||||
int height() const override;
|
||||
bool isTransparent(const Point & coords) const override;
|
||||
Point dimensions() const override;
|
||||
|
||||
void horizontalFlip() override;
|
||||
void verticalFlip() override;
|
||||
|
||||
void shiftPalette(int from, int howMany) override;
|
||||
void adjustPalette(const ColorShifter * shifter) override;
|
||||
void adjustPalette(const ColorFilter & shifter) override;
|
||||
void resetPalette(int colorID) override;
|
||||
void resetPalette() override;
|
||||
|
||||
void setBorderPallete(const BorderPallete & borderPallete) override;
|
||||
void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
|
||||
|
||||
friend class SDLImageLoader;
|
||||
|
||||
@ -134,6 +137,11 @@ public:
|
||||
~SDLImageLoader();
|
||||
};
|
||||
|
||||
std::shared_ptr<IImage> IImage::createFromFile( const std::string & path )
|
||||
{
|
||||
return std::shared_ptr<IImage>(new SDLImage(path));
|
||||
}
|
||||
|
||||
// Extremely simple file cache. TODO: smarter, more general solution
|
||||
class CFileCache
|
||||
{
|
||||
@ -207,32 +215,17 @@ CDefFile::CDefFile(std::string Name):
|
||||
data(nullptr),
|
||||
palette(nullptr)
|
||||
{
|
||||
|
||||
#if 0
|
||||
static SDL_Color H3_ORIG_PALETTE[8] =
|
||||
{
|
||||
{ 0, 255, 255, SDL_ALPHA_OPAQUE},
|
||||
{255, 150, 255, SDL_ALPHA_OPAQUE},
|
||||
{255, 100, 255, SDL_ALPHA_OPAQUE},
|
||||
{255, 50, 255, SDL_ALPHA_OPAQUE},
|
||||
{255, 0, 255, SDL_ALPHA_OPAQUE},
|
||||
{255, 255, 0, SDL_ALPHA_OPAQUE},
|
||||
{180, 0, 255, SDL_ALPHA_OPAQUE},
|
||||
{ 0, 255, 0, SDL_ALPHA_OPAQUE}
|
||||
};
|
||||
#endif // 0
|
||||
|
||||
//First 8 colors in def palette used for transparency
|
||||
static SDL_Color H3Palette[8] =
|
||||
{
|
||||
{ 0, 0, 0, 0},// 100% - transparency
|
||||
{ 0, 0, 0, 32},// 75% - shadow border,
|
||||
{ 0, 0, 0, 64},// TODO: find exact value
|
||||
{ 0, 0, 0, 128},// TODO: for transparency
|
||||
{ 0, 0, 0, 128},// 50% - shadow body
|
||||
{ 0, 0, 0, 0},// 100% - selection highlight
|
||||
{ 0, 0, 0, 128},// 50% - shadow body below selection
|
||||
{ 0, 0, 0, 64} // 75% - shadow border below selection
|
||||
{ 0, 0, 0, 0},// transparency ( used in most images )
|
||||
{ 0, 0, 0, 64},// shadow border ( used in battle, adventure map def's )
|
||||
{ 0, 0, 0, 64},// shadow border ( used in fog-of-war def's )
|
||||
{ 0, 0, 0, 128},// shadow body ( used in fog-of-war def's )
|
||||
{ 0, 0, 0, 128},// shadow body ( used in battle, adventure map def's )
|
||||
{ 0, 0, 0, 0},// selection ( used in battle def's )
|
||||
{ 0, 0, 0, 128},// shadow body below selection ( used in battle def's )
|
||||
{ 0, 0, 0, 64} // shadow border below selection ( used in battle def's )
|
||||
};
|
||||
data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION));
|
||||
|
||||
@ -554,6 +547,15 @@ SDLImageLoader::~SDLImageLoader()
|
||||
IImage::IImage() = default;
|
||||
IImage::~IImage() = default;
|
||||
|
||||
int IImage::width() const
|
||||
{
|
||||
return dimensions().x;
|
||||
}
|
||||
|
||||
int IImage::height() const
|
||||
{
|
||||
return dimensions().y;
|
||||
}
|
||||
|
||||
SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group)
|
||||
: surf(nullptr),
|
||||
@ -640,17 +642,16 @@ SDLImage::SDLImage(std::string filename)
|
||||
}
|
||||
}
|
||||
|
||||
void SDLImage::draw(SDL_Surface *where, int posX, int posY, Rect *src, ui8 alpha) const
|
||||
void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const
|
||||
{
|
||||
if(!surf)
|
||||
return;
|
||||
|
||||
Rect destRect(posX, posY, surf->w, surf->h);
|
||||
|
||||
draw(where, &destRect, src);
|
||||
}
|
||||
|
||||
void SDLImage::draw(SDL_Surface* where, SDL_Rect* dest, SDL_Rect* src, ui8 alpha) const
|
||||
void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src) const
|
||||
{
|
||||
if (!surf)
|
||||
return;
|
||||
@ -730,14 +731,14 @@ void SDLImage::setFlagColor(PlayerColor player)
|
||||
CSDL_Ext::setPlayerColor(surf, player);
|
||||
}
|
||||
|
||||
int SDLImage::width() const
|
||||
bool SDLImage::isTransparent(const Point & coords) const
|
||||
{
|
||||
return fullSize.x;
|
||||
return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
|
||||
}
|
||||
|
||||
int SDLImage::height() const
|
||||
Point SDLImage::dimensions() const
|
||||
{
|
||||
return fullSize.y;
|
||||
return fullSize;
|
||||
}
|
||||
|
||||
void SDLImage::horizontalFlip()
|
||||
@ -790,7 +791,7 @@ void SDLImage::shiftPalette(int from, int howMany)
|
||||
}
|
||||
}
|
||||
|
||||
void SDLImage::adjustPalette(const ColorShifter * shifter)
|
||||
void SDLImage::adjustPalette(const ColorFilter & shifter)
|
||||
{
|
||||
if(originalPalette == nullptr)
|
||||
return;
|
||||
@ -800,7 +801,7 @@ void SDLImage::adjustPalette(const ColorShifter * shifter)
|
||||
// Note: here we skip the first 8 colors in the palette that predefined in H3Palette
|
||||
for(int i = 8; i < palette->ncolors; i++)
|
||||
{
|
||||
palette->colors[i] = shifter->shiftColor(originalPalette->colors[i]);
|
||||
palette->colors[i] = shifter.shiftColor(originalPalette->colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -813,11 +814,20 @@ void SDLImage::resetPalette()
|
||||
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors);
|
||||
}
|
||||
|
||||
void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete)
|
||||
void SDLImage::resetPalette( int colorID )
|
||||
{
|
||||
if(originalPalette == nullptr)
|
||||
return;
|
||||
|
||||
// Always keept the original palette not changed, copy a new palette to assign to surface
|
||||
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1);
|
||||
}
|
||||
|
||||
void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
|
||||
{
|
||||
if(surf->format->palette)
|
||||
{
|
||||
SDL_SetColors(surf, const_cast<SDL_Color *>(borderPallete.data()), 5, 3);
|
||||
SDL_SetColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1080,18 +1090,6 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra
|
||||
load(index, targetGroup);
|
||||
}
|
||||
|
||||
void CAnimation::shiftColor(const ColorShifter * shifter)
|
||||
{
|
||||
for(auto groupIter = images.begin(); groupIter != images.end(); groupIter++)
|
||||
{
|
||||
for(auto frameIter = groupIter->second.begin(); frameIter != groupIter->second.end(); frameIter++)
|
||||
{
|
||||
std::shared_ptr<IImage> image = frameIter->second;
|
||||
image->adjustPalette(shifter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CAnimation::setCustom(std::string filename, size_t frame, size_t group)
|
||||
{
|
||||
if (source[group].size() <= frame)
|
||||
|
@ -29,7 +29,7 @@ VCMI_LIB_NAMESPACE_END
|
||||
|
||||
struct SDL_Surface;
|
||||
class CDefFile;
|
||||
class ColorShifter;
|
||||
class ColorFilter;
|
||||
|
||||
/*
|
||||
* Base class for images, can be used for non-animation pictures as well
|
||||
@ -37,11 +37,11 @@ class ColorShifter;
|
||||
class IImage
|
||||
{
|
||||
public:
|
||||
using BorderPallete = std::array<SDL_Color, 3>;
|
||||
using SpecialPalette = std::array<SDL_Color, 7>;
|
||||
|
||||
//draws image on surface "where" at position
|
||||
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, Rect * src = nullptr, ui8 alpha = 255) const=0;
|
||||
virtual void draw(SDL_Surface * where, SDL_Rect * dest, SDL_Rect * src, ui8 alpha = 255) const = 0;
|
||||
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
|
||||
virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src) const = 0;
|
||||
|
||||
virtual std::shared_ptr<IImage> scaleFast(float scale) const = 0;
|
||||
|
||||
@ -53,22 +53,30 @@ public:
|
||||
//set special color for flag
|
||||
virtual void setFlagColor(PlayerColor player)=0;
|
||||
|
||||
virtual int width() const=0;
|
||||
virtual int height() const=0;
|
||||
//test transparency of specific pixel
|
||||
virtual bool isTransparent(const Point & coords) const = 0;
|
||||
|
||||
virtual Point dimensions() const = 0;
|
||||
int width() const;
|
||||
int height() const;
|
||||
|
||||
//only indexed bitmaps, 16 colors maximum
|
||||
virtual void shiftPalette(int from, int howMany) = 0;
|
||||
virtual void adjustPalette(const ColorShifter * shifter) = 0;
|
||||
virtual void adjustPalette(const ColorFilter & shifter) = 0;
|
||||
virtual void resetPalette(int colorID) = 0;
|
||||
virtual void resetPalette() = 0;
|
||||
|
||||
//only indexed bitmaps, colors 5,6,7 must be special
|
||||
virtual void setBorderPallete(const BorderPallete & borderPallete) = 0;
|
||||
//only indexed bitmaps with 7 special colors
|
||||
virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0;
|
||||
|
||||
virtual void horizontalFlip() = 0;
|
||||
virtual void verticalFlip() = 0;
|
||||
|
||||
IImage();
|
||||
virtual ~IImage();
|
||||
|
||||
/// loads image from specified file. Returns 0-sized images on failure
|
||||
static std::shared_ptr<IImage> createFromFile( const std::string & path );
|
||||
};
|
||||
|
||||
/// Class for handling animation
|
||||
@ -114,9 +122,6 @@ public:
|
||||
//and loads it if animation is preloaded
|
||||
void duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup);
|
||||
|
||||
// adjust the color of the animation, used in battle spell effects, e.g. Cloned objects
|
||||
void shiftColor(const ColorShifter * shifter);
|
||||
|
||||
//add custom surface to the selected position.
|
||||
void setCustom(std::string filename, size_t frame, size_t group=0);
|
||||
|
||||
|
@ -38,34 +38,44 @@ void CCursorHandler::replaceBuffer(CIntObject * payload)
|
||||
updateBuffer(payload);
|
||||
}
|
||||
|
||||
void CCursorHandler::initCursor()
|
||||
CCursorHandler::CCursorHandler()
|
||||
: needUpdate(true)
|
||||
, buffer(nullptr)
|
||||
, cursorLayer(nullptr)
|
||||
, frameTime(0.f)
|
||||
, showing(false)
|
||||
{
|
||||
cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40);
|
||||
SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
xpos = ypos = 0;
|
||||
type = ECursor::DEFAULT;
|
||||
type = Cursor::Type::DEFAULT;
|
||||
dndObject = nullptr;
|
||||
|
||||
cursors =
|
||||
{
|
||||
make_unique<CAnimImage>("CRADVNTR", 0),
|
||||
make_unique<CAnimImage>("CRCOMBAT", 0),
|
||||
make_unique<CAnimImage>("CRDEFLT", 0),
|
||||
make_unique<CAnimImage>("CRSPELL", 0)
|
||||
std::make_unique<CAnimImage>("CRADVNTR", 0),
|
||||
std::make_unique<CAnimImage>("CRCOMBAT", 0),
|
||||
std::make_unique<CAnimImage>("CRDEFLT", 0),
|
||||
std::make_unique<CAnimImage>("CRSPELL", 0)
|
||||
};
|
||||
|
||||
currentCursor = cursors.at(int(ECursor::DEFAULT)).get();
|
||||
currentCursor = cursors.at(static_cast<size_t>(Cursor::Type::DEFAULT)).get();
|
||||
|
||||
buffer = CSDL_Ext::newSurface(40,40);
|
||||
|
||||
SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
changeGraphic(ECursor::ADVENTURE, 0);
|
||||
set(Cursor::Map::POINTER);
|
||||
}
|
||||
|
||||
void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
|
||||
Point CCursorHandler::position() const
|
||||
{
|
||||
return Point(xpos, ypos);
|
||||
}
|
||||
|
||||
void CCursorHandler::changeGraphic(Cursor::Type type, size_t index)
|
||||
{
|
||||
assert(dndObject == nullptr);
|
||||
|
||||
@ -73,7 +83,7 @@ void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
|
||||
{
|
||||
this->type = type;
|
||||
this->frame = index;
|
||||
currentCursor = cursors.at(int(type)).get();
|
||||
currentCursor = cursors.at(static_cast<size_t>(type)).get();
|
||||
currentCursor->setFrame(index);
|
||||
}
|
||||
else if(index != this->frame)
|
||||
@ -85,6 +95,27 @@ void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
|
||||
replaceBuffer(currentCursor);
|
||||
}
|
||||
|
||||
void CCursorHandler::set(Cursor::Default index)
|
||||
{
|
||||
changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
|
||||
}
|
||||
|
||||
void CCursorHandler::set(Cursor::Map index)
|
||||
{
|
||||
changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
|
||||
}
|
||||
|
||||
void CCursorHandler::set(Cursor::Combat index)
|
||||
{
|
||||
changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
|
||||
}
|
||||
|
||||
void CCursorHandler::set(Cursor::Spellcast index)
|
||||
{
|
||||
//Note: this is animated cursor, ignore specified frame and only change type
|
||||
changeGraphic(Cursor::Type::SPELLBOOK, frame);
|
||||
}
|
||||
|
||||
void CCursorHandler::dragAndDropCursor(std::unique_ptr<CAnimImage> object)
|
||||
{
|
||||
dndObject = std::move(object);
|
||||
@ -102,54 +133,57 @@ void CCursorHandler::cursorMove(const int & x, const int & y)
|
||||
|
||||
void CCursorHandler::shiftPos( int &x, int &y )
|
||||
{
|
||||
if(( type == ECursor::COMBAT && frame != ECursor::COMBAT_POINTER) || type == ECursor::SPELLBOOK)
|
||||
if(( type == Cursor::Type::COMBAT && frame != static_cast<size_t>(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK)
|
||||
{
|
||||
x-=16;
|
||||
y-=16;
|
||||
|
||||
// Properly align the melee attack cursors.
|
||||
if (type == ECursor::COMBAT)
|
||||
if (type == Cursor::Type::COMBAT)
|
||||
{
|
||||
switch (frame)
|
||||
switch (static_cast<Cursor::Combat>(frame))
|
||||
{
|
||||
case 7: // Bottom left
|
||||
case Cursor::Combat::HIT_NORTHEAST:
|
||||
x -= 6;
|
||||
y += 16;
|
||||
break;
|
||||
case 8: // Left
|
||||
case Cursor::Combat::HIT_EAST:
|
||||
x -= 16;
|
||||
y += 10;
|
||||
break;
|
||||
case 9: // Top left
|
||||
case Cursor::Combat::HIT_SOUTHEAST:
|
||||
x -= 6;
|
||||
y -= 6;
|
||||
break;
|
||||
case 10: // Top right
|
||||
case Cursor::Combat::HIT_SOUTHWEST:
|
||||
x += 16;
|
||||
y -= 6;
|
||||
break;
|
||||
case 11: // Right
|
||||
case Cursor::Combat::HIT_WEST:
|
||||
x += 16;
|
||||
y += 11;
|
||||
break;
|
||||
case 12: // Bottom right
|
||||
case Cursor::Combat::HIT_NORTHWEST:
|
||||
x += 16;
|
||||
y += 16;
|
||||
break;
|
||||
case 13: // Below
|
||||
case Cursor::Combat::HIT_NORTH:
|
||||
x += 9;
|
||||
y += 16;
|
||||
break;
|
||||
case 14: // Above
|
||||
case Cursor::Combat::HIT_SOUTH:
|
||||
x += 9;
|
||||
y -= 15;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(type == ECursor::ADVENTURE)
|
||||
else if(type == Cursor::Type::ADVENTURE)
|
||||
{
|
||||
if (frame == 0); //to exclude
|
||||
if (frame == 0)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
else if(frame == 2)
|
||||
{
|
||||
x -= 12;
|
||||
@ -221,6 +255,27 @@ void CCursorHandler::render()
|
||||
if(!showing)
|
||||
return;
|
||||
|
||||
if (type == Cursor::Type::SPELLBOOK)
|
||||
{
|
||||
static const float frameDisplayDuration = 0.1f;
|
||||
|
||||
frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
|
||||
size_t newFrame = frame;
|
||||
|
||||
while (frameTime > frameDisplayDuration)
|
||||
{
|
||||
frameTime -= frameDisplayDuration;
|
||||
newFrame++;
|
||||
}
|
||||
|
||||
auto & animation = cursors.at(static_cast<size_t>(type));
|
||||
|
||||
while (newFrame > animation->size())
|
||||
newFrame -= animation->size();
|
||||
|
||||
changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
|
||||
}
|
||||
|
||||
//the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads
|
||||
updateTexture();
|
||||
|
||||
@ -252,15 +307,6 @@ void CCursorHandler::updateTexture()
|
||||
}
|
||||
}
|
||||
|
||||
CCursorHandler::CCursorHandler()
|
||||
: needUpdate(true),
|
||||
buffer(nullptr),
|
||||
cursorLayer(nullptr),
|
||||
showing(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CCursorHandler::~CCursorHandler()
|
||||
{
|
||||
if(buffer)
|
||||
|
@ -12,16 +12,97 @@ class CIntObject;
|
||||
class CAnimImage;
|
||||
struct SDL_Surface;
|
||||
struct SDL_Texture;
|
||||
struct Point;
|
||||
|
||||
namespace ECursor
|
||||
namespace Cursor
|
||||
{
|
||||
enum ECursorTypes { ADVENTURE, COMBAT, DEFAULT, SPELLBOOK };
|
||||
enum class Type {
|
||||
ADVENTURE, // set of various cursors for adventure map
|
||||
COMBAT, // set of various cursors for combat
|
||||
DEFAULT, // default arrow and hourglass cursors
|
||||
SPELLBOOK // animated cursor for spellcasting
|
||||
};
|
||||
|
||||
enum EBattleCursors { COMBAT_BLOCKED, COMBAT_MOVE, COMBAT_FLY, COMBAT_SHOOT,
|
||||
COMBAT_HERO, COMBAT_QUERY, COMBAT_POINTER,
|
||||
//various attack frames
|
||||
COMBAT_SHOOT_PENALTY = 15, COMBAT_SHOOT_CATAPULT, COMBAT_HEAL,
|
||||
COMBAT_SACRIFICE, COMBAT_TELEPORT};
|
||||
enum class Default {
|
||||
POINTER = 0,
|
||||
//ARROW_COPY = 1, // probably unused
|
||||
HOURGLASS = 2,
|
||||
};
|
||||
|
||||
enum class Combat {
|
||||
INVALID = -1,
|
||||
|
||||
BLOCKED = 0,
|
||||
MOVE = 1,
|
||||
FLY = 2,
|
||||
SHOOT = 3,
|
||||
HERO = 4,
|
||||
QUERY = 5,
|
||||
POINTER = 6,
|
||||
HIT_NORTHEAST = 7,
|
||||
HIT_EAST = 8,
|
||||
HIT_SOUTHEAST = 9,
|
||||
HIT_SOUTHWEST = 10,
|
||||
HIT_WEST = 11,
|
||||
HIT_NORTHWEST = 12,
|
||||
HIT_NORTH = 13,
|
||||
HIT_SOUTH = 14,
|
||||
SHOOT_PENALTY = 15,
|
||||
SHOOT_CATAPULT = 16,
|
||||
HEAL = 17,
|
||||
SACRIFICE = 18,
|
||||
TELEPORT = 19
|
||||
};
|
||||
|
||||
enum class Map {
|
||||
POINTER = 0,
|
||||
HOURGLASS = 1,
|
||||
HERO = 2,
|
||||
TOWN = 3,
|
||||
T1_MOVE = 4,
|
||||
T1_ATTACK = 5,
|
||||
T1_SAIL = 6,
|
||||
T1_DISEMBARK = 7,
|
||||
T1_EXCHANGE = 8,
|
||||
T1_VISIT = 9,
|
||||
T2_MOVE = 10,
|
||||
T2_ATTACK = 11,
|
||||
T2_SAIL = 12,
|
||||
T2_DISEMBARK = 13,
|
||||
T2_EXCHANGE = 14,
|
||||
T2_VISIT = 15,
|
||||
T3_MOVE = 16,
|
||||
T3_ATTACK = 17,
|
||||
T3_SAIL = 18,
|
||||
T3_DISEMBARK = 19,
|
||||
T3_EXCHANGE = 20,
|
||||
T3_VISIT = 21,
|
||||
T4_MOVE = 22,
|
||||
T4_ATTACK = 23,
|
||||
T4_SAIL = 24,
|
||||
T4_DISEMBARK = 25,
|
||||
T4_EXCHANGE = 26,
|
||||
T4_VISIT = 27,
|
||||
T1_SAIL_VISIT = 28,
|
||||
T2_SAIL_VISIT = 29,
|
||||
T3_SAIL_VISIT = 30,
|
||||
T4_SAIL_VISIT = 31,
|
||||
SCROLL_NORTH = 32,
|
||||
SCROLL_NORTHEAST = 33,
|
||||
SCROLL_EAST = 34,
|
||||
SCROLL_SOUTHEAST = 35,
|
||||
SCROLL_SOUTH = 36,
|
||||
SCROLL_SOUTHWEST = 37,
|
||||
SCROLL_WEST = 38,
|
||||
SCROLL_NORTHWEST = 39,
|
||||
//POINTER_COPY = 40, // probably unused
|
||||
TELEPORT = 41,
|
||||
SCUTTLE_BOAT = 42
|
||||
};
|
||||
|
||||
enum class Spellcast {
|
||||
SPELL = 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// handles mouse cursor
|
||||
@ -45,19 +126,20 @@ class CCursorHandler final
|
||||
void shiftPos( int &x, int &y );
|
||||
|
||||
void updateTexture();
|
||||
public:
|
||||
|
||||
/// Current cursor
|
||||
Cursor::Type type;
|
||||
size_t frame;
|
||||
float frameTime;
|
||||
|
||||
void changeGraphic(Cursor::Type type, size_t index);
|
||||
|
||||
/// position of cursor
|
||||
int xpos, ypos;
|
||||
|
||||
/// Current cursor
|
||||
ECursor::ECursorTypes type;
|
||||
size_t frame;
|
||||
|
||||
/// inits cursorHandler - run only once, it's not memleak-proof (rev 1333)
|
||||
void initCursor();
|
||||
|
||||
/// changes cursor graphic for type type (0 - adventure, 1 - combat, 2 - default, 3 - spellbook) and frame index (not used for type 3)
|
||||
void changeGraphic(ECursor::ECursorTypes type, int index);
|
||||
public:
|
||||
CCursorHandler();
|
||||
~CCursorHandler();
|
||||
|
||||
/**
|
||||
* Replaces the cursor with a custom image.
|
||||
@ -67,6 +149,27 @@ public:
|
||||
*/
|
||||
void dragAndDropCursor (std::unique_ptr<CAnimImage> image);
|
||||
|
||||
/// Returns current position of the cursor
|
||||
Point position() const;
|
||||
|
||||
/// Changes cursor to specified index
|
||||
void set(Cursor::Default index);
|
||||
void set(Cursor::Map index);
|
||||
void set(Cursor::Combat index);
|
||||
void set(Cursor::Spellcast index);
|
||||
|
||||
/// Returns current index of cursor
|
||||
template<typename Index>
|
||||
Index get()
|
||||
{
|
||||
assert((std::is_same<Index, Cursor::Default>::value )|| type != Cursor::Type::DEFAULT );
|
||||
assert((std::is_same<Index, Cursor::Map>::value )|| type != Cursor::Type::ADVENTURE );
|
||||
assert((std::is_same<Index, Cursor::Combat>::value )|| type != Cursor::Type::COMBAT );
|
||||
assert((std::is_same<Index, Cursor::Spellcast>::value )|| type != Cursor::Type::SPELLBOOK );
|
||||
|
||||
return static_cast<Index>(frame);
|
||||
}
|
||||
|
||||
void render();
|
||||
|
||||
void hide() { showing=false; };
|
||||
@ -77,6 +180,4 @@ public:
|
||||
/// Move cursor to screen center
|
||||
void centerCursor();
|
||||
|
||||
CCursorHandler();
|
||||
~CCursorHandler();
|
||||
};
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../CMT.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../battle/CBattleInterface.h"
|
||||
#include "../battle/BattleInterface.h"
|
||||
|
||||
extern std::queue<SDL_Event> SDLEventsQueue;
|
||||
extern boost::mutex eventsM;
|
||||
@ -121,7 +121,7 @@ void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
|
||||
if(!listInt.empty())
|
||||
listInt.front()->deactivate();
|
||||
listInt.push_front(newInt);
|
||||
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
|
||||
CCS->curh->set(Cursor::Map::POINTER);
|
||||
newInt->activate();
|
||||
objsToBlit.push_back(newInt);
|
||||
totalRedraw();
|
||||
@ -238,7 +238,7 @@ void CGuiHandler::handleCurrentEvent()
|
||||
break;
|
||||
|
||||
case SDLK_F9:
|
||||
//not working yet since CClient::run remain locked after CBattleInterface removal
|
||||
//not working yet since CClient::run remain locked after BattleInterface removal
|
||||
// if(LOCPLINT->battleInt)
|
||||
// {
|
||||
// GH.popInts(1);
|
||||
@ -451,7 +451,7 @@ void CGuiHandler::renderFrame()
|
||||
|
||||
bool acquiredTheLockOnPim = false; //for tracking whether pim mutex locking succeeded
|
||||
while(!terminate_cond->get() && !(acquiredTheLockOnPim = CPlayerInterface::pim->try_lock())) //try acquiring long until it succeeds or we are told to terminate
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(15));
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(1));
|
||||
|
||||
if(acquiredTheLockOnPim)
|
||||
{
|
||||
@ -489,7 +489,7 @@ CGuiHandler::CGuiHandler()
|
||||
statusbar = nullptr;
|
||||
|
||||
// Creates the FPS manager and sets the framerate to 48 which is doubled the value of the original Heroes 3 FPS rate
|
||||
mainFPSmng = new CFramerateManager(48);
|
||||
mainFPSmng = new CFramerateManager(60);
|
||||
//do not init CFramerateManager here --AVS
|
||||
|
||||
terminate_cond = new CondSh<bool>(false);
|
||||
@ -623,8 +623,8 @@ void CFramerateManager::framerateDelay()
|
||||
|
||||
currentTicks = SDL_GetTicks();
|
||||
// recalculate timeElapsed for external calls via getElapsed()
|
||||
// limit it to 1000 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
|
||||
timeElapsed = std::min<ui32>(currentTicks - lastticks, 1000);
|
||||
// limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
|
||||
timeElapsed = std::min<ui32>(currentTicks - lastticks, 100);
|
||||
|
||||
lastticks = SDL_GetTicks();
|
||||
|
||||
|
@ -20,7 +20,7 @@ template <typename T> struct CondSh;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CFramerateManager;
|
||||
class CGStatusBar;
|
||||
class IStatusBar;
|
||||
class CIntObject;
|
||||
class IUpdateable;
|
||||
class IShowActivatable;
|
||||
@ -65,7 +65,7 @@ class CGuiHandler
|
||||
public:
|
||||
CFramerateManager * mainFPSmng; //to keep const framerate
|
||||
std::list<std::shared_ptr<IShowActivatable>> listInt; //list of interfaces - front=foreground; back = background (includes adventure map, window interfaces, all kind of active dialogs, and so on)
|
||||
std::shared_ptr<CGStatusBar> statusbar;
|
||||
std::shared_ptr<IStatusBar> statusbar;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<IShowActivatable>> disposed;
|
||||
@ -165,6 +165,7 @@ struct SSetCaptureState
|
||||
};
|
||||
|
||||
#define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
|
||||
#define OBJ_CONSTRUCTION_TARGETED(obj) SObjectConstruction obj__i(obj)
|
||||
#define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
|
||||
#define OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(actions) SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
|
||||
|
||||
|
@ -267,7 +267,7 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
|
||||
children.push_back(child);
|
||||
child->parent_m = this;
|
||||
if(adjustPosition)
|
||||
child->pos += pos;
|
||||
child->pos += pos.topLeft();
|
||||
|
||||
if (!active && child->active)
|
||||
child->deactivate();
|
||||
@ -289,7 +289,7 @@ void CIntObject::removeChild(CIntObject * child, bool adjustPosition)
|
||||
children -= child;
|
||||
child->parent_m = nullptr;
|
||||
if(adjustPosition)
|
||||
child->pos -= pos;
|
||||
child->pos -= pos.topLeft();
|
||||
}
|
||||
|
||||
void CIntObject::redraw()
|
||||
@ -373,3 +373,6 @@ void WindowBase::close()
|
||||
logGlobal->error("Only top interface must be closed");
|
||||
GH.popInts(1);
|
||||
}
|
||||
|
||||
IStatusBar::~IStatusBar()
|
||||
{}
|
||||
|
@ -165,8 +165,6 @@ public:
|
||||
//request complete redraw of this object
|
||||
void redraw() override;
|
||||
|
||||
enum EAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
|
||||
|
||||
bool isItInLoc(const SDL_Rect &rect, int x, int y);
|
||||
bool isItInLoc(const SDL_Rect &rect, const Point &p);
|
||||
const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
|
||||
@ -217,3 +215,25 @@ public:
|
||||
protected:
|
||||
void close();
|
||||
};
|
||||
|
||||
class IStatusBar
|
||||
{
|
||||
public:
|
||||
virtual ~IStatusBar();
|
||||
|
||||
/// set current text for the status bar
|
||||
virtual void write(const std::string & text) = 0;
|
||||
|
||||
/// remove any current text from the status bar
|
||||
virtual void clear() = 0;
|
||||
|
||||
/// remove text from status bar if current text matches tested text
|
||||
virtual void clearIfMatching(const std::string & testedText) = 0;
|
||||
|
||||
/// enables mode for entering text instead of showing hover text
|
||||
virtual void setEnteringMode(bool on) = 0;
|
||||
|
||||
/// overrides hover text from controls with text entered into in-game console (for chat/cheats)
|
||||
virtual void setEnteredText(const std::string & text) = 0;
|
||||
|
||||
};
|
||||
|
102
client/gui/Canvas.cpp
Normal file
102
client/gui/Canvas.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Canvas.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 "Canvas.h"
|
||||
|
||||
#include "SDL_Extensions.h"
|
||||
#include "Geometries.h"
|
||||
#include "CAnimation.h"
|
||||
|
||||
#include "../Graphics.h"
|
||||
|
||||
Canvas::Canvas(SDL_Surface * surface):
|
||||
surface(surface),
|
||||
renderOffset(0,0)
|
||||
{
|
||||
surface->refcount++;
|
||||
}
|
||||
|
||||
Canvas::Canvas(Canvas & other):
|
||||
surface(other.surface),
|
||||
renderOffset(other.renderOffset)
|
||||
{
|
||||
surface->refcount++;
|
||||
}
|
||||
|
||||
Canvas::Canvas(Canvas & other, const Rect & newClipRect):
|
||||
Canvas(other)
|
||||
{
|
||||
clipRect.emplace();
|
||||
SDL_GetClipRect(surface, clipRect.get_ptr());
|
||||
|
||||
Rect currClipRect = newClipRect + renderOffset;
|
||||
SDL_SetClipRect(surface, &currClipRect);
|
||||
|
||||
renderOffset += newClipRect.topLeft();
|
||||
}
|
||||
|
||||
Canvas::Canvas(const Point & size):
|
||||
renderOffset(0,0)
|
||||
{
|
||||
surface = CSDL_Ext::newSurface(size.x, size.y);
|
||||
}
|
||||
|
||||
Canvas::~Canvas()
|
||||
{
|
||||
if (clipRect)
|
||||
SDL_SetClipRect(surface, clipRect.get_ptr());
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos)
|
||||
{
|
||||
assert(image);
|
||||
if (image)
|
||||
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y);
|
||||
}
|
||||
|
||||
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect)
|
||||
{
|
||||
assert(image);
|
||||
if (image)
|
||||
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect);
|
||||
}
|
||||
|
||||
void Canvas::draw(Canvas & image, const Point & pos)
|
||||
{
|
||||
blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);
|
||||
}
|
||||
|
||||
void Canvas::drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest)
|
||||
{
|
||||
CSDL_Ext::drawLine(surface, renderOffset.x + from.x, renderOffset.y + from.y, renderOffset.x + dest.x, renderOffset.y + dest.y, colorFrom, colorDest);
|
||||
}
|
||||
|
||||
void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::string & text )
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, renderOffset + position);
|
||||
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderOffset + position);
|
||||
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderOffset + position);
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::vector<std::string> & text )
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, renderOffset + position);
|
||||
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderOffset + position);
|
||||
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderOffset + position);
|
||||
}
|
||||
}
|
||||
|
65
client/gui/Canvas.h
Normal file
65
client/gui/Canvas.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Canvas.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 "Geometries.h"
|
||||
|
||||
struct SDL_Color;
|
||||
struct SDL_Surface;
|
||||
class IImage;
|
||||
enum EFonts : int;
|
||||
|
||||
/// Class that represents surface for drawing on
|
||||
class Canvas
|
||||
{
|
||||
/// Target surface
|
||||
SDL_Surface * surface;
|
||||
|
||||
/// Clip rect that was in use on surface originally and needs to be restored on destruction
|
||||
boost::optional<Rect> clipRect;
|
||||
|
||||
/// Current rendering area offset, all rendering operations will be moved into selected area
|
||||
Point renderOffset;
|
||||
|
||||
Canvas & operator = (Canvas & other) = delete;
|
||||
public:
|
||||
|
||||
/// constructs canvas using existing surface. Caller maintains ownership on the surface
|
||||
Canvas(SDL_Surface * surface);
|
||||
|
||||
/// copy contructor
|
||||
Canvas(Canvas & other);
|
||||
|
||||
/// creates canvas that only covers specified subsection of a surface
|
||||
Canvas(Canvas & other, const Rect & clipRect);
|
||||
|
||||
/// constructs canvas of specified size
|
||||
Canvas(const Point & size);
|
||||
|
||||
~Canvas();
|
||||
|
||||
/// renders image onto this canvas at specified position
|
||||
void draw(std::shared_ptr<IImage> image, const Point & pos);
|
||||
|
||||
/// renders section of image bounded by sourceRect at specified position
|
||||
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
|
||||
|
||||
/// renders another canvas onto this canvas
|
||||
void draw(Canvas & image, const Point & pos);
|
||||
|
||||
/// renders continuous, 1-pixel wide line with color gradient
|
||||
void drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest);
|
||||
|
||||
/// renders single line of text with specified parameters
|
||||
void drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::string & text );
|
||||
|
||||
/// renders multiple lines of text with specified parameters
|
||||
void drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::vector<std::string> & text );
|
||||
};
|
162
client/gui/ColorFilter.cpp
Normal file
162
client/gui/ColorFilter.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Canvas.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 "ColorFilter.h"
|
||||
|
||||
#include <SDL2/SDL_pixels.h>
|
||||
|
||||
#include "../../lib/JsonNode.h"
|
||||
|
||||
SDL_Color ColorFilter::shiftColor(const SDL_Color & in) const
|
||||
{
|
||||
int r_out = in.r * r.r + in.g * r.g + in.b * r.b + 255 * r.a;
|
||||
int g_out = in.r * g.r + in.g * g.g + in.b * g.b + 255 * g.a;
|
||||
int b_out = in.r * b.r + in.g * b.g + in.b * b.b + 255 * b.a;
|
||||
int a_out = in.a * a;
|
||||
|
||||
vstd::abetween(r_out, 0, 255);
|
||||
vstd::abetween(g_out, 0, 255);
|
||||
vstd::abetween(b_out, 0, 255);
|
||||
vstd::abetween(a_out, 0, 255);
|
||||
|
||||
return {
|
||||
static_cast<uint8_t>(r_out),
|
||||
static_cast<uint8_t>(g_out),
|
||||
static_cast<uint8_t>(b_out),
|
||||
static_cast<uint8_t>(a_out)
|
||||
};
|
||||
}
|
||||
|
||||
bool ColorFilter::operator != (const ColorFilter & other) const
|
||||
{
|
||||
return !(this->operator==(other));
|
||||
}
|
||||
|
||||
bool ColorFilter::operator == (const ColorFilter & other) const
|
||||
{
|
||||
return
|
||||
r.r == other.r.r && r.g && other.r.g && r.b == other.r.b && r.a == other.r.a &&
|
||||
g.r == other.g.r && g.g && other.g.g && g.b == other.g.b && g.a == other.g.a &&
|
||||
b.r == other.b.r && b.g && other.b.g && b.b == other.b.b && b.a == other.b.a &&
|
||||
a == other.a;
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genEmptyShifter( )
|
||||
{
|
||||
return genAlphaShifter( 1.f);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genAlphaShifter( float alpha )
|
||||
{
|
||||
return genMuxerShifter(
|
||||
{ 1.f, 0.f, 0.f, 0.f },
|
||||
{ 0.f, 1.f, 0.f, 0.f },
|
||||
{ 0.f, 0.f, 1.f, 0.f },
|
||||
alpha);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB )
|
||||
{
|
||||
return genMuxerShifter(
|
||||
{ maxR - minR, 0.f, 0.f, minR },
|
||||
{ 0.f, maxG - minG, 0.f, minG },
|
||||
{ 0.f, 0.f, maxB - minB, minB },
|
||||
1.f);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a )
|
||||
{
|
||||
return ColorFilter(r, g, b, a);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genInterpolated(const ColorFilter & left, const ColorFilter & right, float power)
|
||||
{
|
||||
auto lerpMuxer = [=]( const ChannelMuxer & left, const ChannelMuxer & right ) -> ChannelMuxer
|
||||
{
|
||||
return {
|
||||
vstd::lerp(left.r, right.r, power),
|
||||
vstd::lerp(left.g, right.g, power),
|
||||
vstd::lerp(left.b, right.b, power),
|
||||
vstd::lerp(left.a, right.a, power)
|
||||
};
|
||||
};
|
||||
|
||||
return genMuxerShifter(
|
||||
lerpMuxer(left.r, right.r),
|
||||
lerpMuxer(left.g, right.g),
|
||||
lerpMuxer(left.b, right.b),
|
||||
vstd::lerp(left.a, right.a, power)
|
||||
);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genCombined(const ColorFilter & left, const ColorFilter & right)
|
||||
{
|
||||
// matrix multiplication
|
||||
ChannelMuxer r{
|
||||
left.r.r * right.r.r + left.g.r * right.r.g + left.b.r * right.r.b,
|
||||
left.r.g * right.r.r + left.g.g * right.r.g + left.b.g * right.r.b,
|
||||
left.r.b * right.r.r + left.g.b * right.r.g + left.b.b * right.r.b,
|
||||
left.r.a * right.r.r + left.g.a * right.r.g + left.b.a * right.r.b + 1.f * right.r.a,
|
||||
};
|
||||
|
||||
ChannelMuxer g{
|
||||
left.r.r * right.g.r + left.g.r * right.g.g + left.b.r * right.g.b,
|
||||
left.r.g * right.g.r + left.g.g * right.g.g + left.b.g * right.g.b,
|
||||
left.r.b * right.g.r + left.g.b * right.g.g + left.b.b * right.g.b,
|
||||
left.r.a * right.g.r + left.g.a * right.g.g + left.b.a * right.g.b + 1.f * right.g.a,
|
||||
};
|
||||
|
||||
ChannelMuxer b{
|
||||
left.r.r * right.b.r + left.g.r * right.b.g + left.b.r * right.b.b,
|
||||
left.r.g * right.b.r + left.g.g * right.b.g + left.b.g * right.b.b,
|
||||
left.r.b * right.b.r + left.g.b * right.b.g + left.b.b * right.b.b,
|
||||
left.r.a * right.b.r + left.g.a * right.b.g + left.b.a * right.b.b + 1.f * right.b.a,
|
||||
};
|
||||
|
||||
float a = left.a * right.a;
|
||||
return genMuxerShifter(r,g,b,a);
|
||||
}
|
||||
|
||||
ColorFilter ColorFilter::genFromJson(const JsonNode & entry)
|
||||
{
|
||||
ChannelMuxer r{ 1.f, 0.f, 0.f, 0.f };
|
||||
ChannelMuxer g{ 0.f, 1.f, 0.f, 0.f };
|
||||
ChannelMuxer b{ 0.f, 0.f, 1.f, 0.f };
|
||||
float a{ 1.0};
|
||||
|
||||
if (!entry["red"].isNull())
|
||||
{
|
||||
r.r = entry["red"].Vector()[0].Float();
|
||||
r.g = entry["red"].Vector()[1].Float();
|
||||
r.b = entry["red"].Vector()[2].Float();
|
||||
r.a = entry["red"].Vector()[3].Float();
|
||||
}
|
||||
|
||||
if (!entry["red"].isNull())
|
||||
{
|
||||
g.r = entry["green"].Vector()[0].Float();
|
||||
g.g = entry["green"].Vector()[1].Float();
|
||||
g.b = entry["green"].Vector()[2].Float();
|
||||
g.a = entry["green"].Vector()[3].Float();
|
||||
}
|
||||
|
||||
if (!entry["red"].isNull())
|
||||
{
|
||||
b.r = entry["blue"].Vector()[0].Float();
|
||||
b.g = entry["blue"].Vector()[1].Float();
|
||||
b.b = entry["blue"].Vector()[2].Float();
|
||||
b.a = entry["blue"].Vector()[3].Float();
|
||||
}
|
||||
|
||||
if (!entry["alpha"].isNull())
|
||||
a = entry["alpha"].Float();
|
||||
|
||||
return genMuxerShifter(r,g,b,a);
|
||||
}
|
66
client/gui/ColorFilter.h
Normal file
66
client/gui/ColorFilter.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* ColorFilter.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
|
||||
|
||||
struct SDL_Color;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class JsonNode;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
/// Base class for applying palette transformation on images
|
||||
class ColorFilter
|
||||
{
|
||||
struct ChannelMuxer {
|
||||
float r, g, b, a;
|
||||
};
|
||||
|
||||
ChannelMuxer r;
|
||||
ChannelMuxer g;
|
||||
ChannelMuxer b;
|
||||
float a;
|
||||
|
||||
ColorFilter(ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a):
|
||||
r(r), g(g), b(b), a(a)
|
||||
{}
|
||||
public:
|
||||
SDL_Color shiftColor(const SDL_Color & in) const;
|
||||
|
||||
bool operator == (const ColorFilter & other) const;
|
||||
bool operator != (const ColorFilter & other) const;
|
||||
|
||||
/// Generates empty object that has no effect on image
|
||||
static ColorFilter genEmptyShifter();
|
||||
|
||||
/// Generates object that changes alpha (transparency) of the image
|
||||
static ColorFilter genAlphaShifter( float alpha );
|
||||
|
||||
/// Generates object that transforms each channel independently
|
||||
static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB );
|
||||
|
||||
/// Generates object that performs arbitrary mixing between any channels
|
||||
static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a );
|
||||
|
||||
/// Combines 2 mixers into a single object
|
||||
static ColorFilter genCombined(const ColorFilter & left, const ColorFilter & right);
|
||||
|
||||
/// Scales down strength of a shifter to a specified factor
|
||||
static ColorFilter genInterpolated(const ColorFilter & left, const ColorFilter & right, float power);
|
||||
|
||||
/// Generates object using supplied Json config
|
||||
static ColorFilter genFromJson(const JsonNode & entry);
|
||||
};
|
||||
|
||||
struct ColorMuxerEffect
|
||||
{
|
||||
std::vector<ColorFilter> filters;
|
||||
std::vector<float> timePoints;
|
||||
};
|
@ -11,6 +11,11 @@
|
||||
#include "Geometries.h"
|
||||
#include "../CMT.h"
|
||||
#include <SDL_events.h>
|
||||
#include "../../lib/int3.h"
|
||||
|
||||
Point::Point(const int3 &a)
|
||||
:x(a.x),y(a.y)
|
||||
{}
|
||||
|
||||
Point::Point(const SDL_MouseMotionEvent &a)
|
||||
:x(a.x),y(a.y)
|
||||
@ -21,7 +26,7 @@ Rect Rect::createCentered( int w, int h )
|
||||
return Rect(screen->w/2 - w/2, screen->h/2 - h/2, w, h);
|
||||
}
|
||||
|
||||
Rect Rect::around(const Rect &r, int width) /*creates rect around another */
|
||||
Rect Rect::around(const Rect &r, int width)
|
||||
{
|
||||
return Rect(r.x - width, r.y - width, r.w + width * 2, r.h + width * 2);
|
||||
}
|
||||
@ -30,3 +35,4 @@ Rect Rect::centerIn(const Rect &r)
|
||||
{
|
||||
return Rect(r.x + (r.w - w) / 2, r.y + (r.h - h) / 2, w, h);
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,16 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <SDL_video.h>
|
||||
#include "../../lib/int3.h"
|
||||
#include <SDL2/SDL_rect.h>
|
||||
|
||||
enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
|
||||
|
||||
struct SDL_MouseMotionEvent;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
class int3;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
// A point with x/y coordinate, used mostly for graphic rendering
|
||||
struct Point
|
||||
{
|
||||
@ -24,13 +29,14 @@ struct Point
|
||||
{
|
||||
x = y = 0;
|
||||
};
|
||||
|
||||
Point(int X, int Y)
|
||||
:x(X),y(Y)
|
||||
{};
|
||||
Point(const int3 &a)
|
||||
:x(a.x),y(a.y)
|
||||
{}
|
||||
Point(const SDL_MouseMotionEvent &a);
|
||||
|
||||
Point(const int3 &a);
|
||||
|
||||
explicit Point(const SDL_MouseMotionEvent &a);
|
||||
|
||||
template<typename T>
|
||||
Point operator+(const T &b) const
|
||||
@ -71,10 +77,7 @@ struct Point
|
||||
y -= b.y;
|
||||
return *this;
|
||||
}
|
||||
bool operator<(const Point &b) const //product order
|
||||
{
|
||||
return x < b.x && y < b.y;
|
||||
}
|
||||
|
||||
template<typename T> Point& operator=(const T &t)
|
||||
{
|
||||
x = t.x;
|
||||
@ -94,7 +97,7 @@ struct Point
|
||||
/// Rectangle class, which have a position and a size
|
||||
struct Rect : public SDL_Rect
|
||||
{
|
||||
Rect()//default c-tor
|
||||
Rect()
|
||||
{
|
||||
x = y = w = h = -1;
|
||||
}
|
||||
@ -119,60 +122,59 @@ struct Rect : public SDL_Rect
|
||||
w = r.w;
|
||||
h = r.h;
|
||||
}
|
||||
Rect(const Rect& r) : Rect(static_cast<const SDL_Rect&>(r))
|
||||
{}
|
||||
explicit Rect(const SDL_Surface * const &surf)
|
||||
{
|
||||
x = y = 0;
|
||||
w = surf->w;
|
||||
h = surf->h;
|
||||
}
|
||||
Rect(const Rect& r) = default;
|
||||
|
||||
Rect centerIn(const Rect &r);
|
||||
static Rect createCentered(int w, int h);
|
||||
static Rect around(const Rect &r, int width = 1); //creates rect around another
|
||||
static Rect around(const Rect &r, int width = 1);
|
||||
|
||||
bool isIn(int qx, int qy) const //determines if given point lies inside rect
|
||||
bool isIn(int qx, int qy) const
|
||||
{
|
||||
if (qx > x && qx<x+w && qy>y && qy<y+h)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool isIn(const Point & q) const //determines if given point lies inside rect
|
||||
bool isIn(const Point & q) const
|
||||
{
|
||||
return isIn(q.x,q.y);
|
||||
}
|
||||
Point topLeft() const //top left corner of this rect
|
||||
Point topLeft() const
|
||||
{
|
||||
return Point(x,y);
|
||||
}
|
||||
Point topRight() const //top right corner of this rect
|
||||
Point topRight() const
|
||||
{
|
||||
return Point(x+w,y);
|
||||
}
|
||||
Point bottomLeft() const //bottom left corner of this rect
|
||||
Point bottomLeft() const
|
||||
{
|
||||
return Point(x,y+h);
|
||||
}
|
||||
Point bottomRight() const //bottom right corner of this rect
|
||||
Point bottomRight() const
|
||||
{
|
||||
return Point(x+w,y+h);
|
||||
}
|
||||
Rect operator+(const Rect &p) const //moves this rect by p's rect position
|
||||
Point center() const
|
||||
{
|
||||
return Point(x+w/2,y+h/2);
|
||||
}
|
||||
Point dimensions() const
|
||||
{
|
||||
return Point(w,h);
|
||||
}
|
||||
|
||||
void moveTo(const Point & dest)
|
||||
{
|
||||
x = dest.x;
|
||||
y = dest.y;
|
||||
}
|
||||
|
||||
Rect operator+(const Point &p) const
|
||||
{
|
||||
return Rect(x+p.x,y+p.y,w,h);
|
||||
}
|
||||
Rect operator+(const Point &p) const //moves this rect by p's point position
|
||||
{
|
||||
return Rect(x+p.x,y+p.y,w,h);
|
||||
}
|
||||
Rect& operator=(const Point &p) //assignment operator
|
||||
{
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator=(const Rect &p) //assignment operator
|
||||
|
||||
Rect& operator=(const Rect &p)
|
||||
{
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
@ -180,34 +182,21 @@ struct Rect : public SDL_Rect
|
||||
h = p.h;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator+=(const Rect &p) //works as operator+
|
||||
|
||||
Rect& operator+=(const Point &p)
|
||||
{
|
||||
x += p.x;
|
||||
y += p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator+=(const Point &p) //works as operator+
|
||||
{
|
||||
x += p.x;
|
||||
y += p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator-=(const Rect &p) //works as operator+
|
||||
|
||||
Rect& operator-=(const Point &p)
|
||||
{
|
||||
x -= p.x;
|
||||
y -= p.y;
|
||||
return *this;
|
||||
}
|
||||
Rect& operator-=(const Point &p) //works as operator+
|
||||
{
|
||||
x -= p.x;
|
||||
y -= p.y;
|
||||
return *this;
|
||||
}
|
||||
template<typename T> Rect operator-(const T &t)
|
||||
{
|
||||
return Rect(x - t.x, y - t.y, w, h);
|
||||
}
|
||||
|
||||
Rect operator&(const Rect &p) const //rect intersection
|
||||
{
|
||||
bool intersect = true;
|
||||
|
425
client/gui/InterfaceObjectConfigurable.cpp
Normal file
425
client/gui/InterfaceObjectConfigurable.cpp
Normal file
@ -0,0 +1,425 @@
|
||||
/*
|
||||
* InterfaceBuilder.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 "InterfaceObjectConfigurable.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../widgets/CComponent.h"
|
||||
#include "../widgets/Buttons.h"
|
||||
#include "../widgets/MiscWidgets.h"
|
||||
#include "../widgets/ObjectLists.h"
|
||||
#include "../widgets/TextControls.h"
|
||||
#include "../windows/GUIClasses.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
|
||||
static std::map<std::string, int> KeycodeMap{
|
||||
{"up", SDLK_UP},
|
||||
{"down", SDLK_DOWN},
|
||||
{"left", SDLK_LEFT},
|
||||
{"right", SDLK_RIGHT},
|
||||
{"space", SDLK_SPACE},
|
||||
{"enter", SDLK_RETURN}
|
||||
};
|
||||
|
||||
|
||||
InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
|
||||
InterfaceObjectConfigurable(used, offset)
|
||||
{
|
||||
build(config);
|
||||
}
|
||||
|
||||
InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset):
|
||||
CIntObject(used, offset)
|
||||
{
|
||||
REGISTER_BUILDER("picture", &InterfaceObjectConfigurable::buildPicture);
|
||||
REGISTER_BUILDER("image", &InterfaceObjectConfigurable::buildImage);
|
||||
REGISTER_BUILDER("texture", &InterfaceObjectConfigurable::buildTexture);
|
||||
REGISTER_BUILDER("animation", &InterfaceObjectConfigurable::buildAnimation);
|
||||
REGISTER_BUILDER("label", &InterfaceObjectConfigurable::buildLabel);
|
||||
REGISTER_BUILDER("toggleGroup", &InterfaceObjectConfigurable::buildToggleGroup);
|
||||
REGISTER_BUILDER("toggleButton", &InterfaceObjectConfigurable::buildToggleButton);
|
||||
REGISTER_BUILDER("button", &InterfaceObjectConfigurable::buildButton);
|
||||
REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup);
|
||||
REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider);
|
||||
}
|
||||
|
||||
void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f)
|
||||
{
|
||||
builders[type] = f;
|
||||
}
|
||||
|
||||
void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function<void(int)> callback)
|
||||
{
|
||||
callbacks[callbackName] = callback;
|
||||
}
|
||||
|
||||
void InterfaceObjectConfigurable::deleteWidget(const std::string & name)
|
||||
{
|
||||
auto iter = widgets.find(name);
|
||||
if(iter != widgets.end())
|
||||
widgets.erase(iter);
|
||||
}
|
||||
|
||||
void InterfaceObjectConfigurable::build(const JsonNode &config)
|
||||
{
|
||||
OBJ_CONSTRUCTION;
|
||||
logGlobal->debug("Building configurable interface object");
|
||||
auto * items = &config;
|
||||
|
||||
if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
|
||||
{
|
||||
for(auto & item : config["variables"].Struct())
|
||||
{
|
||||
logGlobal->debug("Read variable named %s", item.first);
|
||||
variables[item.first] = item.second;
|
||||
}
|
||||
|
||||
items = &config["items"];
|
||||
}
|
||||
|
||||
const std::string unnamedObjectPrefix = "__widget_";
|
||||
for(const auto & item : items->Vector())
|
||||
{
|
||||
std::string name = item["name"].isNull()
|
||||
? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
|
||||
: item["name"].String();
|
||||
logGlobal->debug("Building widget with name %s", name);
|
||||
widgets[name] = buildWidget(item);
|
||||
}
|
||||
}
|
||||
|
||||
std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
|
||||
{
|
||||
if(config.isNull())
|
||||
return "";
|
||||
|
||||
std::string s = config.String();
|
||||
logGlobal->debug("Reading text from translations by key: %s", s);
|
||||
return CGI->generaltexth->translate(s);
|
||||
}
|
||||
|
||||
Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
|
||||
{
|
||||
Point p;
|
||||
logGlobal->debug("Reading point");
|
||||
p.x = config["x"].Integer();
|
||||
p.y = config["y"].Integer();
|
||||
return p;
|
||||
}
|
||||
|
||||
Rect InterfaceObjectConfigurable::readRect(const JsonNode & config) const
|
||||
{
|
||||
Rect p;
|
||||
logGlobal->debug("Reading rect");
|
||||
p.x = config["x"].Integer();
|
||||
p.y = config["y"].Integer();
|
||||
p.w = config["w"].Integer();
|
||||
p.h = config["h"].Integer();
|
||||
return p;
|
||||
}
|
||||
|
||||
ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Reading text alignment");
|
||||
if(!config.isNull())
|
||||
{
|
||||
if(config.String() == "center")
|
||||
return ETextAlignment::CENTER;
|
||||
if(config.String() == "left")
|
||||
return ETextAlignment::TOPLEFT;
|
||||
if(config.String() == "right")
|
||||
return ETextAlignment::BOTTOMRIGHT;
|
||||
}
|
||||
logGlobal->debug("Uknown text alignment attribute");
|
||||
return ETextAlignment::CENTER;
|
||||
}
|
||||
|
||||
SDL_Color InterfaceObjectConfigurable::readColor(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Reading color");
|
||||
if(!config.isNull())
|
||||
{
|
||||
if(config.String() == "yellow")
|
||||
return Colors::YELLOW;
|
||||
if(config.String() == "white")
|
||||
return Colors::WHITE;
|
||||
if(config.String() == "gold")
|
||||
return Colors::METALLIC_GOLD;
|
||||
if(config.String() == "green")
|
||||
return Colors::GREEN;
|
||||
if(config.String() == "orange")
|
||||
return Colors::ORANGE;
|
||||
if(config.String() == "bright-yellow")
|
||||
return Colors::BRIGHT_YELLOW;
|
||||
}
|
||||
logGlobal->debug("Uknown color attribute");
|
||||
return Colors::DEFAULT_KEY_COLOR;
|
||||
|
||||
}
|
||||
EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Reading font");
|
||||
if(!config.isNull())
|
||||
{
|
||||
if(config.String() == "big")
|
||||
return EFonts::FONT_BIG;
|
||||
if(config.String() == "medium")
|
||||
return EFonts::FONT_MEDIUM;
|
||||
if(config.String() == "small")
|
||||
return EFonts::FONT_SMALL;
|
||||
if(config.String() == "tiny")
|
||||
return EFonts::FONT_TINY;
|
||||
}
|
||||
logGlobal->debug("Uknown font attribute");
|
||||
return EFonts::FONT_TIMES;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Reading hint text");
|
||||
std::pair<std::string, std::string> result;
|
||||
if(!config.isNull())
|
||||
{
|
||||
if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
|
||||
{
|
||||
result.first = readText(config["hover"]);
|
||||
result.second = readText(config["help"]);
|
||||
return result;
|
||||
}
|
||||
if(config.getType() == JsonNode::JsonType::DATA_STRING)
|
||||
{
|
||||
logGlobal->debug("Reading hint text (help) from generaltext handler:%sd", config.String());
|
||||
result.first = CGI->generaltexth->translate( config.String(), "hover");
|
||||
result.second = CGI->generaltexth->translate( config.String(), "help");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int InterfaceObjectConfigurable::readKeycode(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Reading keycode");
|
||||
if(config.getType() == JsonNode::JsonType::DATA_INTEGER)
|
||||
return config.Integer();
|
||||
|
||||
if(config.getType() == JsonNode::JsonType::DATA_STRING)
|
||||
{
|
||||
auto s = config.String();
|
||||
if(s.size() == 1) //keyboard symbol
|
||||
return s[0];
|
||||
return KeycodeMap[s];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CPicture");
|
||||
auto image = config["image"].String();
|
||||
auto position = readPosition(config["position"]);
|
||||
auto pic = std::make_shared<CPicture>(image, position.x, position.y);
|
||||
if(!config["visible"].isNull())
|
||||
pic->visible = config["visible"].Bool();
|
||||
|
||||
if ( config["playerColored"].Bool() && LOCPLINT)
|
||||
pic->colorize(LOCPLINT->playerID);
|
||||
return pic;
|
||||
}
|
||||
|
||||
std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CLabel");
|
||||
auto font = readFont(config["font"]);
|
||||
auto alignment = readTextAlignment(config["alignment"]);
|
||||
auto color = readColor(config["color"]);
|
||||
auto text = readText(config["text"]);
|
||||
auto position = readPosition(config["position"]);
|
||||
return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text);
|
||||
}
|
||||
|
||||
std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CToggleGroup");
|
||||
auto position = readPosition(config["position"]);
|
||||
auto group = std::make_shared<CToggleGroup>(0);
|
||||
group->pos += position;
|
||||
if(!config["items"].isNull())
|
||||
{
|
||||
OBJ_CONSTRUCTION_TARGETED(group.get());
|
||||
int itemIdx = -1;
|
||||
for(const auto & item : config["items"].Vector())
|
||||
{
|
||||
itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
|
||||
group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
|
||||
}
|
||||
}
|
||||
if(!config["selected"].isNull())
|
||||
group->setSelected(config["selected"].Integer());
|
||||
if(!config["callback"].isNull())
|
||||
group->addCallback(callbacks.at(config["callback"].String()));
|
||||
return group;
|
||||
}
|
||||
|
||||
std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CToggleButton");
|
||||
auto position = readPosition(config["position"]);
|
||||
auto image = config["image"].String();
|
||||
auto help = readHintText(config["help"]);
|
||||
auto button = std::make_shared<CToggleButton>(position, image, help);
|
||||
if(!config["selected"].isNull())
|
||||
button->setSelected(config["selected"].Bool());
|
||||
if(!config["imageOrder"].isNull())
|
||||
{
|
||||
auto imgOrder = config["imageOrder"].Vector();
|
||||
assert(imgOrder.size() >= 4);
|
||||
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
|
||||
}
|
||||
if(!config["callback"].isNull())
|
||||
button->addCallback(callbacks.at(config["callback"].String()));
|
||||
return button;
|
||||
}
|
||||
|
||||
std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CButton");
|
||||
auto position = readPosition(config["position"]);
|
||||
auto image = config["image"].String();
|
||||
auto help = readHintText(config["help"]);
|
||||
auto button = std::make_shared<CButton>(position, image, help);
|
||||
if(!config["items"].isNull())
|
||||
{
|
||||
for(const auto & item : config["items"].Vector())
|
||||
{
|
||||
button->addOverlay(buildWidget(item));
|
||||
}
|
||||
}
|
||||
if(!config["imageOrder"].isNull())
|
||||
{
|
||||
auto imgOrder = config["imageOrder"].Vector();
|
||||
assert(imgOrder.size() >= 4);
|
||||
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
|
||||
}
|
||||
if(!config["callback"].isNull())
|
||||
button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0));
|
||||
if(!config["hotkey"].isNull())
|
||||
{
|
||||
if(config["hotkey"].getType() == JsonNode::JsonType::DATA_VECTOR)
|
||||
{
|
||||
for(auto k : config["hotkey"].Vector())
|
||||
button->assignedKeys.insert(readKeycode(k));
|
||||
}
|
||||
else
|
||||
button->assignedKeys.insert(readKeycode(config["hotkey"]));
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CLabelGroup");
|
||||
auto font = readFont(config["font"]);
|
||||
auto alignment = readTextAlignment(config["alignment"]);
|
||||
auto color = readColor(config["color"]);
|
||||
auto group = std::make_shared<CLabelGroup>(font, alignment, color);
|
||||
if(!config["items"].isNull())
|
||||
{
|
||||
for(const auto & item : config["items"].Vector())
|
||||
{
|
||||
auto position = readPosition(item["position"]);
|
||||
auto text = readText(item["text"]);
|
||||
group->add(position.x, position.y, text);
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CSlider");
|
||||
auto position = readPosition(config["position"]);
|
||||
int length = config["size"].Integer();
|
||||
auto style = config["style"].String() == "brown" ? CSlider::BROWN : CSlider::BLUE;
|
||||
auto itemsVisible = config["itemsVisible"].Integer();
|
||||
auto itemsTotal = config["itemsTotal"].Integer();
|
||||
auto value = config["selected"].Integer();
|
||||
bool horizontal = config["orientation"].String() == "horizontal";
|
||||
return std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
|
||||
}
|
||||
|
||||
std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CAnimImage");
|
||||
auto position = readPosition(config["position"]);
|
||||
auto image = config["image"].String();
|
||||
int group = config["group"].isNull() ? 0 : config["group"].Integer();
|
||||
int frame = config["frame"].isNull() ? 0 : config["frame"].Integer();
|
||||
return std::make_shared<CAnimImage>(image, frame, group, position.x, position.y);
|
||||
}
|
||||
|
||||
std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CFilledTexture");
|
||||
auto image = config["image"].String();
|
||||
auto rect = readRect(config["rect"]);
|
||||
return std::make_shared<CFilledTexture>(image, rect);
|
||||
}
|
||||
|
||||
std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const
|
||||
{
|
||||
logGlobal->debug("Building widget CShowableAnim");
|
||||
auto position = readPosition(config["position"]);
|
||||
auto image = config["image"].String();
|
||||
ui8 flags = 0;
|
||||
if(!config["repeat"].Bool())
|
||||
flags |= CShowableAnim::EFlags::PLAY_ONCE;
|
||||
|
||||
int group = config["group"].isNull() ? 0 : config["group"].Integer();
|
||||
auto anim = std::make_shared<CShowableAnim>(position.x, position.y, image, flags, 4, group);
|
||||
if(!config["alpha"].isNull())
|
||||
anim->setAlpha(config["alpha"].Integer());
|
||||
if(!config["callback"].isNull())
|
||||
anim->callback = std::bind(callbacks.at(config["callback"].String()), 0);
|
||||
if(!config["frames"].isNull())
|
||||
{
|
||||
auto b = config["frames"]["start"].Integer();
|
||||
auto e = config["frames"]["end"].Integer();
|
||||
anim->set(group, b, e);
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode config) const
|
||||
{
|
||||
assert(!config.isNull());
|
||||
logGlobal->debug("Building widget from config");
|
||||
//overrides from variables
|
||||
for(auto & item : config["overrides"].Struct())
|
||||
{
|
||||
logGlobal->debug("Config attribute %s was overriden by variable %s", item.first, item.second.String());
|
||||
config[item.first] = variables[item.second.String()];
|
||||
}
|
||||
|
||||
auto type = config["type"].String();
|
||||
auto buildIterator = builders.find(type);
|
||||
if(buildIterator != builders.end())
|
||||
return (buildIterator->second)(config);
|
||||
|
||||
logGlobal->error("Builder with type %s is not registered", type);
|
||||
return nullptr;
|
||||
}
|
89
client/gui/InterfaceObjectConfigurable.h
Normal file
89
client/gui/InterfaceObjectConfigurable.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* InterfaceBuilder.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 "CIntObject.h"
|
||||
|
||||
#include "../../lib/JsonNode.h"
|
||||
|
||||
class CPicture;
|
||||
class CLabel;
|
||||
class CToggleGroup;
|
||||
class CToggleButton;
|
||||
class CButton;
|
||||
class CLabelGroup;
|
||||
class CSlider;
|
||||
class CAnimImage;
|
||||
class CShowableAnim;
|
||||
class CFilledTexture;
|
||||
|
||||
#define REGISTER_BUILDER(type, method) registerBuilder(type, std::bind(method, this, std::placeholders::_1))
|
||||
|
||||
class InterfaceObjectConfigurable: public CIntObject
|
||||
{
|
||||
public:
|
||||
InterfaceObjectConfigurable(int used=0, Point offset=Point());
|
||||
InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
|
||||
|
||||
protected:
|
||||
|
||||
using BuilderFunction = std::function<std::shared_ptr<CIntObject>(const JsonNode &)>;
|
||||
void registerBuilder(const std::string &, BuilderFunction);
|
||||
|
||||
//must be called after adding callbacks
|
||||
void build(const JsonNode & config);
|
||||
|
||||
void addCallback(const std::string & callbackName, std::function<void(int)> callback);
|
||||
JsonNode variables;
|
||||
|
||||
template<class T>
|
||||
const std::shared_ptr<T> widget(const std::string & name) const
|
||||
{
|
||||
auto iter = widgets.find(name);
|
||||
if(iter == widgets.end())
|
||||
return nullptr;
|
||||
return std::dynamic_pointer_cast<T>(iter->second);
|
||||
}
|
||||
|
||||
void deleteWidget(const std::string & name);
|
||||
|
||||
//basic serializers
|
||||
Point readPosition(const JsonNode &) const;
|
||||
Rect readRect(const JsonNode &) const;
|
||||
ETextAlignment readTextAlignment(const JsonNode &) const;
|
||||
SDL_Color readColor(const JsonNode &) const;
|
||||
EFonts readFont(const JsonNode &) const;
|
||||
std::string readText(const JsonNode &) const;
|
||||
std::pair<std::string, std::string> readHintText(const JsonNode &) const;
|
||||
int readKeycode(const JsonNode &) const;
|
||||
|
||||
//basic widgets
|
||||
std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
|
||||
std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;
|
||||
std::shared_ptr<CToggleGroup> buildToggleGroup(const JsonNode &) const;
|
||||
std::shared_ptr<CToggleButton> buildToggleButton(const JsonNode &) const;
|
||||
std::shared_ptr<CButton> buildButton(const JsonNode &) const;
|
||||
std::shared_ptr<CLabelGroup> buildLabelGroup(const JsonNode &) const;
|
||||
std::shared_ptr<CSlider> buildSlider(const JsonNode &) const;
|
||||
std::shared_ptr<CAnimImage> buildImage(const JsonNode &) const;
|
||||
std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
|
||||
std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
|
||||
|
||||
//composite widgets
|
||||
std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
|
||||
|
||||
private:
|
||||
|
||||
int unnamedObjectId = 0;
|
||||
std::map<std::string, BuilderFunction> builders;
|
||||
std::map<std::string, std::shared_ptr<CIntObject>> widgets;
|
||||
std::map<std::string, std::function<void(int)>> callbacks;
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* NotificationHandler.cpp, part of VCMI engine
|
||||
* NotificationHandler.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user