1
0
mirror of https://github.com/vcmi/vcmi.git synced 2026-05-16 09:28:24 +02:00

Spells configuration version 2 (effect-based)

* Indirect spell effects loading
* Json serializer improvements
* spell->canBeCastAt do not allow useless cast for any spell
* Added proxy caster class for spell-created obstacles
* Handle damage from spell-created obstacles inside mechanics
* Experimental GameState integration/regression tests
* Ignore mod settings and load only "vcmi" mod when running tests
* fixed https://bugs.vcmi.eu/view.php?id=2765 (with tests)
* Huge improvements of BattleAI regarding spell casts
* AI can cast almost any combat spell except TELEPORT, SACRIFICE and obstacle placement spells.
* Possible fix for https://bugs.vcmi.eu/view.php?id=1811
* CStack factored out to several classes
* [Battle] Allowed RETURN_AFTER_STRIKE effect on server side to be optional
* [Battle] Allowed BattleAction have multiple destinations
* [Spells] Converted limit|immunity to target condition
* [Spells] Use partial configuration reload for backward compatibility handling
* [Tests] Started tests for CUnitState
* Partial fixes of fire shield effect
* [Battle] Do HP calculations in 64 bits
* [BattleAI] Use threading for spell cast evaluation
* [BattleAI] Made AI be able to evaluate modified turn order (on hypothetical battle state)
* Implemented https://bugs.vcmi.eu/view.php?id=2811
* plug rare freeze when hypnotized unit shots vertically
* Correctly apply ONLY_MELEE_FIGHT / ONLY_DISTANCE_FIGHT for unit damage, attack & defense
* [BattleAI] Try to not waste a cast if battle is actually won already
* Extended JsonSerializeFormat API
* fixed https://bugs.vcmi.eu/view.php?id=2847
* Any unit effect can be now chained (not only damage like Chain Lightning)
** only damage effect for now actually uses "chainFactor"
* Possible quick fix for https://bugs.vcmi.eu/view.php?id=2860
This commit is contained in:
AlexVinS
2017-07-20 07:08:49 +03:00
parent ff2d01a03d
commit 0b70baa95e
256 changed files with 20904 additions and 7964 deletions
+64 -29
View File
@@ -10,53 +10,88 @@
#include "StdInc.h" #include "StdInc.h"
#include "AttackPossibility.h" #include "AttackPossibility.h"
int AttackPossibility::damageDiff() const AttackPossibility::AttackPossibility(BattleHex tile_, const BattleAttackInfo & attack_)
: tile(tile_),
attack(attack_)
{ {
if (!priorities)
priorities = new Priorities();
const auto dealtDmgValue = priorities->stackEvaluator(enemy) * damageDealt;
const auto receivedDmgValue = priorities->stackEvaluator(attack.attacker) * damageReceived;
return dealtDmgValue - receivedDmgValue;
} }
int AttackPossibility::attackValue() const
int64_t AttackPossibility::damageDiff() const
{
//TODO: use target priority from HypotheticBattle
const auto dealtDmgValue = damageDealt;
const auto receivedDmgValue = damageReceived;
int64_t diff = 0;
//friendly fire or not
if(attack.attacker->unitSide() == attack.defender->unitSide())
diff = -dealtDmgValue - receivedDmgValue;
else
diff = dealtDmgValue - receivedDmgValue;
//mind control
auto actualSide = getCbc()->playerToSide(getCbc()->battleGetOwner(attack.attacker));
if(actualSide && actualSide.get() != attack.attacker->unitSide())
diff = -diff;
return diff;
}
int64_t AttackPossibility::attackValue() const
{ {
return damageDiff() + tacticImpact; return damageDiff() + tacticImpact;
} }
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex) AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex)
{ {
auto attacker = AttackInfo.attacker; const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
auto enemy = AttackInfo.defender; static const auto selectorBlocksRetaliation = Selector::type(Bonus::BLOCKS_RETALIATION);
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks.available()); const bool counterAttacksBlocked = attackInfo.attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
const bool counterAttacksBlocked = attacker->hasBonusOfType(Bonus::BLOCKS_RETALIATION) || enemy->hasBonusOfType(Bonus::NO_RETALIATION);
const int totalAttacks = 1 + AttackInfo.attackerBonuses->getBonuses(Selector::type(Bonus::ADDITIONAL_ATTACK), (Selector::effectRange (Bonus::NO_LIMIT).Or(Selector::effectRange(Bonus::ONLY_MELEE_FIGHT))))->totalValue();
AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0}; AttackPossibility ap(hex, attackInfo);
auto curBai = AttackInfo; //we'll modify here the stack counts ap.attackerState = attackInfo.attacker->acquireState();
for(int i = 0; i < totalAttacks; i++)
const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting);
if(!attackInfo.shooting)
ap.attackerState->setPosition(hex);
auto defenderState = attackInfo.defender->acquireState();
ap.affectedUnits.push_back(defenderState);
for(int i = 0; i < totalAttacks; i++)
{ {
std::pair<ui32, ui32> retaliation(0,0); TDmgRange retaliation(0,0);
auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation); auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation);
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
if(remainingCounterAttacks <= i || counterAttacksBlocked) vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
ap.damageReceived = 0; vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
curBai.attackerHealth = attacker->healthAfterAttacked(ap.damageReceived); vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
curBai.defenderHealth = enemy->healthAfterAttacked(ap.damageDealt); vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth());
if(curBai.attackerHealth.getCount() <= 0)
ap.damageDealt += (attackDmg.first + attackDmg.second) / 2;
ap.attackerState->afterAttack(attackInfo.shooting, false);
//FIXME: use ranged retaliation
if(!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
{
ap.damageReceived += (retaliation.first + retaliation.second) / 2;
defenderState->afterAttack(attackInfo.shooting, true);
}
ap.attackerState->damage(ap.damageReceived);
defenderState->damage(ap.damageDealt);
if(!ap.attackerState->alive() || !defenderState->alive())
break; break;
//TODO what about defender? should we break? but in pessimistic scenario defender might be alive
} }
//TODO other damage related to attack (eg. fire shield and other abilities) //TODO other damage related to attack (eg. fire shield and other abilities)
return ap; return ap;
} }
Priorities* AttackPossibility::priorities = nullptr;
+14 -28
View File
@@ -8,43 +8,29 @@
* *
*/ */
#pragma once #pragma once
#include "../../lib/CStack.h" #include "../../lib/battle/CUnitState.h"
#include "../../CCallback.h" #include "../../CCallback.h"
#include "common.h" #include "common.h"
#include "StackWithBonuses.h"
struct HypotheticChangesToBattleState
{
std::map<const CStack *, const IBonusBearer *> bonusesOfStacks;
std::map<const CStack *, int> counterAttacksLeft;
};
class Priorities
{
public:
std::vector<double> resourceTypeBaseValues;
std::function<double(const CStack *)> stackEvaluator;
Priorities()
{
// range::copy(VLC->objh->resVals, std::back_inserter(resourceTypeBaseValues));
stackEvaluator = [](const CStack*){ return 1.0; };
}
};
class AttackPossibility class AttackPossibility
{ {
public: public:
const CStack *enemy; //redundant (to attack.defender) but looks nice
BattleHex tile; //tile from which we attack BattleHex tile; //tile from which we attack
BattleAttackInfo attack; BattleAttackInfo attack;
int damageDealt; std::shared_ptr<battle::CUnitState> attackerState;
int damageReceived; //usually by counter-attack
int tacticImpact;
int damageDiff() const; std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits;
int attackValue() const;
static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex); int64_t damageDealt = 0;
static Priorities * priorities; int64_t damageReceived = 0; //usually by counter-attack
int64_t tacticImpact = 0;
AttackPossibility(BattleHex tile_, const BattleAttackInfo & attack_);
int64_t damageDiff() const;
int64_t attackValue() const;
static AttackPossibility evaluate(const BattleAttackInfo & attackInfo, BattleHex hex);
}; };
+5
View File
@@ -58,6 +58,7 @@
<Add option="-Wno-sign-compare" /> <Add option="-Wno-sign-compare" />
<Add option="-Wno-unused-parameter" /> <Add option="-Wno-unused-parameter" />
<Add option="-Wno-overloaded-virtual" /> <Add option="-Wno-overloaded-virtual" />
<Add option="-DBOOST_THREAD_USE_LIB" />
<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" /> <Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
<Add option="-D_WIN32_WINNT=0x0501" /> <Add option="-D_WIN32_WINNT=0x0501" />
<Add option="-D_WIN32" /> <Add option="-D_WIN32" />
@@ -65,6 +66,7 @@
<Add directory="../../include" /> <Add directory="../../include" />
</Compiler> </Compiler>
<Linker> <Linker>
<Add option="-lboost_thread$(#boost.libsuffix)" />
<Add option="-lboost_system$(#boost.libsuffix)" /> <Add option="-lboost_system$(#boost.libsuffix)" />
<Add option="-lVCMI_lib" /> <Add option="-lVCMI_lib" />
<Add directory="../.." /> <Add directory="../.." />
@@ -73,8 +75,11 @@
<Unit filename="AttackPossibility.h" /> <Unit filename="AttackPossibility.h" />
<Unit filename="BattleAI.cpp" /> <Unit filename="BattleAI.cpp" />
<Unit filename="BattleAI.h" /> <Unit filename="BattleAI.h" />
<Unit filename="CMakeLists.txt" />
<Unit filename="EnemyInfo.cpp" /> <Unit filename="EnemyInfo.cpp" />
<Unit filename="EnemyInfo.h" /> <Unit filename="EnemyInfo.h" />
<Unit filename="PossibleSpellcast.cpp" />
<Unit filename="PossibleSpellcast.h" />
<Unit filename="PotentialTargets.cpp" /> <Unit filename="PotentialTargets.cpp" />
<Unit filename="PotentialTargets.h" /> <Unit filename="PotentialTargets.h" />
<Unit filename="StackWithBonuses.cpp" /> <Unit filename="StackWithBonuses.cpp" />
+286 -144
View File
@@ -9,13 +9,57 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "BattleAI.h" #include "BattleAI.h"
#include <vstd/RNG.h>
#include "StackWithBonuses.h" #include "StackWithBonuses.h"
#include "EnemyInfo.h" #include "EnemyInfo.h"
#include "PossibleSpellcast.h"
#include "../../lib/CStopWatch.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/CSpellHandler.h"
#include "../../lib/spells/ISpellMechanics.h"
#include "../../lib/CStack.h"//todo: remove
#define LOGL(text) print(text) #define LOGL(text) print(text)
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl)) #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
class RNGStub : public vstd::RNG
{
public:
vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override
{
return [=]()->int64_t
{
return (lower + upper)/2;
};
}
vstd::TRand getDoubleRange(double lower, double upper) override
{
return [=]()->double
{
return (lower + upper)/2;
};
}
};
enum class SpellTypes
{
ADVENTURE, BATTLE, OTHER
};
SpellTypes spellType(const CSpell * spell)
{
if(!spell->isCombatSpell() || spell->isCreatureAbility())
return SpellTypes::OTHER;
if(spell->isOffensiveSpell() || spell->hasEffects() || spell->hasBattleEffects())
return SpellTypes::BATTLE;
return SpellTypes::OTHER;
}
CBattleAI::CBattleAI() CBattleAI::CBattleAI()
: side(-1), wasWaitingForRealize(false), wasUnlockingGs(false) : side(-1), wasWaitingForRealize(false), wasUnlockingGs(false)
{ {
@@ -70,31 +114,38 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
//spellcast may finish battle //spellcast may finish battle
//send special preudo-action //send special preudo-action
BattleAction cancel; BattleAction cancel;
cancel.actionType = Battle::CANCEL; cancel.actionType = EActionType::CANCEL;
return cancel; return cancel;
} }
if(auto action = considerFleeingOrSurrendering()) if(auto action = considerFleeingOrSurrendering())
return *action; return *action;
PotentialTargets targets(stack); //best action is from effective owner point if view, we are effective owner as we received "activeStack"
HypotheticBattle hb(getCbc());
PotentialTargets targets(stack, &hb);
if(targets.possibleAttacks.size()) if(targets.possibleAttacks.size())
{ {
auto hlp = targets.bestAction(); auto hlp = targets.bestAction();
if(hlp.attack.shooting) if(hlp.attack.shooting)
return BattleAction::makeShotAttack(stack, hlp.enemy); return BattleAction::makeShotAttack(stack, hlp.attack.defender);
else else
return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile); return BattleAction::makeMeleeAttack(stack, hlp.attack.defender->getPosition(), hlp.tile);
} }
else else
{ {
if(stack->waited()) if(stack->waited())
{ {
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code. //ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
auto dists = getCbc()->battleGetDistances(stack); auto dists = getCbc()->battleGetDistances(stack, stack->getPosition());
const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists))); if(!targets.unreachableEnemies.empty())
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
{ {
return goTowards(stack, ei.s->position); const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists)));
if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE)
{
return goTowards(stack, ei.s->getPosition());
}
} }
} }
else else
@@ -116,9 +167,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination) BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
{ {
assert(destination.isValid()); if(!destination.isValid())
auto avHexes = cb->battleGetAvailableHexes(stack, false); {
logAi->error("CBattleAI::goTowards: invalid destination");
return BattleAction::makeDefend(stack);
}
auto reachability = cb->getReachability(stack); auto reachability = cb->getReachability(stack);
auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
if(vstd::contains(avHexes, destination)) if(vstd::contains(avHexes, destination))
return BattleAction::makeMove(stack, destination); return BattleAction::makeMove(stack, destination);
auto destNeighbours = destination.neighbouringTiles(); auto destNeighbours = destination.neighbouringTiles();
@@ -156,7 +213,12 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
BattleHex currentDest = bestNeighbor; BattleHex currentDest = bestNeighbor;
while(1) while(1)
{ {
assert(currentDest.isValid()); if(!currentDest.isValid())
{
logAi->error("CBattleAI::goTowards: internal error");
return BattleAction::makeDefend(stack);
}
if(vstd::contains(avHexes, currentDest)) if(vstd::contains(avHexes, currentDest))
return BattleAction::makeMove(stack, currentDest); return BattleAction::makeMove(stack, currentDest);
currentDest = reachability.predecessors[currentDest]; currentDest = reachability.predecessors[currentDest];
@@ -166,22 +228,7 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
BattleAction CBattleAI::useCatapult(const CStack * stack) BattleAction CBattleAI::useCatapult(const CStack * stack)
{ {
throw std::runtime_error("The method or operation is not implemented."); throw std::runtime_error("CBattleAI::useCatapult is not implemented.");
}
enum SpellTypes
{
OFFENSIVE_SPELL, TIMED_EFFECT, OTHER
};
SpellTypes spellType(const CSpell *spell)
{
if (spell->isOffensiveSpell())
return OFFENSIVE_SPELL;
if (spell->hasEffects())
return TIMED_EFFECT;
return OTHER;
} }
void CBattleAI::attemptCastingSpell() void CBattleAI::attemptCastingSpell()
@@ -190,7 +237,7 @@ void CBattleAI::attemptCastingSpell()
if(!hero) if(!hero)
return; return;
if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK) if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
return; return;
LOGL("Casting spells sounds like fun. Let's see..."); LOGL("Casting spells sounds like fun. Let's see...");
@@ -198,21 +245,28 @@ void CBattleAI::attemptCastingSpell()
std::vector<const CSpell*> possibleSpells; std::vector<const CSpell*> possibleSpells;
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool
{ {
return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK; return s->canBeCast(getCbc().get(), spells::Mode::HERO, hero);
}); });
LOGFL("I can cast %d spells.", possibleSpells.size()); LOGFL("I can cast %d spells.", possibleSpells.size());
vstd::erase_if(possibleSpells, [](const CSpell *s) vstd::erase_if(possibleSpells, [](const CSpell *s)
{return spellType(s) == OTHER; }); {
LOGFL("I know about workings of %d of them.", possibleSpells.size()); return spellType(s) != SpellTypes::BATTLE;
});
LOGFL("I know how %d of them works.", possibleSpells.size());
//Get possible spell-target pairs //Get possible spell-target pairs
std::vector<PossibleSpellcast> possibleCasts; std::vector<PossibleSpellcast> possibleCasts;
for(auto spell : possibleSpells) for(auto spell : possibleSpells)
{ {
for(auto hex : getTargetsToConsider(spell, hero)) spells::BattleCast temp(getCbc().get(), hero, spells::Mode::HERO, spell);
for(auto & target : temp.findPotentialTargets())
{ {
PossibleSpellcast ps = {spell, hex, 0}; PossibleSpellcast ps;
ps.dest = target;
ps.spell = spell;
possibleCasts.push_back(ps); possibleCasts.push_back(ps);
} }
} }
@@ -220,141 +274,229 @@ void CBattleAI::attemptCastingSpell()
if(possibleCasts.empty()) if(possibleCasts.empty())
return; return;
std::map<const CStack*, int> valueOfStack; using ValueMap = PossibleSpellcast::ValueMap;
for(auto stack : cb->battleGetStacks())
auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle * state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
{ {
PotentialTargets pt(stack); bool firstRound = true;
valueOfStack[stack] = pt.bestActionValue(); bool enemyHadTurn = false;
size_t ourTurnSpan = 0;
bool stop = false;
for(auto & round : queue)
{
if(!firstRound)
state->nextRound(0);//todo: set actual value?
for(auto unit : round)
{
if(!vstd::contains(values, unit->unitId()))
values[unit->unitId()] = 0;
if(!unit->alive())
continue;
if(state->battleGetOwner(unit) != playerID)
{
enemyHadTurn = true;
if(!firstRound || state->battleCastSpells(unit->unitSide()) == 0)
{
//enemy could counter our spell at this point
//anyway, we do not know what enemy will do
//just stop evaluation
stop = true;
break;
}
}
else if(!enemyHadTurn)
{
ourTurnSpan++;
}
state->nextTurn(unit->unitId());
PotentialTargets pt(unit, state);
if(!pt.possibleAttacks.empty())
{
AttackPossibility ap = pt.bestAction();
auto swb = state->getForUpdate(unit->unitId());
*swb = *ap.attackerState;
if(ap.damageDealt > 0)
swb->removeUnitBonus(Bonus::UntilAttack);
if(ap.damageReceived > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
for(auto affected : ap.affectedUnits)
{
swb = state->getForUpdate(affected->unitId());
*swb = *affected;
if(ap.damageDealt > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked);
if(ap.damageReceived > 0 && ap.attack.defender->unitId() == affected->unitId())
swb->removeUnitBonus(Bonus::UntilAttack);
}
}
auto bav = pt.bestActionValue();
//best action is from effective owner`s point if view, we need to convert to our point if view
if(state->battleGetOwner(unit) != playerID)
bav = -bav;
values[unit->unitId()] += bav;
}
firstRound = false;
if(stop)
break;
}
if(enemyHadTurnOut)
*enemyHadTurnOut = enemyHadTurn;
return ourTurnSpan > minTurnSpan;
};
RNGStub rngStub;
ValueMap valueOfStack;
ValueMap healthOfStack;
TStacks all = cb->battleGetAllStacks(false);
for(auto unit : all)
{
healthOfStack[unit->unitId()] = unit->getAvailableHealth();
valueOfStack[unit->unitId()] = 0;
} }
auto evaluateSpellcast = [&] (const PossibleSpellcast &ps) -> int auto amount = all.size();
std::vector<battle::Units> turnOrder;
cb->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
{ {
const int skillLevel = hero->getSpellSchoolLevel(ps.spell); bool enemyHadTurn = false;
const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
switch(spellType(ps.spell)) HypotheticBattle state(cb);
evaluateQueue(valueOfStack, turnOrder, &state, 0, &enemyHadTurn);
if(!enemyHadTurn)
{ {
case OFFENSIVE_SPELL: auto battleIsFinishedOpt = state.battleIsFinished();
{
int damageDealt = 0, damageReceived = 0; if(battleIsFinishedOpt)
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
if(stacksSuffering.empty())
return -1;
for(auto stack : stacksSuffering)
{ {
const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower); print("No need to cast a spell. Battle will finish soon.");
if(stack->owner == playerID) return;
damageReceived += dmg;
else
damageDealt += dmg;
} }
const int damageDiff = damageDealt - damageReceived * 10;
LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.",
ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size());
//TODO tactic effect too
return damageDiff;
} }
case TIMED_EFFECT: }
auto evaluateSpellcast = [&] (PossibleSpellcast * ps)
{
int64_t totalGain = 0;
HypotheticBattle state(cb);
spells::BattleCast cast(&state, hero, spells::Mode::HERO, ps->spell);
cast.target = ps->dest;
cast.cast(&state, rngStub);
ValueMap newHealthOfStack;
ValueMap newValueOfStack;
size_t ourUnits = 0;
for(auto unit : all)
{ {
auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest); newHealthOfStack[unit->unitId()] = unit->getAvailableHealth();
if(stacksAffected.empty()) newValueOfStack[unit->unitId()] = 0;
return -1;
int totalGain = 0; if(state.battleGetOwner(unit) == playerID && unit->alive() && unit->willMove())
for(const CStack * sta : stacksAffected) ourUnits++;
}
size_t minTurnSpan = ourUnits/3; //todo: tweak this
std::vector<battle::Units> newTurnOrder;
state.battleGetTurnOrder(newTurnOrder, amount, 2);
if(evaluateQueue(newValueOfStack, newTurnOrder, &state, minTurnSpan, nullptr))
{
for(auto unit : all)
{ {
StackWithBonuses swb; auto newValue = getValOr(newValueOfStack, unit->unitId(), 0);
swb.stack = sta; auto oldValue = getValOr(valueOfStack, unit->unitId(), 0);
//todo: handle effect actualization in HypotheticChangesToBattleState
ps.spell->getEffects(swb.bonusesToAdd, skillLevel, false, hero->getEnchantPower(ps.spell)); auto healthDiff = newHealthOfStack[unit->unitId()] - healthOfStack[unit->unitId()];
ps.spell->getEffects(swb.bonusesToAdd, skillLevel, true, hero->getEnchantPower(ps.spell));
HypotheticChangesToBattleState state; if(unit->unitOwner() != playerID)
state.bonusesOfStacks[swb.stack] = &swb; healthDiff = -healthDiff;
PotentialTargets pt(swb.stack, state);
auto newValue = pt.bestActionValue(); auto gain = newValue - oldValue + healthDiff;
auto oldValue = valueOfStack[swb.stack];
auto gain = newValue - oldValue; if(gain != 0)
if(swb.stack->owner != playerID) //enemy totalGain += gain;
gain = -gain;
LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue));
totalGain += gain;
} }
LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain)); ps->value = totalGain;
return totalGain;
} }
default: else
assert(0); {
return 0; ps->value = -1;
} }
}; };
std::vector<std::function<void()>> tasks;
for(PossibleSpellcast & psc : possibleCasts) for(PossibleSpellcast & psc : possibleCasts)
psc.value = evaluateSpellcast(psc); {
auto pscValue = [] (const PossibleSpellcast &ps) -> int tasks.push_back(std::bind(evaluateSpellcast, &psc));
}
uint32_t threadCount = boost::thread::hardware_concurrency();
if(threadCount == 0)
{
logGlobal->warn("No information of CPU cores available");
threadCount = 1;
}
CStopWatch timer;
CThreadHelper threadHelper(&tasks, threadCount);
threadHelper.run();
LOGFL("Evaluation took %d ms", timer.getDiff());
auto pscValue = [] (const PossibleSpellcast &ps) -> int64_t
{ {
return ps.value; return ps.value;
}; };
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue); auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
BattleAction spellcast;
spellcast.actionType = Battle::HERO_SPELL;
spellcast.additionalInfo = castToPerform.spell->id;
spellcast.destinationTile = castToPerform.dest;
spellcast.side = side;
spellcast.stackNumber = (!side) ? -1 : -2;
cb->battleMakeAction(&spellcast);
}
std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, const ISpellCaster * caster) const if(castToPerform.value > 0)
{
const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
std::vector<BattleHex> ret;
if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
{ {
ret.push_back(BattleHex()); LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->name % castToPerform.value);
BattleAction spellcast;
spellcast.actionType = EActionType::HERO_SPELL;
spellcast.actionSubtype = castToPerform.spell->id;
spellcast.setTarget(castToPerform.dest);
spellcast.side = side;
spellcast.stackNumber = (!side) ? -1 : -2;
cb->battleMakeAction(&spellcast);
} }
else else
{ {
switch(targetInfo.type) LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value);
{
case CSpell::CREATURE:
{
for(const CStack * stack : getCbc()->battleAliveStacks())
{
bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
bool casterStack = stack->owner == caster->getOwner();
if(!immune)
switch (spell->positiveness)
{
case CSpell::POSITIVE:
if(casterStack || targetInfo.smart)
ret.push_back(stack->position);
break;
case CSpell::NEUTRAL:
ret.push_back(stack->position);
break;
case CSpell::NEGATIVE:
if(!casterStack || targetInfo.smart)
ret.push_back(stack->position);
break;
}
}
}
break;
case CSpell::LOCATION:
{
for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
if(BattleHex(i).isAvailable())
ret.push_back(i);
}
break;
default:
break;
}
} }
return ret;
} }
int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex) int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances &dists, BattleHex *chosenHex)
@@ -374,18 +516,18 @@ int CBattleAI::distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDi
void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side) void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
{ {
print("battleStart called"); LOG_TRACE(logAi);
side = Side; side = Side;
} }
bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists) bool CBattleAI::isCloser(const EnemyInfo &ei1, const EnemyInfo &ei2, const ReachabilityInfo::TDistances &dists)
{ {
return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists);
} }
void CBattleAI::print(const std::string &text) const void CBattleAI::print(const std::string &text) const
{ {
logAi->trace("CBattleAI [%p]: %s", this, text); logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
} }
boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering() boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
+1 -13
View File
@@ -45,13 +45,6 @@ struct CurrentOffensivePotential
}; };
*/ // These lines may be usefull but they are't used in the code. */ // These lines may be usefull but they are't used in the code.
struct PossibleSpellcast
{
const CSpell *spell;
BattleHex dest;
si32 value;
};
class CBattleAI : public CBattleGameInterface class CBattleAI : public CBattleGameInterface
{ {
int side; int side;
@@ -72,7 +65,6 @@ public:
boost::optional<BattleAction> considerFleeingOrSurrendering(); boost::optional<BattleAction> considerFleeingOrSurrendering();
std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const;
static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr); static int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& dists, BattleHex *chosenHex = nullptr);
static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists); static bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists);
@@ -82,7 +74,7 @@ public:
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero //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 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 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, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
//void battleEnd(const BattleResult *br) override; //void battleEnd(const BattleResult *br) override;
//void battleResultsApplied() override; //called when all effects of last battle are applied //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 battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
@@ -92,9 +84,5 @@ public:
//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks //void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
//void battleTriggerEffect(const BattleTriggerEffect & bte) override; //void battleTriggerEffect(const BattleTriggerEffect & bte) override;
//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 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 battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
//void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
//void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given
//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack //void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
//void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
}; };
+2
View File
@@ -8,6 +8,7 @@ set(battleAI_SRCS
common.cpp common.cpp
EnemyInfo.cpp EnemyInfo.cpp
main.cpp main.cpp
PossibleSpellcast.cpp
PotentialTargets.cpp PotentialTargets.cpp
StackWithBonuses.cpp StackWithBonuses.cpp
ThreatMap.cpp ThreatMap.cpp
@@ -21,6 +22,7 @@ set(battleAI_HEADERS
common.h common.h
EnemyInfo.h EnemyInfo.h
PotentialTargets.h PotentialTargets.h
PossibleSpellcast.h
StackWithBonuses.h StackWithBonuses.h
ThreatMap.h ThreatMap.h
) )
+5 -7
View File
@@ -9,13 +9,11 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "EnemyInfo.h" #include "EnemyInfo.h"
#include "../../lib/CRandomGenerator.h"
#include "../../CCallback.h"
#include "common.h"
void EnemyInfo::calcDmg(const CStack * ourStack) #include "../../lib/battle/Unit.h"
bool EnemyInfo::operator==(const EnemyInfo & ei) const
{ {
TDmgRange retal, dmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); return s->unitId() == ei.s->unitId();
adi = (dmg.first + dmg.second) / 2;
adr = (retal.first + retal.second) / 2;
} }
+7 -12
View File
@@ -9,21 +9,16 @@
*/ */
#pragma once #pragma once
#include "../../lib/battle/BattleHex.h" namespace battle
{
class CStack; class Unit;
}
class EnemyInfo class EnemyInfo
{ {
public: public:
const CStack * s; const battle::Unit * s;
int adi, adr; EnemyInfo(const battle::Unit * _s) : s(_s)
std::vector<BattleHex> attackFrom; //for melee fight
EnemyInfo(const CStack * _s) : s(_s)
{} {}
void calcDmg(const CStack * ourStack); bool operator==(const EnemyInfo & ei) const;
bool operator==(const EnemyInfo& ei) const
{
return s == ei.s;
}
}; };
+21
View File
@@ -0,0 +1,21 @@
/*
* PossibleSpellcast.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 "PossibleSpellcast.h"
PossibleSpellcast::PossibleSpellcast()
: spell(nullptr),
dest(),
value(0)
{
}
PossibleSpellcast::~PossibleSpellcast() = default;
+29
View File
@@ -0,0 +1,29 @@
/*
* PossibleSpellcast.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/Destination.h"
#include "../../lib/spells/Magic.h"
class CSpell;
class PossibleSpellcast
{
public:
using ValueMap = std::map<uint32_t, int64_t>;
const CSpell * spell;
spells::Target dest;
int64_t value;
PossibleSpellcast();
virtual ~PossibleSpellcast();
};
+46 -21
View File
@@ -9,51 +9,76 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "PotentialTargets.h" #include "PotentialTargets.h"
#include "../../lib/CStack.h"//todo: remove
PotentialTargets::PotentialTargets(const CStack * attacker, const HypotheticChangesToBattleState & state) PotentialTargets::PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state)
{ {
auto dists = getCbc()->battleGetDistances(attacker); auto attIter = state->stackStates.find(attacker->unitId());
auto avHexes = getCbc()->battleGetAvailableHexes(attacker, false); const battle::Unit * attackerInfo = (attIter == state->stackStates.end()) ? attacker : attIter->second.get();
for(const CStack *enemy : getCbc()->battleGetStacks()) auto reachability = state->getReachability(attackerInfo);
auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo);
//FIXME: this should part of battleGetAvailableHexes
bool forceTarget = false;
const battle::Unit * forcedTarget = nullptr;
BattleHex forcedHex;
if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE))
{ {
//Consider only stacks of different owner forceTarget = true;
if(enemy->side == attacker->side) auto nearest = state->getNearestStack(attackerInfo);
if(nearest.first != nullptr)
{
forcedTarget = nearest.first;
forcedHex = nearest.second;
}
}
auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit)
{
return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId();
});
for(auto defender : aliveUnits)
{
if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender))
continue; continue;
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
{ {
auto bai = BattleAttackInfo(attacker, enemy, shooting); auto bai = BattleAttackInfo(attackerInfo, defender, shooting);
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
if(hex.isValid()) if(hex.isValid() && !shooting)
{ bai.chargedFields = reachability.distances[hex];
assert(dists[hex] <= attacker->Speed());
bai.chargedFields = dists[hex];
}
return AttackPossibility::evaluate(bai, state, hex); return AttackPossibility::evaluate(bai, hex);
}; };
if(getCbc()->battleCanShoot(attacker, enemy->position)) if(forceTarget)
{
if(forcedTarget && defender->unitId() == forcedTarget->unitId())
possibleAttacks.push_back(GenerateAttackInfo(false, forcedHex));
else
unreachableEnemies.push_back(defender);
}
else if(state->battleCanShoot(attackerInfo, defender->getPosition()))
{ {
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
} }
else else
{ {
for(BattleHex hex : avHexes) for(BattleHex hex : avHexes)
if(CStack::isMeleeAttackPossible(attacker, enemy, hex)) if(CStack::isMeleeAttackPossible(attackerInfo, defender, hex))
possibleAttacks.push_back(GenerateAttackInfo(false, hex)); possibleAttacks.push_back(GenerateAttackInfo(false, hex));
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; })) if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility & pa) { return pa.attack.defender->unitId() == defender->unitId(); }))
unreachableEnemies.push_back(enemy); unreachableEnemies.push_back(defender);
} }
} }
} }
int PotentialTargets::bestActionValue() const int PotentialTargets::bestActionValue() const
{ {
if(possibleAttacks.empty()) if(possibleAttacks.empty())
+2 -4
View File
@@ -14,12 +14,10 @@ class PotentialTargets
{ {
public: public:
std::vector<AttackPossibility> possibleAttacks; std::vector<AttackPossibility> possibleAttacks;
std::vector<const CStack *> unreachableEnemies; std::vector<const battle::Unit *> unreachableEnemies;
//std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
PotentialTargets(){}; PotentialTargets(){};
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState()); PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state);
AttackPossibility bestAction() const; AttackPossibility bestAction() const;
int bestActionValue() const; int bestActionValue() const;
+368 -6
View File
@@ -9,21 +9,383 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "StackWithBonuses.h" #include "StackWithBonuses.h"
#include "../../lib/NetPacksBase.h"
#include "../../lib/CStack.h" #include "../../lib/CStack.h"
void actualizeEffect(TBonusListPtr target, const Bonus & ef)
{
for(auto bonus : *target) //TODO: optimize
{
if(bonus->source == Bonus::SPELL_EFFECT && bonus->type == ef.type && bonus->subtype == ef.subtype)
{
bonus->turnsRemain = std::max(bonus->turnsRemain, ef.turnsRemain);
}
}
}
const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector &selector, const CSelector &limit, StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack)
const CBonusSystemNode * root, const std::string & cachingStr) const : battle::CUnitState(),
origBearer(Stack),
owner(Owner),
type(Stack->unitType()),
baseAmount(Stack->unitBaseAmount()),
id(Stack->unitId()),
side(Stack->unitSide()),
player(Stack->unitOwner()),
slot(Stack->unitSlot())
{
localInit(Owner);
battle::CUnitState::operator=(*Stack);
}
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info)
: battle::CUnitState(),
origBearer(nullptr),
owner(Owner),
baseAmount(info.count),
id(info.id),
side(info.side),
slot(SlotID::SUMMONED_SLOT_PLACEHOLDER)
{
type = info.type.toCreature();
origBearer = type;
player = Owner->getSidePlayer(side);
position = info.position;
summoned = info.summoned;
localInit(Owner);
}
StackWithBonuses::~StackWithBonuses() = default;
StackWithBonuses & StackWithBonuses::operator=(const battle::CUnitState & other)
{
battle::CUnitState::operator=(other);
return *this;
}
const CCreature * StackWithBonuses::unitType() const
{
return type;
}
int32_t StackWithBonuses::unitBaseAmount() const
{
return baseAmount;
}
uint32_t StackWithBonuses::unitId() const
{
return id;
}
ui8 StackWithBonuses::unitSide() const
{
return side;
}
PlayerColor StackWithBonuses::unitOwner() const
{
return player;
}
SlotID StackWithBonuses::unitSlot() const
{
return slot;
}
const TBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
const CBonusSystemNode * root, const std::string & cachingStr) const
{ {
TBonusListPtr ret = std::make_shared<BonusList>(); TBonusListPtr ret = std::make_shared<BonusList>();
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr); const TBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
range::copy(*originalList, std::back_inserter(*ret));
for(auto &bonus : bonusesToAdd) vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
{
return !vstd::contains(bonusesToRemove, b);
});
for(const Bonus & bonus : bonusesToUpdate)
{
if(selector(&bonus) && (!limit || !limit(&bonus)))
{
if(ret->getFirst(Selector::source(Bonus::SPELL_EFFECT, bonus.sid).And(Selector::typeSubtype(bonus.type, bonus.subtype))))
{
actualizeEffect(ret, bonus);
}
else
{
auto b = std::make_shared<Bonus>(bonus);
ret->push_back(b);
}
}
}
for(auto & bonus : bonusesToAdd)
{ {
auto b = std::make_shared<Bonus>(bonus); auto b = std::make_shared<Bonus>(bonus);
if(selector(b.get()) && (!limit || !limit(b.get()))) if(selector(b.get()) && (!limit || !limit(b.get())))
ret->push_back(b); ret->push_back(b);
} }
//TODO limiters? //TODO limiters?
return ret; return ret;
} }
int64_t StackWithBonuses::getTreeVersion() const
{
return owner->getTreeVersion();
}
void StackWithBonuses::addUnitBonus(const std::vector<Bonus> & bonus)
{
vstd::concatenate(bonusesToAdd, bonus);
}
void StackWithBonuses::updateUnitBonus(const std::vector<Bonus> & bonus)
{
//TODO: optimize, actualize to last value
vstd::concatenate(bonusesToUpdate, bonus);
}
void StackWithBonuses::removeUnitBonus(const std::vector<Bonus> & bonus)
{
for(auto & one : bonus)
{
CSelector selector([&one](const Bonus * b) -> bool
{
//compare everything but turnsRemain, limiter and propagator
return one.duration == b->duration
&& one.type == b->type
&& one.subtype == b->subtype
&& one.source == b->source
&& one.val == b->val
&& one.sid == b->sid
&& one.valType == b->valType
&& one.additionalInfo == b->additionalInfo
&& one.effectRange == b->effectRange
&& one.description == b->description;
});
removeUnitBonus(selector);
}
}
void StackWithBonuses::removeUnitBonus(const CSelector & selector)
{
TBonusListPtr toRemove = origBearer->getBonuses(selector);
for(auto b : *toRemove)
bonusesToRemove.insert(b);
vstd::erase_if(bonusesToAdd, [&](const Bonus & b){return selector(&b);});
vstd::erase_if(bonusesToUpdate, [&](const Bonus & b){return selector(&b);});
}
void StackWithBonuses::spendMana(const spells::PacketSender * server, const int spellCost) const
{
//TODO: evaluate cast use
}
HypotheticBattle::HypotheticBattle(Subject realBattle)
: BattleProxy(realBattle),
bonusTreeVersion(1)
{
auto activeUnit = realBattle->battleActiveUnit();
activeUnitId = activeUnit ? activeUnit->unitId() : -1;
nextId = 0xF0000000;
}
bool HypotheticBattle::unitHasAmmoCart(const battle::Unit * unit) const
{
//FIXME: check ammocart alive state here
return false;
}
PlayerColor HypotheticBattle::unitEffectiveOwner(const battle::Unit * unit) const
{
return battleGetOwner(unit);
}
std::shared_ptr<StackWithBonuses> HypotheticBattle::getForUpdate(uint32_t id)
{
auto iter = stackStates.find(id);
if(iter == stackStates.end())
{
const CStack * s = subject->battleGetStackByID(id, false);
auto ret = std::make_shared<StackWithBonuses>(this, s);
stackStates[id] = ret;
return ret;
}
else
{
return iter->second;
}
}
battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const
{
battle::Units proxyed = BattleProxy::getUnitsIf(predicate);
battle::Units ret;
ret.reserve(proxyed.size());
for(auto unit : proxyed)
{
//unit was not changed, trust proxyed data
if(stackStates.find(unit->unitId()) == stackStates.end())
ret.push_back(unit);
}
for(auto id_unit : stackStates)
{
if(predicate(id_unit.second.get()))
ret.push_back(id_unit.second.get());
}
return ret;
}
int32_t HypotheticBattle::getActiveStackID() const
{
return activeUnitId;
}
void HypotheticBattle::nextRound(int32_t roundNr)
{
//TODO:HypotheticBattle::nextRound
for(auto unit : battleAliveUnits())
{
auto forUpdate = getForUpdate(unit->unitId());
//TODO: update Bonus::NTurns effects
forUpdate->afterNewRound();
}
}
void HypotheticBattle::nextTurn(uint32_t unitId)
{
activeUnitId = unitId;
auto unit = getForUpdate(unitId);
unit->removeUnitBonus(Bonus::UntilGetsTurn);
unit->afterGetsTurn();
}
void HypotheticBattle::addUnit(uint32_t id, const JsonNode & data)
{
battle::UnitInfo info;
info.load(id, data);
std::shared_ptr<StackWithBonuses> newUnit = std::make_shared<StackWithBonuses>(this, info);
stackStates[newUnit->unitId()] = newUnit;
}
void HypotheticBattle::moveUnit(uint32_t id, BattleHex destination)
{
std::shared_ptr<StackWithBonuses> changed = getForUpdate(id);
changed->position = destination;
}
void HypotheticBattle::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta)
{
std::shared_ptr<StackWithBonuses> changed = getForUpdate(id);
changed->load(data);
if(healthDelta < 0)
{
changed->removeUnitBonus(Bonus::UntilBeingAttacked);
}
}
void HypotheticBattle::removeUnit(uint32_t id)
{
std::set<uint32_t> ids;
ids.insert(id);
while(!ids.empty())
{
auto toRemoveId = *ids.begin();
auto toRemove = getForUpdate(toRemoveId);
if(!toRemove->ghost)
{
toRemove->onRemoved();
//TODO: emulate detachFromAll() somehow
//stack may be removed instantly (not being killed first)
//handle clone remove also here
if(toRemove->cloneID >= 0)
{
ids.insert(toRemove->cloneID);
toRemove->cloneID = -1;
}
//TODO: cleanup remaining clone links if any
// for(auto s : stacks)
// {
// if(s->cloneID == toRemoveId)
// s->cloneID = -1;
// }
}
ids.erase(toRemoveId);
}
}
void HypotheticBattle::addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
{
getForUpdate(id)->addUnitBonus(bonus);
bonusTreeVersion++;
}
void HypotheticBattle::updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
{
getForUpdate(id)->updateUnitBonus(bonus);
bonusTreeVersion++;
}
void HypotheticBattle::removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
{
getForUpdate(id)->removeUnitBonus(bonus);
bonusTreeVersion++;
}
void HypotheticBattle::setWallState(int partOfWall, si8 state)
{
//TODO:HypotheticBattle::setWallState
}
void HypotheticBattle::addObstacle(const ObstacleChanges & changes)
{
//TODO:HypotheticBattle::addObstacle
}
void HypotheticBattle::removeObstacle(uint32_t id)
{
//TODO:HypotheticBattle::removeObstacle
}
uint32_t HypotheticBattle::nextUnitId() const
{
return nextId++;
}
int64_t HypotheticBattle::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
{
return (damage.first + damage.second) / 2;
}
int64_t HypotheticBattle::getTreeVersion() const
{
return getBattleNode()->getTreeVersion() + bonusTreeVersion;
}
+95 -5
View File
@@ -9,15 +9,105 @@
*/ */
#pragma once #pragma once
#include "../../lib/HeroBonus.h" #include "../../lib/HeroBonus.h"
#include "../../lib/battle/BattleProxy.h"
#include "../../lib/battle/CUnitState.h"
class HypotheticBattle;
class CStack; class CStack;
class StackWithBonuses : public IBonusBearer class StackWithBonuses : public battle::CUnitState, public virtual IBonusBearer
{ {
public: public:
const CStack *stack;
mutable std::vector<Bonus> bonusesToAdd;
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, std::vector<Bonus> bonusesToAdd;
const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; std::vector<Bonus> bonusesToUpdate;
std::set<std::shared_ptr<Bonus>> bonusesToRemove;
StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack);
StackWithBonuses(const HypotheticBattle * Owner, const battle::UnitInfo & info);
virtual ~StackWithBonuses();
StackWithBonuses & operator= (const battle::CUnitState & other);
///IUnitInfo
const CCreature * unitType() const override;
int32_t unitBaseAmount() const override;
uint32_t unitId() const override;
ui8 unitSide() const override;
PlayerColor unitOwner() const override;
SlotID unitSlot() const override;
///IBonusBearer
const TBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
int64_t getTreeVersion() const override;
void addUnitBonus(const std::vector<Bonus> & bonus);
void updateUnitBonus(const std::vector<Bonus> & bonus);
void removeUnitBonus(const std::vector<Bonus> & bonus);
void removeUnitBonus(const CSelector & selector);
void spendMana(const spells::PacketSender * server, const int spellCost) const override;
private:
const IBonusBearer * origBearer;
const HypotheticBattle * owner;
const CCreature * type;
ui32 baseAmount;
uint32_t id;
ui8 side;
PlayerColor player;
SlotID slot;
};
class HypotheticBattle : public BattleProxy, public battle::IUnitEnvironment
{
public:
std::map<uint32_t, std::shared_ptr<StackWithBonuses>> stackStates;
HypotheticBattle(Subject realBattle);
bool unitHasAmmoCart(const battle::Unit * unit) const override;
PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override;
std::shared_ptr<StackWithBonuses> getForUpdate(uint32_t id);
int32_t getActiveStackID() const override;
battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
void nextRound(int32_t roundNr) override;
void nextTurn(uint32_t unitId) override;
void addUnit(uint32_t id, const JsonNode & data) override;
void setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta) override;
void moveUnit(uint32_t id, BattleHex destination) override;
void removeUnit(uint32_t id) override;
void addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
void updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
void removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
void setWallState(int partOfWall, si8 state) override;
void addObstacle(const ObstacleChanges & changes) override;
void removeObstacle(uint32_t id) override;
uint32_t nextUnitId() const override;
int64_t getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
int64_t getTreeVersion() const;
private:
int32_t bonusTreeVersion;
int32_t activeUnitId;
mutable uint32_t nextId;
}; };
+14 -35
View File
@@ -53,7 +53,7 @@ struct EnemyInfo
{} {}
void calcDmg(const CStack * ourStack) void calcDmg(const CStack * ourStack)
{ {
TDmgRange retal, dmg = cbc->battleEstimateDamage(CRandomGenerator::getDefault(), ourStack, s, &retal); TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
adi = (dmg.first + dmg.second) / 2; adi = (dmg.first + dmg.second) / 2;
adr = (retal.first + retal.second) / 2; adr = (retal.first + retal.second) / 2;
} }
@@ -89,7 +89,7 @@ int distToNearestNeighbour(BattleHex hex, const ReachabilityInfo::TDistances& di
bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists) bool isCloser(const EnemyInfo & ei1, const EnemyInfo & ei2, const ReachabilityInfo::TDistances & dists)
{ {
return distToNearestNeighbour(ei1.s->position, dists) < distToNearestNeighbour(ei2.s->position, dists); return distToNearestNeighbour(ei1.s->getPosition(), dists) < distToNearestNeighbour(ei2.s->getPosition(), dists);
} }
} }
@@ -111,17 +111,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
{ {
//boost::this_thread::sleep(boost::posix_time::seconds(2)); //boost::this_thread::sleep(boost::posix_time::seconds(2));
print("activeStack called for " + stack->nodeName()); print("activeStack called for " + stack->nodeName());
auto dists = cb->battleGetDistances(stack); auto dists = cb->battleGetDistances(stack, stack->getPosition());
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable; std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
if(stack->type->idNumber == CreatureID::CATAPULT) if(stack->type->idNumber == CreatureID::CATAPULT)
{ {
BattleAction attack; BattleAction attack;
static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95}; static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
auto seletectedHex = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault()); attack.aimToHex(seletectedHex);
attack.actionType = Battle::CATAPULT; attack.actionType = EActionType::CATAPULT;
attack.additionalInfo = 0;
attack.side = side; attack.side = side;
attack.stackNumber = stack->ID; attack.stackNumber = stack->ID;
@@ -134,13 +133,13 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY)) for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
{ {
if(cb->battleCanShoot(stack, s->position)) if(cb->battleCanShoot(stack, s->getPosition()))
{ {
enemiesShootable.push_back(s); enemiesShootable.push_back(s);
} }
else else
{ {
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false); std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack);
for (BattleHex hex : avHexes) for (BattleHex hex : avHexes)
{ {
@@ -157,7 +156,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
} }
} }
if(!vstd::contains(enemiesReachable, s) && s->position.isValid()) if(!vstd::contains(enemiesReachable, s) && s->getPosition().isValid())
enemiesUnreachable.push_back(s); enemiesUnreachable.push_back(s);
} }
} }
@@ -176,16 +175,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
else if(enemiesReachable.size()) else if(enemiesReachable.size())
{ {
const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable); const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
return BattleAction::makeMeleeAttack(stack, ei.s, *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)); return BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters));
} }
else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
{ {
assert(enemiesUnreachable.size()); assert(enemiesUnreachable.size());
const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), std::bind(isCloser, _1, _2, std::ref(dists))); const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), std::bind(isCloser, _1, _2, std::ref(dists)));
assert(ei.s); assert(ei.s);
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE) if(distToNearestNeighbour(ei.s->getPosition(), dists) < GameConstants::BFIELD_SIZE)
{ {
return goTowards(stack, ei.s->position); return goTowards(stack, ei.s->getPosition());
} }
} }
@@ -197,7 +196,7 @@ void CStupidAI::battleAttack(const BattleAttack *ba)
print("battleAttack called"); print("battleAttack called");
} }
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
{ {
print("battleStacksAttacked called"); print("battleStacksAttacked called");
} }
@@ -243,31 +242,11 @@ void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2
side = Side; side = Side;
} }
void CStupidAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
{
print("battleStacksHealedRes called");
}
void CStupidAI::battleNewStackAppeared(const CStack * stack)
{
print("battleNewStackAppeared called");
}
void CStupidAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
{
print("battleObstaclesRemoved called");
}
void CStupidAI::battleCatapultAttacked(const CatapultAttack & ca) void CStupidAI::battleCatapultAttacked(const CatapultAttack & ca)
{ {
print("battleCatapultAttacked called"); print("battleCatapultAttacked called");
} }
void CStupidAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
{
print("battleStacksRemoved called");
}
void CStupidAI::print(const std::string &text) const void CStupidAI::print(const std::string &text) const
{ {
logAi->trace("CStupidAI [%p]: %s", this, text); logAi->trace("CStupidAI [%p]: %s", this, text);
@@ -276,8 +255,8 @@ void CStupidAI::print(const std::string &text) const
BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination) BattleAction CStupidAI::goTowards(const CStack * stack, BattleHex destination)
{ {
assert(destination.isValid()); assert(destination.isValid());
auto avHexes = cb->battleGetAvailableHexes(stack, false);
auto reachability = cb->getReachability(stack); auto reachability = cb->getReachability(stack);
auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
if(vstd::contains(avHexes, destination)) if(vstd::contains(avHexes, destination))
return BattleAction::makeMove(stack, destination); return BattleAction::makeMove(stack, destination);
+1 -5
View File
@@ -27,7 +27,7 @@ public:
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack 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 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, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
void battleEnd(const BattleResult *br) override; void battleEnd(const BattleResult *br) override;
//void battleResultsApplied() override; //called when all effects of last battle are applied //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 battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
@@ -37,11 +37,7 @@ public:
void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
//void battleTriggerEffect(const BattleTriggerEffect & bte) override; //void battleTriggerEffect(const BattleTriggerEffect & bte) override;
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 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 battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp
void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
BattleAction goTowards(const CStack * stack, BattleHex hex ); BattleAction goTowards(const CStack * stack, BattleHex hex );
+2 -2
View File
@@ -1359,7 +1359,7 @@ static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingI
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP}; BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1, static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR}; BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
static const BuildingID spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3, static const BuildingID _spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5}; BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3, static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
@@ -1416,7 +1416,7 @@ void VCAI::buildStructure(const CGTownInstance * t)
} }
//remaining tasks //remaining tasks
if(tryBuildNextStructure(t, std::vector<BuildingID>(spells, spells + ARRAY_COUNT(spells)))) if(tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
return; return;
if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra)))) if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
return; return;
+6 -5
View File
@@ -174,7 +174,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
int CBattleCallback::battleMakeAction(BattleAction* action) int CBattleCallback::battleMakeAction(BattleAction* action)
{ {
assert(action->actionType == Battle::HERO_SPELL); assert(action->actionType == EActionType::HERO_SPELL);
MakeCustomAction mca(*action); MakeCustomAction mca(*action);
sendRequest(&mca); sendRequest(&mca);
return 0; return 0;
@@ -279,9 +279,11 @@ void CCallback::buildBoat( const IShipyard *obj )
sendRequest(&bb); sendRequest(&bb);
} }
CCallback::CCallback( CGameState * GS, boost::optional<PlayerColor> Player, CClient *C ) CCallback::CCallback(CGameState * GS, boost::optional<PlayerColor> Player, CClient * C)
:CBattleCallback(GS, Player, C) : CBattleCallback(Player, C)
{ {
gs = GS;
waitTillRealize = false; waitTillRealize = false;
unlockGsWhenWaiting = false; unlockGsWhenWaiting = false;
} }
@@ -367,9 +369,8 @@ void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver>
cl->additionalBattleInts[*player] -= battleEvents; cl->additionalBattleInts[*player] -= battleEvents;
} }
CBattleCallback::CBattleCallback(CGameState *GS, boost::optional<PlayerColor> Player, CClient *C ) CBattleCallback::CBattleCallback(boost::optional<PlayerColor> Player, CClient *C )
{ {
gs = GS;
player = Player; player = Player;
cl = C; cl = C;
} }
+2 -3
View File
@@ -17,7 +17,7 @@ class CGameState;
struct CPath; struct CPath;
class CGObjectInstance; class CGObjectInstance;
class CArmedInstance; class CArmedInstance;
struct BattleAction; class BattleAction;
class CGTownInstance; class CGTownInstance;
struct lua_State; struct lua_State;
class CClient; class CClient;
@@ -85,10 +85,9 @@ class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
protected: protected:
int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied) int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied)
CClient *cl; CClient *cl;
//virtual bool hasAccess(int playerId) const;
public: public:
CBattleCallback(CGameState *GS, boost::optional<PlayerColor> Player, CClient *C); CBattleCallback(boost::optional<PlayerColor> Player, CClient *C);
int battleMakeAction(BattleAction* action) override;//for casting spells by hero - DO NOT use it for moving active stack int battleMakeAction(BattleAction* action) override;//for casting spells by hero - DO NOT use it for moving active stack
bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

@@ -0,0 +1,8 @@
{
"basepath": "vcmi/battleQueue/",
"images" :
[
{ "frame" : 0, "file" : "defendBig"},
{ "frame" : 1, "file" : "waitBig"}
]
}
@@ -0,0 +1,8 @@
{
"basepath": "vcmi/battleQueue/",
"images" :
[
{ "frame" : 0, "file" : "defendSmall"},
{ "frame" : 1, "file" : "waitSmall"}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

-356
View File
@@ -1,356 +0,0 @@
/*
* CDefHandler.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 "SDL.h"
#include "CDefHandler.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/VCMI_Lib.h"
#include "CBitmapHandler.h"
#include "gui/SDL_Extensions.h"
#ifdef unused
static long long pow(long long a, int b)
{
if (!b) return 1;
long c = a;
while (--b)
a*=c;
return a;
}
#endif
CDefHandler::CDefHandler()
{
}
CDefHandler::~CDefHandler()
{
for (auto & elem : ourImages)
{
if (elem.bitmap)
{
SDL_FreeSurface(elem.bitmap);
elem.bitmap=nullptr;
}
}
}
void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
{
SDL_Color palette[256];
SDefEntry &de = * reinterpret_cast<SDefEntry *>(table);
ui8 *p;
defName = name;
DEFType = read_le_u32(&de.DEFType);
width = read_le_u32(&de.width);
height = read_le_u32(&de.height);
ui32 totalBlocks = read_le_u32(&de.totalBlocks);
for (ui32 it=0;it<256;it++)
{
palette[it].r = de.palette[it].R;
palette[it].g = de.palette[it].G;
palette[it].b = de.palette[it].B;
palette[it].a = SDL_ALPHA_OPAQUE;
}
// The SDefEntryBlock starts just after the SDefEntry
p = reinterpret_cast<ui8 *>(&de);
p += sizeof(de);
int totalEntries=0;
for (ui32 z=0; z<totalBlocks; z++)
{
SDefEntryBlock &block = * reinterpret_cast<SDefEntryBlock *>(p);
ui32 totalInBlock;
totalInBlock = read_le_u32(&block.totalInBlock);
for (ui32 j=SEntries.size(); j<totalEntries+totalInBlock; j++)
SEntries.push_back(SEntry());
p = block.data;
for (ui32 j=0; j<totalInBlock; j++)
{
char Buffer[13];
memcpy(Buffer, p, 12);
Buffer[12] = 0;
SEntries[totalEntries+j].name=Buffer;
p += 13;
}
for (ui32 j=0; j<totalInBlock; j++)
{
SEntries[totalEntries+j].offset = read_le_u32(p);
p += 4;
}
//totalEntries+=totalInBlock;
for(ui32 hh=0; hh<totalInBlock; ++hh)
{
SEntries[totalEntries].group = z;
++totalEntries;
}
}
for(auto & elem : SEntries)
{
elem.name = elem.name.substr(0, elem.name.find('.')+4);
}
//RWEntries = new ui32[height];
for(ui32 i=0; i < SEntries.size(); ++i)
{
Cimage nimg;
nimg.bitmap = getSprite(i, table, palette);
nimg.imName = SEntries[i].name;
nimg.groupNumber = SEntries[i].group;
ourImages.push_back(nimg);
}
}
SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const
{
SDL_Surface * ret=nullptr;
ui32 BaseOffset,
SpriteWidth, SpriteHeight, //format of sprite
TotalRowLength, // length of read segment
add, FullHeight,FullWidth,
RowAdd,
defType2;
int LeftMargin, RightMargin, TopMargin, BottomMargin;
ui8 SegmentType;
BaseOffset = SEntries[SIndex].offset;
SSpriteDef sd = * reinterpret_cast<const SSpriteDef *>(FDef + BaseOffset);
defType2 = read_le_u32(&sd.defType2);
FullWidth = read_le_u32(&sd.FullWidth);
FullHeight = read_le_u32(&sd.FullHeight);
SpriteWidth = read_le_u32(&sd.SpriteWidth);
SpriteHeight = read_le_u32(&sd.SpriteHeight);
LeftMargin = read_le_u32(&sd.LeftMargin);
TopMargin = read_le_u32(&sd.TopMargin);
RightMargin = FullWidth - SpriteWidth - LeftMargin;
BottomMargin = FullHeight - SpriteHeight - TopMargin;
//if(LeftMargin + RightMargin < 0)
// SpriteWidth += LeftMargin + RightMargin; //ugly construction... TODO: check how to do it nicer
if(LeftMargin<0)
SpriteWidth+=LeftMargin;
if(RightMargin<0)
SpriteWidth+=RightMargin;
// Note: this looks bogus because we allocate only FullWidth, not FullWidth+add
add = 4 - FullWidth%4;
if (add==4)
add=0;
ret = SDL_CreateRGBSurface(SDL_SWSURFACE, FullWidth, FullHeight, 8, 0, 0, 0, 0);
if(nullptr == ret)
{
logGlobal->error("%s: Unable to create surface", __FUNCTION__);
logGlobal->error("%dX%d", FullWidth, FullHeight);
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
BaseOffset += sizeof(SSpriteDef);
int BaseOffsetor = BaseOffset;
SDL_Palette * p = SDL_AllocPalette(256);
SDL_SetPaletteColors(p, palette, 0, 256);
SDL_SetSurfacePalette(ret, p);
SDL_FreePalette(p);
int ftcp=0;
// If there's a margin anywhere, just blank out the whole surface.
if (TopMargin > 0 || BottomMargin > 0 || LeftMargin > 0 || RightMargin > 0) {
memset( reinterpret_cast<char*>(ret->pixels), 0, FullHeight*FullWidth);
}
// Skip top margin
if (TopMargin > 0)
ftcp += TopMargin*(FullWidth+add);
switch(defType2)
{
case 0:
{
for (ui32 i=0;i<SpriteHeight;i++)
{
if (LeftMargin>0)
ftcp += LeftMargin;
memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, &FDef[BaseOffset], SpriteWidth);
ftcp += SpriteWidth;
BaseOffset += SpriteWidth;
if (RightMargin>0)
ftcp += RightMargin;
}
}
break;
case 1:
{
const ui32 * RWEntriesLoc = reinterpret_cast<const ui32 *>(FDef+BaseOffset);
BaseOffset += sizeof(int) * SpriteHeight;
for (ui32 i=0;i<SpriteHeight;i++)
{
BaseOffset=BaseOffsetor + read_le_u32(RWEntriesLoc + i);
if (LeftMargin>0)
ftcp += LeftMargin;
TotalRowLength=0;
do
{
ui32 SegmentLength;
SegmentType=FDef[BaseOffset++];
SegmentLength=FDef[BaseOffset++] + 1;
if (SegmentType==0xFF)
{
memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, FDef + BaseOffset, SegmentLength);
BaseOffset+=SegmentLength;
}
else
{
memset(reinterpret_cast<char*>(ret->pixels)+ftcp, SegmentType, SegmentLength);
}
ftcp += SegmentLength;
TotalRowLength += SegmentLength;
}while(TotalRowLength<SpriteWidth);
RowAdd=SpriteWidth-TotalRowLength;
if (RightMargin>0)
ftcp += RightMargin;
if (add>0)
ftcp += add+RowAdd;
}
}
break;
case 2:
{
BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor);
for (ui32 i=0;i<SpriteHeight;i++)
{
//BaseOffset = BaseOffsetor+RWEntries[i];
if (LeftMargin>0)
ftcp += LeftMargin;
TotalRowLength=0;
do
{
SegmentType=FDef[BaseOffset++];
ui8 code = SegmentType / 32;
ui8 value = (SegmentType & 31) + 1;
if(code==7)
{
memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, &FDef[BaseOffset], value);
ftcp += value;
BaseOffset += value;
}
else
{
memset(reinterpret_cast<char*>(ret->pixels)+ftcp, code, value);
ftcp += value;
}
TotalRowLength+=value;
} while(TotalRowLength<SpriteWidth);
if (RightMargin>0)
ftcp += RightMargin;
RowAdd=SpriteWidth-TotalRowLength;
if (add>0)
ftcp += add+RowAdd;
}
}
break;
case 3:
{
for (ui32 i=0;i<SpriteHeight;i++)
{
BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor+i*2*(SpriteWidth/32));
if (LeftMargin>0)
ftcp += LeftMargin;
TotalRowLength=0;
do
{
SegmentType=FDef[BaseOffset++];
ui8 code = SegmentType / 32;
ui8 value = (SegmentType & 31) + 1;
int len = std::min<ui32>(value, SpriteWidth - TotalRowLength) - std::max(0, -LeftMargin);
vstd::amax(len, 0);
if(code==7)
{
memcpy((ui8*)ret->pixels + ftcp, FDef + BaseOffset, len);
ftcp += len;
BaseOffset += len;
}
else
{
memset((ui8*)ret->pixels + ftcp, code, len);
ftcp += len;
}
TotalRowLength+=( LeftMargin>=0 ? value : value+LeftMargin );
}while(TotalRowLength<SpriteWidth);
if (RightMargin>0)
ftcp += RightMargin;
RowAdd=SpriteWidth-TotalRowLength;
if (add>0)
ftcp += add+RowAdd;
}
}
break;
default:
throw std::runtime_error("Unknown sprite format.");
break;
}
SDL_Color ttcol = ret->format->palette->colors[0];
Uint32 keycol = SDL_MapRGBA(ret->format, ttcol.r, ttcol.b, ttcol.g, ttcol.a);
SDL_SetColorKey(ret, SDL_TRUE, keycol);
return ret;
}
CDefHandler * CDefHandler::giveDef(const std::string & defName)
{
ResourceID resID(std::string("SPRITES/") + defName, EResType::ANIMATION);
auto data = CResourceHandler::get()->load(resID)->readAll().first;
if(!data)
throw std::runtime_error("bad def name!");
auto nh = new CDefHandler();
nh->openFromMemory(data.get(), defName);
return nh;
}
-94
View File
@@ -1,94 +0,0 @@
/*
* CDefHandler.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/vcmi_endian.h"
struct SDL_Surface;
struct SDL_Color;
struct Cimage
{
int groupNumber;
std::string imName; //name without extension
SDL_Surface * bitmap;
};
// Def entry in file. Integer fields are all little endian and will
// need to be converted.
struct SDefEntryBlock
{
ui32 unknown1;
ui32 totalInBlock;
ui32 unknown2;
ui32 unknown3;
ui8 data[0];
} PACKED_STRUCT;
// Def entry in file. Integer fields are all little endian and will
// need to be converted.
struct SDefEntry
{
ui32 DEFType;
ui32 width;
ui32 height;
ui32 totalBlocks;
struct {
ui8 R;
ui8 G;
ui8 B;
} palette[256];
// SDefEntry is followed by a series of SDefEntryBlock
// This is commented out because VC++ doesn't accept C99 syntax.
//struct SDefEntryBlock blocks[];
} PACKED_STRUCT;
// Def entry in file. Integer fields are all little endian and will
// need to be converted.
struct SSpriteDef
{
ui32 prSize;
ui32 defType2;
ui32 FullWidth;
ui32 FullHeight;
ui32 SpriteWidth;
ui32 SpriteHeight;
ui32 LeftMargin;
ui32 TopMargin;
} PACKED_STRUCT;
class CDefHandler
{
private:
ui32 DEFType;
struct SEntry
{
std::string name;
int offset;
int group;
} ;
std::vector<SEntry> SEntries ;
void openFromMemory(ui8 * table, const std::string & name);
SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const;
public:
int width, height; //width and height
std::string defName;
std::vector<Cimage> ourImages;
CDefHandler();
~CDefHandler();
static CDefHandler * giveDef(const std::string & defName);
};
+40
View File
@@ -714,6 +714,46 @@ void processCommand(const std::string &message)
std::cout << "\rExtracting done :)\n"; std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n"; std::cout << " Extracted files can be found in " << outPath << " directory\n";
} }
else if(message=="get config")
{
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted" / "configuration";
bfs::create_directories(outPath);
const std::vector<std::string> contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"};
for(auto contentName : contentNames)
{
auto & content = VLC->modh->content[contentName];
auto contentOutPath = outPath / contentName;
bfs::create_directories(contentOutPath);
for(auto & iter : content.modData)
{
const JsonNode & modData = iter.second.modData;
for(auto & nameAndObject : modData.Struct())
{
const JsonNode & object = nameAndObject.second;
std::string name = CModHandler::normalizeIdentifier(object.meta, "core", nameAndObject.first);
boost::algorithm::replace_all(name,":","_");
const bfs::path filePath = contentOutPath / (name + ".json");
bfs::ofstream file(filePath);
file << object.toJson();
}
}
}
std::cout << "\rExtracting done :)\n";
std::cout << " Extracted files can be found in " << outPath << " directory\n";
}
else if(message=="get txt") else if(message=="get txt")
{ {
std::cout << "Command accepted.\t"; std::cout << "Command accepted.\t";
-2
View File
@@ -45,7 +45,6 @@ set(client_SRCS
CBitmapHandler.cpp CBitmapHandler.cpp
CreatureCostBox.cpp CreatureCostBox.cpp
CDefHandler.cpp
CGameInfo.cpp CGameInfo.cpp
Client.cpp Client.cpp
CMessage.cpp CMessage.cpp
@@ -103,7 +102,6 @@ set(client_HEADERS
CBitmapHandler.h CBitmapHandler.h
CreatureCostBox.h CreatureCostBox.h
CDefHandler.h
CGameInfo.h CGameInfo.h
Client.h Client.h
CMessage.h CMessage.h
+113 -101
View File
@@ -42,7 +42,8 @@
#include "../lib/JsonNode.h" #include "../lib/JsonNode.h"
#include "CMusicHandler.h" #include "CMusicHandler.h"
#include "../lib/CondSh.h" #include "../lib/CondSh.h"
#include "../lib/NetPacks.h" #include "../lib/NetPacksBase.h"
#include "../lib/NetPacks.h"//todo: remove
#include "../lib/mapping/CMap.h" #include "../lib/mapping/CMap.h"
#include "../lib/VCMIDirs.h" #include "../lib/VCMIDirs.h"
#include "mapHandler.h" #include "mapHandler.h"
@@ -695,80 +696,91 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
BATTLE_EVENT_POSSIBLE_RETURN; BATTLE_EVENT_POSSIBLE_RETURN;
} }
void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
void CPlayerInterface::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
{ {
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN; BATTLE_EVENT_POSSIBLE_RETURN;
for (auto & healedStack : healedStacks) for(auto & info : units)
{ {
const CStack * healed = cb->battleGetStackByID(healedStack.first); switch(info.operation)
if (battleInt->creAnims[healed->ID]->isDead())
{ {
//stack has been resurrected case UnitChanges::EOperation::RESET_STATE:
battleInt->creAnims[healed->ID]->setType(CCreatureAnim::HOLDING); {
const battle::Unit * unit = cb->battleGetUnitByID(info.id);
if(!unit)
{
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;
}
CCreatureAnimation * animation = iter->second;
if(unit->alive() && animation->isDead())
animation->setType(CCreatureAnim::HOLDING);
//TODO: handle more cases
}
break;
case UnitChanges::EOperation::REMOVE:
battleInt->stackRemoved(info.id);
break;
case UnitChanges::EOperation::ADD:
{
const CStack * unit = cb->battleGetStackByID(info.id);
if(!unit)
{
logGlobal->error("Invalid unit ID %d", info.id);
continue;
}
battleInt->unitAdded(unit);
}
break;
default:
logGlobal->error("Unknown unit operation %d", (int)info.operation);
break;
} }
} }
if(lifeDrain) battleInt->displayCustomEffects(customEffects);
battleInt->displayBattleLog(battleLog);
}
void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
bool needUpdate = false;
for(auto & change : obstacles)
{ {
const CStack * attacker = cb->battleGetStackByID(healedStacks[0].first, false); if(change.operation == BattleChanges::EOperation::ADD)
const CStack * defender = cb->battleGetStackByID(lifeDrainFrom, false);
if(attacker && defender)
{ {
battleInt->displayEffect(52, attacker->position); //TODO: transparency auto instance = cb->battleGetObstacleByID(change.id);
CCS->soundh->playSound(soundBase::DRAINLIF); if(instance)
battleInt->obstaclePlaced(*instance);
MetaString text; else
attacker->addText(text, MetaString::GENERAL_TXT, 361); logNetwork->error("Invalid obstacle instance %d", change.id);
attacker->addNameReplacement(text, false);
text.addReplacement(healedStacks[0].second);
defender->addNameReplacement(text, true);
battleInt->console->addText(text.toString());
} }
else else
{ {
logGlobal->error("Unable to display life drain info"); needUpdate = true;
} }
} }
if(tentHeal)
{
const CStack * healer = cb->battleGetStackByID(lifeDrainFrom, false);
const CStack * target = cb->battleGetStackByID(healedStacks[0].first, false);
if(healer && target) if(needUpdate)
{ //update accessible hexes
MetaString text; battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
text.addTxt(MetaString::GENERAL_TXT, 414);
healer->addNameReplacement(text, false);
target->addNameReplacement(text, false);
text.addReplacement(healedStacks[0].second);
battleInt->console->addText(text.toString());
}
else
{
logGlobal->error("Unable to display tent heal info");
}
}
}
void CPlayerInterface::battleNewStackAppeared(const CStack * stack)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
battleInt->newStack(stack);
}
void CPlayerInterface::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
//update accessible hexes
battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
} }
void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca) void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
@@ -779,17 +791,6 @@ void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
battleInt->stackIsCatapulting(ca); battleInt->stackIsCatapulting(ca);
} }
void CPlayerInterface::battleStacksRemoved(const BattleStacksRemoved & bsr)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
for (auto & elem : bsr.stackIDs) //for each removed stack
{
battleInt->stackRemoved(elem);
}
}
void CPlayerInterface::battleNewRound(int round) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn void CPlayerInterface::battleNewRound(int round) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
{ {
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -864,9 +865,9 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
BattleAction ret = *(CBattleInterface::givenCommand.data); BattleAction ret = *(CBattleInterface::givenCommand.data);
vstd::clear_pointer(CBattleInterface::givenCommand.data); vstd::clear_pointer(CBattleInterface::givenCommand.data);
if (ret.actionType == Battle::CANCEL) if(ret.actionType == EActionType::CANCEL)
{ {
if (stackId != ret.stackNumber) if(stackId != ret.stackNumber)
logGlobal->error("Not current active stack action canceled"); logGlobal->error("Not current active stack action canceled");
logGlobal->trace("Canceled command for %s", stackName); logGlobal->trace("Canceled command for %s", stackName);
} }
@@ -932,37 +933,36 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
RETURN_IF_QUICK_COMBAT; RETURN_IF_QUICK_COMBAT;
battleInt->battleTriggerEffect(bte); battleInt->battleTriggerEffect(bte);
} }
void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
{ {
EVENT_HANDLER_CALLED_BY_CLIENT; EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN; BATTLE_EVENT_POSSIBLE_RETURN;
std::vector<StackAttackedInfo> arg; std::vector<StackAttackedInfo> arg;
for (auto & elem : bsa) for(auto & elem : bsa)
{ {
const CStack *defender = cb->battleGetStackByID(elem.stackAttacked, false); const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false);
const CStack *attacker = cb->battleGetStackByID(elem.attackerID, false); const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false);
if (elem.isEffect()) if(elem.isEffect())
{ {
if (defender && !elem.isSecondary()) if(defender && !elem.isSecondary())
battleInt->displayEffect(elem.effect, defender->position); battleInt->displayEffect(elem.effect, defender->getPosition());
} }
if (elem.isSpell()) if(elem.isSpell())
{ {
if (defender) if(defender)
battleInt->displaySpellEffect(elem.spellID, defender->position); battleInt->displaySpellEffect(elem.spellID, defender->getPosition());
} }
//FIXME: why action is deleted during enchanter cast? //FIXME: why action is deleted during enchanter cast?
bool remoteAttack = false; bool remoteAttack = false;
if (LOCPLINT->curAction) if(LOCPLINT->curAction)
remoteAttack |= LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK; remoteAttack |= LOCPLINT->curAction->actionType != EActionType::WALK_AND_ATTACK;
StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()}; StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
arg.push_back(to_put); arg.push_back(to_put);
} }
battleInt->stacksAreAttacked(arg, battleLog);
battleInt->stacksAreAttacked(arg);
} }
void CPlayerInterface::battleAttack(const BattleAttack * ba) void CPlayerInterface::battleAttack(const BattleAttack * ba)
{ {
@@ -982,13 +982,13 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
if(ba->lucky()) //lucky hit if(ba->lucky()) //lucky hit
{ {
battleInt->console->addText(attacker->formatGeneralMessage(-45)); battleInt->console->addText(attacker->formatGeneralMessage(-45));
battleInt->displayEffect(18, attacker->position); battleInt->displayEffect(18, attacker->getPosition());
CCS->soundh->playSound(soundBase::GOODLUCK); CCS->soundh->playSound(soundBase::GOODLUCK);
} }
if(ba->unlucky()) //unlucky hit if(ba->unlucky()) //unlucky hit
{ {
battleInt->console->addText(attacker->formatGeneralMessage(-44)); battleInt->console->addText(attacker->formatGeneralMessage(-44));
battleInt->displayEffect(48, attacker->position); battleInt->displayEffect(48, attacker->getPosition());
CCS->soundh->playSound(soundBase::BADLUCK); CCS->soundh->playSound(soundBase::BADLUCK);
} }
if(ba->deathBlow()) if(ba->deathBlow())
@@ -997,12 +997,23 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
for(auto & elem : ba->bsa) for(auto & elem : ba->bsa)
{ {
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->displayEffect(73, attacked->position); battleInt->displayEffect(73, attacked->getPosition());
} }
CCS->soundh->playSound(soundBase::deathBlow); CCS->soundh->playSound(soundBase::deathBlow);
} }
battleInt->displayCustomEffects(ba->customEffects);
battleInt->waitForAnims(); 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()) if(ba->shot())
{ {
for(auto & elem : ba->bsa) for(auto & elem : ba->bsa)
@@ -1010,17 +1021,22 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
if(!elem.isSecondary()) //display projectile only for primary target if(!elem.isSecondary()) //display projectile only for primary target
{ {
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->stackAttacking(attacker, attacked->position, attacked, true); battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
} }
} }
} }
else else
{ {
auto attackFrom = actionTarget.at(0).hexValue;
auto attackTarget = actionTarget.at(1).hexValue;
//TODO: use information from BattleAttack but not curAction
int shift = 0; int shift = 0;
if(ba->counter() && BattleHex::mutualPosition(curAction->destinationTile, attacker->position) < 0) if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0)
{ {
int distp = BattleHex::getDistance(curAction->destinationTile + 1, attacker->position); int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition());
int distm = BattleHex::getDistance(curAction->destinationTile - 1, attacker->position); int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition());
if(distp < distm) if(distp < distm)
shift = 1; shift = 1;
@@ -1028,25 +1044,21 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
shift = -1; shift = -1;
} }
const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked); const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
battleInt->stackAttacking(attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false); battleInt->stackAttacking(attacker, ba->counter() ? BattleHex(attackTarget + shift) : attackTarget, attacked, false);
} }
//battleInt->waitForAnims(); //FIXME: freeze //battleInt->waitForAnims(); //FIXME: freeze
if(ba->spellLike()) if(ba->spellLike())
{ {
//TODO: use information from BattleAttack but not curAction
auto destination = actionTarget.at(0).hexValue;
//display hit animation //display hit animation
SpellID spellID = ba->spellID; SpellID spellID = ba->spellID;
battleInt->displaySpellHit(spellID, curAction->destinationTile); battleInt->displaySpellHit(spellID, destination);
} }
} }
void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
battleInt->obstaclePlaced(obstacle);
}
void CPlayerInterface::battleGateStateChanged(const EGateState state) void CPlayerInterface::battleGateStateChanged(const EGateState state)
{ {
+3 -6
View File
@@ -212,15 +212,12 @@ public:
void battleSpellCast(const BattleSpellCast *sc) override; void battleSpellCast(const BattleSpellCast *sc) override;
void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks 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 battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) 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 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 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 battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; //called when stacks are healed / resurrected void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog) override;
void battleNewStackAppeared(const CStack * stack) override; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override; //called when a certain set of obstacles is removed from batlefield; IDs of them are given
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
void battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
void battleObstaclePlaced(const CObstacleInstance &obstacle) override;
void battleGateStateChanged(const EGateState state) override; void battleGateStateChanged(const EGateState state) override;
void yourTacticPhase(int distance) override; void yourTacticPhase(int distance) override;
+2 -1
View File
@@ -3455,7 +3455,8 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
//get header //get header
std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second; std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
auto buffer = reinterpret_cast<const ui8 *>(headerStr.data()); auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName); CMapService mapService;
ourHeader = mapService.loadMapHeader(buffer, headerStr.size(), scenarioName);
std::map<ui8, std::string> names; std::map<ui8, std::string> names;
names[1] = settings["general"]["playerName"].String(); names[1] = settings["general"]["playerName"].String();
+5 -3
View File
@@ -41,6 +41,7 @@
#include "../lib/VCMI_Lib.h" #include "../lib/VCMI_Lib.h"
#include "../lib/VCMIDirs.h" #include "../lib/VCMIDirs.h"
#include "../lib/mapping/CMap.h" #include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapService.h"
#include "../lib/JsonNode.h" #include "../lib/JsonNode.h"
#include "mapHandler.h" #include "mapHandler.h"
#include "../lib/CConfigHandler.h" #include "../lib/CConfigHandler.h"
@@ -153,7 +154,7 @@ void CClient::waitForMoveAndSend(PlayerColor color)
setThreadName("CClient::waitForMoveAndSend"); setThreadName("CClient::waitForMoveAndSend");
assert(vstd::contains(battleints, color)); assert(vstd::contains(battleints, color));
BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false)); BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
if(ba.actionType != Battle::CANCEL) if(ba.actionType != EActionType::CANCEL)
{ {
logNetwork->trace("Send battle action to server: %s", ba.toString()); logNetwork->trace("Send battle action to server: %s", ba.toString());
MakeAction temp_action(ba); MakeAction temp_action(ba);
@@ -413,10 +414,11 @@ void CClient::newGame( CConnection *con, StartInfo *si )
c.disableSmartPointerSerialization(); c.disableSmartPointerSerialization();
// Initialize game state // Initialize game state
CMapService mapService;
gs = new CGameState(); gs = new CGameState();
logNetwork->info("\tCreating gamestate: %i",tmh.getDiff()); logNetwork->info("\tCreating gamestate: %i",tmh.getDiff());
gs->init(si, settings["general"]["saveRandomMaps"].Bool()); gs->init(&mapService, si, settings["general"]["saveRandomMaps"].Bool());
logNetwork->info("Initializing GameState (together): %d ms", tmh.getDiff()); logNetwork->info("Initializing GameState (together): %d ms", tmh.getDiff());
// Now after possible random map gen, we know exact player count. // Now after possible random map gen, we know exact player count.
@@ -951,7 +953,7 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
if(needCallback) if(needCallback)
{ {
logGlobal->trace("\tInitializing the battle interface for player %s", *color); logGlobal->trace("\tInitializing the battle interface for player %s", *color);
auto cbc = std::make_shared<CBattleCallback>(gs, color, this); auto cbc = std::make_shared<CBattleCallback>(color, this);
battleCallbacks[colorUsed] = cbc; battleCallbacks[colorUsed] = cbc;
battleInterface->init(cbc); battleInterface->init(cbc);
} }
+1 -1
View File
@@ -26,7 +26,7 @@ class CGameState;
class CGameInterface; class CGameInterface;
class CConnection; class CConnection;
class CCallback; class CCallback;
struct BattleAction; class BattleAction;
struct SharedMemory; struct SharedMemory;
class CClient; class CClient;
class CScriptingModule; class CScriptingModule;
+6 -43
View File
@@ -645,11 +645,6 @@ void BattleTriggerEffect::applyCl(CClient * cl)
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, *this); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, *this);
} }
void BattleObstaclePlaced::applyCl(CClient * cl)
{
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclePlaced, *obstacle);
}
void BattleUpdateGateState::applyFirstCl(CClient * cl) void BattleUpdateGateState::applyFirstCl(CClient * cl)
{ {
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleGateStateChanged, state); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleGateStateChanged, state);
@@ -667,30 +662,14 @@ void BattleStackMoved::applyFirstCl(CClient *cl)
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance);
} }
//void BattleStackAttacked::(CClient *cl)
void BattleStackAttacked::applyFirstCl(CClient *cl)
{
std::vector<BattleStackAttacked> bsa;
bsa.push_back(*this);
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
}
void BattleAttack::applyFirstCl(CClient *cl) void BattleAttack::applyFirstCl(CClient *cl)
{ {
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleAttack, this); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleAttack, this);
for (auto & elem : bsa)
{
for (int z=0; z<elem.healedStacks.size(); ++z)
{
elem.healedStacks[z].applyCl(cl);
}
}
} }
void BattleAttack::applyCl(CClient *cl) void BattleAttack::applyCl(CClient *cl)
{ {
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa, battleLog);
} }
void StartAction::applyFirstCl(CClient *cl) void StartAction::applyFirstCl(CClient *cl)
@@ -712,7 +691,7 @@ void SetStackEffect::applyCl(CClient *cl)
void StacksInjured::applyCl(CClient *cl) void StacksInjured::applyCl(CClient *cl)
{ {
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks, battleLog);
} }
void BattleResultsApplied::applyCl(CClient *cl) void BattleResultsApplied::applyCl(CClient *cl)
@@ -722,20 +701,15 @@ void BattleResultsApplied::applyCl(CClient *cl)
callInterfaceIfPresent(cl, PlayerColor::SPECTATOR, &IGameEventsReceiver::battleResultsApplied); callInterfaceIfPresent(cl, PlayerColor::SPECTATOR, &IGameEventsReceiver::battleResultsApplied);
} }
void StacksHealedOrResurrected::applyCl(CClient * cl) void BattleUnitsChanged::applyCl(CClient * cl)
{ {
std::vector<std::pair<ui32, ui32> > shiftedHealed; callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects, battleLog);
for(auto & elem : healedStacks)
{
shiftedHealed.push_back(std::make_pair(elem.stackId, (ui32)elem.delta));
}
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksHealedRes, shiftedHealed, lifeDrain, tentHealing, drainedFrom);
} }
void ObstaclesRemoved::applyCl(CClient *cl) void BattleObstaclesChanged::applyCl(CClient *cl)
{ {
//inform interfaces about removed obstacles //inform interfaces about removed obstacles
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesRemoved, obstacles); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesChanged, changes);
} }
void CatapultAttack::applyCl(CClient *cl) void CatapultAttack::applyCl(CClient *cl)
@@ -744,17 +718,6 @@ void CatapultAttack::applyCl(CClient *cl)
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleCatapultAttacked, *this); callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleCatapultAttacked, *this);
} }
void BattleStacksRemoved::applyFirstCl(CClient * cl)
{
//inform interfaces about removed stacks
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksRemoved, *this);
}
void BattleStackAdded::applyCl(CClient *cl)
{
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleNewStackAppeared, GS(cl)->curB->stacks.back());
}
CGameState* CPackForClient::GS(CClient *cl) CGameState* CPackForClient::GS(CClient *cl)
{ {
return cl->gs; return cl->gs;
-2
View File
@@ -104,8 +104,6 @@
<Unit filename="../CCallback.h" /> <Unit filename="../CCallback.h" />
<Unit filename="CBitmapHandler.cpp" /> <Unit filename="CBitmapHandler.cpp" />
<Unit filename="CBitmapHandler.h" /> <Unit filename="CBitmapHandler.h" />
<Unit filename="CDefHandler.cpp" />
<Unit filename="CDefHandler.h" />
<Unit filename="CGameInfo.cpp" /> <Unit filename="CGameInfo.cpp" />
<Unit filename="CGameInfo.h" /> <Unit filename="CGameInfo.h" />
<Unit filename="CMT.cpp" /> <Unit filename="CMT.cpp" />
+48 -38
View File
@@ -138,7 +138,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
dest(_dest), attackedStack(defender), attackingStack(attacker) dest(_dest), attackedStack(defender), attackingStack(attacker)
{ {
assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n"); assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n");
attackingStackPosBeforeReturn = attackingStack->position; attackingStackPosBeforeReturn = attackingStack->getPosition();
} }
CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner) CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
@@ -184,9 +184,9 @@ bool CDefenceAnimation::init()
//reverse unit if necessary //reverse unit if necessary
if (attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->position, attacker->position, owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID])) if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID]))
{ {
owner->addNewAnim(new CReverseAnimation(owner, stack, stack->position, true)); owner->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
return false; return false;
} }
//unit reversed //unit reversed
@@ -226,11 +226,10 @@ std::string CDefenceAnimation::getMySound()
{ {
if(killed) if(killed)
return battle_sound(stack->getCreature(), killed); return battle_sound(stack->getCreature(), killed);
else if(stack->defendingAnim)
if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
return battle_sound(stack->getCreature(), defend); return battle_sound(stack->getCreature(), defend);
else
return battle_sound(stack->getCreature(), wince); return battle_sound(stack->getCreature(), wince);
} }
CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType() CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
@@ -243,10 +242,10 @@ CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
return CCreatureAnim::DEATH; return CCreatureAnim::DEATH;
} }
if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM)) if(stack->defendingAnim)
return CCreatureAnim::DEFENCE; return CCreatureAnim::DEFENCE;
else
return CCreatureAnim::HITTED; return CCreatureAnim::HITTED;
} }
void CDefenceAnimation::startAnimation() void CDefenceAnimation::startAnimation()
@@ -324,7 +323,7 @@ bool CMeleeAttackAnimation::init()
return false; return false;
} }
bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->position, owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]); bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]);
if(toReverse) if(toReverse)
{ {
@@ -366,7 +365,7 @@ bool CMeleeAttackAnimation::init()
int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest); int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest);
if(mutPos == -1 && attackingStack->doubleWide()) if(mutPos == -1 && attackingStack->doubleWide())
{ {
mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->position); mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->getPosition());
} }
if (mutPos == -1 && attackedStack->doubleWide()) if (mutPos == -1 && attackedStack->doubleWide())
{ {
@@ -558,7 +557,7 @@ CMovementAnimation::CMovementAnimation(CBattleInterface *_owner, const CStack *_
: CBattleStackAnimation(_owner, _stack), : CBattleStackAnimation(_owner, _stack),
destTiles(_destTiles), destTiles(_destTiles),
curentMoveIndex(0), curentMoveIndex(0),
oldPos(stack->position), oldPos(stack->getPosition()),
begX(0), begY(0), begX(0), begY(0),
distanceX(0), distanceY(0), distanceX(0), distanceY(0),
timeToMove(0.0), timeToMove(0.0),
@@ -731,16 +730,18 @@ bool CShootingAnimation::init()
} }
//reverse unit if necessary //reverse unit if necessary
if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
{ {
owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
return false; return false;
} }
// opponent must face attacker ( = different directions) before he can be attacked // opponent must face attacker ( = different directions) before he can be attacked
if (attackingStack && attackedStack && //FIXME: this cause freeze
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
return false; // if (attackingStack && attackedStack &&
// owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
// return false;
// Create the projectile animation // Create the projectile animation
@@ -780,7 +781,7 @@ bool CShootingAnimation::init()
int multiplier = spi.reverse ? -1 : 1; int multiplier = spi.reverse ? -1 : 1;
double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
if(shooter->position < dest) if(shooter->getPosition() < dest)
projectileAngle = -projectileAngle; projectileAngle = -projectileAngle;
// Calculate projectile start position. Offsets are read out of the CRANIM.TXT. // Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
@@ -920,7 +921,7 @@ CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacke
: CRangedAttackAnimation(owner_, attacker, dest_, defender) : CRangedAttackAnimation(owner_, attacker, dest_, defender)
{ {
if(!dest_.isValid() && defender) if(!dest_.isValid() && defender)
dest = defender->position; dest = defender->getPosition();
} }
bool CCastAnimation::init() bool CCastAnimation::init()
@@ -937,17 +938,17 @@ bool CCastAnimation::init()
//reverse unit if necessary //reverse unit if necessary
if(attackedStack) if(attackedStack)
{ {
if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, attackedStack->position, owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
{ {
owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
return false; return false;
} }
} }
else else
{ {
if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->position, dest, owner->creDir[attackingStack->ID], false, false)) if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, owner->creDir[attackingStack->ID], false, false))
{ {
owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->position, true)); owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
return false; return false;
} }
} }
@@ -962,13 +963,13 @@ bool CCastAnimation::init()
// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft(); fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft();
//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); //xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner);
destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner); destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
if(attackingStack->position < dest) if(attackingStack->getPosition() < dest)
projectileAngle = -projectileAngle; projectileAngle = -projectileAngle;
@@ -1045,7 +1046,6 @@ void CCastAnimation::endAnim()
CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom) CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
: CBattleAnimation(_owner), : CBattleAnimation(_owner),
destTile(BattleHex::INVALID), destTile(BattleHex::INVALID),
customAnim(_customAnim),
x(_x), x(_x),
y(_y), y(_y),
dx(_dx), dx(_dx),
@@ -1053,13 +1053,29 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo
Vflip(_Vflip), Vflip(_Vflip),
alignToBottom(_alignToBottom) alignToBottom(_alignToBottom)
{ {
logAnim->debug("Created effect animation %s", customAnim); logAnim->debug("Created effect animation %s", _customAnim);
customAnim = std::make_shared<CAnimation>(_customAnim);
} }
CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx, int _dy)
: CBattleAnimation(_owner),
destTile(BattleHex::INVALID),
customAnim(_customAnim),
x(_x),
y(_y),
dx(_dx),
dy(_dy),
Vflip(false),
alignToBottom(false)
{
logAnim->debug("Created custom effect animation");
}
CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom) CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
: CBattleAnimation(_owner), : CBattleAnimation(_owner),
destTile(_destTile), destTile(_destTile),
customAnim(_customAnim),
x(-1), x(-1),
y(-1), y(-1),
dx(0), dx(0),
@@ -1067,24 +1083,18 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo
Vflip(_Vflip), Vflip(_Vflip),
alignToBottom(_alignToBottom) alignToBottom(_alignToBottom)
{ {
logAnim->debug("Created effect animation %s", customAnim); logAnim->debug("Created effect animation %s", _customAnim);
customAnim = std::make_shared<CAnimation>(_customAnim);
} }
bool CEffectAnimation::init() bool CEffectAnimation::init()
{ {
if(!isEarliest(true)) if(!isEarliest(true))
return false; return false;
if(customAnim.empty())
{
endAnim();
return false;
}
const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1); const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
std::shared_ptr<CAnimation> animation = std::make_shared<CAnimation>(customAnim); std::shared_ptr<CAnimation> animation = customAnim;
animation->preload(); animation->preload();
if(Vflip) if(Vflip)
+4 -1
View File
@@ -238,7 +238,7 @@ class CEffectAnimation : public CBattleAnimation
{ {
private: private:
BattleHex destTile; BattleHex destTile;
std::string customAnim; std::shared_ptr<CAnimation> customAnim;
int x, y, dx, dy; int x, y, dx, dy;
bool Vflip; bool Vflip;
bool alignToBottom; bool alignToBottom;
@@ -248,6 +248,9 @@ public:
void endAnim() 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::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); CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
virtual ~CEffectAnimation(){}; virtual ~CEffectAnimation(){};
}; };
File diff suppressed because it is too large Load Diff
+22 -21
View File
@@ -19,7 +19,6 @@
class CLabel; class CLabel;
class CCreatureSet; class CCreatureSet;
class CGHeroInstance; class CGHeroInstance;
class CDefHandler;
class CStack; class CStack;
class CCallback; class CCallback;
class CButton; class CButton;
@@ -30,7 +29,7 @@ struct BattleSpellCast;
struct CObstacleInstance; struct CObstacleInstance;
template <typename T> struct CondSh; template <typename T> struct CondSh;
struct SetStackEffect; struct SetStackEffect;
struct BattleAction; class BattleAction;
class CGTownInstance; class CGTownInstance;
struct CatapultAttack; struct CatapultAttack;
struct CatapultProjectileInfo; struct CatapultProjectileInfo;
@@ -46,15 +45,16 @@ struct ProjectileInfo;
class CClickableHex; class CClickableHex;
struct BattleHex; struct BattleHex;
struct InfoAboutHero; struct InfoAboutHero;
struct BattleAction;
class CBattleGameInterface; class CBattleGameInterface;
struct CustomEffectInfo;
class CAnimation; class CAnimation;
class IImage;
/// Small struct which contains information about the id of the attacked stack, the damage dealt,... /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
struct StackAttackedInfo struct StackAttackedInfo
{ {
const CStack *defender; //attacked stack const CStack *defender; //attacked stack
int32_t dmg; //damage dealt int64_t dmg; //damage dealt
unsigned int amountKilled; //how many creatures in stack has been killed unsigned int amountKilled; //how many creatures in stack has been killed
const CStack *attacker; //attacking stack const CStack *attacker; //attacking stack
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
@@ -132,15 +132,8 @@ private:
std::map<int, std::shared_ptr<CAnimation>> idToProjectile; std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
std::map<int, CDefHandler *> idToObstacle; //obstacles located on the battlefield std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
std::map<int, SDL_Surface *> idToAbsoluteObstacle; //obstacles located on the battlefield std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
//TODO these should be loaded only when needed (and freed then) but I believe it's rather work for resource manager,
//so I didn't implement that (having ongoing RM development)
CDefHandler *landMine;
CDefHandler *quicksand;
CDefHandler *fireWall;
CDefHandler *smallForceField[2], *bigForceField[2]; // [side]
std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
ui8 animCount; ui8 animCount;
@@ -185,7 +178,9 @@ private:
void printConsoleAttacked(const CStack *defender, int dmg, int killed, const CStack *attacker, bool Multiple); void printConsoleAttacked(const CStack *defender, int dmg, int killed, const CStack *attacker, bool Multiple);
std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield
void giveCommand(Battle::ActionType action, BattleHex tile, ui32 stackID, si32 additional=-1, si32 selectedStack = -1); 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 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 bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
@@ -259,12 +254,14 @@ private:
BattleObjectsByHex sortObjectsByHex(); BattleObjectsByHex sortObjectsByHex();
void updateBattleAnimations(); void updateBattleAnimations();
SDL_Surface *getObstacleImage(const CObstacleInstance &oi); IImage * getObstacleImage(const CObstacleInstance & oi);
Point getObstaclePosition(SDL_Surface *image, const CObstacleInstance &obstacle);
Point getObstaclePosition(IImage * image, const CObstacleInstance & obstacle);
void redrawBackgroundWithHexes(const CStack *activeStack); void redrawBackgroundWithHexes(const CStack *activeStack);
/** End of battle screen blitting methods */ /** End of battle screen blitting methods */
PossibleActions getCasterAction(const CSpell *spell, const ISpellCaster *caster, ECastingMode::ECastingMode mode) const; PossibleActions getCasterAction(const CSpell *spell, const spells::Caster *caster, spells::Mode mode) const;
void setHeroAnimation(ui8 side, int phase); void setHeroAnimation(ui8 side, int phase);
public: public:
@@ -329,12 +326,12 @@ public:
//call-ins //call-ins
void startAction(const BattleAction* action); void startAction(const BattleAction* action);
void newStack(const CStack *stack); //new stack appeared on battlefield void unitAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(int stackID); //stack disappeared from batlefiled void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed 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 stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void waitForAnims(); void waitForAnims();
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos, const std::vector<MetaString> & battleLog); //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 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 newRoundFirst( int round );
void newRound(int number); //caled when round is ended; number is the number of round void newRound(int number); //caled when round is ended; number is the number of round
@@ -345,6 +342,10 @@ public:
void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell 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 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 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 displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
@@ -370,7 +371,7 @@ public:
void gateStateChanged(const EGateState state); void gateStateChanged(const EGateState state);
void initStackProjectile(const CStack *stack); void initStackProjectile(const CStack * stack);
const CGHeroInstance *currentHero() const; const CGHeroInstance *currentHero() const;
InfoAboutHero enemyHero() const; InfoAboutHero enemyHero() const;
+90 -65
View File
@@ -203,7 +203,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
if(!myHero || down || !myOwner->myTurn) if(!myHero || down || !myOwner->myTurn)
return; return;
if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, 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 for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
{ {
@@ -505,9 +505,9 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
assert(cbi); assert(cbi);
Point ret(-500, -500); //returned value Point ret(-500, -500); //returned value
if(stack && stack->position < 0) //creatures in turrets if(stack && stack->initialPosition < 0) //creatures in turrets
{ {
switch(stack->position) switch(stack->initialPosition)
{ {
case -2: //keep case -2: //keep
ret = cbi->siegeH->town->town->clientInfo.siegePositions[18]; ret = cbi->siegeH->town->town->clientInfo.siegePositions[18];
@@ -669,105 +669,130 @@ CHeroInfoWindow::CHeroInfoWindow(const InfoAboutHero &hero, Point *position) : C
new CLabel(39, 186, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints)); new CLabel(39, 186, EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, std::to_string(currentSpellPoints) + "/" + std::to_string(maxSpellPoints));
} }
void CStackQueue::update()
{
stacksSorted.clear();
owner->getCurrentPlayerInterface()->cb->battleGetStackQueue(stacksSorted, stackBoxes.size());
if(stacksSorted.size())
{
for (int i = 0; i < stackBoxes.size() ; i++)
{
stackBoxes[i]->setStack(stacksSorted[i]);
}
}
else
{
//no stacks on battlefield... what to do with queue?
}
}
CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner) CStackQueue::CStackQueue(bool Embedded, CBattleInterface * _owner)
:embedded(Embedded), owner(_owner) : embedded(Embedded),
owner(_owner)
{ {
OBJ_CONSTRUCTION_CAPTURING_ALL; OBJ_CONSTRUCTION_CAPTURING_ALL;
if(embedded) if(embedded)
{ {
bg = nullptr;
pos.w = QUEUE_SIZE * 37; pos.w = QUEUE_SIZE * 37;
pos.h = 46; pos.h = 46;
pos.x = screen->w/2 - pos.w/2; pos.x = screen->w/2 - pos.w/2;
pos.y = (screen->h - 600)/2 + 10; pos.y = (screen->h - 600)/2 + 10;
icons = std::make_shared<CAnimation>("CPRSMALL");
stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
} }
else else
{ {
bg = BitmapHandler::loadBitmap("DIBOXBCK");
pos.w = 800; pos.w = 800;
pos.h = 85; pos.h = 85;
new CFilledTexture("DIBOXBCK", Rect(0,0, pos.w, pos.h));
icons = std::make_shared<CAnimation>("TWCRPORT");
stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
//TODO: where use big icons?
//stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESBIG");
} }
stateIcons->preload();
stackBoxes.resize(QUEUE_SIZE); stackBoxes.resize(QUEUE_SIZE);
for (int i = 0; i < stackBoxes.size(); i++) for (int i = 0; i < stackBoxes.size(); i++)
{ {
stackBoxes[i] = new StackBox(embedded); stackBoxes[i] = new StackBox(this);
stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80)*i, 0)); stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80)*i, 0));
} }
} }
CStackQueue::~CStackQueue() CStackQueue::~CStackQueue()
{ {
SDL_FreeSurface(bg);
} }
void CStackQueue::showAll(SDL_Surface * to) void CStackQueue::update()
{ {
blitBg(to); std::vector<battle::Units> queueData;
CIntObject::showAll(to); owner->getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
}
void CStackQueue::blitBg( SDL_Surface * to ) size_t boxIndex = 0;
{
if(bg) for(size_t turn = 0; turn < queueData.size() && boxIndex < stackBoxes.size(); turn++)
{ {
SDL_SetClipRect(to, &pos); for(size_t unitIndex = 0; unitIndex < queueData[turn].size() && boxIndex < stackBoxes.size(); boxIndex++, unitIndex++)
CSDL_Ext::fillTexture(to, bg); stackBoxes[boxIndex]->setStack(queueData[turn][unitIndex], turn);
SDL_SetClipRect(to, nullptr);
} }
for(; boxIndex < stackBoxes.size(); boxIndex++)
stackBoxes[boxIndex]->setStack(nullptr);
} }
void CStackQueue::StackBox::showAll(SDL_Surface * to) CStackQueue::StackBox::StackBox(CStackQueue * owner)
{ : bg(nullptr),
assert(stack); icon(nullptr),
bg->colorize(stack->owner); amount(nullptr),
CIntObject::showAll(to); stateIcon(nullptr)
if(small)
printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 7, FONT_SMALL, Colors::WHITE, to);
else
printAtMiddleLoc(makeNumberShort(stack->getCount()), pos.w/2, pos.h - 8, FONT_MEDIUM, Colors::WHITE, to);
}
void CStackQueue::StackBox::setStack( const CStack *stack )
{
this->stack = stack;
assert(stack);
icon->setFrame(stack->getCreature()->iconIndex);
}
CStackQueue::StackBox::StackBox(bool small):
stack(nullptr),
small(small)
{ {
OBJ_CONSTRUCTION_CAPTURING_ALL; OBJ_CONSTRUCTION_CAPTURING_ALL;
bg = new CPicture(small ? "StackQueueSmall" : "StackQueueLarge" ); bg = new CPicture(owner->embedded ? "StackQueueSmall" : "StackQueueLarge" );
if (small)
{
icon = new CAnimImage("CPRSMALL", 0, 0, 5, 2);
}
else
icon = new CAnimImage("TWCRPORT", 0, 0, 9, 1);
pos.w = bg->pos.w; pos.w = bg->pos.w;
pos.h = bg->pos.h; pos.h = bg->pos.h;
if(owner->embedded)
{
icon = new CAnimImage(owner->icons, 0, 0, 5, 2);
amount = new CLabel(pos.w/2, pos.h - 7, FONT_SMALL, CENTER, Colors::WHITE);
}
else
{
icon = new CAnimImage(owner->icons, 0, 0, 9, 1);
amount = new CLabel(pos.w/2, pos.h - 8, FONT_MEDIUM, CENTER, Colors::WHITE);
int icon_x = pos.w - 17;
int icon_y = pos.h - 18;
stateIcon = new CAnimImage(owner->stateIcons, 0, 0, icon_x, icon_y);
stateIcon->visible = false;
}
}
void CStackQueue::StackBox::setStack(const battle::Unit * nStack, size_t turn)
{
if(nStack)
{
bg->colorize(nStack->unitOwner());
icon->visible = true;
icon->setFrame(nStack->creatureIconIndex());
amount->setText(makeNumberShort(nStack->getCount()));
if(stateIcon)
{
if(nStack->defended(turn) || (turn > 0 && nStack->defended(turn - 1)))
{
stateIcon->setFrame(0, 0);
stateIcon->visible = true;
}
else if(nStack->waited(turn))
{
stateIcon->setFrame(1, 0);
stateIcon->visible = true;
}
else
{
stateIcon->visible = false;
}
}
}
else
{
bg->colorize(PlayerColor::NEUTRAL);
icon->visible = false;
icon->setFrame(0);
amount->setText("");
if(stateIcon)
stateIcon->visible = false;
}
} }
+11 -11
View File
@@ -23,6 +23,10 @@ class CToggleGroup;
class CLabel; class CLabel;
struct BattleResult; struct BattleResult;
class CStack; class CStack;
namespace battle
{
class Unit;
}
class CAnimImage; class CAnimImage;
class CPlayerInterface; class CPlayerInterface;
@@ -141,27 +145,23 @@ class CStackQueue : public CIntObject
public: public:
CPicture * bg; CPicture * bg;
CAnimImage * icon; CAnimImage * icon;
const CStack *stack; CLabel * amount;
bool small; CAnimImage * stateIcon;
void showAll(SDL_Surface * to) override; void setStack(const battle::Unit * nStack, size_t turn = 0);
void setStack(const CStack *nStack); StackBox(CStackQueue * owner);
StackBox(bool small);
}; };
public: public:
static const int QUEUE_SIZE = 10; static const int QUEUE_SIZE = 10;
const bool embedded; const bool embedded;
std::vector<const CStack *> stacksSorted;
std::vector<StackBox *> stackBoxes; std::vector<StackBox *> stackBoxes;
SDL_Surface * bg;
CBattleInterface * owner; CBattleInterface * owner;
std::shared_ptr<CAnimation> icons;
std::shared_ptr<CAnimation> stateIcons;
CStackQueue(bool Embedded, CBattleInterface * _owner); CStackQueue(bool Embedded, CBattleInterface * _owner);
~CStackQueue(); ~CStackQueue();
void update(); void update();
void showAll(SDL_Surface *to) override;
void blitBg(SDL_Surface * to);
}; };
+3 -3
View File
@@ -1641,11 +1641,11 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
new CLabel(78, 11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl); new CLabel(78, 11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl);
Rect sizes(287, 4, 96, 18); Rect sizes(287, 4, 96, 18);
values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->Attack())); values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[190], CGI->generaltexth->fcommands[0], getMyCreature()->getAttack(false)));
sizes.y+=20; sizes.y+=20;
values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->Defense())); values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[191], CGI->generaltexth->fcommands[1], getMyCreature()->getDefence(false)));
sizes.y+=21; sizes.y+=21;
values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(), getMyCreature()->getMaxDamage())); values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[199], CGI->generaltexth->fcommands[2], getMyCreature()->getMinDamage(false), getMyCreature()->getMaxDamage(false)));
sizes.y+=20; sizes.y+=20;
values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->MaxHealth())); values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->MaxHealth()));
sizes.y+=21; sizes.y+=21;
+7 -7
View File
@@ -219,7 +219,7 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
int dmgMultiply = 1; int dmgMultiply = 1;
if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON)) if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON))
dmgMultiply += parent->info->owner->Attack(); dmgMultiply += parent->info->owner->getPrimSkillLevel(PrimarySkill::ATTACK);
new CPicture("stackWindow/icons", 117, 32); new CPicture("stackWindow/icons", 117, 32);
@@ -230,9 +230,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
if(battleStack != nullptr) // in battle if(battleStack != nullptr) // in battle
{ {
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), battleStack->Attack()); printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->getAttack(battleStack->isShooter()), battleStack->getAttack(battleStack->isShooter()));
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), battleStack->Defense()); printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->getDefence(battleStack->isShooter()), battleStack->getDefence(battleStack->isShooter()));
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, battleStack->getMaxDamage() * dmgMultiply); printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply);
printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), battleStack->MaxHealth()); printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), battleStack->MaxHealth());
printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), battleStack->Speed()); printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), battleStack->Speed());
@@ -250,9 +250,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
const bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS); const bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS);
const bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS); const bool caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), parent->info->stackNode->Attack()); printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter));
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), parent->info->stackNode->Defense()); printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->getDefence(shooter), parent->info->stackNode->getDefence(shooter));
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply); printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage(shooter) * dmgMultiply, parent->info->stackNode->getMaxDamage(shooter) * dmgMultiply);
printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth()); printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->MaxHealth(), parent->info->stackNode->MaxHealth());
printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed()); printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed());
+5
View File
@@ -61,6 +61,11 @@ const TBonusListPtr CHeroWithMaybePickedArtifact::getAllBonuses(const CSelector
return out; return out;
} }
int64_t CHeroWithMaybePickedArtifact::getTreeVersion() const
{
return hero->getTreeVersion(); //this assumes that hero and artifact belongs to main bonus tree
}
CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero) CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero)
: hero(Hero), cww(Cww) : hero(Hero), cww(Cww)
{ {
+3 -1
View File
@@ -39,7 +39,7 @@ public:
}; };
//helper class for calculating values of hero bonuses without bonuses from picked up artifact //helper class for calculating values of hero bonuses without bonuses from picked up artifact
class CHeroWithMaybePickedArtifact : public IBonusBearer class CHeroWithMaybePickedArtifact : public virtual IBonusBearer
{ {
public: public:
const CGHeroInstance *hero; const CGHeroInstance *hero;
@@ -47,6 +47,8 @@ public:
CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero); CHeroWithMaybePickedArtifact(CWindowWithArtifacts *Cww, const CGHeroInstance *Hero);
const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override; const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
int64_t getTreeVersion() const override;
}; };
class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts
+38 -94
View File
@@ -34,14 +34,13 @@
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../../lib/CStack.h"
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/spells/CSpellHandler.h" #include "../../lib/spells/CSpellHandler.h"
#include "../../lib/spells/Problem.h"
#include "../../lib/GameConstants.h" #include "../../lib/GameConstants.h"
#include "../../lib/CGameState.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGHeroInstance.h"
CSpellWindow::InteractiveArea::InteractiveArea(const SDL_Rect & myRect, std::function<void()> funcL, int helpTextId, CSpellWindow * _owner) CSpellWindow::InteractiveArea::InteractiveArea(const SDL_Rect & myRect, std::function<void()> funcL, int helpTextId, CSpellWindow * _owner)
{ {
@@ -125,7 +124,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
++sitesPerOurTab[4]; ++sitesPerOurTab[4];
spell->forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop) spell->forEachSchool([&sitesPerOurTab](const spells::SchoolInfo & school, bool & stop)
{ {
++sitesPerOurTab[(ui8)school.id]; ++sitesPerOurTab[(ui8)school.id];
}); });
@@ -536,101 +535,46 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana)); owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[206]) % spellCost % owner->myHero->mana));
return; return;
} }
//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
if((mySpell->isCombatSpell() && !owner->myInt->battleInt) //anything that is not combat spell is adventure spell
|| (mySpell->isAdventureSpell() && (owner->myInt->battleInt || owner->myInt->castleInt))) //this not an error in general to cast even creature ability with hero
const bool combatSpell = mySpell->isCombatSpell();
if(mySpell->isCombatSpell() != !mySpell->isAdventureSpell())
{ {
std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell->id, 0)); logGlobal->error("Spell have invalid flags");
owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
return; return;
} }
//we will cast a spell const bool inCombat = owner->myInt->battleInt != nullptr;
if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open const bool inCastle = owner->myInt->castleInt != nullptr;
//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
if((combatSpell ^ inCombat) || inCastle)
{ {
ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero); std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell->id, 0));
switch (problem) owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
}
else if(combatSpell)
{
spells::detail::ProblemImpl problem;
if(mySpell->canBeCast(problem, owner->myInt->cb.get(), spells::Mode::HERO, owner->myHero))
{ {
case ESpellCastProblem::OK: owner->myInt->battleInt->castThisSpell(mySpell->id);
{ owner->fexitb();
owner->myInt->battleInt->castThisSpell(mySpell->id); }
owner->fexitb(); else
return; {
} std::vector<std::string> texts;
break; problem.getAll(texts);
case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED: if(!texts.empty())
{ owner->myInt->showInfoDialog(texts.front());
std::string text = CGI->generaltexth->allTexts[538], elemental, caster; else
const PlayerColor player = owner->myInt->playerID; owner->myInt->showInfoDialog("Unknown problem with this spell, no more information available.");
const TStacks stacks = owner->myInt->cb->battleGetStacksIf([player](const CStack * s)
{
return s->owner == player
&& vstd::contains(s->state, EBattleStackState::SUMMONED)
&& !s->isClone();
});
for(const CStack * s : stacks)
{
elemental = s->getCreature()->namePl;
}
if (owner->myHero->type->sex)
{ //female
caster = CGI->generaltexth->allTexts[540];
}
else
{ //male
caster = CGI->generaltexth->allTexts[539];
}
std::string summoner = owner->myHero->name;
text = boost::str(boost::format(text) % summoner % elemental % caster);
owner->myInt->showInfoDialog(text);
}
break;
case ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED:
{
//Recanter's Cloak or similar effect. Try to retrieve bonus
const auto b = owner->myHero->getBonusLocalFirst(Selector::type(Bonus::BLOCK_MAGIC_ABOVE));
//TODO what about other values and non-artifact sources?
if(b && b->val == 2 && b->source == Bonus::ARTIFACT)
{
std::string artName = CGI->arth->artifacts[b->sid]->Name();
//The %s prevents %s from casting 3rd level or higher spells.
owner->myInt->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[536])
% artName % owner->myHero->name));
}
else if(b && b->source == Bonus::TERRAIN_OVERLAY && b->sid == BFieldType::CURSED_GROUND)
{
owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[537]);
}
else
{
// General message:
// %s recites the incantations but they seem to have no effect.
std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
text = boost::str(boost::format(text) % caster);
owner->myInt->showInfoDialog(text);
}
}
break;
case ESpellCastProblem::NO_APPROPRIATE_TARGET:
{
owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[185]);
}
break;
default:
{
// General message:
std::string text = CGI->generaltexth->allTexts[541], caster = owner->myHero->name;
text = boost::str(boost::format(text) % caster);
owner->myInt->showInfoDialog(text);
}
} }
} }
else if(mySpell->isAdventureSpell() && !owner->myInt->battleInt) //adventure spell and not in battle else //adventure spell
{ {
const CGHeroInstance *h = owner->myHero; const CGHeroInstance * h = owner->myHero;
GH.popInt(owner); GH.popInt(owner);
auto guard = vstd::makeScopeGuard([this]() auto guard = vstd::makeScopeGuard([this]()
@@ -639,9 +583,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage; owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
}); });
if(mySpell->getTargetType() == CSpell::LOCATION) if(mySpell->getTargetType() == spells::AimType::LOCATION)
adventureInt->enterCastingMode(mySpell); adventureInt->enterCastingMode(mySpell);
else if(mySpell->getTargetType() == CSpell::NO_TARGET) else if(mySpell->getTargetType() == spells::AimType::NO_TARGET)
owner->myInt->cb->castSpell(h, mySpell->id); owner->myInt->cb->castSpell(h, mySpell->id);
else else
logGlobal->error("Invalid spell target type"); logGlobal->error("Invalid spell target type");
@@ -654,7 +598,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
if(mySpell && down) if(mySpell && down)
{ {
std::string dmgInfo; std::string dmgInfo;
int causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero); auto causedDmg = owner->myInt->cb->estimateSpellDamage(mySpell, owner->myHero);
if(causedDmg == 0 || mySpell->id == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included if(causedDmg == 0 || mySpell->id == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included
dmgInfo = ""; dmgInfo = "";
else else
+1
View File
@@ -16,6 +16,7 @@
#include "../CreatureCostBox.h" #include "../CreatureCostBox.h"
#include "QuickRecruitmentWindow.h" #include "QuickRecruitmentWindow.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../../lib/CCreatureHandler.h"
void CreaturePurchaseCard::initButtons() void CreaturePurchaseCard::initButtons()
{ {
+2 -1
View File
@@ -15,7 +15,8 @@
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../CreatureCostBox.h" #include "../CreatureCostBox.h"
#include "../lib/ResourceSet.h" #include "../../lib/ResourceSet.h"
#include "../../lib/CCreatureHandler.h"
#include "CreaturePurchaseCard.h" #include "CreaturePurchaseCard.h"
+6 -1
View File
@@ -196,7 +196,7 @@
"type" : "object", "type" : "object",
"additionalProperties" : false, "additionalProperties" : false,
"default": {}, "default": {},
"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue" ], "required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize" ],
"properties" : { "properties" : {
"animationSpeed" : { "animationSpeed" : {
"type" : "number", "type" : "number",
@@ -217,6 +217,11 @@
"showQueue" : { "showQueue" : {
"type" : "boolean", "type" : "boolean",
"default" : true "default" : true
},
"queueSize" : {
"type" : "string",
"default" : "auto",
"enum" : [ "auto", "small", "big" ]
} }
} }
}, },
+10 -1
View File
@@ -97,6 +97,12 @@
"$ref" : "vcmi:bonus" "$ref" : "vcmi:bonus"
} }
}, },
"battleEffects":{
"type": "object",
"additionalProperties" : {
"type": "object"
}
},
"targetModifier":{ "targetModifier":{
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@@ -246,7 +252,10 @@
"$ref" : "#/definitions/flags", "$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated." "description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated."
}, },
"targetCondition":{
"type": "object",
"additionalProperties" : true
},
"animation":{"$ref": "#/definitions/animation"}, "animation":{"$ref": "#/definitions/animation"},
"graphics":{ "graphics":{
+101 -62
View File
@@ -16,7 +16,7 @@
"notActive" : { "notActive" : {
"val" : 0, "val" : 0,
"type" : "NOT_ACTIVE", "type" : "NOT_ACTIVE",
"subtype" : 62, "subtype" : "spell.stoneGaze",
"duration" : [ "duration" : [
"UNTIL_BEING_ATTACKED", "UNTIL_BEING_ATTACKED",
"N_TURNS" "N_TURNS"
@@ -43,11 +43,13 @@
} }
} }
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"indifferent": true "indifferent": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"poison" : { "poison" : {
@@ -80,15 +82,15 @@
} }
} }
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true
},
"immunity" : {
"UNDEAD": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "normal"
}
} }
}, },
"bind" : { "bind" : {
@@ -149,15 +151,15 @@
} }
} }
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true
},
"immunity" : {
"UNDEAD": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "normal"
}
} }
}, },
"paralyze" : { "paralyze" : {
@@ -178,7 +180,7 @@
"notActive" : { "notActive" : {
"val" : 0, "val" : 0,
"type" : "NOT_ACTIVE", "type" : "NOT_ACTIVE",
"subtype" : 74, "subtype" : "spell.paralyze",
"duration" : [ "duration" : [
"UNTIL_BEING_ATTACKED", "UNTIL_BEING_ATTACKED",
"N_TURNS" "N_TURNS"
@@ -195,11 +197,13 @@
} }
} }
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"age" : { "age" : {
@@ -226,15 +230,15 @@
} }
} }
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true
},
"immunity" : {
"UNDEAD": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "normal"
}
} }
}, },
"deathCloud" : { "deathCloud" : {
@@ -249,18 +253,23 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0-1" "range" : "0-1",
"battleEffects":{
"damage":{
"type":"core:damage"
}
}
} }
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true
},
"immunity" : {
"UNDEAD": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"indifferent": true "indifferent": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "normal"
}
} }
}, },
"thunderbolt" : { "thunderbolt" : {
@@ -275,12 +284,16 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0" "range" : "0",
"battleEffects":{
"damage":{
"type":"core:damage"
}
}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
} }
}, },
@@ -296,6 +309,15 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"battleEffects":{
"dispel":{
"type":"core:dispel",
"ignoreImmunity" : true,
"dispelNegative":false,
"dispelNeutral":false,
"dispelPositive":true
}
},
"range" : "0" "range" : "0"
} }
}, },
@@ -316,41 +338,50 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"battleEffects":{
"destruction":{
"type":"core:damage",
"killByCount": true
}
},
"range" : "0" "range" : "0"
} }
}, },
"absoluteImmunity" : {
"UNDEAD": true,
"SIEGE_WEAPON": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"indifferent": true "indifferent": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "absolute",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"acidBreath" : { "acidBreath" : {
"index" : 80, "index" : 80,
"targetType": "NO_TARGET", "targetType": "NO_TARGET",
"animation":{
//???
},
"sounds": {
"cast": "ACID"
},
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "battleEffects":{
"targetModifier":{"smart":true}, "timed":{
"cumulativeEffects" : { "type":"core:timed",
"primarySkill" : { "cumulative":true,
"val" : -3, "ignoreImmunity" : true,
"type" : "PRIMARY_SKILL", "bonus" :{
"subtype" : "primSkill.defence", "primarySkill" : {
"duration" : "PERMANENT", "val" : -3,
"valueType" : "ADDITIVE_VALUE" "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.defence",
"duration" : "PERMANENT",
"valueType" : "ADDITIVE_VALUE"
}
}
} }
} },
"range" : "0",
"targetModifier":{"smart":true}
} }
}, },
"flags" : { "flags" : {
@@ -369,6 +400,12 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"battleEffects":{
"damage":{
"type":"core:damage",
"ignoreImmunity" : true
}
},
"range" : "0" "range" : "0"
} }
}, },
@@ -376,8 +413,10 @@
"damage": true, "damage": true,
"indifferent": true "indifferent": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
} }
} }
+131 -70
View File
@@ -2,7 +2,7 @@
"magicArrow" : { "magicArrow" : {
"index" : 15, "index" : 15,
"targetType": "CREATURE", "targetType": "CREATURE",
"animation":{ "animation":{
"projectile": [ "projectile": [
{"minimumAngle": 0 ,"defName":"C20SPX4"}, {"minimumAngle": 0 ,"defName":"C20SPX4"},
@@ -19,22 +19,26 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"iceBolt" : { "iceBolt" : {
"index" : 16, "index" : 16,
"targetType": "CREATURE", "targetType": "CREATURE",
"animation":{ "animation":{
"projectile": [ "projectile": [
{"minimumAngle": 0 ,"defName":"C08SPW4"}, {"minimumAngle": 0 ,"defName":"C08SPW4"},
@@ -44,54 +48,62 @@
{"minimumAngle": 1.50 ,"defName":"C08SPW0"} {"minimumAngle": 1.50 ,"defName":"C08SPW0"}
], ],
"hit":["C08SPW5"] "hit":["C08SPW5"]
}, },
"sounds": { "sounds": {
"cast": "ICERAY" "cast": "ICERAY"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"lightningBolt" : { "lightningBolt" : {
"index" : 17, "index" : 17,
"targetType": "CREATURE", "targetType": "CREATURE",
"animation":{ "animation":{
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
}, },
"sounds": { "sounds": {
"cast": "LIGHTBLT" "cast": "LIGHTBLT"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"implosion" : { "implosion" : {
"index" : 18, "index" : 18,
"targetType": "CREATURE", "targetType": "CREATURE",
"animation":{ "animation":{
"affect":["C05SPE0"] "affect":["C05SPE0"]
}, },
@@ -101,245 +113,294 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"absoluteImmunity" : { "targetCondition" : {
"SIEGE_WEAPON": true "noneOf" : {
}, "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal",
"immunity" : { "bonus.SIEGE_WEAPON" : "absolute"
"DIRECT_DAMAGE_IMMUNITY": true }
} }
}, },
"chainLightning" : { "chainLightning" : {
"index" : 19, "index" : 19,
"targetType": "CREATURE", "targetType": "CREATURE",
"animation":{ "animation":{
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] "affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
}, },
"sounds": { "sounds": {
"cast": "CHAINLTE" "cast": "CHAINLTE"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects" : {
"directDamage" : {
"type" : "core:damage",
"chainFactor" : 0.5,
"chainLength" : 4
}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
},
"advanced" : {
"battleEffects" : {
"directDamage" : {
"chainLength" : 5
}
}
},
"expert" : {
"battleEffects" : {
"directDamage" : {
"chainLength" : 5
}
}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
} }
}, },
"frostRing" : { "frostRing" : {
"index" : 20, "index" : 20,
"targetType": "LOCATION", "targetType": "LOCATION",
"animation":{ "animation":{
"hit":["C07SPW"] //C07SPW0 ??? "hit":["C07SPW"] //C07SPW0 ???
}, },
"sounds": { "sounds": {
"cast": "FROSTING" "cast": "FROSTING"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "1", "range" : "1",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"fireball" : { "fireball" : {
"index" : 21, "index" : 21,
"targetType": "LOCATION", "targetType": "LOCATION",
"animation":{ "animation":{
"hit":["C13SPF"] //C13SPF0 ??? "hit":["C13SPF"] //C13SPF0 ???
}, },
"sounds": { "sounds": {
"cast": "SPONTCOMB" "cast": "SPONTCOMB"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0,1", "range" : "0,1",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"inferno" : { "inferno" : {
"index" : 22, "index" : 22,
"targetType": "LOCATION", "targetType": "LOCATION",
"animation":{ "animation":{
"hit":["C04SPF0"] "hit":["C04SPF0"]
}, },
"sounds": { "sounds": {
"cast": "FIREBLST" "cast": "FIREBLST"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0-2", "range" : "0-2",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"meteorShower" : { "meteorShower" : {
"index" : 23, "index" : 23,
"targetType": "LOCATION", "targetType": "LOCATION",
"animation":{ "animation":{
"hit":["C08SPE0"] "hit":["C08SPE0"]
}, },
"sounds": { "sounds": {
"cast": "METEOR" "cast": "METEOR"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0,1", "range" : "0,1",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"deathRipple" : { "deathRipple" : {
"index" : 24, "index" : 24,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C04SPE0"] "affect":["C04SPE0"]
}, },
"sounds": { "sounds": {
"cast": "DEATHRIP" "cast": "DEATHRIP"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X", "range" : "X",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"absoluteImmunity":{ "targetCondition" : {
"SIEGE_WEAPON": true, "noneOf" : {
"UNDEAD": true, "bonus.DIRECT_DAMAGE_IMMUNITY" : "normal",
}, "bonus.SIEGE_WEAPON" : "absolute",
"immunity" : { "bonus.UNDEAD" : "absolute"
"DIRECT_DAMAGE_IMMUNITY": true }
} }
}, },
"destroyUndead" : { "destroyUndead" : {
"index" : 25, "index" : 25,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"affect":["C14SPA0"] "affect":["C14SPA0"]
}, },
"sounds": { "sounds": {
"cast": "SACBRETH" "cast": "SACBRETH"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X", "range" : "X",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"absoluteLimit" : { "targetCondition" : {
"UNDEAD": true "allOf" : {
}, "bonus.UNDEAD" : "absolute"
"immunity" : { },
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"armageddon" : { "armageddon" : {
"index" : 26, "index" : 26,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"hit":["C06SPF0"] "hit":["C06SPF0"]
}, },
"sounds": { "sounds": {
"cast": "ARMGEDN" "cast": "ARMGEDN"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X", "range" : "X",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":false} "targetModifier":{"smart":false}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true "negative": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"titanBolt" : { "titanBolt" : {
"index" : 57, "index" : 57,
"targetType" : "CREATURE", "targetType" : "CREATURE",
"animation":{ "animation":{
"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"] "hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
}, },
"sounds": { "sounds": {
"cast": "LIGHTBLT" "cast": "LIGHTBLT"
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects" : {
"directDamage" : {"type":"core:damage"}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"offensive": true,
"negative": true, "negative": true,
"special": true "special": true
} }
+405 -28
View File
@@ -8,7 +8,42 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X" "range" : "X",
"battleEffects" : {
"obstacle" : {
"type":"core:obstacle",
"hidden" : true,
"passable" : true,
"trap" : true,
"trigger" : false,
"patchCount" : 4,
"turnsRemaining" : -1,
"attacker" :{
"animation" : "C17SPE1",
"appearAnimation" : "C17SPE0",
"offsetY" : -42
},
"defender" :{
"animation" : "C17SPE1",
"appearAnimation" : "C17SPE0",
"offsetY" : -42
}
}
}
},
"advanced":{
"battleEffects":{
"obstacle":{
"patchCount" : 6
}
}
},
"expert":{
"battleEffects":{
"obstacle":{
"patchCount" : 8
}
}
} }
}, },
"flags" : { "flags" : {
@@ -24,15 +59,57 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X" "range" : "X",
"battleEffects" : {
"obstacle" : {
"type":"core:obstacle",
"hidden" : true,
"passable" : true,
"trap" : false,
"trigger" : true,
"removeOnTrigger" : true,
"patchCount" : 4,
"turnsRemaining" : -1,
"attacker" :{
"animation" : "C09SPF1",
"appearAnimation" : "C09SPF0"
},
"defender" :{
"animation" : "C09SPF1",
"appearAnimation" : "C09SPF0"
}
},
"damage":{
"type":"core:damage",
"optional":false,
"indirect":true,
"customEffectId" : 82
}
}
},
"advanced":{
"battleEffects":{
"obstacle":{
"patchCount" : 6
}
}
},
"expert":{
"battleEffects":{
"obstacle":{
"patchCount" : 8
}
}
} }
}, },
"flags" : { "flags" : {
"damage": true, "damage": true,
"indifferent": true "indifferent": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"forceField" : { "forceField" : {
@@ -47,6 +124,61 @@
"range" : "0", "range" : "0",
"targetModifier":{ "targetModifier":{
"clearAffected": true "clearAffected": true
},
"battleEffects":{
"obstacle":{
"type":"core:obstacle",
"hidden" : false,
"passable" : false,
"trap" : false,
"trigger" : false,
"patchCount" : 1,
"turnsRemaining" : 2,
"attacker" :{
"range" : [[""]],
"shape" : [[""], ["TR"]],
"animation" : "C15SPE1",
"appearAnimation" : "C15SPE0"
},
"defender" :{
"range" : [[""]],
"shape" : [[""], ["TL"]],
"animation" : "C15SPE4",
"appearAnimation" : "C15SPE0"
}
}
}
},
"advanced":{
"battleEffects":{
"obstacle":{
"attacker" :{
"shape" : [[""], ["TR"], ["TR", "TL"]],
"animation" : "C15SPE10",
"appearAnimation" : "C15SPE9"
},
"defender" :{
"shape" : [[""], ["TL"], ["TL", "TR"]],
"animation" : "C15SPE7",
"appearAnimation" : "C15SPE6"
}
}
}
},
"expert":{
"battleEffects":{
"obstacle":{
"attacker" :{
"shape" : [[""], ["TR"], ["TR", "TL"]],
"animation" : "C15SPE10",
"appearAnimation" : "C15SPE9"
},
"defender" :{
"shape" : [[""], ["TL"], ["TL", "TR"]],
"animation" : "C15SPE7",
"appearAnimation" : "C15SPE6"
}
}
} }
} }
}, },
@@ -66,6 +198,58 @@
"range" : "0", "range" : "0",
"targetModifier":{ "targetModifier":{
"clearAffected": true "clearAffected": true
},
"battleEffects":{
"obstacle":{
"type":"core:obstacle",
"hidden" : false,
"passable" : true,
"trap" : false,
"trigger" : true,
"patchCount" : 1,
"turnsRemaining" : 2,
"attacker" :{
"shape" : [[""]],
"range" : [[""], ["TR"]],
"animation" : "C07SPF61",
"appearAnimation" : "C07SPF60"
},
"defender" :{
"shape" : [[""]],
"range" : [[""], ["TL"]],
"animation" : "C07SPF61",
"appearAnimation" : "C07SPF60"
}
},
"damage":{
"type":"core:damage",
"optional":false,
"indirect":true
}
}
},
"advanced":{
"battleEffects":{
"obstacle":{
"attacker" :{
"range" : [[""], ["TR"], ["TR", "TL"]]
},
"defender" :{
"range" : [[""], ["TL"], ["TL", "TR"]]
}
}
}
},
"expert":{
"battleEffects":{
"obstacle":{
"attacker" :{
"range" : [[""], ["TR"], ["TR", "TL"]]
},
"defender" :{
"range" : [[""], ["TL"], ["TL", "TR"]]
}
}
} }
} }
}, },
@@ -73,8 +257,10 @@
"damage": true, "damage": true,
"indifferent": true "indifferent": true
}, },
"immunity" : { "targetCondition" : {
"DIRECT_DAMAGE_IMMUNITY": true "noneOf" : {
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
} }
}, },
"earthquake" : { "earthquake" : {
@@ -87,7 +273,27 @@
"levels" : { "levels" : {
"base":{ "base":{
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"battleEffects":{
"catapult":{
"type":"core:catapult",
"targetsToAttack": 2
}
},
"range" : "X" "range" : "X"
},
"advanced":{
"battleEffects":{
"catapult":{
"targetsToAttack": 3
}
}
},
"expert":{
"battleEffects":{
"catapult":{
"targetsToAttack": 4
}
}
} }
}, },
"flags" : { "flags" : {
@@ -107,7 +313,19 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"targetModifier":{"smart":true}, "targetModifier":{
"smart":true
},
"battleEffects":{
"dispel":{
"type":"core:dispel",
"optional":false,
"ignoreImmunity" : true,
"dispelNegative":true,
"dispelNeutral":true,
"dispelPositive":true
}
},
"range" : "0" "range" : "0"
}, },
"advanced":{ "advanced":{
@@ -115,6 +333,16 @@
}, },
"expert":{ "expert":{
"targetModifier":{"smart":false}, "targetModifier":{"smart":false},
"battleEffects":{
"dispel":{
"optional":true
},
"removeObstacle":{
"optional":true,
"type":"core:removeObstacle",
"removeAllSpells" : true
}
},
"range" : "X" "range" : "X"
} }
}, },
@@ -135,6 +363,21 @@
"levels" : { "levels" : {
"base":{ "base":{
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"battleEffects":{
"heal":{
"type":"core:heal",
"healLevel":"heal",
"healPower":"permanent",
"optional":true
},
"cure":{
"type":"core:dispel",
"optional":true,
"dispelNegative":true,
"dispelNeutral":false,
"dispelPositive":false
}
},
"range" : "0" "range" : "0"
}, },
"expert":{ "expert":{
@@ -158,17 +401,49 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects":{
"heal":{
"type":"core:heal",
"healLevel":"resurrect",
"healPower":"oneBattle",
"minFullUnits" : 1
},
"cure":{
"type":"core:dispel",
"indirect": true,
"optional":true,
"dispelNegative":true,
"dispelNeutral":false,
"dispelPositive":false
}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
},
"advanced":{
"battleEffects":{
"heal":{
"healPower":"permanent"
}
}
},
"expert":{
"battleEffects":{
"heal":{
"healPower":"permanent"
}
}
} }
}, },
"flags" : { "flags" : {
"rising": true, "rising": true,
"positive": true "positive": true
}, },
"absoluteImmunity" : { "targetCondition" : {
"UNDEAD": true, "noneOf" : {
"SIEGE_WEAPON": true, "bonus.NON_LIVING" : "absolute",
"NON_LIVING": true "bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"animateDead" : { "animateDead" : {
@@ -184,6 +459,14 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects":{
"heal":{
"type":"core:heal",
"healLevel":"resurrect",
"healPower":"permanent",
"minFullUnits" : 1
}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
@@ -191,8 +474,10 @@
"rising": true, "rising": true,
"positive": true "positive": true
}, },
"absoluteLimit" : { "targetCondition" : {
"UNDEAD": true "allOf" : {
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"sacrifice" : { "sacrifice" : {
@@ -208,6 +493,14 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects":{
"sacrifice":{
"type":"core:sacrifice",
"healLevel":"resurrect",
"healPower":"permanent",
"minFullUnits" : 0
}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
@@ -215,10 +508,12 @@
"rising": true, "rising": true,
"positive": true "positive": true
}, },
"absoluteImmunity" : { "targetCondition" : {
"SIEGE_WEAPON": true, "noneOf" : {
"UNDEAD": true, "bonus.NON_LIVING" : "absolute",
"NON_LIVING": true "bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"teleport" : { "teleport" : {
@@ -231,14 +526,21 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects":{
"teleport":{
"type":"core:teleport"
}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
} }
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"removeObstacle" : { "removeObstacle" : {
@@ -252,7 +554,28 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0" "range" : "0",
"battleEffects": {
"removeObstacle" : {
"optional":false,
"type":"core:removeObstacle",
"removeUsual" : true
}
}
},
"advanced" : {
"battleEffects": {
"removeObstacle" : {
"removeSpells" : ["fireWall"]
}
}
},
"expert" : {
"battleEffects": {
"removeObstacle" : {
"removeAllSpells" : true
}
}
} }
}, },
"flags" : { "flags" : {
@@ -271,14 +594,36 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
"battleEffects":{
"clone":{
"maxTier":5,
"type":"core:clone"
}
},
"targetModifier":{"smart":true} "targetModifier":{"smart":true}
},
"advanced":{
"battleEffects":{
"clone":{
"maxTier":6
}
}
},
"expert":{
"battleEffects":{
"clone":{
"maxTier":1000
}
}
} }
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"fireElemental" : { "fireElemental" : {
@@ -292,7 +637,15 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X" "range" : "X",
"battleEffects":{
"summon":{
"exclusive":true,
"id":"fireElemental",
"permanent":false,
"type":"core:summon"
}
}
} }
}, },
"flags" : { "flags" : {
@@ -310,7 +663,15 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X" "range" : "X",
"battleEffects":{
"summon":{
"exclusive":true,
"id":"earthElemental",
"permanent":false,
"type":"core:summon"
}
}
} }
}, },
"flags" : { "flags" : {
@@ -328,7 +689,15 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X" "range" : "X",
"battleEffects":{
"summon":{
"exclusive":true,
"id":"waterElemental",
"permanent":false,
"type":"core:summon"
}
}
} }
}, },
"flags" : { "flags" : {
@@ -346,7 +715,15 @@
}, },
"levels" : { "levels" : {
"base":{ "base":{
"range" : "X" "range" : "X",
"battleEffects":{
"summon":{
"exclusive":true,
"id":"airElemental",
"permanent":false,
"type":"core:summon"
}
}
} }
}, },
"flags" : { "flags" : {
+186 -95
View File
@@ -108,11 +108,24 @@
"spellDamageReduction" : { "spellDamageReduction" : {
"type" : "SPELL_DAMAGE_REDUCTION", "type" : "SPELL_DAMAGE_REDUCTION",
"subtype" : 0, "subtype" : 0,
"duration" : "N_TURNS" "duration" : "N_TURNS",
"val" : 30
}
}
},
"advanced" : {
"effects" : {
"spellDamageReduction" : {
"val" : 50
} }
} }
}, },
"expert":{ "expert":{
"effects" : {
"spellDamageReduction" : {
"val" : 50
}
},
"range" : "X" "range" : "X"
} }
}, },
@@ -138,11 +151,24 @@
"spellDamageReduction" : { "spellDamageReduction" : {
"type" : "SPELL_DAMAGE_REDUCTION", "type" : "SPELL_DAMAGE_REDUCTION",
"subtype" : 1, "subtype" : 1,
"duration" : "N_TURNS" "duration" : "N_TURNS",
"val" : 30
}
}
},
"advanced" : {
"effects" : {
"spellDamageReduction" : {
"val" : 50
} }
} }
}, },
"expert":{ "expert":{
"effects" : {
"spellDamageReduction" : {
"val" : 50
}
},
"range" : "X" "range" : "X"
} }
}, },
@@ -168,11 +194,24 @@
"spellDamageReduction" : { "spellDamageReduction" : {
"type" : "SPELL_DAMAGE_REDUCTION", "type" : "SPELL_DAMAGE_REDUCTION",
"subtype" : 2, "subtype" : 2,
"duration" : "N_TURNS" "duration" : "N_TURNS",
"val" : 30
}
}
},
"advanced" : {
"effects" : {
"spellDamageReduction" : {
"val" : 50
} }
} }
}, },
"expert":{ "expert":{
"effects" : {
"spellDamageReduction" : {
"val" : 50
}
},
"range" : "X" "range" : "X"
} }
}, },
@@ -198,11 +237,24 @@
"spellDamageReduction" : { "spellDamageReduction" : {
"type" : "SPELL_DAMAGE_REDUCTION", "type" : "SPELL_DAMAGE_REDUCTION",
"subtype" : 3, "subtype" : 3,
"duration" : "N_TURNS" "duration" : "N_TURNS",
"val" : 30
}
}
},
"advanced" : {
"effects" : {
"spellDamageReduction" : {
"val" : 50
} }
} }
}, },
"expert":{ "expert":{
"effects" : {
"spellDamageReduction" : {
"val" : 50
}
},
"range" : "X" "range" : "X"
} }
}, },
@@ -224,26 +276,47 @@
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "battleEffects":{
"levelSpellImmunity" : { "spellImmunity":{
"val" : 3, "type":"core:timed",
"type" : "LEVEL_SPELL_IMMUNITY", "bonus":{
"valueType" : "INDEPENDENT_MAX", "levelSpellImmunity":{
"duration" : "N_TURNS" "addInfo" : 1, //absolute
"val" : 3,
"type" : "LEVEL_SPELL_IMMUNITY",
"valueType" : "INDEPENDENT_MAX",
"duration" : "N_TURNS"
}
}
},
"dispel":{
"type":"core:dispel",
"optional":true,
"dispelNegative":true,
"dispelNeutral":true,
"dispelPositive":false
} }
} }
}, },
"advanced":{ "advanced":{
"effects" : { "battleEffects":{
"levelSpellImmunity" : { "spellImmunity":{
"val" : 4 "bonus" :{
"levelSpellImmunity":{
"val" : 4
}
}
} }
} }
}, },
"expert":{ "expert":{
"effects" : { "battleEffects":{
"levelSpellImmunity" : { "spellImmunity":{
"val" : 5 "bonus":{
"levelSpellImmunity":{
"val" : 5
}
}
} }
} }
} }
@@ -310,12 +383,14 @@
"counters" : { "counters" : {
"spell.curse": true "spell.curse": true
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true,
"UNDEAD": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"curse" : { "curse" : {
@@ -350,12 +425,14 @@
"counters" : { "counters" : {
"spell.bless": true "spell.bless": true
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true,
"UNDEAD": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"bloodlust" : { "bloodlust" : {
@@ -401,11 +478,13 @@
"counters" : { "counters" : {
"spell.weakness": true "spell.weakness": true
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"precision" : { "precision" : {
@@ -448,11 +527,13 @@
} }
} }
}, },
"absoluteLimit" : {
"SHOOTER": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"allOf" : {
"bonus.SHOOTER" : "absolute"
}
} }
}, },
"weakness" : { "weakness" : {
@@ -697,16 +778,16 @@
"counters" : { "counters" : {
"spell.sorrow":true "spell.sorrow":true
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true,
"UNDEAD": true,
},
"immunity" : {
"MIND_IMMUNITY": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.MIND_IMMUNITY" : "normal",
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"sorrow" : { "sorrow" : {
@@ -750,16 +831,16 @@
"counters" : { "counters" : {
"spell.mirth":true "spell.mirth":true
}, },
"absoluteImmunity":{
"SIEGE_WEAPON": true,
"UNDEAD": true,
},
"immunity" : {
"MIND_IMMUNITY": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.MIND_IMMUNITY" : "normal",
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
}, },
"fortune" : { "fortune" : {
@@ -894,11 +975,13 @@
"counters" : { "counters" : {
"spell.slow": true "spell.slow": true
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"slow" : { "slow" : {
@@ -941,15 +1024,16 @@
} }
} }
}, },
"counters" : { "counters" : {
"spell.haste":true "spell.haste":true
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"slayer" : { "slayer" : {
@@ -1024,8 +1108,7 @@
"inFrenzy" : { "inFrenzy" : {
"type" : "IN_FRENZY", "type" : "IN_FRENZY",
"val" : 100, "val" : 100,
"duration" : "N_TURNS", "duration" : "UNTIL_ATTACK"
"turns" : 1
} }
} }
}, },
@@ -1044,11 +1127,13 @@
} }
} }
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"counterstrike" : { "counterstrike" : {
@@ -1089,11 +1174,13 @@
} }
} }
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"flags" : { "flags" : {
"positive": true "positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
} }
}, },
"berserk" : { "berserk" : {
@@ -1127,16 +1214,16 @@
"counters" : { "counters" : {
"spell.hypnotize": true "spell.hypnotize": true
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"immunity" : {
"MIND_IMMUNITY": true,
"UNDEAD": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.MIND_IMMUNITY" : "normal",
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "normal"
}
} }
}, },
"hypnotize" : { "hypnotize" : {
@@ -1192,13 +1279,16 @@
"counters" : { "counters" : {
"spell.berserk": true "spell.berserk": true
}, },
"absoluteImmunity" : { "targetCondition" : {
"SIEGE_WEAPON": true "allOf" : {
}, "healthValueSpecial" : "absolute"
"immunity" : { },
"MIND_IMMUNITY": true, "noneOf" : {
"UNDEAD": true, "bonus.SIEGE_WEAPON":"absolute",
"NON_LIVING": true "bonus.MIND_IMMUNITY":"normal",
"bonus.UNDEAD":"normal",
"bonus.NON_LIVING":"normal"
}
}, },
"flags" : { "flags" : {
"negative": true "negative": true
@@ -1255,19 +1345,19 @@
} }
} }
}, },
"absoluteLimit" : {
"SHOOTER": true
},
"absoluteImmunity" : {
"SIEGE_WEAPON": true
},
"immunity" : {
"MIND_IMMUNITY": true,
"UNDEAD": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"allOf" : {
"bonus.SHOOTER" : "absolute"
},
"noneOf" : {
"bonus.MIND_IMMUNITY" : "normal",
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "normal"
}
} }
}, },
"blind" : { "blind" : {
@@ -1316,16 +1406,17 @@
} }
} }
}, },
"absoluteImmunity" : {
"SIEGE_WEAPON": true,
"UNDEAD": true
},
"immunity" : {
"MIND_IMMUNITY": true,
"NON_LIVING": true
},
"flags" : { "flags" : {
"negative": true "negative": true
},
"targetCondition" : {
"noneOf" : {
"bonus.MIND_IMMUNITY" : "normal",
"bonus.NON_LIVING" : "normal",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute"
}
} }
} }
} }
+58
View File
@@ -0,0 +1,58 @@
/*
* RNG.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
namespace vstd
{
typedef std::function<int64_t()> TRandI64;
typedef std::function<double()> TRand;
class DLL_LINKAGE RNG
{
public:
virtual ~RNG() = default;
virtual TRandI64 getInt64Range(int64_t lower, int64_t upper) = 0;
virtual TRand getDoubleRange(double lower, double upper) = 0;
};
}
namespace RandomGeneratorUtil
{
template<typename Container>
auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
{
assert(!container.empty());
return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
}
template<typename Container>
auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
{
assert(!container.empty());
return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
}
template<typename T>
void randomShuffle(std::vector<T> & container, vstd::RNG & rand)
{
int64_t n = (container.end() - container.begin());
for(int64_t i = n-1; i>0; --i)
{
std::swap(container.begin()[i],container.begin()[rand.getInt64Range(0, i)()]);
}
}
}
+3 -17
View File
@@ -728,20 +728,6 @@ void CArtHandler::afterLoadFinalization()
CBonusSystemNode::treeHasChanged(); CBonusSystemNode::treeHasChanged();
} }
si32 CArtHandler::decodeArfifact(const std::string& identifier)
{
auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
if(rawId)
return rawId.get();
else
return -1;
}
std::string CArtHandler::encodeArtifact(const si32 index)
{
return VLC->arth->artifacts[index]->identifier;
}
CArtifactInstance::CArtifactInstance() CArtifactInstance::CArtifactInstance()
{ {
init(); init();
@@ -1407,7 +1393,7 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map)
for(const ArtSlotInfo & info : artifactsInBackpack) for(const ArtSlotInfo & info : artifactsInBackpack)
backpackTemp.push_back(info.artifact->artType->id); backpackTemp.push_back(info.artifact->artType->id);
} }
handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact); handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp);
if(!handler.saving) if(!handler.saving)
{ {
for(const ArtifactID & artifactID : backpackTemp) for(const ArtifactID & artifactID : backpackTemp)
@@ -1441,12 +1427,12 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa
if(info != nullptr && !info->locked) if(info != nullptr && !info->locked)
{ {
artifactID = info->artifact->artType->id; artifactID = info->artifact->artType->id;
handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact); handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE);
} }
} }
else else
{ {
handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact); handler.serializeId(NArtifactPosition::namesHero[slot.num], artifactID, ArtifactID::NONE);
if(artifactID != ArtifactID::NONE) if(artifactID != ArtifactID::NONE)
{ {
-6
View File
@@ -271,12 +271,6 @@ public:
std::vector<bool> getDefaultAllowed() const override; std::vector<bool> getDefaultAllowed() const override;
///json serialization helper
static si32 decodeArfifact(const std::string & identifier);
///json serialization helper
static std::string encodeArtifact(const si32 index);
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & artifacts; h & artifacts;
-14
View File
@@ -479,20 +479,6 @@ std::vector<bool> CCreatureHandler::getDefaultAllowed() const
return ret; return ret;
} }
si32 CCreatureHandler::decodeCreature(const std::string& identifier)
{
auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier);
if(rawId)
return rawId.get();
else
return -1;
}
std::string CCreatureHandler::encodeCreature(const si32 index)
{
return VLC->creh->creatures[index]->identifier;
}
void CCreatureHandler::loadCrExpBon() void CCreatureHandler::loadCrExpBon()
{ {
if (VLC->modh->modules.STACK_EXP) //reading default stack experience bonuses if (VLC->modh->modules.STACK_EXP) //reading default stack experience bonuses
-6
View File
@@ -259,12 +259,6 @@ public:
std::vector<bool> getDefaultAllowed() const override; std::vector<bool> getDefaultAllowed() const override;
///json serialization helper
static si32 decodeCreature(const std::string & identifier);
///json serialization helper
static std::string encodeCreature(const si32 index);
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature) //TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)
+3 -3
View File
@@ -189,14 +189,14 @@ int CGameInfoCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * ca
return caster->getSpellCost(sp); return caster->getSpellCost(sp);
} }
int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const int64_t CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const
{ {
//boost::shared_lock<boost::shared_mutex> lock(*gs->mx); //boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1); ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
if (hero) //we see hero's spellbook if(hero) //we see hero's spellbook
return sp->calculateDamage(hero, nullptr, hero->getEffectLevel(sp), hero->getEffectPower(sp)); return sp->calculateDamage(hero);
else else
return 0; //mage guild return 0; //mage guild
} }
+5 -2
View File
@@ -9,6 +9,7 @@
*/ */
#pragma once #pragma once
#include "int3.h"
#include "ResourceSet.h" // for Res::ERes #include "ResourceSet.h" // for Res::ERes
#include "battle/CPlayerBattleCallback.h" #include "battle/CPlayerBattleCallback.h"
@@ -28,13 +29,15 @@ class CGTeleport;
class CMapHeader; class CMapHeader;
struct TeamState; struct TeamState;
struct QuestInfo; struct QuestInfo;
class int3;
struct ShashInt3; struct ShashInt3;
class CGameState;
class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
{ {
protected: protected:
CGameState * gs;
CGameInfoCallback(); CGameInfoCallback();
CGameInfoCallback(CGameState *GS, boost::optional<PlayerColor> Player); CGameInfoCallback(CGameState *GS, boost::optional<PlayerColor> Player);
bool hasAccess(boost::optional<PlayerColor> playerId) const; bool hasAccess(boost::optional<PlayerColor> playerId) const;
@@ -72,7 +75,7 @@ public:
int getHeroCount(PlayerColor player, bool includeGarrisoned) const; int getHeroCount(PlayerColor player, bool includeGarrisoned) const;
bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const; bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const;
int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction
int estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const; const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const; const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
+7 -18
View File
@@ -134,7 +134,7 @@ std::shared_ptr<CScriptingModule> CDynLibHandler::getNewScriptingModule(std::str
BattleAction CGlobalAI::activeStack(const CStack * stack) BattleAction CGlobalAI::activeStack(const CStack * stack)
{ {
BattleAction ba; BattleAction ba;
ba.actionType = Battle::DEFEND; ba.actionType = EActionType::DEFEND;
ba.stackNumber = stack->ID; ba.stackNumber = stack->ID;
return ba; return ba;
} }
@@ -164,9 +164,9 @@ void CAdventureAI::battleStart(const CCreatureSet * army1, const CCreatureSet *
battleAI->battleStart(army1, army2, tile, hero1, hero2, side); battleAI->battleStart(army1, army2, tile, hero1, hero2, side);
} }
void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
{ {
battleAI->battleStacksAttacked(bsa); battleAI->battleStacksAttacked(bsa, battleLog);
} }
void CAdventureAI::actionStarted(const BattleAction & action) void CAdventureAI::actionStarted(const BattleAction & action)
@@ -189,19 +189,9 @@ void CAdventureAI::battleStacksEffectsSet(const SetStackEffect & sse)
battleAI->battleStacksEffectsSet(sse); battleAI->battleStacksEffectsSet(sse);
} }
void CAdventureAI::battleStacksRemoved(const BattleStacksRemoved & bsr) void CAdventureAI::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
{ {
battleAI->battleStacksRemoved(bsr); battleAI->battleObstaclesChanged(obstacles);
}
void CAdventureAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
{
battleAI->battleObstaclesRemoved(removedObstacles);
}
void CAdventureAI::battleNewStackAppeared(const CStack * stack)
{
battleAI->battleNewStackAppeared(stack);
} }
void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
@@ -225,10 +215,9 @@ void CAdventureAI::battleEnd(const BattleResult * br)
battleAI.reset(); battleAI.reset();
} }
void CAdventureAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, void CAdventureAI::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
bool tentHeal, si32 lifeDrainFrom)
{ {
battleAI->battleStacksHealedRes(healedStacks, lifeDrain, tentHeal, lifeDrainFrom); battleAI->battleUnitsChanged(units, customEffects, battleLog);
} }
BattleAction CAdventureAI::activeStack(const CStack * stack) BattleAction CAdventureAI::activeStack(const CStack * stack)
+3 -7
View File
@@ -43,7 +43,6 @@ struct Bonus;
struct PackageApplied; struct PackageApplied;
struct SetObjectProperty; struct SetObjectProperty;
struct CatapultAttack; struct CatapultAttack;
struct BattleStacksRemoved;
struct StackLocation; struct StackLocation;
class CStackInstance; class CStackInstance;
class CCommanderInstance; class CCommanderInstance;
@@ -134,20 +133,17 @@ public:
virtual void battleNewRound(int round) override; virtual void battleNewRound(int round) override;
virtual void battleCatapultAttacked(const CatapultAttack & ca) override; virtual void battleCatapultAttacked(const CatapultAttack & ca) override;
virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override;
virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override;
virtual void actionStarted(const BattleAction &action) override; virtual void actionStarted(const BattleAction &action) override;
virtual void battleNewRoundFirst(int round) override; virtual void battleNewRoundFirst(int round) override;
virtual void actionFinished(const BattleAction &action) override; virtual void actionFinished(const BattleAction &action) override;
virtual void battleStacksEffectsSet(const SetStackEffect & sse) override; virtual void battleStacksEffectsSet(const SetStackEffect & sse) override;
//virtual void battleTriggerEffect(const BattleTriggerEffect & bte); virtual void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
virtual void battleStacksRemoved(const BattleStacksRemoved & bsr) override;
virtual void battleObstaclesRemoved(const std::set<si32> & removedObstacles) override;
virtual void battleNewStackAppeared(const CStack * stack) override;
virtual void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override; virtual void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
virtual void battleAttack(const BattleAttack *ba) override; virtual void battleAttack(const BattleAttack *ba) override;
virtual void battleSpellCast(const BattleSpellCast *sc) override; virtual void battleSpellCast(const BattleSpellCast *sc) override;
virtual void battleEnd(const BattleResult *br) override; virtual void battleEnd(const BattleResult *br) override;
virtual void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom) override; virtual void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog) override;
virtual void saveGame(BinarySerializer & h, const int version) override; //saving virtual void saveGame(BinarySerializer & h, const int version) override; //saving
virtual void loadGame(BinaryDeserializer & h, const int version) override; //loading virtual void loadGame(BinaryDeserializer & h, const int version) override; //loading
+8 -9
View File
@@ -33,7 +33,6 @@
#include "rmg/CMapGenerator.h" #include "rmg/CMapGenerator.h"
#include "CStopWatch.h" #include "CStopWatch.h"
#include "mapping/CMapEditManager.h" #include "mapping/CMapEditManager.h"
#include "mapping/CMapService.h"
#include "serializer/CTypeList.h" #include "serializer/CTypeList.h"
#include "serializer/CMemorySerializer.h" #include "serializer/CMemorySerializer.h"
#include "VCMIDirs.h" #include "VCMIDirs.h"
@@ -703,7 +702,7 @@ CGameState::~CGameState()
ptr.second.dellNull(); ptr.second.dellNull();
} }
void CGameState::init(StartInfo * si, bool allowSavingRandomMap) void CGameState::init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap)
{ {
logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed); logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed);
getRandomGenerator().setSeed(si->seedToBeUsed); getRandomGenerator().setSeed(si->seedToBeUsed);
@@ -714,10 +713,10 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
switch(scenarioOps->mode) switch(scenarioOps->mode)
{ {
case StartInfo::NEW_GAME: case StartInfo::NEW_GAME:
initNewGame(allowSavingRandomMap); initNewGame(mapService, allowSavingRandomMap);
break; break;
case StartInfo::CAMPAIGN: case StartInfo::CAMPAIGN:
initCampaign(); initCampaign(mapService);
break; break;
default: default:
logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode)); logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
@@ -773,7 +772,7 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
} }
} }
void CGameState::initNewGame(bool allowSavingRandomMap) void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap)
{ {
if(scenarioOps->createRandomMap()) if(scenarioOps->createRandomMap())
{ {
@@ -800,7 +799,7 @@ void CGameState::initNewGame(bool allowSavingRandomMap)
const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed ); const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed );
const auto fullPath = path / fileName; const auto fullPath = path / fileName;
CMapService::saveMap(randomMap, fullPath); mapService->saveMap(randomMap, fullPath);
logGlobal->info("Random map has been saved to:"); logGlobal->info("Random map has been saved to:");
logGlobal->info(fullPath.string()); logGlobal->info(fullPath.string());
@@ -841,11 +840,11 @@ void CGameState::initNewGame(bool allowSavingRandomMap)
{ {
logGlobal->info("Open map file: %s", scenarioOps->mapname); logGlobal->info("Open map file: %s", scenarioOps->mapname);
const ResourceID mapURI(scenarioOps->mapname, EResType::MAP); const ResourceID mapURI(scenarioOps->mapname, EResType::MAP);
map = CMapService::loadMap(mapURI).release(); map = mapService->loadMap(mapURI).release();
} }
} }
void CGameState::initCampaign() void CGameState::initCampaign(const IMapService * mapService)
{ {
logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.get()); logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.get());
auto campaign = scenarioOps->campState; auto campaign = scenarioOps->campState;
@@ -857,7 +856,7 @@ void CGameState::initCampaign()
std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap]; std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
auto buffer = reinterpret_cast<const ui8 *>(mapContent.data()); auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
map = CMapService::loadMap(buffer, mapContent.size(), scenarioName).release(); map = mapService->loadMap(buffer, mapContent.size(), scenarioName).release();
} }
void CGameState::checkMapChecksum() void CGameState::checkMapChecksum()
+5 -5
View File
@@ -26,7 +26,6 @@ class CTown;
class CCallback; class CCallback;
class IGameCallback; class IGameCallback;
class CCreatureSet; class CCreatureSet;
class CStack;
class CQuest; class CQuest;
class CGHeroInstance; class CGHeroInstance;
class CGTownInstance; class CGTownInstance;
@@ -54,6 +53,7 @@ class CQuest;
class CCampaignScenario; class CCampaignScenario;
struct EventCondition; struct EventCondition;
class CScenarioTravel; class CScenarioTravel;
class IMapService;
namespace boost namespace boost
{ {
@@ -127,7 +127,7 @@ struct UpgradeInfo
UpgradeInfo(){oldID = CreatureID::NONE;}; UpgradeInfo(){oldID = CreatureID::NONE;};
}; };
struct BattleInfo; class BattleInfo;
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult); DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult);
@@ -152,7 +152,7 @@ public:
CGameState(); CGameState();
virtual ~CGameState(); virtual ~CGameState();
void init(StartInfo * si, bool allowSavingRandomMap = false); void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false);
ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized) ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
PlayerColor currentPlayer; //ID of player currently having turn PlayerColor currentPlayer; //ID of player currently having turn
@@ -245,8 +245,8 @@ private:
// ----- initialization ----- // ----- initialization -----
void initNewGame(bool allowSavingRandomMap); void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
void initCampaign(); void initCampaign(const IMapService * mapService);
void checkMapChecksum(); void checkMapChecksum();
void initGrailPosition(); void initGrailPosition();
void initRandomFactionsForPlayers(); void initRandomFactionsForPlayers();
+49 -4
View File
@@ -10,14 +10,19 @@ set(lib_SRCS
battle/BattleAttackInfo.cpp battle/BattleAttackInfo.cpp
battle/BattleHex.cpp battle/BattleHex.cpp
battle/BattleInfo.cpp battle/BattleInfo.cpp
battle/BattleProxy.cpp
battle/CBattleInfoCallback.cpp battle/CBattleInfoCallback.cpp
battle/CBattleInfoEssentials.cpp battle/CBattleInfoEssentials.cpp
battle/CCallbackBase.cpp battle/CCallbackBase.cpp
battle/CObstacleInstance.cpp battle/CObstacleInstance.cpp
battle/CPlayerBattleCallback.cpp battle/CPlayerBattleCallback.cpp
battle/CUnitState.cpp
battle/Destination.cpp
battle/IBattleState.cpp
battle/ReachabilityInfo.cpp battle/ReachabilityInfo.cpp
battle/SideInBattle.cpp battle/SideInBattle.cpp
battle/SiegeInfo.cpp battle/SiegeInfo.cpp
battle/Unit.cpp
filesystem/AdapterLoaders.cpp filesystem/AdapterLoaders.cpp
filesystem/CArchiveLoader.cpp filesystem/CArchiveLoader.cpp
@@ -93,12 +98,29 @@ set(lib_SRCS
spells/AdventureSpellMechanics.cpp spells/AdventureSpellMechanics.cpp
spells/BattleSpellMechanics.cpp spells/BattleSpellMechanics.cpp
spells/CDefaultSpellMechanics.cpp
spells/CreatureSpellMechanics.cpp
spells/CSpellHandler.cpp spells/CSpellHandler.cpp
spells/ISpellMechanics.cpp spells/ISpellMechanics.cpp
spells/Problem.cpp
spells/TargetCondition.cpp
spells/ViewSpellInt.cpp spells/ViewSpellInt.cpp
spells/effects/Catapult.cpp
spells/effects/Clone.cpp
spells/effects/Damage.cpp
spells/effects/Dispel.cpp
spells/effects/Effect.cpp
spells/effects/Effects.cpp
spells/effects/Heal.cpp
spells/effects/LocationEffect.cpp
spells/effects/Obstacle.cpp
spells/effects/Registry.cpp
spells/effects/UnitEffect.cpp
spells/effects/Summon.cpp
spells/effects/Teleport.cpp
spells/effects/Timed.cpp
spells/effects/RemoveObstacle.cpp
spells/effects/Sacrifice.cpp
CAndroidVMHelper.cpp CAndroidVMHelper.cpp
CArtHandler.cpp CArtHandler.cpp
CBonusTypeHandler.cpp CBonusTypeHandler.cpp
@@ -143,14 +165,20 @@ set(lib_HEADERS
battle/BattleAttackInfo.h battle/BattleAttackInfo.h
battle/BattleHex.h battle/BattleHex.h
battle/BattleInfo.h battle/BattleInfo.h
battle/BattleProxy.h
battle/CBattleInfoCallback.h battle/CBattleInfoCallback.h
battle/CBattleInfoEssentials.h battle/CBattleInfoEssentials.h
battle/CCallbackBase.h battle/CCallbackBase.h
battle/CObstacleInstance.h battle/CObstacleInstance.h
battle/CPlayerBattleCallback.h battle/CPlayerBattleCallback.h
battle/CUnitState.h
battle/Destination.h
battle/IBattleState.h
battle/IUnitInfo.h
battle/ReachabilityInfo.h battle/ReachabilityInfo.h
battle/SideInBattle.h battle/SideInBattle.h
battle/SiegeInfo.h battle/SiegeInfo.h
battle/Unit.h
filesystem/AdapterLoaders.h filesystem/AdapterLoaders.h
filesystem/CArchiveLoader.h filesystem/CArchiveLoader.h
@@ -227,14 +255,31 @@ set(lib_HEADERS
spells/AdventureSpellMechanics.h spells/AdventureSpellMechanics.h
spells/BattleSpellMechanics.h spells/BattleSpellMechanics.h
spells/CDefaultSpellMechanics.h
spells/CreatureSpellMechanics.h
spells/CSpellHandler.h spells/CSpellHandler.h
spells/ISpellMechanics.h spells/ISpellMechanics.h
spells/Magic.h spells/Magic.h
spells/SpellMechanics.h spells/SpellMechanics.h
spells/Problem.h
spells/TargetCondition.h
spells/ViewSpellInt.h spells/ViewSpellInt.h
spells/effects/Catapult.h
spells/effects/Clone.h
spells/effects/Damage.h
spells/effects/Dispel.h
spells/effects/Effect.h
spells/effects/Effects.h
spells/effects/EffectsFwd.h
spells/effects/Heal.h
spells/effects/LocationEffect.h
spells/effects/Obstacle.h
spells/effects/Registry.h
spells/effects/UnitEffect.h
spells/effects/Summon.h
spells/effects/Teleport.h
spells/effects/Timed.h
spells/effects/RemoveObstacle.h
spells/effects/Sacrifice.h
AI_Base.h AI_Base.h
CAndroidVMHelper.h CAndroidVMHelper.h
CArtHandler.h CArtHandler.h
+84 -41
View File
@@ -316,7 +316,7 @@ void CIdentifierStorage::finalize()
state = FINISHED; state = FINISHED;
} }
CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName): ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName):
handler(handler), handler(handler),
objectName(objectName), objectName(objectName),
originalData(handler->loadLegacyData(VLC->modh->settings.data["textData"][objectName].Float())) originalData(handler->loadLegacyData(VLC->modh->settings.data["textData"][objectName].Float()))
@@ -327,7 +327,7 @@ CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler,
} }
} }
bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList, bool validate) bool ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList, bool validate)
{ {
bool result; bool result;
JsonNode data = JsonUtils::assembleFromFiles(fileList, result); JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
@@ -362,7 +362,7 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st
return result; return result;
} }
bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate) bool ContentTypeHandler::loadMod(std::string modName, bool validate)
{ {
ModInfo & modInfo = modData[modName]; ModInfo & modInfo = modData[modName];
bool result = true; bool result = true;
@@ -387,42 +387,47 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali
// try to add H3 object data // try to add H3 object data
size_t index = data["index"].Float(); size_t index = data["index"].Float();
if (originalData.size() > index) if(originalData.size() > index)
{ {
logMod->trace("found original data in loadMod(%s) at index %d", name, index); logMod->trace("found original data in loadMod(%s) at index %d", name, index);
JsonUtils::merge(originalData[index], data); JsonUtils::merge(originalData[index], data);
performValidate(originalData[index],name); std::swap(originalData[index], data);
handler->loadObject(modName, name, originalData[index], index);
originalData[index].clear(); // do not use same data twice (same ID) originalData[index].clear(); // do not use same data twice (same ID)
} }
else else
{ {
logMod->debug("no original data in loadMod(%s) at index %d", name, index); logMod->warn("no original data in loadMod(%s) at index %d", name, index);
performValidate(data, name);
handler->loadObject(modName, name, data, index);
} }
continue; performValidate(data, name);
handler->loadObject(modName, name, data, index);
}
else
{
// normal new object
logMod->trace("no index in loadMod(%s)", name);
performValidate(data,name);
handler->loadObject(modName, name, data);
} }
// normal new object
logMod->trace("no index in loadMod(%s)", name);
performValidate(data,name);
handler->loadObject(modName, name, data);
} }
return result; return result;
} }
void CContentHandler::ContentTypeHandler::loadCustom() void ContentTypeHandler::loadCustom()
{ {
handler->loadCustom(); handler->loadCustom();
} }
void CContentHandler::ContentTypeHandler::afterLoadFinalization() void ContentTypeHandler::afterLoadFinalization()
{ {
handler->afterLoadFinalization(); handler->afterLoadFinalization();
} }
CContentHandler::CContentHandler() CContentHandler::CContentHandler()
{
}
void CContentHandler::init()
{ {
handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass"))); handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass")));
handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact"))); handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact")));
@@ -507,6 +512,11 @@ void CContentHandler::load(CModInfo & mod)
logMod->info("\t\t[SKIP] %s", mod.name); logMod->info("\t\t[SKIP] %s", mod.name);
} }
const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const
{
return handlers.at(name);
}
static JsonNode loadModSettings(std::string path) static JsonNode loadModSettings(std::string path)
{ {
if (CResourceHandler::get("local")->existsResource(ResourceID(path))) if (CResourceHandler::get("local")->existsResource(ResourceID(path)))
@@ -810,31 +820,42 @@ std::vector<std::string> CModHandler::getModList(std::string path)
void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods) void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods)
{ {
for (std::string modName : getModList(path)) for(std::string modName : getModList(path))
loadOneMod(modName, parent, modSettings, enableMods);
}
void CModHandler::loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods)
{
boost::to_lower(modName);
std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
if(CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
{ {
boost::to_lower(modName); CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
std::string modFullName = parent.empty() ? modName : parent + '.' + modName; if (!parent.empty()) // this is submod, add parent to dependencies
mod.dependencies.insert(parent);
if (CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName)))) allMods[modFullName] = mod;
{ if (mod.enabled && enableMods)
CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName)))); activeMods.push_back(modFullName);
if (!parent.empty()) // this is submod, add parent to dependecies
mod.dependencies.insert(parent);
allMods[modFullName] = mod; loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
if (mod.enabled && enableMods)
activeMods.push_back(modFullName);
loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
}
} }
} }
void CModHandler::loadMods() void CModHandler::loadMods(bool onlyEssential)
{ {
const JsonNode modConfig = loadModSettings("config/modSettings.json"); JsonNode modConfig;
loadMods("", "", modConfig["activeMods"], true); if(onlyEssential)
{
loadOneMod("vcmi", "", modConfig, true);//only vcmi and submods
}
else
{
modConfig = loadModSettings("config/modSettings.json");
loadMods("", "", modConfig["activeMods"], true);
}
coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json"))); coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json")));
coreMod.name = "Original game files"; coreMod.name = "Original game files";
@@ -941,9 +962,10 @@ void CModHandler::load()
{ {
CStopWatch totalTime, timer; CStopWatch totalTime, timer;
CContentHandler content;
logMod->info("\tInitializing content handler: %d ms", timer.getDiff()); logMod->info("\tInitializing content handler: %d ms", timer.getDiff());
content.init();
for(const TModID & modName : activeMods) for(const TModID & modName : activeMods)
{ {
logMod->trace("Generating checksum for %s", modName); logMod->trace("Generating checksum for %s", modName);
@@ -976,7 +998,7 @@ void CModHandler::load()
logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff()); logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff());
} }
void CModHandler::afterLoad() void CModHandler::afterLoad(bool onlyEssential)
{ {
JsonNode modSettings; JsonNode modSettings;
for (auto & modEntry : allMods) for (auto & modEntry : allMods)
@@ -987,8 +1009,12 @@ void CModHandler::afterLoad()
} }
modSettings["core"] = coreMod.saveLocalData(); modSettings["core"] = coreMod.saveLocalData();
FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc); if(!onlyEssential)
file << modSettings.toJson(); {
FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
file << modSettings.toJson();
}
} }
std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier)
@@ -1026,10 +1052,27 @@ void CModHandler::parseIdentifier(const std::string & fullIdentifier, std::strin
std::string CModHandler::makeFullIdentifier(const std::string & scope, const std::string & type, const std::string & identifier) std::string CModHandler::makeFullIdentifier(const std::string & scope, const std::string & type, const std::string & identifier)
{ {
auto p = splitString(identifier, ':'); if(type == "")
logGlobal->error("Full identifier (%s %s) requires type name", scope, identifier);
if(p.first != "") std::string actualScope = scope;
return p.first + ":" + type + "." + p.second;//ignore type if identifier is scoped std::string actualName = identifier;
//ignore scope if identifier is scoped
auto scopeAndName = splitString(identifier, ':');
if(scopeAndName.first != "")
{
actualScope = scopeAndName.first;
actualName = scopeAndName.second;
}
if(actualScope == "")
{
return actualName == "" ? type : type + "." + actualName;
}
else else
return scope == "" ? (identifier == "" ? type : type + "." + identifier) : scope + ":" + type + "." + identifier; {
return actualName == "" ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName;
}
} }
+38 -34
View File
@@ -108,40 +108,38 @@ public:
} }
}; };
/// class used to load all game data into handlers. Used only during loading /// internal type to handle loading of one data type (e.g. artifacts, creatures)
class CContentHandler class DLL_LINKAGE ContentTypeHandler
{ {
/// internal type to handle loading of one data type (e.g. artifacts, creatures) public:
class ContentTypeHandler struct ModInfo
{ {
struct ModInfo /// mod data from this mod and for this mod
{ JsonNode modData;
/// mod data from this mod and for this mod /// mod data for this mod from other mods (patches)
JsonNode modData; JsonNode patches;
/// mod data for this mod from other mods (patches)
JsonNode patches;
};
/// handler to which all data will be loaded
IHandlerBase * handler;
std::string objectName;
/// contains all loaded H3 data
std::vector<JsonNode> originalData;
std::map<std::string, ModInfo> modData;
public:
ContentTypeHandler(IHandlerBase * handler, std::string objectName);
/// local version of methods in ContentHandler
/// returns true if loading was successful
bool preloadModData(std::string modName, std::vector<std::string> fileList, bool validate);
bool loadMod(std::string modName, bool validate);
void loadCustom();
void afterLoadFinalization();
}; };
/// handler to which all data will be loaded
IHandlerBase * handler;
std::string objectName;
/// contains all loaded H3 data
std::vector<JsonNode> originalData;
std::map<std::string, ModInfo> modData;
ContentTypeHandler(IHandlerBase * handler, std::string objectName);
/// local version of methods in ContentHandler
/// returns true if loading was successful
bool preloadModData(std::string modName, std::vector<std::string> fileList, bool validate);
bool loadMod(std::string modName, bool validate);
void loadCustom();
void afterLoadFinalization();
};
/// class used to load all game data into handlers. Used only during loading
class DLL_LINKAGE CContentHandler
{
/// preloads all data from fileList as data from modName. /// preloads all data from fileList as data from modName.
bool preloadModData(std::string modName, JsonNode modConfig, bool validate); bool preloadModData(std::string modName, JsonNode modConfig, bool validate);
@@ -150,9 +148,10 @@ class CContentHandler
std::map<std::string, ContentTypeHandler> handlers; std::map<std::string, ContentTypeHandler> handlers;
public: public:
/// fully initialize object. Will cause reading of H3 config files
CContentHandler(); CContentHandler();
void init();
/// preloads all data from fileList as data from modName. /// preloads all data from fileList as data from modName.
void preloadData(CModInfo & mod); void preloadData(CModInfo & mod);
@@ -163,6 +162,8 @@ public:
/// all data was loaded, time for final validation / integration /// all data was loaded, time for final validation / integration
void afterLoadFinalization(); void afterLoadFinalization();
const ContentTypeHandler & operator[] (const std::string & name) const;
}; };
typedef std::string TModID; typedef std::string TModID;
@@ -246,14 +247,17 @@ class DLL_LINKAGE CModHandler
std::vector <TModID> resolveDependencies(std::vector<TModID> input) const; std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
std::vector<std::string> getModList(std::string path); std::vector<std::string> getModList(std::string path);
void loadMods(std::string path, std::string namePrefix, const JsonNode & modSettings, bool enableMods); void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods);
void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods);
public: public:
CIdentifierStorage identifiers; CIdentifierStorage identifiers;
CContentHandler content; //(!)Do not serialize
/// receives list of available mods and trying to load mod.json from all of them /// receives list of available mods and trying to load mod.json from all of them
void initializeConfig(); void initializeConfig();
void loadMods(); void loadMods(bool onlyEssential = false);
void loadModFilesystems(); void loadModFilesystems();
CModInfo & getModData(TModID modId); CModInfo & getModData(TModID modId);
@@ -264,7 +268,7 @@ public:
/// load content from all available mods /// load content from all available mods
void load(); void load();
void afterLoad(); void afterLoad(bool onlyEssential);
struct DLL_LINKAGE hardcodedFeatures struct DLL_LINKAGE hardcodedFeatures
{ {
+7 -2
View File
@@ -32,7 +32,12 @@ void CRandomGenerator::resetSeed()
TRandI CRandomGenerator::getIntRange(int lower, int upper) TRandI CRandomGenerator::getIntRange(int lower, int upper)
{ {
return std::bind(TIntDist(lower, upper), std::ref(rand)); return std::bind(TIntDist(lower, upper), std::ref(rand));
}
vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper)
{
return std::bind(TInt64Dist(lower, upper), std::ref(rand));
} }
int CRandomGenerator::nextInt(int upper) int CRandomGenerator::nextInt(int upper)
@@ -50,7 +55,7 @@ int CRandomGenerator::nextInt()
return TIntDist()(rand); return TIntDist()(rand);
} }
TRand CRandomGenerator::getDoubleRange(double lower, double upper) vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper)
{ {
return std::bind(TRealDist(lower, upper), std::ref(rand)); return std::bind(TRealDist(lower, upper), std::ref(rand));
} }
+10 -32
View File
@@ -10,16 +10,18 @@
#pragma once #pragma once
#include <vstd/RNG.h>
typedef std::mt19937 TGenerator; typedef std::mt19937 TGenerator;
typedef std::uniform_int_distribution<int> TIntDist; typedef std::uniform_int_distribution<int> TIntDist;
typedef std::uniform_int_distribution<int64_t> TInt64Dist;
typedef std::uniform_real_distribution<double> TRealDist; typedef std::uniform_real_distribution<double> TRealDist;
typedef std::function<int()> TRandI; typedef std::function<int()> TRandI;
typedef std::function<double()> TRand;
/// The random generator randomly generates integers and real numbers("doubles") between /// The random generator randomly generates integers and real numbers("doubles") between
/// a given range. This is a header only class and mainly a wrapper for /// a given range. This is a header only class and mainly a wrapper for
/// convenient usage of the standard random API. An instance of this RNG is not thread safe. /// convenient usage of the standard random API. An instance of this RNG is not thread safe.
class DLL_LINKAGE CRandomGenerator : boost::noncopyable class DLL_LINKAGE CRandomGenerator : public vstd::RNG, boost::noncopyable
{ {
public: public:
/// Seeds the generator by default with the product of the current time in milliseconds and the /// Seeds the generator by default with the product of the current time in milliseconds and the
@@ -36,22 +38,24 @@ public:
/// e.g.: auto a = gen.getIntRange(0,10); a(); a(); a(); /// e.g.: auto a = gen.getIntRange(0,10); a(); a(); a();
/// requires: lower <= upper /// requires: lower <= upper
TRandI getIntRange(int lower, int upper); TRandI getIntRange(int lower, int upper);
vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override;
/// Generates an integer between 0 and upper. /// Generates an integer between 0 and upper.
/// requires: 0 <= upper /// requires: 0 <= upper
int nextInt(int upper); int nextInt(int upper);
/// requires: lower <= upper /// requires: lower <= upper
int nextInt(int lower, int upper); int nextInt(int lower, int upper);
/// Generates an integer between 0 and the maximum value it can hold. /// Generates an integer between 0 and the maximum value it can hold.
int nextInt(); int nextInt();
/// Generate several double/real numbers within the same range. /// Generate several double/real numbers within the same range.
/// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a(); /// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a();
/// requires: lower <= upper /// requires: lower <= upper
TRand getDoubleRange(double lower, double upper); vstd::TRand getDoubleRange(double lower, double upper) override;
/// Generates a double between 0 and upper. /// Generates a double between 0 and upper.
/// requires: 0 <= upper /// requires: 0 <= upper
double nextDouble(double upper); double nextDouble(double upper);
@@ -94,29 +98,3 @@ public:
} }
}; };
namespace RandomGeneratorUtil
{
template<typename Container>
auto nextItem(const Container & container, CRandomGenerator & rand) -> decltype(std::begin(container))
{
assert(!container.empty());
return std::next(container.begin(), rand.nextInt(container.size() - 1));
}
template<typename Container>
auto nextItem(Container & container, CRandomGenerator & rand) -> decltype(std::begin(container))
{
assert(!container.empty());
return std::next(container.begin(), rand.nextInt(container.size() - 1));
}
template<typename T>
void randomShuffle(std::vector<T>& container, CRandomGenerator & rand)
{
int n = (container.end() - container.begin());
for (int i = n-1; i>0; --i)
{
std::swap (container.begin()[i],container.begin()[rand.nextInt(i)]);
}
}
}
+1 -5
View File
@@ -22,10 +22,6 @@
#include "CModHandler.h" #include "CModHandler.h"
#include "StringConstants.h" #include "StringConstants.h"
#include "CStack.h"
#include "battle/BattleInfo.h"
#include "battle/CBattleInfoCallback.h"
///CSkill ///CSkill
CSkill::LevelInfo::LevelInfo() CSkill::LevelInfo::LevelInfo()
{ {
@@ -158,7 +154,7 @@ const std::string & CSkillHandler::skillName(int skill) const
return objects[skill]->name; return objects[skill]->name;
} }
CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier) CSkill * CSkillHandler::loadFromJson(const JsonNode & json, const std::string & identifier, size_t index)
{ {
CSkill * skill = nullptr; CSkill * skill = nullptr;
+1 -1
View File
@@ -83,5 +83,5 @@ public:
} }
protected: protected:
CSkill * loadFromJson(const JsonNode & json, const std::string & identifier) override; CSkill * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) override;
}; };
+144 -680
View File
@@ -9,326 +9,34 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "CStack.h" #include "CStack.h"
#include <vstd/RNG.h>
#include "CGeneralTextHandler.h" #include "CGeneralTextHandler.h"
#include "battle/BattleInfo.h" #include "battle/BattleInfo.h"
#include "spells/CSpellHandler.h" #include "spells/CSpellHandler.h"
#include "CRandomGenerator.h"
#include "NetPacks.h" #include "NetPacks.h"
///CAmmo
CAmmo::CAmmo(const CStack * Owner, CSelector totalSelector):
CStackResource(Owner), totalProxy(Owner, totalSelector)
{
}
int32_t CAmmo::available() const
{
return total() - used;
}
bool CAmmo::canUse(int32_t amount) const
{
return available() - amount >= 0;
}
void CAmmo::reset()
{
used = 0;
}
int32_t CAmmo::total() const
{
return totalProxy->totalValue();
}
void CAmmo::use(int32_t amount)
{
if(available() - amount < 0)
{
logGlobal->error("Stack ammo overuse");
used += available();
}
else
used += amount;
}
///CShots
CShots::CShots(const CStack * Owner):
CAmmo(Owner, Selector::type(Bonus::SHOTS))
{
}
void CShots::use(int32_t amount)
{
//don't remove ammo if we control a working ammo cart
bool hasAmmoCart = false;
for(const CStack * st : owner->battle->stacks)
{
if(owner->battle->battleMatchOwner(st, owner, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive())
{
hasAmmoCart = true;
break;
}
}
if(!hasAmmoCart)
CAmmo::use(amount);
}
///CCasts
CCasts::CCasts(const CStack * Owner):
CAmmo(Owner, Selector::type(Bonus::CASTS))
{
}
///CRetaliations
CRetaliations::CRetaliations(const CStack * Owner):
CAmmo(Owner, Selector::type(Bonus::ADDITIONAL_RETALIATION)), totalCache(0)
{
}
int32_t CRetaliations::total() const
{
//after dispell bonus should remain during current round
int32_t val = 1 + totalProxy->totalValue();
vstd::amax(totalCache, val);
return totalCache;
}
void CRetaliations::reset()
{
CAmmo::reset();
totalCache = 0;
}
///CHealth
CHealth::CHealth(const IUnitHealthInfo * Owner):
owner(Owner)
{
reset();
}
CHealth::CHealth(const CHealth & other):
owner(other.owner),
firstHPleft(other.firstHPleft),
fullUnits(other.fullUnits),
resurrected(other.resurrected)
{
}
void CHealth::init()
{
reset();
fullUnits = owner->unitBaseAmount() > 1 ? owner->unitBaseAmount() - 1 : 0;
firstHPleft = owner->unitBaseAmount() > 0 ? owner->unitMaxHealth() : 0;
}
void CHealth::addResurrected(int32_t amount)
{
resurrected += amount;
vstd::amax(resurrected, 0);
}
int64_t CHealth::available() const
{
return static_cast<int64_t>(firstHPleft) + owner->unitMaxHealth() * fullUnits;
}
int64_t CHealth::total() const
{
return static_cast<int64_t>(owner->unitMaxHealth()) * owner->unitBaseAmount();
}
void CHealth::damage(int32_t & amount)
{
const int32_t oldCount = getCount();
const bool withKills = amount >= firstHPleft;
if(withKills)
{
int64_t totalHealth = available();
if(amount > totalHealth)
amount = totalHealth;
totalHealth -= amount;
if(totalHealth <= 0)
{
fullUnits = 0;
firstHPleft = 0;
}
else
{
setFromTotal(totalHealth);
}
}
else
{
firstHPleft -= amount;
}
addResurrected(getCount() - oldCount);
}
void CHealth::heal(int32_t & amount, EHealLevel level, EHealPower power)
{
const int32_t unitHealth = owner->unitMaxHealth();
const int32_t oldCount = getCount();
int32_t maxHeal = std::numeric_limits<int32_t>::max();
switch(level)
{
case EHealLevel::HEAL:
maxHeal = std::max(0, unitHealth - firstHPleft);
break;
case EHealLevel::RESURRECT:
maxHeal = total() - available();
break;
default:
assert(level == EHealLevel::OVERHEAL);
break;
}
vstd::amax(maxHeal, 0);
vstd::abetween(amount, 0, maxHeal);
if(amount == 0)
return;
int64_t availableHealth = available();
availableHealth += amount;
setFromTotal(availableHealth);
if(power == EHealPower::ONE_BATTLE)
addResurrected(getCount() - oldCount);
else
assert(power == EHealPower::PERMANENT);
}
void CHealth::setFromTotal(const int64_t totalHealth)
{
const int32_t unitHealth = owner->unitMaxHealth();
firstHPleft = totalHealth % unitHealth;
fullUnits = totalHealth / unitHealth;
if(firstHPleft == 0 && fullUnits >= 1)
{
firstHPleft = unitHealth;
fullUnits -= 1;
}
}
void CHealth::reset()
{
fullUnits = 0;
firstHPleft = 0;
resurrected = 0;
}
int32_t CHealth::getCount() const
{
return fullUnits + (firstHPleft > 0 ? 1 : 0);
}
int32_t CHealth::getFirstHPleft() const
{
return firstHPleft;
}
int32_t CHealth::getResurrected() const
{
return resurrected;
}
void CHealth::fromInfo(const CHealthInfo & info)
{
firstHPleft = info.firstHPleft;
fullUnits = info.fullUnits;
resurrected = info.resurrected;
}
void CHealth::toInfo(CHealthInfo & info) const
{
info.firstHPleft = firstHPleft;
info.fullUnits = fullUnits;
info.resurrected = resurrected;
}
void CHealth::takeResurrected()
{
if(resurrected != 0)
{
int64_t totalHealth = available();
totalHealth -= resurrected * owner->unitMaxHealth();
vstd::amax(totalHealth, 0);
setFromTotal(totalHealth);
resurrected = 0;
}
}
///CStack ///CStack
CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S): CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S)
base(Base), ID(I), owner(O), slot(S), side(Side), : CBonusSystemNode(STACK_BATTLE),
counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1), CUnitState(),
position() base(Base),
ID(I),
type(Base->type),
baseAmount(base->count),
owner(O),
slot(S),
side(Side),
initialPosition()
{ {
assert(base);
type = base->type;
baseAmount = base->count;
health.init(); //??? health.init(); //???
setNodeType(STACK_BATTLE);
} }
CStack::CStack(): CStack::CStack()
counterAttacks(this), shots(this), casts(this), health(this) : CBonusSystemNode(STACK_BATTLE),
{ CUnitState()
init();
setNodeType(STACK_BATTLE);
}
CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S):
base(nullptr), ID(I), owner(O), slot(S), side(Side),
counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1),
position()
{
type = stack->type;
baseAmount = stack->count;
health.init(); //???
setNodeType(STACK_BATTLE);
}
int32_t CStack::getKilled() const
{
int32_t res = baseAmount - health.getCount() + health.getResurrected();
vstd::amax(res, 0);
return res;
}
int32_t CStack::getCount() const
{
return health.getCount();
}
int32_t CStack::getFirstHPleft() const
{
return health.getFirstHPleft();
}
const CCreature * CStack::getCreature() const
{
return type;
}
void CStack::init()
{ {
base = nullptr; base = nullptr;
type = nullptr; type = nullptr;
@@ -337,14 +45,32 @@ void CStack::init()
owner = PlayerColor::NEUTRAL; owner = PlayerColor::NEUTRAL;
slot = SlotID(255); slot = SlotID(255);
side = 1; side = 1;
position = BattleHex(); initialPosition = BattleHex();
cloneID = -1; }
CStack::CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S)
: CBonusSystemNode(STACK_BATTLE),
CUnitState(),
base(nullptr),
ID(I),
type(stack->type),
baseAmount(stack->count),
owner(O),
slot(S),
side(Side),
initialPosition()
{
health.init(); //???
}
const CCreature * CStack::getCreature() const
{
return type;
} }
void CStack::localInit(BattleInfo * battleInfo) void CStack::localInit(BattleInfo * battleInfo)
{ {
battle = battleInfo; battle = battleInfo;
cloneID = -1;
assert(type); assert(type);
exportBonuses(); exportBonuses();
@@ -359,185 +85,35 @@ void CStack::localInit(BattleInfo * battleInfo)
attachTo(const_cast<CCreature *>(type)); attachTo(const_cast<CCreature *>(type));
} }
shots.reset(); CUnitState::localInit(this);
counterAttacks.reset(); position = initialPosition;
casts.reset();
health.init();
} }
ui32 CStack::level() const ui32 CStack::level() const
{ {
if(base) if(base)
return base->getLevel(); //creatture or commander return base->getLevel(); //creature or commander
else else
return std::max(1, (int)getCreature()->level); //war machine, clone etc return std::max(1, (int)getCreature()->level); //war machine, clone etc
} }
si32 CStack::magicResistance() const si32 CStack::magicResistance() const
{ {
si32 magicResistance; si32 magicResistance = IBonusBearer::magicResistance();
if(base) //TODO: make war machines receive aura of magic resistance
si32 auraBonus = 0;
for(auto one : battle->battleAdjacentUnits(this))
{ {
magicResistance = base->magicResistance(); if(one->unitOwner() == owner)
int auraBonus = 0; vstd::amax(auraBonus, one->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
for(const CStack * stack : base->armyObj->battle-> batteAdjacentCreatures(this))
{
if(stack->owner == owner)
{
vstd::amax(auraBonus, stack->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
}
}
magicResistance += auraBonus;
vstd::amin(magicResistance, 100);
} }
else magicResistance += auraBonus;
magicResistance = type->magicResistance(); vstd::amin(magicResistance, 100);
return magicResistance; return magicResistance;
} }
bool CStack::willMove(int turn) const
{
return (turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING))
&& !moved(turn)
&& canMove(turn);
}
bool CStack::canMove(int turn) const
{
return alive()
&& !hasBonus(Selector::type(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature
}
bool CStack::canCast() const
{
return casts.canUse(1);//do not check specific cast abilities here
}
bool CStack::isCaster() const
{
return casts.total() > 0;//do not check specific cast abilities here
}
bool CStack::canShoot() const
{
return shots.canUse(1) && hasBonusOfType(Bonus::SHOOTER);
}
bool CStack::isShooter() const
{
return shots.total() > 0 && hasBonusOfType(Bonus::SHOOTER);
}
bool CStack::moved(int turn) const
{
if(!turn)
return vstd::contains(state, EBattleStackState::MOVED);
else
return false;
}
bool CStack::waited(int turn) const
{
if(!turn)
return vstd::contains(state, EBattleStackState::WAITING);
else
return false;
}
bool CStack::doubleWide() const
{
return getCreature()->doubleWide;
}
BattleHex CStack::occupiedHex() const
{
return occupiedHex(position);
}
BattleHex CStack::occupiedHex(BattleHex assumedPos) const
{
if(doubleWide())
{
if(side == BattleSide::ATTACKER)
return assumedPos - 1;
else
return assumedPos + 1;
}
else
{
return BattleHex::INVALID;
}
}
std::vector<BattleHex> CStack::getHexes() const
{
return getHexes(position);
}
std::vector<BattleHex> CStack::getHexes(BattleHex assumedPos) const
{
return getHexes(assumedPos, doubleWide(), side);
}
std::vector<BattleHex> CStack::getHexes(BattleHex assumedPos, bool twoHex, ui8 side)
{
std::vector<BattleHex> hexes;
hexes.push_back(assumedPos);
if(twoHex)
{
if(side == BattleSide::ATTACKER)
hexes.push_back(assumedPos - 1);
else
hexes.push_back(assumedPos + 1);
}
return hexes;
}
bool CStack::coversPos(BattleHex pos) const
{
return vstd::contains(getHexes(), pos);
}
std::vector<BattleHex> CStack::getSurroundingHexes(BattleHex attackerPos) const
{
BattleHex hex = (attackerPos != BattleHex::INVALID) ? attackerPos : position; //use hypothetical position
std::vector<BattleHex> hexes;
if(doubleWide())
{
const int WN = GameConstants::BFIELD_WIDTH;
if(side == BattleSide::ATTACKER)
{
//position is equal to front hex
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 2 : WN + 1), hexes);
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes);
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes);
BattleHex::checkAndPush(hex - 2, hexes);
BattleHex::checkAndPush(hex + 1, hexes);
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 2 : WN - 1), hexes);
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes);
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes);
}
else
{
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN + 1 : WN), hexes);
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN : WN - 1), hexes);
BattleHex::checkAndPush(hex - ((hex / WN) % 2 ? WN - 1 : WN - 2), hexes);
BattleHex::checkAndPush(hex + 2, hexes);
BattleHex::checkAndPush(hex - 1, hexes);
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN - 1 : WN), hexes);
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN : WN + 1), hexes);
BattleHex::checkAndPush(hex + ((hex / WN) % 2 ? WN + 1 : WN + 2), hexes);
}
return hexes;
}
else
{
return hex.neighbouringTiles();
}
}
BattleHex::EDir CStack::destShiftDir() const BattleHex::EDir CStack::destShiftDir() const
{ {
if(doubleWide()) if(doubleWide())
@@ -595,7 +171,8 @@ const CGHeroInstance * CStack::getMyHero() const
std::string CStack::nodeName() const std::string CStack::nodeName() const
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "Battle stack [" << ID << "]: " << health.getCount() << " creatures of "; oss << owner.getStr();
oss << " battle stack [" << ID << "]: " << getCount() << " of ";
if(type) if(type)
oss << type->namePl; oss << type->namePl;
else else
@@ -607,156 +184,95 @@ std::string CStack::nodeName() const
return oss.str(); return oss.str();
} }
CHealth CStack::healthAfterAttacked(int32_t & damage) const void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const
{ {
return healthAfterAttacked(damage, health); auto newState = acquireState();
prepareAttacked(bsa, rand, newState);
} }
CHealth CStack::healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, std::shared_ptr<battle::CUnitState> customState)
{ {
CHealth res = customHealth; auto initialCount = customState->getCount();
if(isClone()) customState->damage(bsa.damageAmount);
{
// block ability should not kill clone (0 damage)
if(damage > 0)
{
damage = 1;//??? what should be actual damage against clone?
res.reset();
}
}
else
{
res.damage(damage);
}
return res; bsa.killedAmount = initialCount - customState->getCount();
}
CHealth CStack::healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const if(!customState->alive() && customState->isClone())
{
CHealth res = health;
if(level == EHealLevel::HEAL && power == EHealPower::ONE_BATTLE)
logGlobal->error("Heal for one battle does not make sense", nodeName(), toHeal);
else if(isClone())
logGlobal->error("Attempt to heal clone: %s for %d HP", nodeName(), toHeal);
else
res.heal(toHeal, level, power);
return res;
}
void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const
{
prepareAttacked(bsa, rand, health);
}
void CStack::prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const
{
CHealth afterAttack = healthAfterAttacked(bsa.damageAmount, customHealth);
bsa.killedAmount = customHealth.getCount() - afterAttack.getCount();
afterAttack.toInfo(bsa.newHealth);
bsa.newHealth.stackId = ID;
bsa.newHealth.delta = -bsa.damageAmount;
if(afterAttack.available() <= 0 && isClone())
{ {
bsa.flags |= BattleStackAttacked::CLONE_KILLED; bsa.flags |= BattleStackAttacked::CLONE_KILLED;
return; // no rebirth I believe
} }
else if(!customState->alive()) //stack killed
if(afterAttack.available() <= 0) //stack killed
{ {
bsa.flags |= BattleStackAttacked::KILLED; bsa.flags |= BattleStackAttacked::KILLED;
int resurrectFactor = valOfBonuses(Bonus::REBIRTH); auto resurrectValue = customState->valOfBonuses(Bonus::REBIRTH);
if(resurrectFactor > 0 && canCast()) //there must be casts left
{
int resurrectedStackCount = baseAmount * resurrectFactor / 100;
// last stack has proportional chance to rebirth if(resurrectValue > 0 && customState->canCast()) //there must be casts left
//FIXME: diff is always 0 {
auto diff = baseAmount * resurrectFactor / 100.0 - resurrectedStackCount; double resurrectFactor = resurrectValue / 100;
if(diff > rand.nextDouble(0, 0.99))
auto baseAmount = customState->unitBaseAmount();
double resurrectedRaw = baseAmount * resurrectFactor;
int32_t resurrectedCount = static_cast<int32_t>(floor(resurrectedRaw));
int32_t resurrectedAdd = static_cast<int32_t>(baseAmount - (resurrectedCount/resurrectFactor));
auto rangeGen = rand.getInt64Range(0, 99);
for(int32_t i = 0; i < resurrectedAdd; i++)
{ {
resurrectedStackCount += 1; if(resurrectValue > rangeGen())
resurrectedCount += 1;
} }
if(hasBonusOfType(Bonus::REBIRTH, 1)) if(customState->hasBonusOfType(Bonus::REBIRTH, 1))
{ {
// resurrect at least one Sacred Phoenix // resurrect at least one Sacred Phoenix
vstd::amax(resurrectedStackCount, 1); vstd::amax(resurrectedCount, 1);
} }
if(resurrectedStackCount > 0) if(resurrectedCount > 0)
{ {
customState->casts.use();
bsa.flags |= BattleStackAttacked::REBIRTH; bsa.flags |= BattleStackAttacked::REBIRTH;
//TODO: use StackHealedOrResurrected int64_t toHeal = customState->MaxHealth() * resurrectedCount;
bsa.newHealth.firstHPleft = MaxHealth(); //TODO: add one-battle rebirth?
bsa.newHealth.fullUnits = resurrectedStackCount - 1; customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
bsa.newHealth.resurrected = 0; //TODO: add one-battle rebirth? customState->counterAttacks.use(customState->counterAttacks.available());
} }
} }
} }
customState->save(bsa.newState.data);
bsa.newState.healthDelta = -bsa.damageAmount;
bsa.newState.id = customState->unitId();
bsa.newState.operation = UnitChanges::EOperation::RESET_STATE;
} }
bool CStack::isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos, BattleHex defenderPos) bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos)
{ {
if(!attackerPos.isValid()) if(!attackerPos.isValid())
attackerPos = attacker->position; attackerPos = attacker->getPosition();
if(!defenderPos.isValid()) if(!defenderPos.isValid())
defenderPos = defender->position; defenderPos = defender->getPosition();
return return
(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0)//front <=> front (BattleHex::mutualPosition(attackerPos, defenderPos) >= 0)//front <=> front
|| (attacker->doubleWide()//back <=> front || (attacker->doubleWide()//back <=> front
&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0) && BattleHex::mutualPosition(attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1), defenderPos) >= 0)
|| (defender->doubleWide()//front <=> back || (defender->doubleWide()//front <=> back
&& BattleHex::mutualPosition(attackerPos, defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0) && BattleHex::mutualPosition(attackerPos, defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1)) >= 0)
|| (defender->doubleWide() && attacker->doubleWide()//back <=> back || (defender->doubleWide() && attacker->doubleWide()//back <=> back
&& BattleHex::mutualPosition(attackerPos + (attacker->side == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->side == BattleSide::ATTACKER ? -1 : 1)) >= 0); && BattleHex::mutualPosition(attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1), defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1)) >= 0);
} }
bool CStack::ableToRetaliate() const
{
return alive()
&& (counterAttacks.canUse() || hasBonusOfType(Bonus::UNLIMITED_RETALIATIONS))
&& !hasBonusOfType(Bonus::SIEGE_WEAPON)
&& !hasBonusOfType(Bonus::HYPNOTIZED)
&& !hasBonusOfType(Bonus::NO_RETALIATION);
}
std::string CStack::getName() const std::string CStack::getName() const
{ {
return (health.getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base return (getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base
}
bool CStack::isValidTarget(bool allowDead) const
{
return (alive() || (allowDead && isDead())) && position.isValid() && !isTurret();
}
bool CStack::isDead() const
{
return !alive() && !isGhost();
}
bool CStack::isClone() const
{
return vstd::contains(state, EBattleStackState::CLONED);
}
bool CStack::isGhost() const
{
return vstd::contains(state, EBattleStackState::GHOST);
}
bool CStack::isTurret() const
{
return type->idNumber == CreatureID::ARROW_TOWERS;
} }
bool CStack::canBeHealed() const bool CStack::canBeHealed() const
@@ -766,75 +282,9 @@ bool CStack::canBeHealed() const
&& !hasBonusOfType(Bonus::SIEGE_WEAPON); && !hasBonusOfType(Bonus::SIEGE_WEAPON);
} }
void CStack::makeGhost() const CCreature * CStack::unitType() const
{ {
state.erase(EBattleStackState::ALIVE); return type;
state.insert(EBattleStackState::GHOST_PENDING);
}
bool CStack::alive() const //determines if stack is alive
{
return vstd::contains(state, EBattleStackState::ALIVE);
}
ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
{
int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
vstd::abetween(skill, 0, 3);
return skill;
}
ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
{
//stacks does not have sorcery-like bonuses (yet?)
return base;
}
int CStack::getEffectLevel(const CSpell * spell) const
{
return getSpellSchoolLevel(spell);
}
int CStack::getEffectPower(const CSpell * spell) const
{
return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * health.getCount() / 100;
}
int CStack::getEnchantPower(const CSpell * spell) const
{
int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
if(res <= 0)
res = 3;//default for creatures
return res;
}
int CStack::getEffectValue(const CSpell * spell) const
{
return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * health.getCount();
}
const PlayerColor CStack::getOwner() const
{
return battle->battleGetOwner(this);
}
void CStack::getCasterName(MetaString & text) const
{
//always plural name in case of spell cast.
addNameReplacement(text, true);
}
void CStack::getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const
{
text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s
//todo: use text 566 for single creature
getCasterName(text);
text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
}
int32_t CStack::unitMaxHealth() const
{
return MaxHealth();
} }
int32_t CStack::unitBaseAmount() const int32_t CStack::unitBaseAmount() const
@@ -842,46 +292,60 @@ int32_t CStack::unitBaseAmount() const
return baseAmount; return baseAmount;
} }
void CStack::addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural) const bool CStack::unitHasAmmoCart(const battle::Unit * unit) const
{ {
if(boost::logic::indeterminate(plural)) bool hasAmmoCart = false;
serial = VLC->generaltexth->pluralText(serial, health.getCount());
else if(plural)
serial = VLC->generaltexth->pluralText(serial, 2);
else
serial = VLC->generaltexth->pluralText(serial, 1);
text.addTxt(type, serial); for(const CStack * st : battle->stacks)
{
if(battle->battleMatchOwner(st, unit, true) && st->getCreature()->idNumber == CreatureID::AMMO_CART && st->alive())
{
hasAmmoCart = true;
break;
}
}
return hasAmmoCart;
} }
void CStack::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const PlayerColor CStack::unitEffectiveOwner(const battle::Unit * unit) const
{ {
if(boost::logic::indeterminate(plural)) return battle->battleGetOwner(unit);
text.addCreReplacement(type->idNumber, health.getCount());
else if(plural)
text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num);
else
text.addReplacement(MetaString::CRE_SING_NAMES, type->idNumber.num);
} }
std::string CStack::formatGeneralMessage(const int32_t baseTextId) const uint32_t CStack::unitId() const
{ {
const int32_t textId = VLC->generaltexth->pluralText(baseTextId, health.getCount()); return ID;
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, textId);
text.addCreReplacement(type->idNumber, health.getCount());
return text.toString();
} }
void CStack::setHealth(const CHealthInfo & value) ui8 CStack::unitSide() const
{ {
health.reset(); return side;
health.fromInfo(value);
} }
void CStack::setHealth(const CHealth & value) PlayerColor CStack::unitOwner() const
{ {
health = value; return owner;
}
SlotID CStack::unitSlot() const
{
return slot;
}
std::string CStack::getDescription() const
{
return nodeName();
}
void CStack::spendMana(const spells::PacketSender * server, const int spellCost) const
{
if(spellCost != 1)
logGlobal->warn("Unexpected spell cost %d for creature", spellCost);
BattleSetStackProperty ssp;
ssp.stackID = unitId();
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -spellCost;
ssp.absolute = false;
server->sendAndApply(&ssp);
} }
+26 -212
View File
@@ -9,179 +9,42 @@
*/ */
#pragma once #pragma once
#include "JsonNode.h"
#include "HeroBonus.h"
#include "CCreatureHandler.h" //todo: remove
#include "battle/BattleHex.h" #include "battle/BattleHex.h"
#include "CCreatureHandler.h"
#include "mapObjects/CGHeroInstance.h" // for commander serialization #include "mapObjects/CGHeroInstance.h" // for commander serialization
#include "battle/CUnitState.h"
struct BattleStackAttacked; struct BattleStackAttacked;
struct BattleInfo; class BattleInfo;
class CStack;
class CHealthInfo;
template <typename Quantity> class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment
class DLL_LINKAGE CStackResource
{
public:
CStackResource(const CStack * Owner):
owner(Owner)
{
reset();
}
virtual void reset()
{
used = 0;
};
protected:
const CStack * owner;
Quantity used;
};
class DLL_LINKAGE CAmmo : public CStackResource<int32_t>
{
public:
CAmmo(const CStack * Owner, CSelector totalSelector);
int32_t available() const;
bool canUse(int32_t amount = 1) const;
virtual void reset() override;
virtual int32_t total() const;
virtual void use(int32_t amount = 1);
template <typename Handler> void serialize(Handler & h, const int version)
{
if(!h.saving)
reset();
h & used;
}
protected:
CBonusProxy totalProxy;
};
class DLL_LINKAGE CShots : public CAmmo
{
public:
CShots(const CStack * Owner);
void use(int32_t amount = 1) override;
};
class DLL_LINKAGE CCasts : public CAmmo
{
public:
CCasts(const CStack * Owner);
};
class DLL_LINKAGE CRetaliations : public CAmmo
{
public:
CRetaliations(const CStack * Owner);
int32_t total() const override;
void reset() override;
private:
mutable int32_t totalCache;
};
class DLL_LINKAGE IUnitHealthInfo
{
public:
virtual int32_t unitMaxHealth() const = 0;
virtual int32_t unitBaseAmount() const = 0;
};
class DLL_LINKAGE CHealth
{
public:
CHealth(const IUnitHealthInfo * Owner);
CHealth(const CHealth & other);
void init();
void reset();
void damage(int32_t & amount);
void heal(int32_t & amount, EHealLevel level, EHealPower power);
int32_t getCount() const;
int32_t getFirstHPleft() const;
int32_t getResurrected() const;
int64_t available() const;
int64_t total() const;
void toInfo(CHealthInfo & info) const;
void fromInfo(const CHealthInfo & info);
void takeResurrected();
template <typename Handler> void serialize(Handler & h, const int version)
{
if(!h.saving)
reset();
h & firstHPleft;
h & fullUnits;
h & resurrected;
}
private:
void addResurrected(int32_t amount);
void setFromTotal(const int64_t totalHealth);
const IUnitHealthInfo * owner;
int32_t firstHPleft;
int32_t fullUnits;
int32_t resurrected;
};
class DLL_LINKAGE CStack : public CBonusSystemNode, public ISpellCaster, public IUnitHealthInfo
{ {
public: public:
const CStackInstance * base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc) const CStackInstance * base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
ui32 ID; //unique ID of stack ui32 ID; //unique ID of stack
ui32 baseAmount;
const CCreature * type; const CCreature * type;
ui32 baseAmount;
PlayerColor owner; //owner - player color (255 for neutrals) PlayerColor owner; //owner - player color (255 for neutrals)
SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures) SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures)
ui8 side; ui8 side;
BattleHex position; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower BattleHex initialPosition; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
CRetaliations counterAttacks;
CShots shots;
CCasts casts;
CHealth health;
///id of alive clone of this stack clone if any
si32 cloneID;
std::set<EBattleStackState::EBattleStackState> state;
CStack(const CStackInstance * base, PlayerColor O, int I, ui8 Side, SlotID S); CStack(const CStackInstance * base, PlayerColor O, int I, ui8 Side, SlotID S);
CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S = SlotID(255)); CStack(const CStackBasicDescriptor * stack, PlayerColor O, int I, ui8 Side, SlotID S = SlotID(255));
CStack(); CStack();
~CStack(); ~CStack();
int32_t getKilled() const; const CCreature * getCreature() const; //deprecated
int32_t getCount() const;
int32_t getFirstHPleft() const;
const CCreature * getCreature() const;
std::string nodeName() const override; std::string nodeName() const override;
void init(); //set initial (invalid) values
void localInit(BattleInfo * battleInfo); void localInit(BattleInfo * battleInfo);
std::string getName() const; //plural or singular std::string getName() const; //plural or singular
bool willMove(int turn = 0) const; //if stack has remaining move this turn
bool ableToRetaliate() const; //if stack can retaliate after attacked
bool moved(int turn = 0) const; //if stack was already moved this turn
bool waited(int turn = 0) const;
bool canCast() const;
bool isCaster() const;
bool canMove(int turn = 0) const; //if stack can move
bool canShoot() const;
bool isShooter() const;
bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
@@ -190,68 +53,32 @@ public:
std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
static bool isMeleeAttackPossible(const CStack * attacker, const CStack * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID); static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
bool doubleWide() const;
BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1
BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1
std::vector<BattleHex> getHexes() const; //up to two occupied hexes, starting from front
std::vector<BattleHex> getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front
static std::vector<BattleHex> getHexes(BattleHex assumedPos, bool twoHex, ui8 side); //up to two occupied hexes, starting from front
bool coversPos(BattleHex position) const; //checks also if unit is double-wide
std::vector<BattleHex> getSurroundingHexes(BattleHex attackerPos = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size
BattleHex::EDir destShiftDir() const; BattleHex::EDir destShiftDir() const;
CHealth healthAfterAttacked(int32_t & damage) const; void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const; //requires bsa.damageAmout filled
CHealth healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const; static void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, std::shared_ptr<battle::CUnitState> customState); //requires bsa.damageAmout filled
CHealth healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const; const CCreature * unitType() const override;
void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand) const; //requires bsa.damageAmout filled
void prepareAttacked(BattleStackAttacked & bsa, CRandomGenerator & rand, const CHealth & customHealth) const; //requires bsa.damageAmout filled
///ISpellCaster
ui8 getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool = nullptr) const override;
ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
///default spell school level for effect calculation
int getEffectLevel(const CSpell * spell) const override;
///default spell-power for damage/heal calculation
int getEffectPower(const CSpell * spell) const override;
///default spell-power for timed effects duration
int getEnchantPower(const CSpell * spell) const override;
///damage/heal override(ignores spell configuration, effect level and effect power)
int getEffectValue(const CSpell * spell) const override;
const PlayerColor getOwner() const override;
void getCasterName(MetaString & text) const override;
void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
///IUnitHealthInfo
int32_t unitMaxHealth() const override;
int32_t unitBaseAmount() const override; int32_t unitBaseAmount() const override;
///MetaStrings uint32_t unitId() const override;
ui8 unitSide() const override;
PlayerColor unitOwner() const override;
SlotID unitSlot() const override;
void addText(MetaString & text, ui8 type, int32_t serial, const boost::logic::tribool & plural = boost::logic::indeterminate) const; std::string getDescription() const override;
void addNameReplacement(MetaString & text, const boost::logic::tribool & plural = boost::logic::indeterminate) const;
std::string formatGeneralMessage(const int32_t baseTextId) const;
///Non const API for NetPacks bool unitHasAmmoCart(const battle::Unit * unit) const override;
PlayerColor unitEffectiveOwner(const battle::Unit * unit) const override;
///stack will be ghost in next battle state update void spendMana(const spells::PacketSender * server, const int spellCost) const override;
void makeGhost();
void setHealth(const CHealthInfo & value);
void setHealth(const CHealth & value);
template <typename Handler> void serialize(Handler & h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
//this assumes that stack objects is newly created
//stackState is not serialized here
assert(isIndependentNode()); assert(isIndependentNode());
h & static_cast<CBonusSystemNode&>(*this); h & static_cast<CBonusSystemNode&>(*this);
h & type; h & type;
@@ -260,12 +87,7 @@ public:
h & owner; h & owner;
h & slot; h & slot;
h & side; h & side;
h & position; h & initialPosition;
h & state;
h & shots;
h & casts;
h & counterAttacks;
h & health;
const CArmedInstance * army = (base ? base->armyObj : nullptr); const CArmedInstance * army = (base ? base->armyObj : nullptr);
SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID()); SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID());
@@ -279,6 +101,7 @@ public:
{ {
h & army; h & army;
h & extSlot; h & extSlot;
if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER) if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
{ {
auto hero = dynamic_cast<const CGHeroInstance *>(army); auto hero = dynamic_cast<const CGHeroInstance *>(army);
@@ -300,17 +123,8 @@ public:
base = &army->getStack(extSlot); base = &army->getStack(extSlot);
} }
} }
} }
bool alive() const;
bool isClone() const;
bool isDead() const;
bool isGhost() const; //determines if stack was removed
bool isValidTarget(bool allowDead = false) const; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
bool isTurret() const;
friend class CShots; //for BattleInfo access
private: private:
const BattleInfo * battle; //do not serialize const BattleInfo * battle; //do not serialize
}; };
+2 -4
View File
@@ -25,13 +25,11 @@ CThreadHelper::CThreadHelper(std::vector<std::function<void()> > *Tasks, int Thr
void CThreadHelper::run() void CThreadHelper::run()
{ {
boost::thread_group grupa; boost::thread_group grupa;
std::vector<boost::thread *> thr;
for(int i=0;i<threads;i++) for(int i=0;i<threads;i++)
thr.push_back(grupa.create_thread(std::bind(&CThreadHelper::processTasks,this))); grupa.create_thread(std::bind(&CThreadHelper::processTasks,this));
grupa.join_all(); grupa.join_all();
for(auto thread : thr) //thread group deletes threads, do not free manually
delete thread;
} }
void CThreadHelper::processTasks() void CThreadHelper::processTasks()
{ {
+61 -18
View File
@@ -24,6 +24,7 @@
#include "CSkillHandler.h" #include "CSkillHandler.h"
#include "StringConstants.h" #include "StringConstants.h"
#include "CGeneralTextHandler.h" #include "CGeneralTextHandler.h"
#include "CModHandler.h"
const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3); const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
@@ -51,11 +52,39 @@ const CArtifact * ArtifactID::toArtifact() const
return VLC->arth->artifacts.at(*this); return VLC->arth->artifacts.at(*this);
} }
si32 ArtifactID::decode(const std::string & identifier)
{
auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
if(rawId)
return rawId.get();
else
return -1;
}
std::string ArtifactID::encode(const si32 index)
{
return VLC->arth->artifacts.at(index)->identifier;
}
const CCreature * CreatureID::toCreature() const const CCreature * CreatureID::toCreature() const
{ {
return VLC->creh->creatures.at(*this); return VLC->creh->creatures.at(*this);
} }
si32 CreatureID::decode(const std::string & identifier)
{
auto rawId = VLC->modh->identifiers.getIdentifier("core", "creature", identifier);
if(rawId)
return rawId.get();
else
return -1;
}
std::string CreatureID::encode(const si32 index)
{
return VLC->creh->creatures.at(index)->identifier;
}
const CSpell * SpellID::toSpell() const const CSpell * SpellID::toSpell() const
{ {
if(num < 0 || num >= VLC->spellh->objects.size()) if(num < 0 || num >= VLC->spellh->objects.size())
@@ -66,6 +95,20 @@ const CSpell * SpellID::toSpell() const
return VLC->spellh->objects[*this]; return VLC->spellh->objects[*this];
} }
si32 SpellID::decode(const std::string & identifier)
{
auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier);
if(rawId)
return rawId.get();
else
return -1;
}
std::string SpellID::encode(const si32 index)
{
return VLC->spellh->objects.at(index)->identifier;
}
const CSkill * SecondarySkill::toSkill() const const CSkill * SecondarySkill::toSkill() const
{ {
return VLC->skillh->objects.at(*this); return VLC->skillh->objects.at(*this);
@@ -110,26 +153,26 @@ std::string PlayerColor::getStrCap(bool L10n) const
return ret; return ret;
} }
std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType) std::ostream & operator<<(std::ostream & os, const EActionType actionType)
{ {
static const std::map<Battle::ActionType, std::string> actionTypeToString = static const std::map<EActionType, std::string> actionTypeToString =
{ {
{Battle::END_TACTIC_PHASE, "End tactic phase"}, {EActionType::END_TACTIC_PHASE, "End tactic phase"},
{Battle::INVALID, "Invalid"}, {EActionType::INVALID, "Invalid"},
{Battle::NO_ACTION, "No action"}, {EActionType::NO_ACTION, "No action"},
{Battle::HERO_SPELL, "Hero spell"}, {EActionType::HERO_SPELL, "Hero spell"},
{Battle::WALK, "Walk"}, {EActionType::WALK, "Walk"},
{Battle::DEFEND, "Defend"}, {EActionType::DEFEND, "Defend"},
{Battle::RETREAT, "Retreat"}, {EActionType::RETREAT, "Retreat"},
{Battle::SURRENDER, "Surrender"}, {EActionType::SURRENDER, "Surrender"},
{Battle::WALK_AND_ATTACK, "Walk and attack"}, {EActionType::WALK_AND_ATTACK, "Walk and attack"},
{Battle::SHOOT, "Shoot"}, {EActionType::SHOOT, "Shoot"},
{Battle::WAIT, "Wait"}, {EActionType::WAIT, "Wait"},
{Battle::CATAPULT, "Catapult"}, {EActionType::CATAPULT, "Catapult"},
{Battle::MONSTER_SPELL, "Monster spell"}, {EActionType::MONSTER_SPELL, "Monster spell"},
{Battle::BAD_MORALE, "Bad morale"}, {EActionType::BAD_MORALE, "Bad morale"},
{Battle::STACK_HEAL, "Stack heal"}, {EActionType::STACK_HEAL, "Stack heal"},
{Battle::DAEMON_SUMMONING, "Daemon summoning"} {EActionType::DAEMON_SUMMONING, "Daemon summoning"}
}; };
auto it = actionTypeToString.find(actionType); auto it = actionTypeToString.find(actionType);
+34 -63
View File
@@ -15,10 +15,6 @@ namespace GameConstants
{ {
DLL_LINKAGE extern const std::string VCMI_VERSION; DLL_LINKAGE extern const std::string VCMI_VERSION;
const int BFIELD_WIDTH = 17;
const int BFIELD_HEIGHT = 11;
const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
const int PUZZLE_MAP_PIECES = 48; const int PUZZLE_MAP_PIECES = 48;
const int MAX_HEROES_PER_PLAYER = 8; const int MAX_HEROES_PER_PLAYER = 8;
@@ -443,27 +439,15 @@ namespace ESpellCastProblem
{ {
enum ESpellCastProblem enum ESpellCastProblem
{ {
OK, NO_HERO_TO_CAST_SPELL, ALREADY_CASTED_THIS_TURN, NO_SPELLBOOK, ANOTHER_ELEMENTAL_SUMMONED, OK, NO_HERO_TO_CAST_SPELL, CASTS_PER_TURN_LIMIT, NO_SPELLBOOK,
HERO_DOESNT_KNOW_SPELL, NOT_ENOUGH_MANA, ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL, HERO_DOESNT_KNOW_SPELL, NOT_ENOUGH_MANA, ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL,
SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL,
NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE, NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE,
MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all
NOT_DECIDED,
INVALID INVALID
}; };
} }
namespace ECastingMode
{
enum ECastingMode
{
HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
SPELL_LIKE_ATTACK,
PASSIVE_CASTING//f.e. opening battle spells
};
}
namespace EMarketMode namespace EMarketMode
{ {
enum EMarketMode enum EMarketMode
@@ -474,26 +458,6 @@ namespace EMarketMode
}; };
} }
namespace EBattleStackState
{
enum EBattleStackState
{
ALIVE = 180,
SUMMONED, CLONED,
GHOST, //stack was removed from battlefield
HAD_MORALE,
WAITING,
MOVED,
DEFENDING,
FEAR,
//remember to drain mana only once per turn
DRAINED_MANA,
//only for defending animation
DEFENDING_ANIM,
GHOST_PENDING// stack will become GHOST in next battle state update
};
}
namespace ECommander namespace ECommander
{ {
enum SecondarySkills {ATTACK, DEFENSE, HEALTH, DAMAGE, SPEED, SPELL_POWER, CASTS, RESISTANCE}; enum SecondarySkills {ATTACK, DEFENSE, HEALTH, DAMAGE, SPEED, SPELL_POWER, CASTS, RESISTANCE};
@@ -518,8 +482,6 @@ namespace EWallState
DESTROYED, DESTROYED,
DAMAGED, DAMAGED,
INTACT INTACT
}; };
} }
@@ -778,30 +740,27 @@ namespace Date
}; };
} }
namespace Battle enum class EActionType : int32_t
{ {
enum ActionType CANCEL = -3,
{ END_TACTIC_PHASE = -2,
CANCEL = -3, INVALID = -1,
END_TACTIC_PHASE = -2, NO_ACTION = 0,
INVALID = -1, HERO_SPELL,
NO_ACTION = 0, WALK, DEFEND,
HERO_SPELL, RETREAT,
WALK, DEFEND, SURRENDER,
RETREAT, WALK_AND_ATTACK,
SURRENDER, SHOOT,
WALK_AND_ATTACK, WAIT,
SHOOT, CATAPULT,
WAIT, MONSTER_SPELL,
CATAPULT, BAD_MORALE,
MONSTER_SPELL, STACK_HEAL,
BAD_MORALE, DAEMON_SUMMONING
STACK_HEAL, };
DAEMON_SUMMONING
};
}
std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType); DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EActionType actionType);
class DLL_LINKAGE ETerrainType class DLL_LINKAGE ETerrainType
{ {
@@ -969,6 +928,10 @@ public:
DLL_LINKAGE const CArtifact * toArtifact() const; DLL_LINKAGE const CArtifact * toArtifact() const;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
ID_LIKE_CLASS_COMMON(ArtifactID, EArtifactID) ID_LIKE_CLASS_COMMON(ArtifactID, EArtifactID)
EArtifactID num; EArtifactID num;
@@ -1017,6 +980,10 @@ public:
ID_LIKE_CLASS_COMMON(CreatureID, ECreatureID) ID_LIKE_CLASS_COMMON(CreatureID, ECreatureID)
ECreatureID num; ECreatureID num;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
}; };
ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID)
@@ -1060,6 +1027,10 @@ public:
ID_LIKE_CLASS_COMMON(SpellID, ESpellID) ID_LIKE_CLASS_COMMON(SpellID, ESpellID)
ESpellID num; ESpellID num;
///json serialization helpers
static si32 decode(const std::string & identifier);
static std::string encode(const si32 index);
}; };
ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID)
@@ -1108,7 +1079,7 @@ enum class EHealPower : ui8
// Typedef declarations // Typedef declarations
typedef ui8 TFaction; typedef ui8 TFaction;
typedef si64 TExpType; typedef si64 TExpType;
typedef std::pair<ui32, ui32> TDmgRange; typedef std::pair<si64, si64> TDmgRange;
typedef si32 TBonusSubtype; typedef si32 TBonusSubtype;
typedef si32 TQuantity; typedef si32 TQuantity;
+99 -52
View File
@@ -81,20 +81,59 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
}; //untested }; //untested
///CBonusProxy ///CBonusProxy
CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector): CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector)
cachedLast(0), target(Target), selector(Selector), data() : cachedLast(0),
target(Target),
selector(Selector),
data()
{ {
} }
CBonusProxy::CBonusProxy(const CBonusProxy & other)
: cachedLast(other.cachedLast),
target(other.target),
selector(other.selector),
data(other.data)
{
}
CBonusProxy::CBonusProxy(CBonusProxy && other)
: cachedLast(0),
target(other.target),
selector(),
data()
{
std::swap(cachedLast, other.cachedLast);
std::swap(selector, other.selector);
std::swap(data, other.data);
}
CBonusProxy & CBonusProxy::operator=(const CBonusProxy & other)
{
cachedLast = other.cachedLast;
selector = other.selector;
data = other.data;
return *this;
}
CBonusProxy & CBonusProxy::operator=(CBonusProxy && other)
{
std::swap(cachedLast, other.cachedLast);
std::swap(selector, other.selector);
std::swap(data, other.data);
return *this;
}
TBonusListPtr CBonusProxy::get() const TBonusListPtr CBonusProxy::get() const
{ {
if(CBonusSystemNode::treeChanged != cachedLast || !data) if(target->getTreeVersion() != cachedLast || !data)
{ {
//TODO: support limiters //TODO: support limiters
data = target->getAllBonuses(selector, nullptr); data = target->getAllBonuses(selector, Selector::all);
data->eliminateDuplicates(); data->eliminateDuplicates();
cachedLast = CBonusSystemNode::treeChanged; cachedLast = target->getTreeVersion();
} }
return data; return data;
} }
@@ -104,7 +143,7 @@ const BonusList * CBonusProxy::operator->() const
return get().get(); return get().get();
} }
int CBonusSystemNode::treeChanged = 1; std::atomic<int32_t> CBonusSystemNode::treeChanged(1);
const bool CBonusSystemNode::cachingEnabled = true; const bool CBonusSystemNode::cachingEnabled = true;
BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree) BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree)
@@ -408,48 +447,44 @@ int IBonusBearer::LuckVal() const
return vstd::abetween(ret, -3, +3); return vstd::abetween(ret, -3, +3);
} }
si32 IBonusBearer::Attack() const
{
si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
if (double frenzyPower = valOfBonuses(Bonus::IN_FRENZY)) //frenzy for attacker
{
ret += (frenzyPower/100) * (double)Defense(false);
}
vstd::amax(ret, 0);
return ret;
}
si32 IBonusBearer::Defense(bool withFrenzy) const
{
si32 ret = valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
if(withFrenzy && hasBonusOfType(Bonus::IN_FRENZY)) //frenzy for defender
{
return 0;
}
vstd::amax(ret, 0);
return ret;
}
ui32 IBonusBearer::MaxHealth() const ui32 IBonusBearer::MaxHealth() const
{ {
return std::max(1, valOfBonuses(Bonus::STACK_HEALTH)); //never 0 const std::string cachingStr = "type_STACK_HEALTH";
static const auto selector = Selector::type(Bonus::STACK_HEALTH);
auto value = valOfBonuses(selector, cachingStr);
return std::max(1, value); //never 0
} }
ui32 IBonusBearer::getMinDamage() const int IBonusBearer::getAttack(bool ranged) const
{ {
std::stringstream cachingStr; const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK";
cachingStr << "type_" << Bonus::CREATURE_DAMAGE << "s_0Otype_" << Bonus::CREATURE_DAMAGE << "s_1";
return valOfBonuses(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1)), cachingStr.str()); static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK);
return getBonuses(selector, nullptr, cachingStr)->totalValue();
} }
ui32 IBonusBearer::getMaxDamage() const
int IBonusBearer::getDefence(bool ranged) const
{ {
std::stringstream cachingStr; const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE";
cachingStr << "type_" << Bonus::CREATURE_DAMAGE << "s_0Otype_" << Bonus::CREATURE_DAMAGE << "s_2";
return valOfBonuses(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2)), cachingStr.str()); static const auto selector = Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE);
return getBonuses(selector, nullptr, cachingStr)->totalValue();
}
int IBonusBearer::getMinDamage(bool ranged) const
{
const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_1";
static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1));
return valOfBonuses(selector, cachingStr);
}
int IBonusBearer::getMaxDamage(bool ranged) const
{
const std::string cachingStr = "type_CREATURE_DAMAGEs_0Otype_CREATURE_DAMAGEs_2";
static const auto selector = Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2));
return valOfBonuses(selector, cachingStr);
} }
si32 IBonusBearer::manaLimit() const si32 IBonusBearer::manaLimit() const
@@ -461,13 +496,7 @@ si32 IBonusBearer::manaLimit() const
int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
{ {
int ret = 0; int ret = valOfBonuses(Bonus::PRIMARY_SKILL, id);
if(id == PrimarySkill::ATTACK)
ret = Attack();
else if(id == PrimarySkill::DEFENSE)
ret = Defense();
else
ret = valOfBonuses(Bonus::PRIMARY_SKILL, id);
vstd::amax(ret, id/2); //minimal value is 0 for attack and defense and 1 for spell power and knowledge vstd::amax(ret, id/2); //minimal value is 0 for attack and defense and 1 for spell power and knowledge
return ret; return ret;
@@ -478,7 +507,7 @@ si32 IBonusBearer::magicResistance() const
return valOfBonuses(Bonus::MAGIC_RESISTANCE); return valOfBonuses(Bonus::MAGIC_RESISTANCE);
} }
ui32 IBonusBearer::Speed(int turn, bool useBind ) const ui32 IBonusBearer::Speed(int turn, bool useBind) const
{ {
//war machines cannot move //war machines cannot move
if(hasBonus(Selector::type(Bonus::SIEGE_WEAPON).And(Selector::turns(turn)))) if(hasBonus(Selector::type(Bonus::SIEGE_WEAPON).And(Selector::turns(turn))))
@@ -505,8 +534,8 @@ bool IBonusBearer::isLiving() const //TODO: theoreticaly there exists "LIVING" b
const std::shared_ptr<Bonus> IBonusBearer::getBonus(const CSelector &selector) const const std::shared_ptr<Bonus> IBonusBearer::getBonus(const CSelector &selector) const
{ {
auto bonuses = getAllBonuses(Selector::all, Selector::all); auto bonuses = getAllBonuses(selector, Selector::all);
return bonuses->getFirst(selector); return bonuses->getFirst(Selector::all);
} }
std::shared_ptr<Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector &selector) std::shared_ptr<Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector &selector)
@@ -657,7 +686,19 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
return ret; return ret;
} }
CBonusSystemNode::CBonusSystemNode() : bonuses(true), exportedBonuses(true), nodeType(UNKNOWN), cachedLast(0) CBonusSystemNode::CBonusSystemNode()
: bonuses(true),
exportedBonuses(true),
nodeType(UNKNOWN),
cachedLast(0)
{
}
CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType)
: bonuses(true),
exportedBonuses(true),
nodeType(NodeType),
cachedLast(0)
{ {
} }
@@ -1048,6 +1089,12 @@ void CBonusSystemNode::treeHasChanged()
treeChanged++; treeChanged++;
} }
int64_t CBonusSystemNode::getTreeVersion() const
{
int64_t ret = treeChanged;
return ret << 32;
}
int NBonus::valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype) int NBonus::valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype)
{ {
if(obj) if(obj)
+22 -9
View File
@@ -64,16 +64,21 @@ public:
} }
}; };
class DLL_LINKAGE CBonusProxy : public boost::noncopyable class DLL_LINKAGE CBonusProxy
{ {
public: public:
CBonusProxy(const IBonusBearer * Target, CSelector Selector); CBonusProxy(const IBonusBearer * Target, CSelector Selector);
CBonusProxy(const CBonusProxy & other);
CBonusProxy(CBonusProxy && other);
CBonusProxy & operator=(CBonusProxy && other);
CBonusProxy & operator=(const CBonusProxy & other);
TBonusListPtr get() const; TBonusListPtr get() const;
const BonusList * operator->() const; const BonusList * operator->() const;
private: private:
mutable int cachedLast; mutable int64_t cachedLast;
const IBonusBearer * target; const IBonusBearer * target;
CSelector selector; CSelector selector;
mutable TBonusListPtr data; mutable TBonusListPtr data;
@@ -607,12 +612,15 @@ public:
bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const; bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const;
//various hlp functions for non-trivial values //various hlp functions for non-trivial values
ui32 getMinDamage() const; //used for stacks and creatures only //used for stacks and creatures only
ui32 getMaxDamage() const;
virtual int getMinDamage(bool ranged) const;
virtual int getMaxDamage(bool ranged) const;
virtual int getAttack(bool ranged) const;
virtual int getDefence(bool ranged) const;
int MoraleVal() const; //range [-3, +3] int MoraleVal() const; //range [-3, +3]
int LuckVal() const; //range [-3, +3] int LuckVal() const; //range [-3, +3]
si32 Attack() const; //get attack of stack with all modificators
si32 Defense(bool withFrenzy = true) const; //get defense of stack with all modificators
ui32 MaxHealth() const; //get max HP of stack with all modifiers ui32 MaxHealth() const; //get max HP of stack with all modifiers
bool isLiving() const; //non-undead, non-non living or alive bool isLiving() const; //non-undead, non-non living or alive
virtual si32 magicResistance() const; virtual si32 magicResistance() const;
@@ -620,9 +628,11 @@ public:
si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge) si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge)
int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const; int getPrimSkillLevel(PrimarySkill::PrimarySkill id) const;
virtual int64_t getTreeVersion() const = 0;
}; };
class DLL_LINKAGE CBonusSystemNode : public IBonusBearer, public boost::noncopyable class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::noncopyable
{ {
public: public:
enum ENodeTypes enum ENodeTypes
@@ -642,8 +652,8 @@ private:
static const bool cachingEnabled; static const bool cachingEnabled;
mutable BonusList cachedBonuses; mutable BonusList cachedBonuses;
mutable int cachedLast; mutable int64_t cachedLast;
static int treeChanged; static std::atomic<int32_t> treeChanged;
// Setting a value to cachingStr before getting any bonuses caches the result for later requests. // Setting a value to cachingStr before getting any bonuses caches the result for later requests.
// This string needs to be unique, that's why it has to be setted in the following manner: // This string needs to be unique, that's why it has to be setted in the following manner:
@@ -656,6 +666,7 @@ private:
public: public:
explicit CBonusSystemNode(); explicit CBonusSystemNode();
explicit CBonusSystemNode(ENodeTypes NodeType);
CBonusSystemNode(CBonusSystemNode && other); CBonusSystemNode(CBonusSystemNode && other);
virtual ~CBonusSystemNode(); virtual ~CBonusSystemNode();
@@ -711,6 +722,8 @@ public:
static void treeHasChanged(); static void treeHasChanged();
int64_t getTreeVersion() const override;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
// h & bonuses; // h & bonuses;
+2 -2
View File
@@ -19,6 +19,6 @@ class IBonusTypeHandler
public: public:
virtual ~IBonusTypeHandler(){}; virtual ~IBonusTypeHandler(){};
virtual std::string bonusToString(const std::shared_ptr<Bonus>& bonus, const IBonusBearer *bearer, bool description) const = 0; virtual std::string bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer, bool description) const = 0;
virtual std::string bonusToGraphics(const std::shared_ptr<Bonus>& bonus) const = 0; virtual std::string bonusToGraphics(const std::shared_ptr<Bonus> & bonus) const = 0;
}; };
+8 -8
View File
@@ -32,12 +32,11 @@ struct Bonus;
class IMarket; class IMarket;
struct SetObjectProperty; struct SetObjectProperty;
struct PackageApplied; struct PackageApplied;
struct BattleAction; class BattleAction;
struct BattleStackAttacked; struct BattleStackAttacked;
struct BattleResult; struct BattleResult;
struct BattleSpellCast; struct BattleSpellCast;
struct CatapultAttack; struct CatapultAttack;
struct BattleStacksRemoved;
class CStack; class CStack;
class CCreatureSet; class CCreatureSet;
struct BattleAttack; struct BattleAttack;
@@ -47,6 +46,10 @@ class CComponent;
struct CObstacleInstance; struct CObstacleInstance;
struct CPackForServer; struct CPackForServer;
class EVictoryLossCheckResult; class EVictoryLossCheckResult;
struct MetaString;
struct CustomEffectInfo;
class ObstacleChanges;
class UnitChanges;
class DLL_LINKAGE IBattleEventsReceiver class DLL_LINKAGE IBattleEventsReceiver
{ {
@@ -54,7 +57,7 @@ public:
virtual void actionFinished(const BattleAction &action){};//occurs AFTER every action taken by any stack or by the hero virtual void actionFinished(const BattleAction &action){};//occurs AFTER every action taken by any stack or by the hero
virtual void actionStarted(const BattleAction &action){};//occurs BEFORE every action taken by any stack or by the hero virtual void actionStarted(const BattleAction &action){};//occurs BEFORE every action taken by any stack or by the hero
virtual void battleAttack(const BattleAttack *ba){}; //called when stack is performing attack virtual void battleAttack(const BattleAttack *ba){}; //called when stack is performing attack
virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa){}; //called when stack receives damage (after battleAttack()) virtual void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog){}; //called when stack receives damage (after battleAttack())
virtual void battleEnd(const BattleResult *br){}; virtual void battleEnd(const BattleResult *br){};
virtual void battleNewRoundFirst(int round){}; //called at the beginning of each turn before changes are applied; virtual void battleNewRoundFirst(int round){}; //called at the beginning of each turn before changes are applied;
virtual void battleNewRound(int round){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn virtual void battleNewRound(int round){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
@@ -64,12 +67,9 @@ public:
virtual void battleTriggerEffect(const BattleTriggerEffect & bte){}; //called for various one-shot effects virtual void battleTriggerEffect(const BattleTriggerEffect & bte){}; //called for various one-shot effects
virtual void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) {}; //called just before battle start virtual void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) {}; //called just before battle start
virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side){}; //called by engine when battle starts; side=0 - left, side=1 - right virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side){}; //called by engine when battle starts; side=0 - left, side=1 - right
virtual void battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom){}; //called when stacks are healed / resurrected first element of pair - stack id, second - healed hp virtual void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog){};
virtual void battleNewStackAppeared(const CStack * stack){}; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned virtual void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles){};
virtual void battleObstaclesRemoved(const std::set<si32> & removedObstacles){}; //called when a certain set of obstacles is removed from batlefield; IDs of them are given
virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack
virtual void battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield
virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){};
virtual void battleGateStateChanged(const EGateState state){}; virtual void battleGateStateChanged(const EGateState state){};
}; };
+3 -6
View File
@@ -11,7 +11,6 @@
#include "../lib/ConstTransitivePtr.h" #include "../lib/ConstTransitivePtr.h"
#include "VCMI_Lib.h" #include "VCMI_Lib.h"
//#include "CModHandler.h"
class JsonNode; class JsonNode;
@@ -69,8 +68,7 @@ public:
void loadObject(std::string scope, std::string name, const JsonNode & data) override void loadObject(std::string scope, std::string name, const JsonNode & data) override
{ {
auto type_name = getTypeName(); auto type_name = getTypeName();
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), objects.size());
object->id = _ObjectID(objects.size());
objects.push_back(object); objects.push_back(object);
@@ -79,8 +77,7 @@ public:
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override
{ {
auto type_name = getTypeName(); auto type_name = getTypeName();
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index);
object->id = _ObjectID(index);
assert(objects[index] == nullptr); // ensure that this id was not loaded before assert(objects[index] == nullptr); // ensure that this id was not loaded before
objects[index] = object; objects[index] = object;
@@ -101,7 +98,7 @@ public:
return objects[raw_id]; return objects[raw_id];
} }
protected: protected:
virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier) = 0; virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) = 0;
virtual const std::string getTypeName() const = 0; virtual const std::string getTypeName() const = 0;
public: //todo: make private public: //todo: make private
std::vector<ConstTransitivePtr<_Object>> objects; std::vector<ConstTransitivePtr<_Object>> objects;
+1 -1
View File
@@ -305,7 +305,7 @@ namespace JsonDetail
return node.Bool(); return node.Bool();
} }
}; };
} // namespace JsonDetail }
template<typename Type> template<typename Type>
Type JsonNode::convertTo() const Type JsonNode::convertTo() const
+67 -187
View File
@@ -12,7 +12,6 @@
#include "NetPacksBase.h" #include "NetPacksBase.h"
#include "battle/BattleAction.h" #include "battle/BattleAction.h"
#include "JsonNode.h"
#include "mapObjects/CGHeroInstance.h" #include "mapObjects/CGHeroInstance.h"
#include "ConstTransitivePtr.h" #include "ConstTransitivePtr.h"
#include "int3.h" #include "int3.h"
@@ -23,10 +22,6 @@
#include "spells/ViewSpellInt.h" #include "spells/ViewSpellInt.h"
class CClient;
class CGameState;
class CGameHandler;
class CConnection;
class CCampaignState; class CCampaignState;
class CArtifact; class CArtifact;
class CSelectionScreen; class CSelectionScreen;
@@ -37,45 +32,7 @@ struct ArtSlotInfo;
struct QuestInfo; struct QuestInfo;
class CMapInfo; class CMapInfo;
struct StartInfo; struct StartInfo;
class IBattleState;
struct CPackForClient : public CPack
{
CPackForClient(){};
CGameState* GS(CClient *cl);
void applyFirstCl(CClient *cl)//called before applying to gs
{}
void applyCl(CClient *cl)//called after applying to gs
{}
};
struct CPackForServer : public CPack
{
PlayerColor player;
CConnection *c;
CGameState* GS(CGameHandler *gh);
CPackForServer():
player(PlayerColor::NEUTRAL),
c(nullptr)
{
}
bool applyGh(CGameHandler *gh) //called after applying to gs
{
logGlobal->error("Should not happen... applying plain CPackForServer");
return false;
}
protected:
void throwNotAllowedAction();
void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);
void throwOnWrongPlayer(CGameHandler * gh, PlayerColor player);
void throwAndCompain(CGameHandler * gh, std::string txt);
bool isPlayerOwns(CGameHandler * gh, ObjectInstanceID id);
private:
void wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedplayer);
};
struct Query : public CPackForClient struct Query : public CPackForClient
{ {
@@ -1351,7 +1308,7 @@ struct MapObjectSelectDialog : public Query
} }
}; };
struct BattleInfo; class BattleInfo;
struct BattleStart : public CPackForClient struct BattleStart : public CPackForClient
{ {
BattleStart() BattleStart()
@@ -1440,12 +1397,16 @@ struct BattleStackMoved : public CPackForClient
{ {
ui32 stack; ui32 stack;
std::vector<BattleHex> tilesToMove; std::vector<BattleHex> tilesToMove;
ui8 distance, teleporting; int distance;
bool teleporting;
BattleStackMoved() BattleStackMoved()
:stack(0), distance(0), teleporting(0) : stack(0),
distance(0),
teleporting(false)
{}; {};
void applyFirstCl(CClient *cl); void applyFirstCl(CClient *cl);
void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
DLL_LINKAGE void applyBattle(IBattleState * battleState);
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & stack; h & stack;
@@ -1454,52 +1415,46 @@ struct BattleStackMoved : public CPackForClient
} }
}; };
struct StacksHealedOrResurrected : public CPackForClient struct BattleUnitsChanged : public CPackForClient
{ {
StacksHealedOrResurrected() BattleUnitsChanged(){}
:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false)
{}
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
DLL_LINKAGE void applyBattle(IBattleState * battleState);
void applyCl(CClient *cl); void applyCl(CClient *cl);
std::vector<CHealthInfo> healedStacks; std::vector<UnitChanges> changedStacks;
bool lifeDrain; //if true, this heal is an effect of life drain or soul steal std::vector<MetaString> battleLog;
bool tentHealing; //if true, than it's healing via First Aid Tent std::vector<CustomEffectInfo> customEffects;
si32 drainedFrom; //if life drain or soul steal - then stack life was drain from, if tentHealing - stack that is a healer
bool cure; //archangel cast also remove negative effects
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & healedStacks; h & changedStacks;
h & lifeDrain; h & battleLog;
h & tentHealing; h & customEffects;
h & drainedFrom;
h & cure;
} }
}; };
struct BattleStackAttacked : public CPackForClient struct BattleStackAttacked
{ {
BattleStackAttacked(): BattleStackAttacked():
stackAttacked(0), attackerID(0), stackAttacked(0), attackerID(0),
killedAmount(0), damageAmount(0), killedAmount(0), damageAmount(0),
newHealth(), newState(),
flags(0), effect(0), spellID(SpellID::NONE) flags(0), effect(0), spellID(SpellID::NONE)
{}; {};
void applyFirstCl(CClient * cl);
//void applyCl(CClient *cl);
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
DLL_LINKAGE void applyBattle(IBattleState * battleState);
ui32 stackAttacked, attackerID; ui32 stackAttacked, attackerID;
ui32 killedAmount; ui32 killedAmount;
si32 damageAmount; int64_t damageAmount;
CHealthInfo newHealth; UnitChanges newState;
enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */}; enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */};
ui32 flags; //uses EFlags (above) ui32 flags; //uses EFlags (above)
ui32 effect; //set only if flag EFFECT is set ui32 effect; //set only if flag EFFECT is set
SpellID spellID; //only if flag SPELL_EFFECT is set SpellID spellID; //only if flag SPELL_EFFECT is set
std::vector<StacksHealedOrResurrected> healedStacks; //used when life drain
bool killed() const//if target stack was killed bool killed() const//if target stack was killed
{ {
@@ -1526,20 +1481,15 @@ struct BattleStackAttacked : public CPackForClient
{ {
return flags & REBIRTH; return flags & REBIRTH;
} }
bool lifeDrain() const //if this attack involves life drain effect
{
return healedStacks.size() > 0;
}
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & stackAttacked; h & stackAttacked;
h & attackerID; h & attackerID;
h & newHealth; h & newState;
h & flags; h & flags;
h & killedAmount; h & killedAmount;
h & damageAmount; h & damageAmount;
h & effect; h & effect;
h & healedStacks;
h & spellID; h & spellID;
} }
bool operator<(const BattleStackAttacked &b) const bool operator<(const BattleStackAttacked &b) const
@@ -1557,6 +1507,8 @@ struct BattleAttack : public CPackForClient
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState *gs);
void applyCl(CClient *cl); void applyCl(CClient *cl);
BattleUnitsChanged attackerChanges;
std::vector<BattleStackAttacked> bsa; std::vector<BattleStackAttacked> bsa;
ui32 stackAttacking; ui32 stackAttacking;
ui32 flags; //uses Eflags (below) ui32 flags; //uses Eflags (below)
@@ -1564,6 +1516,9 @@ struct BattleAttack : public CPackForClient
SpellID spellID; //for SPELL_LIKE SpellID spellID; //for SPELL_LIKE
std::vector<MetaString> battleLog;
std::vector<CustomEffectInfo> customEffects;
bool shot() const//distance attack - decrease number of shots bool shot() const//distance attack - decrease number of shots
{ {
return flags & SHOT; return flags & SHOT;
@@ -1598,6 +1553,9 @@ struct BattleAttack : public CPackForClient
h & stackAttacking; h & stackAttacking;
h & flags; h & flags;
h & spellID; h & spellID;
h & battleLog;
h & customEffects;
h & attackerChanges;
} }
}; };
@@ -1627,24 +1585,9 @@ struct EndAction : public CPackForClient
struct BattleSpellCast : public CPackForClient struct BattleSpellCast : public CPackForClient
{ {
///custom effect (resistance, reflection, etc)
struct CustomEffect
{
/// WoG AC format
ui32 effect;
ui32 stack;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & effect;
h & stack;
}
};
BattleSpellCast() BattleSpellCast()
{ {
side = 0; side = 0;
id = 0;
skill = 0;
manaGained = 0; manaGained = 0;
casterStack = -1; casterStack = -1;
castByHero = true; castByHero = true;
@@ -1655,11 +1598,10 @@ struct BattleSpellCast : public CPackForClient
bool activeCast; bool activeCast;
ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
ui32 id; //id of spell SpellID spellID; //id of spell
ui8 skill; //caster's skill level
ui8 manaGained; //mana channeling ability ui8 manaGained; //mana channeling ability
BattleHex tile; //destination tile (may not be set in some global/mass spells BattleHex tile; //destination tile (may not be set in some global/mass spells
std::vector<CustomEffect> customEffects; std::vector<CustomEffectInfo> customEffects;
std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure) std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
bool castByHero; //if true - spell has been cast by hero, otherwise by a creature bool castByHero; //if true - spell has been cast by hero, otherwise by a creature
@@ -1668,8 +1610,7 @@ struct BattleSpellCast : public CPackForClient
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & side; h & side;
h & id; h & spellID;
h & skill;
h & manaGained; h & manaGained;
h & tile; h & tile;
h & customEffects; h & customEffects;
@@ -1684,27 +1625,20 @@ struct BattleSpellCast : public CPackForClient
struct SetStackEffect : public CPackForClient struct SetStackEffect : public CPackForClient
{ {
SetStackEffect(){}; SetStackEffect(){};
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState * gs);
void applyCl(CClient *cl); DLL_LINKAGE void applyBattle(IBattleState * battleState);
void applyCl(CClient * cl);
std::vector<ui32> stacks; //affected stacks (IDs) std::vector<std::pair<ui32, std::vector<Bonus>>> toAdd;
std::vector<std::pair<ui32, std::vector<Bonus>>> toUpdate;
//regular effects std::vector<std::pair<ui32, std::vector<Bonus>>> toRemove;
std::vector<Bonus> effect; //bonuses to apply
std::vector<std::pair<ui32, Bonus> > uniqueBonuses; //bonuses per single stack
//cumulative effects
std::vector<Bonus> cumulativeEffects; //bonuses to apply
std::vector<std::pair<ui32, Bonus> > cumulativeUniqueBonuses; //bonuses per single stack
std::vector<MetaString> battleLog; std::vector<MetaString> battleLog;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & stacks; h & toAdd;
h & effect; h & toUpdate;
h & uniqueBonuses; h & toRemove;
h & cumulativeEffects;
h & cumulativeUniqueBonuses;
h & battleLog; h & battleLog;
} }
}; };
@@ -1712,13 +1646,18 @@ struct SetStackEffect : public CPackForClient
struct StacksInjured : public CPackForClient struct StacksInjured : public CPackForClient
{ {
StacksInjured(){} StacksInjured(){}
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState * gs);
void applyCl(CClient *cl); DLL_LINKAGE void applyBattle(IBattleState * battleState);
void applyCl(CClient * cl);
std::vector<BattleStackAttacked> stacks; std::vector<BattleStackAttacked> stacks;
template <typename Handler> void serialize(Handler &h, const int version) std::vector<MetaString> battleLog;
template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & stacks; h & stacks;
h & battleLog;
} }
}; };
@@ -1736,18 +1675,19 @@ struct BattleResultsApplied : public CPackForClient
} }
}; };
struct ObstaclesRemoved : public CPackForClient struct BattleObstaclesChanged : public CPackForClient
{ {
ObstaclesRemoved(){} BattleObstaclesChanged(){}
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState * gs);
void applyCl(CClient *cl); DLL_LINKAGE void applyBattle(IBattleState * battleState);
void applyCl(CClient * cl);
std::set<si32> obstacles; //uniqueIDs of removed obstacles std::vector<ObstacleChanges> changes;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & obstacles; h & changes;
} }
}; };
@@ -1759,9 +1699,7 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
ui8 attackedPart; ui8 attackedPart;
ui8 damageDealt; ui8 damageDealt;
DLL_LINKAGE std::string toString() const; template <typename Handler> void serialize(Handler & h, const int version)
template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & destinationTile; h & destinationTile;
h & attackedPart; h & attackedPart;
@@ -1772,9 +1710,9 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
DLL_LINKAGE CatapultAttack(); DLL_LINKAGE CatapultAttack();
DLL_LINKAGE ~CatapultAttack(); DLL_LINKAGE ~CatapultAttack();
DLL_LINKAGE void applyGs(CGameState *gs); DLL_LINKAGE void applyGs(CGameState * gs);
void applyCl(CClient *cl); DLL_LINKAGE void applyBattle(IBattleState * battleState);
DLL_LINKAGE std::string toString() const override; void applyCl(CClient * cl);
std::vector< AttackInfo > attackedParts; std::vector< AttackInfo > attackedParts;
int attacker; //if -1, then a spell caused this int attacker; //if -1, then a spell caused this
@@ -1786,49 +1724,6 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
} }
}; };
struct BattleStacksRemoved : public CPackForClient
{
BattleStacksRemoved(){}
DLL_LINKAGE void applyGs(CGameState *gs);
void applyFirstCl(CClient *cl);//inform client before stack objects are destroyed
std::set<ui32> stackIDs; //IDs of removed stacks
template <typename Handler> void serialize(Handler &h, const int version)
{
h & stackIDs;
}
};
struct BattleStackAdded : public CPackForClient
{
BattleStackAdded()
: side(0), amount(0), pos(0), summoned(0), newStackID(0)
{};
DLL_LINKAGE void applyGs(CGameState *gs);
void applyCl(CClient *cl);
ui8 side;
CreatureID creID;
int amount;
int pos;
int summoned; //if true, remove it afterwards
///Actual stack ID, set on apply, do not serialize
int newStackID;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & side;
h & creID;
h & amount;
h & pos;
h & summoned;
}
};
struct BattleSetStackProperty : public CPackForClient struct BattleSetStackProperty : public CPackForClient
{ {
BattleSetStackProperty() BattleSetStackProperty()
@@ -1877,21 +1772,6 @@ struct BattleTriggerEffect : public CPackForClient
} }
}; };
struct BattleObstaclePlaced : public CPackForClient
{
BattleObstaclePlaced(){};
DLL_LINKAGE void applyGs(CGameState *gs); //effect
void applyCl(CClient *cl); //play animations & stuff
std::shared_ptr<CObstacleInstance> obstacle;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & obstacle;
}
};
struct BattleUpdateGateState : public CPackForClient struct BattleUpdateGateState : public CPackForClient
{ {
BattleUpdateGateState():state(EGateState::NONE){}; BattleUpdateGateState():state(EGateState::NONE){};
+139 -17
View File
@@ -9,7 +9,10 @@
*/ */
#pragma once #pragma once
class CClient;
class CGameState; class CGameState;
class CGameHandler;
class CConnection;
class CStackBasicDescriptor; class CStackBasicDescriptor;
class CGHeroInstance; class CGHeroInstance;
class CStackInstance; class CStackInstance;
@@ -17,9 +20,11 @@ class CArmedInstance;
class CArtifactSet; class CArtifactSet;
class CBonusSystemNode; class CBonusSystemNode;
struct ArtSlotInfo; struct ArtSlotInfo;
class BattleInfo;
#include "ConstTransitivePtr.h" #include "ConstTransitivePtr.h"
#include "GameConstants.h" #include "GameConstants.h"
#include "JsonNode.h"
struct DLL_LINKAGE CPack struct DLL_LINKAGE CPack
{ {
@@ -31,11 +36,49 @@ struct DLL_LINKAGE CPack
logNetwork->error("CPack serialized... this should not happen!"); logNetwork->error("CPack serialized... this should not happen!");
assert(false && "CPack serialized"); assert(false && "CPack serialized");
} }
void applyGs(CGameState *gs) { }
virtual std::string toString() const { return boost::str(boost::format("{CPack: type '%s'}") % typeid(this).name()); } void applyGs(CGameState * gs)
{}
}; };
std::ostream & operator<<(std::ostream & out, const CPack * pack); struct CPackForClient : public CPack
{
CPackForClient(){};
CGameState* GS(CClient *cl);
void applyFirstCl(CClient *cl)//called before applying to gs
{}
void applyCl(CClient *cl)//called after applying to gs
{}
};
struct CPackForServer : public CPack
{
PlayerColor player;
CConnection *c;
CGameState* GS(CGameHandler *gh);
CPackForServer():
player(PlayerColor::NEUTRAL),
c(nullptr)
{
}
bool applyGh(CGameHandler *gh) //called after applying to gs
{
logGlobal->error("Should not happen... applying plain CPackForServer");
return false;
}
protected:
void throwNotAllowedAction();
void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);
void throwOnWrongPlayer(CGameHandler * gh, PlayerColor player);
void throwAndCompain(CGameHandler * gh, std::string txt);
bool isPlayerOwns(CGameHandler * gh, ObjectInstanceID id);
private:
void wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedplayer);
};
struct DLL_LINKAGE MetaString struct DLL_LINKAGE MetaString
{ {
@@ -196,25 +239,104 @@ struct ArtifactLocation
} }
}; };
class CHealthInfo ///custom effect (resistance, reflection, etc)
struct CustomEffectInfo
{ {
public: CustomEffectInfo()
CHealthInfo(): :effect(0),
stackId(0), delta(0), firstHPleft(0), fullUnits(0), resurrected(0) sound(0),
stack(0)
{
}
/// WoG AC format
ui32 effect;
ui32 sound;
ui32 stack;
template <typename Handler> void serialize(Handler & h, const int version)
{
h & effect;
h & sound;
h & stack;
}
};
class BattleChanges
{
public:
enum class EOperation : si8
{
ADD,
RESET_STATE,
UPDATE,
REMOVE
};
JsonNode data;
EOperation operation;
BattleChanges()
: operation(EOperation::RESET_STATE),
data()
{
}
BattleChanges(EOperation operation_)
: operation(operation_),
data()
{
}
};
class UnitChanges : public BattleChanges
{
public:
uint32_t id;
int64_t healthDelta;
UnitChanges()
: BattleChanges(EOperation::RESET_STATE),
id(0),
healthDelta(0)
{
}
UnitChanges(uint32_t id_, EOperation operation_)
: BattleChanges(operation_),
id(id_),
healthDelta(0)
{ {
} }
uint32_t stackId;
int32_t delta;
int32_t firstHPleft;
int32_t fullUnits;
int32_t resurrected;
template <typename Handler> void serialize(Handler & h, const int version) template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & stackId; h & id;
h & delta; h & healthDelta;
h & firstHPleft; h & data;
h & fullUnits; h & operation;
h & resurrected; }
};
class ObstacleChanges : public BattleChanges
{
public:
uint32_t id;
ObstacleChanges()
: BattleChanges(EOperation::RESET_STATE),
id(0)
{
}
ObstacleChanges(uint32_t id_, EOperation operation_)
: BattleChanges(operation_),
id(id_)
{
}
template <typename Handler> void serialize(Handler & h, const int version)
{
h & id;
h & data;
h & operation;
} }
}; };
+109 -396
View File
@@ -32,11 +32,6 @@
#undef max #undef max
std::ostream & operator<<(std::ostream & out, const CPack * pack)
{
return out << (pack? pack->toString() : "<nullptr>");
}
DLL_LINKAGE void SetResources::applyGs(CGameState *gs) DLL_LINKAGE void SetResources::applyGs(CGameState *gs)
{ {
assert(player < PlayerColor::PLAYER_LIMIT); assert(player < PlayerColor::PLAYER_LIMIT);
@@ -1232,50 +1227,12 @@ DLL_LINKAGE void BattleStart::applyGs(CGameState *gs)
DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs) DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs)
{ {
for (int i = 0; i < 2; ++i) gs->curB->nextRound(round);
{
gs->curB->sides[i].castSpellsCount = 0;
vstd::amax(--gs->curB->sides[i].enchanterCounter, 0);
}
gs->curB->round = round;
for(CStack *s : gs->curB->stacks)
{
s->state -= EBattleStackState::DEFENDING;
s->state -= EBattleStackState::WAITING;
s->state -= EBattleStackState::MOVED;
s->state -= EBattleStackState::HAD_MORALE;
s->state -= EBattleStackState::FEAR;
s->state -= EBattleStackState::DRAINED_MANA;
s->counterAttacks.reset();
// new turn effects
s->updateBonuses(Bonus::NTurns);
if(s->alive() && s->isClone())
{
//cloned stack has special lifetime marker
//check it after bonuses updated in battleTurnPassed()
if(!s->hasBonus(Selector::type(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE))))
s->makeGhost();
}
}
for(auto &obst : gs->curB->obstacles)
obst->battleTurnPassed();
} }
DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs) DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs)
{ {
gs->curB->activeStack = stack; gs->curB->nextTurn(stack);
CStack *st = gs->curB->getStack(stack);
//remove bonuses that last until when stack gets new turn
st->popBonuses(Bonus::UntilGetsTurn);
if(vstd::contains(st->state,EBattleStackState::MOVED)) //if stack is moving second time this turn it must had a high morale bonus
st->state.insert(EBattleStackState::HAD_MORALE);
} }
DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs) DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
@@ -1286,15 +1243,14 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
{ {
case Bonus::HP_REGENERATION: case Bonus::HP_REGENERATION:
{ {
int32_t toHeal = val; int64_t toHeal = val;
CHealth health = st->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT); st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
st->setHealth(health);
break; break;
} }
case Bonus::MANA_DRAIN: case Bonus::MANA_DRAIN:
{ {
CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo)); CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
st->state.insert (EBattleStackState::DRAINED_MANA); st->drainedMana = true;
h->mana -= val; h->mana -= val;
vstd::amax(h->mana, 0); vstd::amax(h->mana, 0);
break; break;
@@ -1310,18 +1266,13 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
case Bonus::ENCHANTER: case Bonus::ENCHANTER:
break; break;
case Bonus::FEAR: case Bonus::FEAR:
st->state.insert(EBattleStackState::FEAR); st->fear = true;
break; break;
default: default:
logNetwork->error("Unrecognized trigger effect type %d", effect); logNetwork->error("Unrecognized trigger effect type %d", effect);
} }
} }
DLL_LINKAGE void BattleObstaclePlaced::applyGs(CGameState *gs)
{
gs->curB->obstacles.push_back(obstacle);
}
DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs) DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs)
{ {
if(gs->curB) if(gs->curB)
@@ -1330,15 +1281,6 @@ DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs)
void BattleResult::applyGs(CGameState *gs) void BattleResult::applyGs(CGameState *gs)
{ {
for (CStack *s : gs->curB->stacks)
{
if (s->base && s->base->armyObj && vstd::contains(s->state, EBattleStackState::SUMMONED))
{
//stack with SUMMONED flag but coming from garrison -> most likely resurrected, needs to be removed
assert(&s->base->armyObj->getStack(s->slot) == s->base);
const_cast<CArmedInstance*>(s->base->armyObj)->eraseStack(s->slot);
}
}
for (auto & elem : gs->curB->stacks) for (auto & elem : gs->curB->stacks)
delete elem; delete elem;
@@ -1373,90 +1315,24 @@ void BattleResult::applyGs(CGameState *gs)
gs->curB.dellNull(); gs->curB.dellNull();
} }
void BattleStackMoved::applyGs(CGameState *gs) DLL_LINKAGE void BattleStackMoved::applyGs(CGameState *gs)
{ {
CStack *s = gs->curB->getStack(stack); applyBattle(gs->curB);
assert(s);
BattleHex dest = tilesToMove.back();
//if unit ended movement on quicksands that were created by enemy, that quicksand patch becomes visible for owner
for(auto &oi : gs->curB->obstacles)
{
if(oi->obstacleType == CObstacleInstance::QUICKSAND
&& vstd::contains(oi->getAffectedTiles(), tilesToMove.back()))
{
SpellCreatedObstacle *sands = dynamic_cast<SpellCreatedObstacle*>(oi.get());
assert(sands);
if(sands->casterSide != s->side)
sands->visibleForAnotherSide = true;
}
}
s->position = dest;
} }
DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs) DLL_LINKAGE void BattleStackMoved::applyBattle(IBattleState * battleState)
{ {
CStack * at = gs->curB->getStack(stackAttacked); battleState->moveUnit(stack, tilesToMove.back());
assert(at); }
at->popBonuses(Bonus::UntilBeingAttacked);
if(willRebirth()) DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState * gs)
at->health.reset();//kill stack first {
else applyBattle(gs->curB);
at->setHealth(newHealth); }
if(killed()) DLL_LINKAGE void BattleStackAttacked::applyBattle(IBattleState * battleState)
{ {
at->state -= EBattleStackState::ALIVE; battleState->setUnitState(newState.id, newState.data, newState.healthDelta);
if(at->cloneID >= 0)
{
//remove clone as well
CStack * clone = gs->curB->getStack(at->cloneID);
if(clone)
clone->makeGhost();
at->cloneID = -1;
}
}
//life drain handling
for(auto & elem : healedStacks)
elem.applyGs(gs);
if(willRebirth())
{
//TODO: handle rebirth with StacksHealedOrResurrected
at->casts.use();
at->state.insert(EBattleStackState::ALIVE);
at->setHealth(newHealth);
//removing all spells effects
auto selector = [](const Bonus * b)
{
//Special case: DISRUPTING_RAY is "immune" to dispell
//Other even PERMANENT effects can be removed
if(b->source == Bonus::SPELL_EFFECT)
return b->sid != SpellID::DISRUPTING_RAY;
else
return false;
};
at->popBonuses(selector);
}
if(cloneKilled())
{
//"hide" killed creatures instead so we keep info about it
at->makeGhost();
for(CStack * s : gs->curB->stacks)
{
if(s->cloneID == at->ID)
s->cloneID = -1;
}
}
//killed summoned creature should be removed like clone
if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED))
at->makeGhost();
} }
DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs) DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
@@ -1464,11 +1340,7 @@ DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
CStack * attacker = gs->curB->getStack(stackAttacking); CStack * attacker = gs->curB->getStack(stackAttacking);
assert(attacker); assert(attacker);
if(counter()) attackerChanges.applyGs(gs);
attacker->counterAttacks.use();
if(shot())
attacker->shots.use();
for(BattleStackAttacked & stackAttacked : bsa) for(BattleStackAttacked & stackAttacked : bsa)
stackAttacked.applyGs(gs); stackAttacked.applyGs(gs);
@@ -1480,7 +1352,7 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs)
{ {
CStack *st = gs->curB->getStack(ba.stackNumber); CStack *st = gs->curB->getStack(ba.stackNumber);
if(ba.actionType == Battle::END_TACTIC_PHASE) if(ba.actionType == EActionType::END_TACTIC_PHASE)
{ {
gs->curB->tacticDistance = 0; gs->curB->tacticDistance = 0;
return; return;
@@ -1493,216 +1365,130 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs)
return; return;
} }
if(ba.actionType != Battle::HERO_SPELL) //don't check for stack if it's custom action by hero if(ba.actionType != EActionType::HERO_SPELL) //don't check for stack if it's custom action by hero
{ {
assert(st); assert(st);
} }
else else
{ {
gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.additionalInfo).toSpell()); gs->curB->sides[ba.side].usedSpellsHistory.push_back(SpellID(ba.actionSubtype).toSpell());
} }
switch(ba.actionType) switch(ba.actionType)
{ {
case Battle::DEFEND: case EActionType::DEFEND:
st->state -= EBattleStackState::DEFENDING_ANIM; st->waiting = false;
st->state.insert(EBattleStackState::DEFENDING); st->defending = true;
st->state.insert(EBattleStackState::DEFENDING_ANIM); st->defendingAnim = true;
break;
case EActionType::WAIT:
st->defendingAnim = false;
st->waiting = true;
break;
case EActionType::HERO_SPELL: //no change in current stack state
break; break;
case Battle::WAIT:
st->state -= EBattleStackState::DEFENDING_ANIM;
st->state.insert(EBattleStackState::WAITING);
return;
case Battle::HERO_SPELL: //no change in current stack state
return;
default: //any active stack action - attack, catapult, heal, spell... default: //any active stack action - attack, catapult, heal, spell...
st->state -= EBattleStackState::DEFENDING_ANIM; st->waiting = false;
st->state.insert(EBattleStackState::MOVED); st->defendingAnim = false;
st->movedThisRound = true;
break; break;
} }
if(st)
st->state -= EBattleStackState::WAITING; //if stack was waiting it has made move, so it won't be "waiting" anymore (if the action was WAIT, then we have returned)
} }
DLL_LINKAGE void BattleSpellCast::applyGs(CGameState *gs) DLL_LINKAGE void BattleSpellCast::applyGs(CGameState *gs)
{ {
assert(gs->curB); assert(gs->curB);
const CSpell * spell = SpellID(id).toSpell(); if(castByHero)
spell->applyBattle(gs->curB, this);
}
void actualizeEffect(CStack * s, const Bonus & ef)
{
for(auto stackBonus : s->getBonusList()) //TODO: optimize
{ {
if(stackBonus->source == Bonus::SPELL_EFFECT && stackBonus->type == ef.type && stackBonus->subtype == ef.subtype) if(side < 2)
{ {
stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, ef.turnsRemain); gs->curB->sides[side].castSpellsCount++;
} }
} }
CBonusSystemNode::treeHasChanged();
}
void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
{
//actualizing features vector
for(const Bonus &fromEffect : ef)
{
actualizeEffect(s, fromEffect);
}
} }
DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs) DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
{ {
if(effect.empty() && cumulativeEffects.empty()) applyBattle(gs->curB);
{
logGlobal->error("Trying to apply SetStackEffect with no effects");
return;
}
si32 spellid = effect.empty() ? cumulativeEffects.begin()->sid : effect.begin()->sid; //effects' source ID
auto processEffect = [spellid, this](CStack * sta, const Bonus & effect, bool cumulative)
{
if(cumulative || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype))))
{
//no such effect or cumulative - add new
logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), effect.Description());
sta->addNewBonus(std::make_shared<Bonus>(effect));
}
else
{
logBonus->trace("%s updated bonus: %s", sta->nodeName(), effect.Description());
actualizeEffect(sta, effect);
}
};
for(ui32 id : stacks)
{
CStack *s = gs->curB->getStack(id);
if(s)
{
for(const Bonus & fromEffect : effect)
processEffect(s, fromEffect, false);
for(const Bonus & fromEffect : cumulativeEffects)
processEffect(s, fromEffect, true);
}
else
logNetwork->error("Cannot find stack %d", id);
}
for(auto & para : uniqueBonuses)
{
CStack *s = gs->curB->getStack(para.first);
if(s)
processEffect(s, para.second, false);
else
logNetwork->error("Cannot find stack %d", para.first);
}
for(auto & para : cumulativeUniqueBonuses)
{
CStack *s = gs->curB->getStack(para.first);
if(s)
processEffect(s, para.second, true);
else
logNetwork->error("Cannot find stack %d", para.first);
}
} }
DLL_LINKAGE void SetStackEffect::applyBattle(IBattleState * battleState)
{
for(const auto & stackData : toRemove)
battleState->removeUnitBonus(stackData.first, stackData.second);
for(const auto & stackData : toUpdate)
battleState->updateUnitBonus(stackData.first, stackData.second);
for(const auto & stackData : toAdd)
battleState->addUnitBonus(stackData.first, stackData.second);
}
DLL_LINKAGE void StacksInjured::applyGs(CGameState *gs) DLL_LINKAGE void StacksInjured::applyGs(CGameState *gs)
{
applyBattle(gs->curB);
}
DLL_LINKAGE void StacksInjured::applyBattle(IBattleState * battleState)
{ {
for(BattleStackAttacked stackAttacked : stacks) for(BattleStackAttacked stackAttacked : stacks)
stackAttacked.applyGs(gs); stackAttacked.applyBattle(battleState);
} }
DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs) DLL_LINKAGE void BattleUnitsChanged::applyGs(CGameState *gs)
{ {
for(auto & elem : healedStacks) applyBattle(gs->curB);
}
DLL_LINKAGE void BattleUnitsChanged::applyBattle(IBattleState * battleState)
{
for(auto & elem : changedStacks)
{ {
CStack * changedStack = gs->curB->getStack(elem.stackId, false); switch(elem.operation)
assert(changedStack);
//checking if we resurrect a stack that is under a living stack
auto accessibility = gs->curB->getAccesibility();
if(!changedStack->alive() && !accessibility.accessible(changedStack->position, changedStack))
{ {
logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->position.hex); case BattleChanges::EOperation::RESET_STATE:
return; //position is already occupied battleState->setUnitState(elem.id, elem.data, elem.healthDelta);
} break;
case BattleChanges::EOperation::REMOVE:
//applying changes battleState->removeUnit(elem.id);
bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed break;
if(resurrected) case BattleChanges::EOperation::ADD:
{ battleState->addUnit(elem.id, elem.data);
if(auto totalHealth = changedStack->health.available()) break;
logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), totalHealth); default:
logNetwork->error("Unknown unit operation %d", (int)elem.operation);
changedStack->state.insert(EBattleStackState::ALIVE); break;
}
changedStack->setHealth(elem);
if(resurrected)
{
//removing all spells effects
auto selector = [](const Bonus * b)
{
//Special case: DISRUPTING_RAY is "immune" to dispell
//Other even PERMANENT effects can be removed
if(b->source == Bonus::SPELL_EFFECT)
return b->sid != SpellID::DISRUPTING_RAY;
else
return false;
};
changedStack->popBonuses(selector);
}
else if(cure)
{
//removing all effects from negative spells
auto selector = [](const Bonus * b)
{
//Special case: DISRUPTING_RAY is "immune" to dispell
//Other even PERMANENT effects can be removed
if(b->source == Bonus::SPELL_EFFECT)
{
const CSpell * sourceSpell = SpellID(b->sid).toSpell();
if(!sourceSpell)
return false;
return sourceSpell->id != SpellID::DISRUPTING_RAY && sourceSpell->isNegative();
}
else
return false;
};
changedStack->popBonuses(selector);
} }
} }
} }
DLL_LINKAGE void ObstaclesRemoved::applyGs(CGameState *gs) DLL_LINKAGE void BattleObstaclesChanged::applyGs(CGameState * gs)
{ {
if(gs->curB) //if there is a battle if(gs->curB)
applyBattle(gs->curB);
}
DLL_LINKAGE void BattleObstaclesChanged::applyBattle(IBattleState * battleState)
{
for(const auto & change : changes)
{ {
for(const si32 rem_obst :obstacles) switch(change.operation)
{ {
for(int i=0; i<gs->curB->obstacles.size(); ++i) case BattleChanges::EOperation::REMOVE:
{ battleState->removeObstacle(change.id);
if(gs->curB->obstacles[i]->uniqueID == rem_obst) //remove this obstacle break;
{ case BattleChanges::EOperation::ADD:
gs->curB->obstacles.erase(gs->curB->obstacles.begin() + i); battleState->addObstacle(change);
break; break;
} default:
} logNetwork->error("Unknown obstacle operation %d", (int)change.operation);
break;
} }
} }
} }
DLL_LINKAGE CatapultAttack::CatapultAttack() DLL_LINKAGE CatapultAttack::CatapultAttack()
{ {
attacker = -1; attacker = -1;
@@ -1712,99 +1498,26 @@ DLL_LINKAGE CatapultAttack::~CatapultAttack()
{ {
} }
DLL_LINKAGE void CatapultAttack::applyGs(CGameState *gs) DLL_LINKAGE void CatapultAttack::applyGs(CGameState * gs)
{ {
if(gs->curB && gs->curB->town && gs->curB->town->fortLevel() != CGTownInstance::NONE) //if there is a battle and it's a siege if(gs->curB)
{ applyBattle(gs->curB);
for(const auto &it :attackedParts)
{
gs->curB->si.wallState[it.attackedPart] =
SiegeInfo::applyDamage(EWallState::EWallState(gs->curB->si.wallState[it.attackedPart]), it.damageDealt);
}
}
} }
DLL_LINKAGE std::string CatapultAttack::AttackInfo::toString() const DLL_LINKAGE void CatapultAttack::applyBattle(IBattleState * battleState)
{ {
return boost::str(boost::format("{AttackInfo: destinationTile '%d', attackedPart '%d', damageDealt '%d'}") auto town = battleState->getDefendedTown();
% destinationTile % static_cast<int>(attackedPart) % static_cast<int>(damageDealt)); if(!town)
}
DLL_LINKAGE std::string CatapultAttack::toString() const
{
std::ostringstream out;
out << "[";
for(auto it = attackedParts.begin(); it != attackedParts.end(); ++it)
{
out << it->toString();
if(std::prev(attackedParts.end()) != it) out << ", ";
}
out << "]";
return boost::str(boost::format("{CatapultAttack: attackedParts '%s', attacker '%d'}") % out.str() % attacker);
}
DLL_LINKAGE void BattleStacksRemoved::applyGs(CGameState *gs)
{
if(!gs->curB)
return; return;
while(!stackIDs.empty()) if(town->fortLevel() == CGTownInstance::NONE)
{
ui32 rem_stack = *stackIDs.begin();
for(int b=0; b<gs->curB->stacks.size(); ++b) //find it in vector of stacks
{
if(gs->curB->stacks[b]->ID == rem_stack) //if found
{
CStack * toRemove = gs->curB->stacks[b];
toRemove->state.erase(EBattleStackState::ALIVE);
toRemove->state.erase(EBattleStackState::GHOST_PENDING);
toRemove->state.insert(EBattleStackState::GHOST);
toRemove->detachFromAll();//TODO: may be some bonuses should remain
//stack may be removed instantly (not being killed first)
//handle clone remove also here
if(toRemove->cloneID >= 0)
{
stackIDs.insert(toRemove->cloneID);
toRemove->cloneID = -1;
}
//cleanup remaining clone links if any
for(CStack * s : gs->curB->stacks)
{
if(s->cloneID == toRemove->ID)
s->cloneID = -1;
}
break;
}
}
stackIDs.erase(rem_stack);
}
}
DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
{
newStackID = 0;
if(!BattleHex(pos).isValid())
{
logNetwork->warn("No place found for new stack!");
return; return;
for(const auto & part : attackedParts)
{
auto newWallState = SiegeInfo::applyDamage(EWallState::EWallState(battleState->getWallState(part.attackedPart)), part.damageDealt);
battleState->setWallState(part.attackedPart, newWallState);
} }
CStackBasicDescriptor csbd(creID, amount);
CStack * addedStack = gs->curB->generateNewStack(csbd, side, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks?
if(summoned)
addedStack->state.insert(EBattleStackState::SUMMONED);
addedStack->localInit(gs->curB.get());
gs->curB->stacks.push_back(addedStack);
newStackID = addedStack->ID;
} }
DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs) DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
@@ -1837,7 +1550,7 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
} }
case CLONED: case CLONED:
{ {
stack->state.insert(EBattleStackState::CLONED); stack->cloned = true;
break; break;
} }
case HAS_CLONE: case HAS_CLONE:
+8 -8
View File
@@ -33,13 +33,13 @@
LibClasses * VLC = nullptr; LibClasses * VLC = nullptr;
DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential)
{ {
console = Console; console = Console;
VLC = new LibClasses(); VLC = new LibClasses();
try try
{ {
VLC->loadFilesystem(); VLC->loadFilesystem(onlyEssential);
} }
catch(...) catch(...)
{ {
@@ -48,9 +48,9 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console)
} }
} }
DLL_LINKAGE void loadDLLClasses() DLL_LINKAGE void loadDLLClasses(bool onlyEssential)
{ {
VLC->init(); VLC->init(onlyEssential);
} }
const IBonusTypeHandler * LibClasses::getBth() const const IBonusTypeHandler * LibClasses::getBth() const
@@ -58,7 +58,7 @@ const IBonusTypeHandler * LibClasses::getBth() const
return bth; return bth;
} }
void LibClasses::loadFilesystem() void LibClasses::loadFilesystem(bool onlyEssential)
{ {
CStopWatch totalTime; CStopWatch totalTime;
CStopWatch loadTime; CStopWatch loadTime;
@@ -72,7 +72,7 @@ void LibClasses::loadFilesystem()
modh = new CModHandler(); modh = new CModHandler();
logGlobal->info("\tMod handler: %d ms", loadTime.getDiff()); logGlobal->info("\tMod handler: %d ms", loadTime.getDiff());
modh->loadMods(); modh->loadMods(onlyEssential);
modh->loadModFilesystems(); modh->loadModFilesystems();
logGlobal->info("\tMod filesystems: %d ms", loadTime.getDiff()); logGlobal->info("\tMod filesystems: %d ms", loadTime.getDiff());
@@ -90,7 +90,7 @@ template <class Handler> void createHandler(Handler *&handler, const std::string
logHandlerLoaded(name, timer); logHandlerLoaded(name, timer);
} }
void LibClasses::init() void LibClasses::init(bool onlyEssential)
{ {
CStopWatch pomtime, totalTime; CStopWatch pomtime, totalTime;
@@ -124,7 +124,7 @@ void LibClasses::init()
modh->load(); modh->load();
modh->afterLoad(); modh->afterLoad(onlyEssential);
//FIXME: make sure that everything is ok after game restart //FIXME: make sure that everything is ok after game restart
//TODO: This should be done every time mod config changes //TODO: This should be done every time mod config changes
+4 -4
View File
@@ -53,11 +53,11 @@ public:
LibClasses(); //c-tor, loads .lods and NULLs handlers LibClasses(); //c-tor, loads .lods and NULLs handlers
~LibClasses(); ~LibClasses();
void init(); //uses standard config file void init(bool onlyEssential); //uses standard config file
void clear(); //deletes all handlers and its data void clear(); //deletes all handlers and its data
void loadFilesystem();// basic initialization. should be called before init() void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init()
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
@@ -85,6 +85,6 @@ public:
extern DLL_LINKAGE LibClasses * VLC; extern DLL_LINKAGE LibClasses * VLC;
DLL_LINKAGE void preinitDLL(CConsoleHandler *Console); DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false);
DLL_LINKAGE void loadDLLClasses(); DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false);
+61 -9
View File
@@ -17,8 +17,10 @@
<Option run_host_application_in_terminal="1" /> <Option run_host_application_in_terminal="1" />
<Option createStaticLib="1" /> <Option createStaticLib="1" />
<Compiler> <Compiler>
<Add option="-g" />
<Add option="-Og" /> <Add option="-Og" />
<Add option="-g" />
<Add directory="$(#zlib.include)" />
<Add directory="lib/" />
</Compiler> </Compiler>
<Linker> <Linker>
<Add option="-lws2_32" /> <Add option="-lws2_32" />
@@ -33,6 +35,8 @@
<Add option="-liconv" /> <Add option="-liconv" />
<Add option="-ldbghelp" /> <Add option="-ldbghelp" />
<Add directory="$(#boost.lib32)" /> <Add directory="$(#boost.lib32)" />
<Add directory="$(#sdl2.lib)" />
<Add directory="$(#zlib.lib)" />
</Linker> </Linker>
</Target> </Target>
<Target title="Release-win32"> <Target title="Release-win32">
@@ -45,6 +49,8 @@
<Compiler> <Compiler>
<Add option="-fomit-frame-pointer" /> <Add option="-fomit-frame-pointer" />
<Add option="-O2" /> <Add option="-O2" />
<Add directory="$(#zlib.include)" />
<Add directory="lib/" />
</Compiler> </Compiler>
<Linker> <Linker>
<Add option="-s" /> <Add option="-s" />
@@ -59,6 +65,8 @@
<Add option="-lboost_date_time$(#boost.libsuffix)" /> <Add option="-lboost_date_time$(#boost.libsuffix)" />
<Add option="-liconv" /> <Add option="-liconv" />
<Add directory="$(#boost.lib32)" /> <Add directory="$(#boost.lib32)" />
<Add directory="$(#sdl2.lib)" />
<Add directory="$(#zlib.lib)" />
</Linker> </Linker>
</Target> </Target>
<Target title="Debug-win64"> <Target title="Debug-win64">
@@ -74,6 +82,7 @@
<Add option="-Og" /> <Add option="-Og" />
<Add option="-g" /> <Add option="-g" />
<Add directory="$(#zlib64.include)" /> <Add directory="$(#zlib64.include)" />
<Add directory="lib/" />
</Compiler> </Compiler>
<Linker> <Linker>
<Add option="-lws2_32" /> <Add option="-lws2_32" />
@@ -87,8 +96,8 @@
<Add option="-lboost_date_time$(#boost.libsuffix)" /> <Add option="-lboost_date_time$(#boost.libsuffix)" />
<Add option="-liconv" /> <Add option="-liconv" />
<Add option="-ldbghelp" /> <Add option="-ldbghelp" />
<Add directory="$(#sdl2.lib64)" />
<Add directory="$(#boost.lib64)" /> <Add directory="$(#boost.lib64)" />
<Add directory="$(#sdl2.lib64)" />
<Add directory="$(#zlib64.lib)" /> <Add directory="$(#zlib64.lib)" />
</Linker> </Linker>
</Target> </Target>
@@ -113,17 +122,15 @@
<Add option="-DVCMI_NO_EXTRA_VERSION" /> <Add option="-DVCMI_NO_EXTRA_VERSION" />
<Add directory="." /> <Add directory="." />
<Add directory="$(#sdl2.include)" /> <Add directory="$(#sdl2.include)" />
<Add directory="$(#zlib.include)" />
<Add directory="../include" /> <Add directory="../include" />
</Compiler> </Compiler>
<Linker> <Linker>
<Add directory="../" /> <Add directory="../" />
<Add directory="$(#sdl2.lib)" />
<Add directory="$(#zlib.lib)" />
</Linker> </Linker>
<Unit filename="../Global.h" /> <Unit filename="../Global.h" />
<Unit filename="../Version.h" /> <Unit filename="../Version.h" />
<Unit filename="../include/vstd/CLoggerBase.h" /> <Unit filename="../include/vstd/CLoggerBase.h" />
<Unit filename="../include/vstd/RNG.h" />
<Unit filename="AI_Base.h" /> <Unit filename="AI_Base.h" />
<Unit filename="CArtHandler.cpp" /> <Unit filename="CArtHandler.cpp" />
<Unit filename="CArtHandler.h" /> <Unit filename="CArtHandler.h" />
@@ -216,6 +223,8 @@
<Unit filename="battle/BattleHex.h" /> <Unit filename="battle/BattleHex.h" />
<Unit filename="battle/BattleInfo.cpp" /> <Unit filename="battle/BattleInfo.cpp" />
<Unit filename="battle/BattleInfo.h" /> <Unit filename="battle/BattleInfo.h" />
<Unit filename="battle/BattleProxy.cpp" />
<Unit filename="battle/BattleProxy.h" />
<Unit filename="battle/CBattleInfoCallback.cpp" /> <Unit filename="battle/CBattleInfoCallback.cpp" />
<Unit filename="battle/CBattleInfoCallback.h" /> <Unit filename="battle/CBattleInfoCallback.h" />
<Unit filename="battle/CBattleInfoEssentials.cpp" /> <Unit filename="battle/CBattleInfoEssentials.cpp" />
@@ -226,12 +235,21 @@
<Unit filename="battle/CObstacleInstance.h" /> <Unit filename="battle/CObstacleInstance.h" />
<Unit filename="battle/CPlayerBattleCallback.cpp" /> <Unit filename="battle/CPlayerBattleCallback.cpp" />
<Unit filename="battle/CPlayerBattleCallback.h" /> <Unit filename="battle/CPlayerBattleCallback.h" />
<Unit filename="battle/CUnitState.cpp" />
<Unit filename="battle/CUnitState.h" />
<Unit filename="battle/Destination.cpp" />
<Unit filename="battle/Destination.h" />
<Unit filename="battle/IBattleState.cpp" />
<Unit filename="battle/IBattleState.h" />
<Unit filename="battle/IUnitInfo.h" />
<Unit filename="battle/ReachabilityInfo.cpp" /> <Unit filename="battle/ReachabilityInfo.cpp" />
<Unit filename="battle/ReachabilityInfo.h" /> <Unit filename="battle/ReachabilityInfo.h" />
<Unit filename="battle/SideInBattle.cpp" /> <Unit filename="battle/SideInBattle.cpp" />
<Unit filename="battle/SideInBattle.h" /> <Unit filename="battle/SideInBattle.h" />
<Unit filename="battle/SiegeInfo.cpp" /> <Unit filename="battle/SiegeInfo.cpp" />
<Unit filename="battle/SiegeInfo.h" /> <Unit filename="battle/SiegeInfo.h" />
<Unit filename="battle/Unit.cpp" />
<Unit filename="battle/Unit.h" />
<Unit filename="filesystem/AdapterLoaders.cpp" /> <Unit filename="filesystem/AdapterLoaders.cpp" />
<Unit filename="filesystem/AdapterLoaders.h" /> <Unit filename="filesystem/AdapterLoaders.h" />
<Unit filename="filesystem/CArchiveLoader.cpp" /> <Unit filename="filesystem/CArchiveLoader.cpp" />
@@ -362,21 +380,55 @@
<Unit filename="serializer/JsonSerializeFormat.h" /> <Unit filename="serializer/JsonSerializeFormat.h" />
<Unit filename="serializer/JsonSerializer.cpp" /> <Unit filename="serializer/JsonSerializer.cpp" />
<Unit filename="serializer/JsonSerializer.h" /> <Unit filename="serializer/JsonSerializer.h" />
<Unit filename="serializer/JsonTreeSerializer.h" />
<Unit filename="spells/AdventureSpellMechanics.cpp" /> <Unit filename="spells/AdventureSpellMechanics.cpp" />
<Unit filename="spells/AdventureSpellMechanics.h" /> <Unit filename="spells/AdventureSpellMechanics.h" />
<Unit filename="spells/BattleSpellMechanics.cpp" /> <Unit filename="spells/BattleSpellMechanics.cpp" />
<Unit filename="spells/BattleSpellMechanics.h" /> <Unit filename="spells/BattleSpellMechanics.h" />
<Unit filename="spells/CDefaultSpellMechanics.cpp" />
<Unit filename="spells/CDefaultSpellMechanics.h" />
<Unit filename="spells/CSpellHandler.cpp" /> <Unit filename="spells/CSpellHandler.cpp" />
<Unit filename="spells/CSpellHandler.h" /> <Unit filename="spells/CSpellHandler.h" />
<Unit filename="spells/CreatureSpellMechanics.cpp" />
<Unit filename="spells/CreatureSpellMechanics.h" />
<Unit filename="spells/ISpellMechanics.cpp" /> <Unit filename="spells/ISpellMechanics.cpp" />
<Unit filename="spells/ISpellMechanics.h" /> <Unit filename="spells/ISpellMechanics.h" />
<Unit filename="spells/Magic.h" /> <Unit filename="spells/Magic.h" />
<Unit filename="spells/Problem.cpp" />
<Unit filename="spells/Problem.h" />
<Unit filename="spells/TargetCondition.cpp" />
<Unit filename="spells/TargetCondition.h" />
<Unit filename="spells/ViewSpellInt.cpp" /> <Unit filename="spells/ViewSpellInt.cpp" />
<Unit filename="spells/ViewSpellInt.h" /> <Unit filename="spells/ViewSpellInt.h" />
<Unit filename="spells/effects/Catapult.cpp" />
<Unit filename="spells/effects/Catapult.h" />
<Unit filename="spells/effects/Clone.cpp" />
<Unit filename="spells/effects/Clone.h" />
<Unit filename="spells/effects/Damage.cpp" />
<Unit filename="spells/effects/Damage.h" />
<Unit filename="spells/effects/Dispel.cpp" />
<Unit filename="spells/effects/Dispel.h" />
<Unit filename="spells/effects/Effect.cpp" />
<Unit filename="spells/effects/Effect.h" />
<Unit filename="spells/effects/Effects.cpp" />
<Unit filename="spells/effects/Effects.h" />
<Unit filename="spells/effects/EffectsFwd.h" />
<Unit filename="spells/effects/Heal.cpp" />
<Unit filename="spells/effects/Heal.h" />
<Unit filename="spells/effects/LocationEffect.cpp" />
<Unit filename="spells/effects/LocationEffect.h" />
<Unit filename="spells/effects/Obstacle.cpp" />
<Unit filename="spells/effects/Obstacle.h" />
<Unit filename="spells/effects/Registry.cpp" />
<Unit filename="spells/effects/Registry.h" />
<Unit filename="spells/effects/RemoveObstacle.cpp" />
<Unit filename="spells/effects/RemoveObstacle.h" />
<Unit filename="spells/effects/Sacrifice.cpp" />
<Unit filename="spells/effects/Sacrifice.h" />
<Unit filename="spells/effects/Summon.cpp" />
<Unit filename="spells/effects/Summon.h" />
<Unit filename="spells/effects/Teleport.cpp" />
<Unit filename="spells/effects/Teleport.h" />
<Unit filename="spells/effects/Timed.cpp" />
<Unit filename="spells/effects/Timed.h" />
<Unit filename="spells/effects/UnitEffect.cpp" />
<Unit filename="spells/effects/UnitEffect.h" />
<Unit filename="vcmi_endian.h" /> <Unit filename="vcmi_endian.h" />
<Extensions> <Extensions>
<code_completion /> <code_completion />
+22
View File
@@ -222,7 +222,18 @@
<ClCompile Include="spells\BattleSpellMechanics.cpp" /> <ClCompile Include="spells\BattleSpellMechanics.cpp" />
<ClCompile Include="spells\CreatureSpellMechanics.cpp" /> <ClCompile Include="spells\CreatureSpellMechanics.cpp" />
<ClCompile Include="spells\CDefaultSpellMechanics.cpp" /> <ClCompile Include="spells\CDefaultSpellMechanics.cpp" />
<ClCompile Include="spells\Problem.cpp" />
<ClCompile Include="spells\ViewSpellInt.cpp" /> <ClCompile Include="spells\ViewSpellInt.cpp" />
<ClCompile Include="spells\effects\Effect.cpp" />
<ClCompile Include="spells\effects\Effects.cpp" />
<ClCompile Include="spells\effects\Clone.cpp" />
<ClCompile Include="spells\effects\Damage.cpp" />
<ClCompile Include="spells\effects\GlobalEffect.cpp" />
<ClCompile Include="spells\effects\LocationEffect.cpp" />
<ClCompile Include="spells\effects\Registry.cpp" />
<ClCompile Include="spells\effects\StackEffect.cpp" />
<ClCompile Include="spells\effects\Summon.cpp" />
<ClCompile Include="spells\effects\Timed.cpp" />
<ClCompile Include="filesystem\AdapterLoaders.cpp" /> <ClCompile Include="filesystem\AdapterLoaders.cpp" />
<ClCompile Include="filesystem\CArchiveLoader.cpp" /> <ClCompile Include="filesystem\CArchiveLoader.cpp" />
<ClCompile Include="filesystem\CBinaryReader.cpp" /> <ClCompile Include="filesystem\CBinaryReader.cpp" />
@@ -429,7 +440,18 @@
<ClInclude Include="spells\ISpellMechanics.h" /> <ClInclude Include="spells\ISpellMechanics.h" />
<ClInclude Include="spells\Magic.h" /> <ClInclude Include="spells\Magic.h" />
<ClInclude Include="spells\SpellMechanics.h" /> <ClInclude Include="spells\SpellMechanics.h" />
<ClInclude Include="spells\Problem.h" />
<ClInclude Include="spells\ViewSpellInt.h" /> <ClInclude Include="spells\ViewSpellInt.h" />
<ClInclude Include="spells\effects\Effect.h" />
<ClInclude Include="spells\effects\Effects.h" />
<ClInclude Include="spells\effects\Clone.h" />
<ClInclude Include="spells\effects\Damage.h" />
<ClInclude Include="spells\effects\GlobalEffect.h" />
<ClInclude Include="spells\effects\LocationEffect.h" />
<ClInclude Include="spells\effects\Registry.h" />
<ClInclude Include="spells\effects\StackEffect.h" />
<ClInclude Include="spells\effects\Summon.h" />
<ClInclude Include="spells\effects\Timed.h" />
<ClInclude Include="StartInfo.h" /> <ClInclude Include="StartInfo.h" />
<ClInclude Include="StdInc.h" /> <ClInclude Include="StdInc.h" />
<ClInclude Include="StringConstants.h" /> <ClInclude Include="StringConstants.h" />
+14 -12
View File
@@ -9,29 +9,31 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "AccessibilityInfo.h" #include "AccessibilityInfo.h"
#include "../CStack.h" #include "Unit.h"
#include "../GameConstants.h" #include "../GameConstants.h"
bool AccessibilityInfo::accessible(BattleHex tile, const CStack * stack) const bool AccessibilityInfo::accessible(BattleHex tile, const battle::Unit * stack) const
{ {
return accessible(tile, stack->doubleWide(), stack->side); return accessible(tile, stack->doubleWide(), stack->unitSide());
} }
bool AccessibilityInfo::accessible(BattleHex tile, bool doubleWide, ui8 side) const bool AccessibilityInfo::accessible(BattleHex tile, bool doubleWide, ui8 side) const
{ {
// All hexes that stack would cover if standing on tile have to be accessible. // All hexes that stack would cover if standing on tile have to be accessible.
for(auto hex : CStack::getHexes(tile, doubleWide, side)) //do not use getHexes for speed reasons
if(!tile.isValid())
return false;
if(at(tile) != EAccessibility::ACCESSIBLE && !(at(tile) == EAccessibility::GATE && side == BattleSide::DEFENDER))
return false;
if(doubleWide)
{ {
// If the hex is out of range then the tile isn't accessible auto otherHex = battle::Unit::occupiedHex(tile, doubleWide, side);
if(!hex.isValid()) if(!otherHex.isValid())
return false; return false;
// If we're no defender which step on gate and the hex isn't accessible, then the tile if(at(otherHex) != EAccessibility::ACCESSIBLE && !(at(otherHex) == EAccessibility::GATE && side == BattleSide::DEFENDER))
// isn't accessible
else if(at(hex) != EAccessibility::ACCESSIBLE &&
!(at(hex) == EAccessibility::GATE && side == BattleSide::DEFENDER))
{
return false; return false;
}
} }
return true; return true;
} }
+5 -2
View File
@@ -11,7 +11,10 @@
#include "BattleHex.h" #include "BattleHex.h"
#include "../GameConstants.h" #include "../GameConstants.h"
class CStack; namespace battle
{
class Unit;
}
//Accessibility is property of hex in battle. It doesn't depend on stack, side's perspective and so on. //Accessibility is property of hex in battle. It doesn't depend on stack, side's perspective and so on.
enum class EAccessibility enum class EAccessibility
@@ -30,6 +33,6 @@ typedef std::array<EAccessibility, GameConstants::BFIELD_SIZE> TAccessibilityArr
struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
{ {
bool accessible(BattleHex tile, const CStack * stack) const; //checks for both tiles if stack is double wide bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
bool accessible(BattleHex tile, bool doubleWide, ui8 side) const; //checks for both tiles if stack is double wide bool accessible(BattleHex tile, bool doubleWide, ui8 side) const; //checks for both tiles if stack is double wide
}; };
+107 -44
View File
@@ -10,77 +10,77 @@
#include "StdInc.h" #include "StdInc.h"
#include "BattleAction.h" #include "BattleAction.h"
#include "../CStack.h" #include "Unit.h"
#include "CBattleInfoCallback.h"
using namespace Battle; static const int32_t INVALID_UNIT_ID = -1000;
BattleAction::BattleAction(): BattleAction::BattleAction():
side(-1), side(-1),
stackNumber(-1), stackNumber(-1),
actionType(INVALID), actionType(EActionType::INVALID),
destinationTile(-1), actionSubtype(-1)
additionalInfo(-1),
selectedStack(-1)
{ {
} }
BattleAction BattleAction::makeHeal(const CStack * healer, const CStack * healed) BattleAction BattleAction::makeHeal(const battle::Unit * healer, const battle::Unit * healed)
{ {
BattleAction ba; BattleAction ba;
ba.side = healer->side; ba.side = healer->unitSide();
ba.actionType = STACK_HEAL; ba.actionType = EActionType::STACK_HEAL;
ba.stackNumber = healer->ID; ba.stackNumber = healer->unitId();
ba.destinationTile = healed->position; ba.aimToUnit(healed);
return ba; return ba;
} }
BattleAction BattleAction::makeDefend(const CStack * stack) BattleAction BattleAction::makeDefend(const battle::Unit * stack)
{ {
BattleAction ba; BattleAction ba;
ba.side = stack->side; ba.side = stack->unitSide();
ba.actionType = DEFEND; ba.actionType = EActionType::DEFEND;
ba.stackNumber = stack->ID; ba.stackNumber = stack->unitId();
return ba; return ba;
} }
BattleAction BattleAction::makeMeleeAttack(const battle::Unit * stack, BattleHex destination, BattleHex attackFrom, bool returnAfterAttack)
BattleAction BattleAction::makeMeleeAttack(const CStack * stack, const CStack * attacked, BattleHex attackFrom)
{ {
BattleAction ba; BattleAction ba;
ba.side = stack->side; ba.side = stack->unitSide(); //FIXME: will it fail if stack mind controlled?
ba.actionType = WALK_AND_ATTACK; ba.actionType = EActionType::WALK_AND_ATTACK;
ba.stackNumber = stack->ID; ba.stackNumber = stack->unitId();
ba.destinationTile = attackFrom; ba.aimToHex(attackFrom);
ba.additionalInfo = attacked->position; ba.aimToHex(destination);
return ba; if(returnAfterAttack && stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
ba.aimToHex(stack->getPosition());
}
BattleAction BattleAction::makeWait(const CStack * stack)
{
BattleAction ba;
ba.side = stack->side;
ba.actionType = WAIT;
ba.stackNumber = stack->ID;
return ba; return ba;
} }
BattleAction BattleAction::makeShotAttack(const CStack * shooter, const CStack * target) BattleAction BattleAction::makeWait(const battle::Unit * stack)
{ {
BattleAction ba; BattleAction ba;
ba.side = shooter->side; ba.side = stack->unitSide();
ba.actionType = SHOOT; ba.actionType = EActionType::WAIT;
ba.stackNumber = shooter->ID; ba.stackNumber = stack->unitId();
ba.destinationTile = target->position;
return ba; return ba;
} }
BattleAction BattleAction::makeMove(const CStack * stack, BattleHex dest) BattleAction BattleAction::makeShotAttack(const battle::Unit * shooter, const battle::Unit * target)
{ {
BattleAction ba; BattleAction ba;
ba.side = stack->side; ba.side = shooter->unitSide();
ba.actionType = WALK; ba.actionType = EActionType::SHOOT;
ba.stackNumber = stack->ID; ba.stackNumber = shooter->unitId();
ba.destinationTile = dest; ba.aimToUnit(target);
return ba;
}
BattleAction BattleAction::makeMove(const battle::Unit * stack, BattleHex dest)
{
BattleAction ba;
ba.side = stack->unitSide();
ba.actionType = EActionType::WALK;
ba.stackNumber = stack->unitId();
ba.aimToHex(dest);
return ba; return ba;
} }
@@ -88,7 +88,7 @@ BattleAction BattleAction::makeEndOFTacticPhase(ui8 side)
{ {
BattleAction ba; BattleAction ba;
ba.side = side; ba.side = side;
ba.actionType = END_TACTIC_PHASE; ba.actionType = EActionType::END_TACTIC_PHASE;
return ba; return ba;
} }
@@ -97,11 +97,74 @@ std::string BattleAction::toString() const
std::stringstream actionTypeStream; std::stringstream actionTypeStream;
actionTypeStream << actionType; actionTypeStream << actionType;
boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', destinationTile '%s', additionalInfo '%d', selectedStack '%d'}"); std::stringstream targetStream;
fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % destinationTile % additionalInfo % selectedStack;
for(const DestinationInfo & info : target)
{
if(info.unitValue == INVALID_UNIT_ID)
{
targetStream << info.hexValue;
}
else
{
targetStream << info.unitValue;
targetStream << "@";
targetStream << info.hexValue;
}
targetStream << ",";
}
boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', actionSubtype '%d', target {%s}}");
fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % actionSubtype % targetStream.str();
return fmt.str(); return fmt.str();
} }
void BattleAction::aimToHex(const BattleHex & destination)
{
DestinationInfo info;
info.hexValue = destination;
info.unitValue = INVALID_UNIT_ID;
target.push_back(info);
}
void BattleAction::aimToUnit(const battle::Unit * destination)
{
DestinationInfo info;
info.hexValue = destination->getPosition();
info.unitValue = destination->unitId();
target.push_back(info);
}
battle::Target BattleAction::getTarget(const CBattleInfoCallback * cb) const
{
battle::Target ret;
for(auto & destination : target)
{
if(destination.unitValue == INVALID_UNIT_ID)
ret.emplace_back(destination.hexValue);
else
ret.emplace_back(cb->battleGetUnitByID(destination.unitValue));
}
return ret;
}
void BattleAction::setTarget(const battle::Target & target_)
{
target.clear();
for(auto & destination : target_)
{
if(destination.unitValue == nullptr)
aimToHex(destination.hexValue);
else
aimToUnit(destination.unitValue);
}
}
std::ostream & operator<<(std::ostream & os, const BattleAction & ba) std::ostream & operator<<(std::ostream & os, const BattleAction & ba)
{ {
os << ba.toString(); os << ba.toString();
+46 -21
View File
@@ -8,42 +8,67 @@
* *
*/ */
#pragma once #pragma once
#include "BattleHex.h" #include "Destination.h"
#include "../GameConstants.h" #include "../GameConstants.h"
class CStack; class CBattleInfoCallback;
namespace battle
{
class Unit;
}
/// A struct which handles battle actions like defending, walking,... - represents a creature stack in a battle /// A struct which handles battle actions like defending, walking,... - represents a creature stack in a battle
struct DLL_LINKAGE BattleAction class DLL_LINKAGE BattleAction
{ {
ui8 side; //who made this action: false - left, true - right player public:
ui8 side; //who made this action
ui32 stackNumber; //stack ID, -1 left hero, -2 right hero, ui32 stackNumber; //stack ID, -1 left hero, -2 right hero,
Battle::ActionType actionType; //use ActionType enum for values EActionType actionType; //use ActionType enum for values
BattleHex destinationTile;
si32 additionalInfo; // e.g. spell number if type is 1 || 10; tile to attack if type is 6
si32 selectedStack; //spell subject for teleport / sacrifice
template <typename Handler> void serialize(Handler &h, const int version) si32 actionSubtype;
BattleAction();
static BattleAction makeHeal(const battle::Unit * healer, const battle::Unit * healed);
static BattleAction makeDefend(const battle::Unit * stack);
static BattleAction makeWait(const battle::Unit * stack);
static BattleAction makeMeleeAttack(const battle::Unit * stack, BattleHex destination, BattleHex attackFrom, bool returnAfterAttack = true);
static BattleAction makeShotAttack(const battle::Unit * shooter, const battle::Unit * target);
static BattleAction makeMove(const battle::Unit * stack, BattleHex dest);
static BattleAction makeEndOFTacticPhase(ui8 side);
std::string toString() const;
void aimToHex(const BattleHex & destination);
void aimToUnit(const battle::Unit * destination);
battle::Target getTarget(const CBattleInfoCallback * cb) const;
void setTarget(const battle::Target & target_);
template <typename Handler> void serialize(Handler & h, const int version)
{ {
h & side; h & side;
h & stackNumber; h & stackNumber;
h & actionType; h & actionType;
h & destinationTile; h & actionSubtype;
h & additionalInfo; h & target;
h & selectedStack;
} }
private:
BattleAction(); struct DestinationInfo
{
int32_t unitValue;
BattleHex hexValue;
static BattleAction makeHeal(const CStack * healer, const CStack * healed); template <typename Handler> void serialize(Handler & h, const int version)
static BattleAction makeDefend(const CStack * stack); {
static BattleAction makeWait(const CStack * stack); h & unitValue;
static BattleAction makeMeleeAttack(const CStack * stack, const CStack * attacked, BattleHex attackFrom = BattleHex::INVALID); h & hexValue;
static BattleAction makeShotAttack(const CStack * shooter, const CStack * target); }
static BattleAction makeMove(const CStack * stack, BattleHex dest); };
static BattleAction makeEndOFTacticPhase(ui8 side);
std::string toString() const; std::vector<DestinationInfo> target;
}; };
DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleAction & ba); //todo: remove DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleAction & ba); //todo: remove
+10 -21
View File
@@ -9,40 +9,29 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "BattleAttackInfo.h" #include "BattleAttackInfo.h"
#include "CUnitState.h"
BattleAttackInfo::BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting)
BattleAttackInfo::BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting): : attacker(Attacker),
attackerHealth(Attacker->health), defenderHealth(Defender->health) defender(Defender)
{ {
attacker = Attacker;
defender = Defender;
attackerBonuses = Attacker;
defenderBonuses = Defender;
attackerPosition = Attacker->position;
defenderPosition = Defender->position;
shooting = Shooting; shooting = Shooting;
chargedFields = 0; chargedFields = 0;
additiveBonus = 0.0;
luckyHit = false; multBonus = 1.0;
unluckyHit = false;
deathBlow = false;
ballistaDoubleDamage = false;
} }
BattleAttackInfo BattleAttackInfo::reverse() const BattleAttackInfo BattleAttackInfo::reverse() const
{ {
BattleAttackInfo ret = *this; BattleAttackInfo ret = *this;
std::swap(ret.attacker, ret.defender); std::swap(ret.attacker, ret.defender);
std::swap(ret.attackerBonuses, ret.defenderBonuses);
std::swap(ret.attackerPosition, ret.defenderPosition);
std::swap(ret.attackerHealth, ret.defenderHealth);
ret.shooting = false; ret.shooting = false;
ret.chargedFields = 0; ret.chargedFields = 0;
ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
ret.additiveBonus = 0.0;
ret.multBonus = 1.0;
return ret; return ret;
} }
+10 -13
View File
@@ -8,27 +8,24 @@
* *
*/ */
#pragma once #pragma once
#include "BattleHex.h"
#include "../CStack.h"
class IBonusBearer; namespace battle
{
class Unit;
class CUnitState;
}
struct DLL_LINKAGE BattleAttackInfo struct DLL_LINKAGE BattleAttackInfo
{ {
const IBonusBearer *attackerBonuses, *defenderBonuses; const battle::Unit * attacker;
const CStack *attacker, *defender; const battle::Unit * defender;
BattleHex attackerPosition, defenderPosition;
CHealth attackerHealth, defenderHealth;
bool shooting; bool shooting;
int chargedFields; int chargedFields;
bool luckyHit; double additiveBonus;
bool unluckyHit; double multBonus;
bool deathBlow;
bool ballistaDoubleDamage;
BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting = false); BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting = false);
BattleAttackInfo reverse() const; BattleAttackInfo reverse() const;
}; };
+25 -2
View File
@@ -9,7 +9,6 @@
*/ */
#include "StdInc.h" #include "StdInc.h"
#include "BattleHex.h" #include "BattleHex.h"
#include "../GameConstants.h"
BattleHex::BattleHex() : hex(INVALID) {} BattleHex::BattleHex() : hex(INVALID) {}
@@ -53,7 +52,11 @@ void BattleHex::setY(si16 y)
void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid)
{ {
if(hasToBeValid) if(hasToBeValid)
assert(x >= 0 && x < GameConstants::BFIELD_WIDTH && y >= 0 && y < GameConstants::BFIELD_HEIGHT); {
if(!(x >= 0 && x < GameConstants::BFIELD_WIDTH && y >= 0 && y < GameConstants::BFIELD_HEIGHT))
throw std::runtime_error("Valid hex required");
}
hex = x + y * GameConstants::BFIELD_WIDTH; hex = x + y * GameConstants::BFIELD_WIDTH;
} }
@@ -129,6 +132,7 @@ BattleHex BattleHex::operator+(BattleHex::EDir dir) const
std::vector<BattleHex> BattleHex::neighbouringTiles() const std::vector<BattleHex> BattleHex::neighbouringTiles() const
{ {
std::vector<BattleHex> ret; std::vector<BattleHex> ret;
ret.reserve(6);
for(EDir dir = EDir(0); dir <= EDir(5); dir = EDir(dir+1)) for(EDir dir = EDir(0); dir <= EDir(5); dir = EDir(dir+1))
checkAndPush(cloneInDirection(dir, false), ret); checkAndPush(cloneInDirection(dir, false), ret);
return ret; return ret;
@@ -201,3 +205,22 @@ std::ostream & operator<<(std::ostream & os, const BattleHex & hex)
{ {
return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex);
} }
static BattleHex::NeighbouringTilesCache calculateNeighbouringTiles()
{
BattleHex::NeighbouringTilesCache ret;
ret.resize(GameConstants::BFIELD_SIZE);
for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++)
{
auto hexes = BattleHex(hex).neighbouringTiles();
size_t index = 0;
for(auto neighbour : hexes)
ret[hex].at(index++) = neighbour;
}
return ret;
}
const BattleHex::NeighbouringTilesCache BattleHex::neighbouringTilesCache = calculateNeighbouringTiles();
+12
View File
@@ -20,6 +20,13 @@ namespace BattleSide
}; };
} }
namespace GameConstants
{
const int BFIELD_WIDTH = 17;
const int BFIELD_HEIGHT = 11;
const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
}
typedef boost::optional<ui8> BattleSideOpt; typedef boost::optional<ui8> BattleSideOpt;
// for battle stacks' positions // for battle stacks' positions
@@ -67,6 +74,11 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
{ {
h & hex; h & hex;
} }
using NeighbouringTiles = std::array<BattleHex, 6>;
using NeighbouringTilesCache = std::vector<NeighbouringTiles>;
static const NeighbouringTilesCache neighbouringTilesCache;
}; };
DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex);
+433 -129
View File
@@ -16,48 +16,7 @@
#include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CGTownInstance.h"
#include "../CGeneralTextHandler.h" #include "../CGeneralTextHandler.h"
const CStack * BattleInfo::getNextStack() const ///BattleInfo
{
std::vector<const CStack *> hlp;
battleGetStackQueue(hlp, 1, -1);
if(hlp.size())
return hlp[0];
else
return nullptr;
}
int BattleInfo::getAvaliableHex(CreatureID creID, ui8 side, int initialPos) const
{
bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
//bool flying = VLC->creh->creatures[creID]->isFlying();
int pos;
if (initialPos > -1)
pos = initialPos;
else //summon elementals depending on player side
{
if(side == BattleSide::ATTACKER)
pos = 0; //top left
else
pos = GameConstants::BFIELD_WIDTH - 1; //top right
}
auto accessibility = getAccesibility();
std::set<BattleHex> occupyable;
for(int i = 0; i < accessibility.size(); i++)
if(accessibility.accessible(i, twoHex, side))
occupyable.insert(i);
if (occupyable.empty())
{
return BattleHex::INVALID; //all tiles are covered
}
return BattleHex::getClosestTile(side, pos, occupyable);
}
std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const CStack * stack) std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const CStack * stack)
{ {
auto reachability = getReachability(stack); auto reachability = getReachability(stack);
@@ -79,31 +38,6 @@ std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, Ba
return std::make_pair(path, reachability.distances[dest]); return std::make_pair(path, reachability.distances[dest]);
} }
ui32 BattleInfo::calculateDmg(const CStack * attacker, const CStack * defender,
bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg, CRandomGenerator & rand)
{
BattleAttackInfo bai(attacker, defender, shooting);
bai.chargedFields = charge;
bai.luckyHit = lucky;
bai.unluckyHit = unlucky;
bai.deathBlow = deathBlow;
bai.ballistaDoubleDamage = ballistaDoubleDmg;
TDmgRange range = calculateDmgRange(bai);
if(range.first != range.second)
{
ui32 sum = 0;
ui32 howManyToAv = std::min<ui32>(10, attacker->getCount());
for(int g=0; g<howManyToAv; ++g)
sum += (ui32)rand.nextInt(range.first, range.second);
return sum / howManyToAv;
}
else
return range.first;
}
void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const
{ {
for(auto & elem : stacks)//setting casualties for(auto & elem : stacks)//setting casualties
@@ -115,26 +49,24 @@ void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const
} }
} }
CStack * BattleInfo::generateNewStack(const CStackInstance & base, ui8 side, SlotID slot, BattleHex position) const CStack * BattleInfo::generateNewStack(uint32_t id, const CStackInstance & base, ui8 side, SlotID slot, BattleHex position)
{ {
int stackID = getIdForNewStack();
PlayerColor owner = sides[side].color; PlayerColor owner = sides[side].color;
assert((owner >= PlayerColor::PLAYER_LIMIT) || assert((owner >= PlayerColor::PLAYER_LIMIT) ||
(base.armyObj && base.armyObj->tempOwner == owner)); (base.armyObj && base.armyObj->tempOwner == owner));
auto ret = new CStack(&base, owner, stackID, side, slot); auto ret = new CStack(&base, owner, id, side, slot);
ret->position = getAvaliableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found? ret->initialPosition = getAvaliableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found?
ret->state.insert(EBattleStackState::ALIVE); //alive state indication stacks.push_back(ret);
return ret; return ret;
} }
CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor & base, ui8 side, SlotID slot, BattleHex position) const CStack * BattleInfo::generateNewStack(uint32_t id, const CStackBasicDescriptor & base, ui8 side, SlotID slot, BattleHex position)
{ {
int stackID = getIdForNewStack();
PlayerColor owner = sides[side].color; PlayerColor owner = sides[side].color;
auto ret = new CStack(&base, owner, stackID, side, slot); auto ret = new CStack(&base, owner, id, side, slot);
ret->position = position; ret->initialPosition = position;
ret->state.insert(EBattleStackState::ALIVE); //alive state indication stacks.push_back(ret);
return ret; return ret;
} }
@@ -436,7 +368,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
CreatureID cre = warMachineArt->artType->warMachine; CreatureID cre = warMachineArt->artType->warMachine;
if(cre != CreatureID::NONE) if(cre != CreatureID::NONE)
stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex)); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);
} }
}; };
@@ -481,8 +413,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
if(creatureBank && i->second->type->isDoubleWide()) if(creatureBank && i->second->type->isDoubleWide())
pos += side ? BattleHex::LEFT : BattleHex::RIGHT; pos += side ? BattleHex::LEFT : BattleHex::RIGHT;
CStack * stack = curB->generateNewStack(*i->second, side, i->first, pos); curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos);
stacks.push_back(stack);
} }
} }
@@ -491,9 +422,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
{ {
if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive) if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive)
{ {
CStack * stack = curB->generateNewStack (*heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, creatureBank ? commanderBank[i] : commanderField[i]);
creatureBank ? commanderBank[i] : commanderField[i]);
stacks.push_back(stack);
} }
} }
@@ -501,16 +430,14 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL) if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL)
{ {
// keep tower // keep tower
CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2);
stacks.push_back(stack);
if (curB->town->fortLevel() >= CGTownInstance::CASTLE) if (curB->town->fortLevel() >= CGTownInstance::CASTLE)
{ {
// lower tower + upper tower // lower tower + upper tower
CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -4); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -4);
stacks.push_back(stack);
stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -3); curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -3);
stacks.push_back(stack);
} }
//moat //moat
@@ -523,10 +450,6 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
std::stable_sort(stacks.begin(),stacks.end(),cmpst); std::stable_sort(stacks.begin(),stacks.end(),cmpst);
//spell level limiting bonus
curB->addNewBonus(std::make_shared<Bonus>(Bonus::ONE_BATTLE, Bonus::LEVEL_SPELL_IMMUNITY, Bonus::OTHER,
0, -1, -1, Bonus::INDEPENDENT_MAX));
auto neutral = std::make_shared<CreatureAlignmentLimiter>(EAlignment::NEUTRAL); auto neutral = std::make_shared<CreatureAlignmentLimiter>(EAlignment::NEUTRAL);
auto good = std::make_shared<CreatureAlignmentLimiter>(EAlignment::GOOD); auto good = std::make_shared<CreatureAlignmentLimiter>(EAlignment::GOOD);
auto evil = std::make_shared<CreatureAlignmentLimiter>(EAlignment::EVIL); auto evil = std::make_shared<CreatureAlignmentLimiter>(EAlignment::EVIL);
@@ -660,11 +583,6 @@ const CGHeroInstance * BattleInfo::getHero(PlayerColor player) const
return nullptr; return nullptr;
} }
PlayerColor BattleInfo::theOtherPlayer(PlayerColor player) const
{
return sides[!whatSide(player)].color;
}
ui8 BattleInfo::whatSide(PlayerColor player) const ui8 BattleInfo::whatSide(PlayerColor player) const
{ {
for(int i = 0; i < sides.size(); i++) for(int i = 0; i < sides.size(); i++)
@@ -675,29 +593,6 @@ ui8 BattleInfo::whatSide(PlayerColor player) const
return -1; return -1;
} }
int BattleInfo::getIdForNewStack() const
{
if(stacks.size())
{
//stacks vector may be sorted not by ID and they may be not contiguous -> find stack with max ID
auto highestIDStack = *std::max_element(stacks.begin(), stacks.end(),
[](const CStack *a, const CStack *b) { return a->ID < b->ID; });
return highestIDStack->ID + 1;
}
return 0;
}
std::shared_ptr<CObstacleInstance> BattleInfo::getObstacleOnTile(BattleHex tile) const
{
for(auto &obs : obstacles)
if(vstd::contains(obs->getAffectedTiles(), tile))
return obs;
return std::shared_ptr<CObstacleInstance>();
}
BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType) BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType)
{ {
static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap = static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap =
@@ -728,7 +623,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
} }
BattleInfo::BattleInfo() BattleInfo::BattleInfo()
: round(-1), activeStack(-1), selectedStack(-1), town(nullptr), tile(-1,-1,-1), : round(-1), activeStack(-1), town(nullptr), tile(-1,-1,-1),
battlefieldType(BFieldType::NONE), terrainType(ETerrainType::WRONG), battlefieldType(BFieldType::NONE), terrainType(ETerrainType::WRONG),
tacticsSide(0), tacticDistance(0) tacticsSide(0), tacticDistance(0)
{ {
@@ -736,6 +631,416 @@ BattleInfo::BattleInfo()
setNodeType(BATTLE); setNodeType(BATTLE);
} }
BattleInfo::~BattleInfo() = default;
int32_t BattleInfo::getActiveStackID() const
{
return activeStack;
}
TStacks BattleInfo::getStacksIf(TStackFilter predicate) const
{
TStacks ret;
vstd::copy_if(stacks, std::back_inserter(ret), predicate);
return ret;
}
battle::Units BattleInfo::getUnitsIf(battle::UnitFilter predicate) const
{
battle::Units ret;
vstd::copy_if(stacks, std::back_inserter(ret), predicate);
return ret;
}
BFieldType BattleInfo::getBattlefieldType() const
{
return battlefieldType;
}
ETerrainType BattleInfo::getTerrainType() const
{
return terrainType;
}
IBattleInfo::ObstacleCList BattleInfo::getAllObstacles() const
{
ObstacleCList ret;
for(auto iter = obstacles.cbegin(); iter != obstacles.cend(); iter++)
ret.push_back(*iter);
return ret;
}
PlayerColor BattleInfo::getSidePlayer(ui8 side) const
{
return sides.at(side).color;
}
const CArmedInstance * BattleInfo::getSideArmy(ui8 side) const
{
return sides.at(side).armyObject;
}
const CGHeroInstance * BattleInfo::getSideHero(ui8 side) const
{
return sides.at(side).hero;
}
ui8 BattleInfo::getTacticDist() const
{
return tacticDistance;
}
ui8 BattleInfo::getTacticsSide() const
{
return tacticsSide;
}
const CGTownInstance * BattleInfo::getDefendedTown() const
{
return town;
}
si8 BattleInfo::getWallState(int partOfWall) const
{
return si.wallState.at(partOfWall);
}
EGateState BattleInfo::getGateState() const
{
return si.gateState;
}
uint32_t BattleInfo::getCastSpells(ui8 side) const
{
return sides.at(side).castSpellsCount;
}
int32_t BattleInfo::getEnchanterCounter(ui8 side) const
{
return sides.at(side).enchanterCounter;
}
const IBonusBearer * BattleInfo::asBearer() const
{
return this;
}
int64_t BattleInfo::getActualDamage(const TDmgRange & damage, int32_t attackerCount, vstd::RNG & rng) const
{
if(damage.first != damage.second)
{
int64_t sum = 0;
auto howManyToAv = std::min<int32_t>(10, attackerCount);
auto rangeGen = rng.getInt64Range(damage.first, damage.second);
for(int32_t g = 0; g < howManyToAv; ++g)
sum += rangeGen();
return sum / howManyToAv;
}
else
{
return damage.first;
}
}
void BattleInfo::nextRound(int32_t roundNr)
{
for(int i = 0; i < 2; ++i)
{
sides.at(i).castSpellsCount = 0;
vstd::amax(--sides.at(i).enchanterCounter, 0);
}
round = roundNr;
for(CStack * s : stacks)
{
// new turn effects
s->updateBonuses(Bonus::NTurns);
s->afterNewRound();
}
for(auto & obst : obstacles)
obst->battleTurnPassed();
}
void BattleInfo::nextTurn(uint32_t unitId)
{
activeStack = unitId;
CStack * st = getStack(activeStack);
//remove bonuses that last until when stack gets new turn
st->popBonuses(Bonus::UntilGetsTurn);
st->afterGetsTurn();
}
void BattleInfo::addUnit(uint32_t id, const JsonNode & data)
{
battle::UnitInfo info;
info.load(id, data);
CStackBasicDescriptor base(info.type, info.count);
PlayerColor owner = getSidePlayer(info.side);
auto ret = new CStack(&base, owner, info.id, info.side, SlotID::SUMMONED_SLOT_PLACEHOLDER);
ret->initialPosition = info.position;
stacks.push_back(ret);
ret->summoned = info.summoned;
ret->localInit(this);
}
void BattleInfo::moveUnit(uint32_t id, BattleHex destination)
{
auto sta = getStack(id);
if(!sta)
{
logGlobal->error("Cannot find stack %d", id);
return;
}
for(auto & oi : obstacles)
{
if((oi->obstacleType == CObstacleInstance::SPELL_CREATED) && vstd::contains(oi->getAffectedTiles(), destination))
{
SpellCreatedObstacle * obstacle = dynamic_cast<SpellCreatedObstacle*>(oi.get());
assert(obstacle);
if(obstacle->casterSide != sta->unitSide() && obstacle->hidden)
obstacle->revealed = true;
}
}
sta->position = destination;
}
void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t healthDelta)
{
CStack * changedStack = getStack(id, false);
if(!changedStack)
throw std::runtime_error("Invalid unit id in BattleInfo update");
if(!changedStack->alive() && healthDelta > 0)
{
//checking if we resurrect a stack that is under a living stack
auto accessibility = getAccesibility();
if(!accessibility.accessible(changedStack->getPosition(), changedStack))
{
logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition().hex);
return; //position is already occupied
}
}
bool killed = (-healthDelta) >= changedStack->getAvailableHealth();//todo: check using alive state once rebirth will be handled separately
bool resurrected = !changedStack->alive() && healthDelta > 0;
//applying changes
changedStack->load(data);
if(healthDelta < 0)
{
changedStack->popBonuses(Bonus::UntilBeingAttacked);
}
resurrected = resurrected || (killed && changedStack->alive());
if(killed)
{
if(changedStack->cloneID >= 0)
{
//remove clone as well
CStack * clone = getStack(changedStack->cloneID);
if(clone)
clone->makeGhost();
changedStack->cloneID = -1;
}
}
if(resurrected || killed)
{
//removing all spells effects
auto selector = [](const Bonus * b)
{
//Special case: DISRUPTING_RAY is absolutely permanent
if(b->source == Bonus::SPELL_EFFECT)
return b->sid != SpellID::DISRUPTING_RAY;
else
return false;
};
changedStack->popBonuses(selector);
}
if(!changedStack->alive() && changedStack->isClone())
{
for(CStack * s : stacks)
{
if(s->cloneID == changedStack->unitId())
s->cloneID = -1;
}
}
}
void BattleInfo::removeUnit(uint32_t id)
{
std::set<uint32_t> ids;
ids.insert(id);
while(!ids.empty())
{
auto toRemoveId = *ids.begin();
auto toRemove = getStack(toRemoveId, false);
if(!toRemove)
{
logGlobal->error("Cannot find stack %d", toRemoveId);
return;
}
if(!toRemove->ghost)
{
toRemove->onRemoved();
toRemove->detachFromAll();
//stack may be removed instantly (not being killed first)
//handle clone remove also here
if(toRemove->cloneID >= 0)
{
ids.insert(toRemove->cloneID);
toRemove->cloneID = -1;
}
//cleanup remaining clone links if any
for(auto s : stacks)
{
if(s->cloneID == toRemoveId)
s->cloneID = -1;
}
}
ids.erase(toRemoveId);
}
}
void BattleInfo::addUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
{
CStack * sta = getStack(id, false);
if(!sta)
{
logGlobal->error("Cannot find stack %d", id);
return;
}
for(const Bonus & b : bonus)
addOrUpdateUnitBonus(sta, b, true);
}
void BattleInfo::updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
{
CStack * sta = getStack(id, false);
if(!sta)
{
logGlobal->error("Cannot find stack %d", id);
return;
}
for(const Bonus & b : bonus)
addOrUpdateUnitBonus(sta, b, false);
}
void BattleInfo::removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
{
CStack * sta = getStack(id, false);
if(!sta)
{
logGlobal->error("Cannot find stack %d", id);
return;
}
for(const Bonus & one : bonus)
{
auto selector = [one](const Bonus * b)
{
//compare everything but turnsRemain, limiter and propagator
return one.duration == b->duration
&& one.type == b->type
&& one.subtype == b->subtype
&& one.source == b->source
&& one.val == b->val
&& one.sid == b->sid
&& one.valType == b->valType
&& one.additionalInfo == b->additionalInfo
&& one.effectRange == b->effectRange
&& one.description == b->description;
};
sta->popBonuses(selector);
}
}
uint32_t BattleInfo::nextUnitId() const
{
return stacks.size();
}
void BattleInfo::addOrUpdateUnitBonus(CStack * sta, const Bonus & value, bool forceAdd)
{
if(forceAdd || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, value.sid).And(Selector::typeSubtype(value.type, value.subtype))))
{
//no such effect or cumulative - add new
logBonus->trace("%s receives a new bonus: %s", sta->nodeName(), value.Description());
sta->addNewBonus(std::make_shared<Bonus>(value));
}
else
{
logBonus->trace("%s updated bonus: %s", sta->nodeName(), value.Description());
for(auto stackBonus : sta->getExportedBonusList()) //TODO: optimize
{
if(stackBonus->source == value.source && stackBonus->sid == value.sid && stackBonus->type == value.type && stackBonus->subtype == value.subtype)
{
stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, value.turnsRemain);
}
}
CBonusSystemNode::treeHasChanged();
}
}
void BattleInfo::setWallState(int partOfWall, si8 state)
{
si.wallState.at(partOfWall) = state;
}
void BattleInfo::addObstacle(const ObstacleChanges & changes)
{
std::shared_ptr<SpellCreatedObstacle> obstacle = std::make_shared<SpellCreatedObstacle>();
obstacle->fromInfo(changes);
obstacles.push_back(obstacle);
}
void BattleInfo::removeObstacle(uint32_t id)
{
for(int i=0; i < obstacles.size(); ++i)
{
if(obstacles[i]->uniqueID == id) //remove this obstacle
{
obstacles.erase(obstacles.begin() + i);
break;
}
}
}
CArmedInstance * BattleInfo::battleGetArmyObject(ui8 side) const CArmedInstance * BattleInfo::battleGetArmyObject(ui8 side) const
{ {
return const_cast<CArmedInstance*>(CBattleInfoEssentials::battleGetArmyObject(side)); return const_cast<CArmedInstance*>(CBattleInfoEssentials::battleGetArmyObject(side));
@@ -746,30 +1051,29 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side)); return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side));
} }
bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b)
bool CMP_stack::operator()(const CStack* a, const CStack* b)
{ {
switch(phase) switch(phase)
{ {
case 0: //catapult moves after turrets case 0: //catapult moves after turrets
return a->getCreature()->idNumber > b->getCreature()->idNumber; //catapult is 145 and turrets are 149 return a->creatureIndex() > b->creatureIndex(); //catapult is 145 and turrets are 149
case 1: //fastest first, upper slot first case 1: //fastest first, upper slot first
{ {
int as = a->Speed(turn), bs = b->Speed(turn); int as = a->getInitiative(turn), bs = b->getInitiative(turn);
if(as != bs) if(as != bs)
return as > bs; return as > bs;
else else
return a->slot < b->slot; return a->unitSlot() < b->unitSlot(); //FIXME: what about summoned stacks?
} }
case 2: //fastest last, upper slot first case 2: //fastest last, upper slot first
//TODO: should be replaced with order of receiving morale! //TODO: should be replaced with order of receiving morale!
case 3: //fastest last, upper slot first case 3: //fastest last, upper slot first
{ {
int as = a->Speed(turn), bs = b->Speed(turn); int as = a->getInitiative(turn), bs = b->getInitiative(turn);
if(as != bs) if(as != bs)
return as < bs; return as < bs;
else else
return a->slot < b->slot; return a->unitSlot() < b->unitSlot();
} }
default: default:
assert(0); assert(0);

Some files were not shown because too many files have changed in this diff Show More