mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-03 14:52:11 +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:
parent
ff2d01a03d
commit
0b70baa95e
@ -10,53 +10,88 @@
|
||||
#include "StdInc.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;
|
||||
}
|
||||
|
||||
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex)
|
||||
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex)
|
||||
{
|
||||
auto attacker = AttackInfo.attacker;
|
||||
auto enemy = AttackInfo.defender;
|
||||
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
||||
static const auto selectorBlocksRetaliation = Selector::type(Bonus::BLOCKS_RETALIATION);
|
||||
|
||||
const int remainingCounterAttacks = getValOr(state.counterAttacksLeft, enemy, enemy->counterAttacks.available());
|
||||
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();
|
||||
const bool counterAttacksBlocked = attackInfo.attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||
|
||||
AttackPossibility ap = {enemy, hex, AttackInfo, 0, 0, 0};
|
||||
AttackPossibility ap(hex, attackInfo);
|
||||
|
||||
auto curBai = AttackInfo; //we'll modify here the stack counts
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
ap.attackerState = attackInfo.attacker->acquireState();
|
||||
|
||||
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);
|
||||
auto attackDmg = getCbc()->battleEstimateDamage(CRandomGenerator::getDefault(), curBai, &retaliation);
|
||||
ap.damageDealt = (attackDmg.first + attackDmg.second) / 2;
|
||||
ap.damageReceived = (retaliation.first + retaliation.second) / 2;
|
||||
TDmgRange retaliation(0,0);
|
||||
auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation);
|
||||
|
||||
if(remainingCounterAttacks <= i || counterAttacksBlocked)
|
||||
ap.damageReceived = 0;
|
||||
vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
|
||||
|
||||
curBai.attackerHealth = attacker->healthAfterAttacked(ap.damageReceived);
|
||||
curBai.defenderHealth = enemy->healthAfterAttacked(ap.damageDealt);
|
||||
if(curBai.attackerHealth.getCount() <= 0)
|
||||
vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
|
||||
vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth());
|
||||
|
||||
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;
|
||||
//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)
|
||||
|
||||
return ap;
|
||||
}
|
||||
|
||||
|
||||
Priorities* AttackPossibility::priorities = nullptr;
|
||||
|
@ -8,43 +8,29 @@
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/battle/CUnitState.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "common.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; };
|
||||
}
|
||||
};
|
||||
#include "StackWithBonuses.h"
|
||||
|
||||
class AttackPossibility
|
||||
{
|
||||
public:
|
||||
const CStack *enemy; //redundant (to attack.defender) but looks nice
|
||||
BattleHex tile; //tile from which we attack
|
||||
BattleAttackInfo attack;
|
||||
|
||||
int damageDealt;
|
||||
int damageReceived; //usually by counter-attack
|
||||
int tacticImpact;
|
||||
std::shared_ptr<battle::CUnitState> attackerState;
|
||||
|
||||
int damageDiff() const;
|
||||
int attackValue() const;
|
||||
std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits;
|
||||
|
||||
static AttackPossibility evaluate(const BattleAttackInfo &AttackInfo, const HypotheticChangesToBattleState &state, BattleHex hex);
|
||||
static Priorities * priorities;
|
||||
int64_t damageDealt = 0;
|
||||
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);
|
||||
};
|
||||
|
@ -58,6 +58,7 @@
|
||||
<Add option="-Wno-sign-compare" />
|
||||
<Add option="-Wno-unused-parameter" />
|
||||
<Add option="-Wno-overloaded-virtual" />
|
||||
<Add option="-DBOOST_THREAD_USE_LIB" />
|
||||
<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
|
||||
<Add option="-D_WIN32_WINNT=0x0501" />
|
||||
<Add option="-D_WIN32" />
|
||||
@ -65,6 +66,7 @@
|
||||
<Add directory="../../include" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lboost_thread$(#boost.libsuffix)" />
|
||||
<Add option="-lboost_system$(#boost.libsuffix)" />
|
||||
<Add option="-lVCMI_lib" />
|
||||
<Add directory="../.." />
|
||||
@ -73,8 +75,11 @@
|
||||
<Unit filename="AttackPossibility.h" />
|
||||
<Unit filename="BattleAI.cpp" />
|
||||
<Unit filename="BattleAI.h" />
|
||||
<Unit filename="CMakeLists.txt" />
|
||||
<Unit filename="EnemyInfo.cpp" />
|
||||
<Unit filename="EnemyInfo.h" />
|
||||
<Unit filename="PossibleSpellcast.cpp" />
|
||||
<Unit filename="PossibleSpellcast.h" />
|
||||
<Unit filename="PotentialTargets.cpp" />
|
||||
<Unit filename="PotentialTargets.h" />
|
||||
<Unit filename="StackWithBonuses.cpp" />
|
||||
|
@ -9,13 +9,57 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleAI.h"
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
#include "StackWithBonuses.h"
|
||||
#include "EnemyInfo.h"
|
||||
#include "PossibleSpellcast.h"
|
||||
#include "../../lib/CStopWatch.h"
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/CStack.h"//todo: remove
|
||||
|
||||
#define LOGL(text) print(text)
|
||||
#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()
|
||||
: side(-1), wasWaitingForRealize(false), wasUnlockingGs(false)
|
||||
{
|
||||
@ -70,31 +114,38 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||
//spellcast may finish battle
|
||||
//send special preudo-action
|
||||
BattleAction cancel;
|
||||
cancel.actionType = Battle::CANCEL;
|
||||
cancel.actionType = EActionType::CANCEL;
|
||||
return cancel;
|
||||
}
|
||||
|
||||
if(auto action = considerFleeingOrSurrendering())
|
||||
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())
|
||||
{
|
||||
auto hlp = targets.bestAction();
|
||||
if(hlp.attack.shooting)
|
||||
return BattleAction::makeShotAttack(stack, hlp.enemy);
|
||||
return BattleAction::makeShotAttack(stack, hlp.attack.defender);
|
||||
else
|
||||
return BattleAction::makeMeleeAttack(stack, hlp.enemy, hlp.tile);
|
||||
return BattleAction::makeMeleeAttack(stack, hlp.attack.defender->getPosition(), hlp.tile);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(stack->waited())
|
||||
{
|
||||
//ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
|
||||
auto dists = getCbc()->battleGetDistances(stack);
|
||||
const EnemyInfo &ei= *range::min_element(targets.unreachableEnemies, std::bind(isCloser, _1, _2, std::ref(dists)));
|
||||
if(distToNearestNeighbour(ei.s->position, dists) < GameConstants::BFIELD_SIZE)
|
||||
auto dists = getCbc()->battleGetDistances(stack, stack->getPosition());
|
||||
if(!targets.unreachableEnemies.empty())
|
||||
{
|
||||
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
|
||||
@ -116,9 +167,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
|
||||
|
||||
BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
||||
{
|
||||
assert(destination.isValid());
|
||||
auto avHexes = cb->battleGetAvailableHexes(stack, false);
|
||||
if(!destination.isValid())
|
||||
{
|
||||
logAi->error("CBattleAI::goTowards: invalid destination");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
auto reachability = cb->getReachability(stack);
|
||||
auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
|
||||
|
||||
if(vstd::contains(avHexes, destination))
|
||||
return BattleAction::makeMove(stack, destination);
|
||||
auto destNeighbours = destination.neighbouringTiles();
|
||||
@ -156,7 +213,12 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
||||
BattleHex currentDest = bestNeighbor;
|
||||
while(1)
|
||||
{
|
||||
assert(currentDest.isValid());
|
||||
if(!currentDest.isValid())
|
||||
{
|
||||
logAi->error("CBattleAI::goTowards: internal error");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
if(vstd::contains(avHexes, currentDest))
|
||||
return BattleAction::makeMove(stack, currentDest);
|
||||
currentDest = reachability.predecessors[currentDest];
|
||||
@ -166,22 +228,7 @@ BattleAction CBattleAI::goTowards(const CStack * stack, BattleHex destination)
|
||||
|
||||
BattleAction CBattleAI::useCatapult(const CStack * stack)
|
||||
{
|
||||
throw std::runtime_error("The method or operation 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;
|
||||
throw std::runtime_error("CBattleAI::useCatapult is not implemented.");
|
||||
}
|
||||
|
||||
void CBattleAI::attemptCastingSpell()
|
||||
@ -190,7 +237,7 @@ void CBattleAI::attemptCastingSpell()
|
||||
if(!hero)
|
||||
return;
|
||||
|
||||
if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK)
|
||||
if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
|
||||
return;
|
||||
|
||||
LOGL("Casting spells sounds like fun. Let's see...");
|
||||
@ -198,21 +245,28 @@ void CBattleAI::attemptCastingSpell()
|
||||
std::vector<const CSpell*> possibleSpells;
|
||||
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());
|
||||
|
||||
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
|
||||
std::vector<PossibleSpellcast> possibleCasts;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -220,141 +274,229 @@ void CBattleAI::attemptCastingSpell()
|
||||
if(possibleCasts.empty())
|
||||
return;
|
||||
|
||||
std::map<const CStack*, int> valueOfStack;
|
||||
for(auto stack : cb->battleGetStacks())
|
||||
using ValueMap = PossibleSpellcast::ValueMap;
|
||||
|
||||
auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle * state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
|
||||
{
|
||||
PotentialTargets pt(stack);
|
||||
valueOfStack[stack] = pt.bestActionValue();
|
||||
bool firstRound = true;
|
||||
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);
|
||||
const int spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
|
||||
switch(spellType(ps.spell))
|
||||
bool enemyHadTurn = false;
|
||||
|
||||
HypotheticBattle state(cb);
|
||||
evaluateQueue(valueOfStack, turnOrder, &state, 0, &enemyHadTurn);
|
||||
|
||||
if(!enemyHadTurn)
|
||||
{
|
||||
case OFFENSIVE_SPELL:
|
||||
{
|
||||
int damageDealt = 0, damageReceived = 0;
|
||||
auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
|
||||
if(stacksSuffering.empty())
|
||||
return -1;
|
||||
for(auto stack : stacksSuffering)
|
||||
auto battleIsFinishedOpt = state.battleIsFinished();
|
||||
|
||||
if(battleIsFinishedOpt)
|
||||
{
|
||||
const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower);
|
||||
if(stack->owner == playerID)
|
||||
damageReceived += dmg;
|
||||
else
|
||||
damageDealt += dmg;
|
||||
print("No need to cast a spell. Battle will finish soon.");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
if(stacksAffected.empty())
|
||||
return -1;
|
||||
int totalGain = 0;
|
||||
for(const CStack * sta : stacksAffected)
|
||||
newHealthOfStack[unit->unitId()] = unit->getAvailableHealth();
|
||||
newValueOfStack[unit->unitId()] = 0;
|
||||
|
||||
if(state.battleGetOwner(unit) == playerID && unit->alive() && unit->willMove())
|
||||
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;
|
||||
swb.stack = sta;
|
||||
//todo: handle effect actualization in HypotheticChangesToBattleState
|
||||
ps.spell->getEffects(swb.bonusesToAdd, skillLevel, false, hero->getEnchantPower(ps.spell));
|
||||
ps.spell->getEffects(swb.bonusesToAdd, skillLevel, true, hero->getEnchantPower(ps.spell));
|
||||
HypotheticChangesToBattleState state;
|
||||
state.bonusesOfStacks[swb.stack] = &swb;
|
||||
PotentialTargets pt(swb.stack, state);
|
||||
auto newValue = pt.bestActionValue();
|
||||
auto oldValue = valueOfStack[swb.stack];
|
||||
auto gain = newValue - oldValue;
|
||||
if(swb.stack->owner != playerID) //enemy
|
||||
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;
|
||||
auto newValue = getValOr(newValueOfStack, unit->unitId(), 0);
|
||||
auto oldValue = getValOr(valueOfStack, unit->unitId(), 0);
|
||||
|
||||
auto healthDiff = newHealthOfStack[unit->unitId()] - healthOfStack[unit->unitId()];
|
||||
|
||||
if(unit->unitOwner() != playerID)
|
||||
healthDiff = -healthDiff;
|
||||
|
||||
auto gain = newValue - oldValue + healthDiff;
|
||||
|
||||
if(gain != 0)
|
||||
totalGain += gain;
|
||||
}
|
||||
|
||||
LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain));
|
||||
return totalGain;
|
||||
ps->value = totalGain;
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
ps->value = -1;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::function<void()>> tasks;
|
||||
|
||||
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;
|
||||
};
|
||||
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
|
||||
{
|
||||
const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
|
||||
std::vector<BattleHex> ret;
|
||||
if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
|
||||
if(castToPerform.value > 0)
|
||||
{
|
||||
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
|
||||
{
|
||||
switch(targetInfo.type)
|
||||
{
|
||||
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;
|
||||
}
|
||||
LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
print("battleStart called");
|
||||
LOG_TRACE(logAi);
|
||||
side = Side;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
logAi->trace("CBattleAI [%p]: %s", this, text);
|
||||
logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
|
||||
}
|
||||
|
||||
boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
|
||||
|
@ -45,13 +45,6 @@ struct CurrentOffensivePotential
|
||||
};
|
||||
*/ // 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
|
||||
{
|
||||
int side;
|
||||
@ -72,7 +65,6 @@ public:
|
||||
|
||||
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 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 actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||
//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
|
||||
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
|
||||
//void battleEnd(const BattleResult *br) override;
|
||||
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
||||
//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
|
||||
@ -92,9 +84,5 @@ public:
|
||||
//void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||
//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 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 battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ set(battleAI_SRCS
|
||||
common.cpp
|
||||
EnemyInfo.cpp
|
||||
main.cpp
|
||||
PossibleSpellcast.cpp
|
||||
PotentialTargets.cpp
|
||||
StackWithBonuses.cpp
|
||||
ThreatMap.cpp
|
||||
@ -21,6 +22,7 @@ set(battleAI_HEADERS
|
||||
common.h
|
||||
EnemyInfo.h
|
||||
PotentialTargets.h
|
||||
PossibleSpellcast.h
|
||||
StackWithBonuses.h
|
||||
ThreatMap.h
|
||||
)
|
||||
|
@ -9,13 +9,11 @@
|
||||
*/
|
||||
#include "StdInc.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);
|
||||
adi = (dmg.first + dmg.second) / 2;
|
||||
adr = (retal.first + retal.second) / 2;
|
||||
return s->unitId() == ei.s->unitId();
|
||||
}
|
||||
|
||||
|
@ -9,21 +9,16 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
|
||||
class CStack;
|
||||
namespace battle
|
||||
{
|
||||
class Unit;
|
||||
}
|
||||
|
||||
class EnemyInfo
|
||||
{
|
||||
public:
|
||||
const CStack * s;
|
||||
int adi, adr;
|
||||
std::vector<BattleHex> attackFrom; //for melee fight
|
||||
EnemyInfo(const CStack * _s) : s(_s)
|
||||
const battle::Unit * s;
|
||||
EnemyInfo(const battle::Unit * _s) : s(_s)
|
||||
{}
|
||||
void calcDmg(const CStack * ourStack);
|
||||
bool operator==(const EnemyInfo& ei) const
|
||||
{
|
||||
return s == ei.s;
|
||||
}
|
||||
bool operator==(const EnemyInfo & ei) const;
|
||||
};
|
||||
|
21
AI/BattleAI/PossibleSpellcast.cpp
Normal file
21
AI/BattleAI/PossibleSpellcast.cpp
Normal 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
AI/BattleAI/PossibleSpellcast.h
Normal file
29
AI/BattleAI/PossibleSpellcast.h
Normal 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();
|
||||
};
|
@ -9,51 +9,76 @@
|
||||
*/
|
||||
#include "StdInc.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 avHexes = getCbc()->battleGetAvailableHexes(attacker, false);
|
||||
auto attIter = state->stackStates.find(attacker->unitId());
|
||||
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
|
||||
if(enemy->side == attacker->side)
|
||||
forceTarget = true;
|
||||
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;
|
||||
|
||||
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
|
||||
{
|
||||
auto bai = BattleAttackInfo(attacker, enemy, shooting);
|
||||
bai.attackerBonuses = getValOr(state.bonusesOfStacks, bai.attacker, bai.attacker);
|
||||
bai.defenderBonuses = getValOr(state.bonusesOfStacks, bai.defender, bai.defender);
|
||||
auto bai = BattleAttackInfo(attackerInfo, defender, shooting);
|
||||
|
||||
if(hex.isValid())
|
||||
{
|
||||
assert(dists[hex] <= attacker->Speed());
|
||||
bai.chargedFields = dists[hex];
|
||||
}
|
||||
if(hex.isValid() && !shooting)
|
||||
bai.chargedFields = reachability.distances[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));
|
||||
}
|
||||
else
|
||||
{
|
||||
for(BattleHex hex : avHexes)
|
||||
if(CStack::isMeleeAttackPossible(attacker, enemy, hex))
|
||||
if(CStack::isMeleeAttackPossible(attackerInfo, defender, hex))
|
||||
possibleAttacks.push_back(GenerateAttackInfo(false, hex));
|
||||
|
||||
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility &pa) { return pa.enemy == enemy; }))
|
||||
unreachableEnemies.push_back(enemy);
|
||||
if(!vstd::contains_if(possibleAttacks, [=](const AttackPossibility & pa) { return pa.attack.defender->unitId() == defender->unitId(); }))
|
||||
unreachableEnemies.push_back(defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int PotentialTargets::bestActionValue() const
|
||||
{
|
||||
if(possibleAttacks.empty())
|
||||
|
@ -14,12 +14,10 @@ class PotentialTargets
|
||||
{
|
||||
public:
|
||||
std::vector<AttackPossibility> possibleAttacks;
|
||||
std::vector<const CStack *> unreachableEnemies;
|
||||
|
||||
//std::function<AttackPossibility(bool,BattleHex)> GenerateAttackInfo; //args: shooting, destHex
|
||||
std::vector<const battle::Unit *> unreachableEnemies;
|
||||
|
||||
PotentialTargets(){};
|
||||
PotentialTargets(const CStack *attacker, const HypotheticChangesToBattleState &state = HypotheticChangesToBattleState());
|
||||
PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state);
|
||||
|
||||
AttackPossibility bestAction() const;
|
||||
int bestActionValue() const;
|
||||
|
@ -9,21 +9,383 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "StackWithBonuses.h"
|
||||
#include "../../lib/NetPacksBase.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,
|
||||
const CBonusSystemNode * root, const std::string & cachingStr) const
|
||||
StackWithBonuses::StackWithBonuses(const HypotheticBattle * Owner, const CStack * Stack)
|
||||
: 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>();
|
||||
const TBonusListPtr originalList = stack->getAllBonuses(selector, limit, root, cachingStr);
|
||||
range::copy(*originalList, std::back_inserter(*ret));
|
||||
for(auto &bonus : bonusesToAdd)
|
||||
const TBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
|
||||
|
||||
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);
|
||||
if(selector(b.get()) && (!limit || !limit(b.get())))
|
||||
if(selector(b.get()) && (!limit || !limit(b.get())))
|
||||
ret->push_back(b);
|
||||
}
|
||||
//TODO limiters?
|
||||
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;
|
||||
}
|
||||
|
@ -9,15 +9,105 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include "../../lib/HeroBonus.h"
|
||||
#include "../../lib/battle/BattleProxy.h"
|
||||
#include "../../lib/battle/CUnitState.h"
|
||||
|
||||
class HypotheticBattle;
|
||||
class CStack;
|
||||
|
||||
class StackWithBonuses : public IBonusBearer
|
||||
class StackWithBonuses : public battle::CUnitState, public virtual IBonusBearer
|
||||
{
|
||||
public:
|
||||
const CStack *stack;
|
||||
mutable std::vector<Bonus> bonusesToAdd;
|
||||
|
||||
virtual const TBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit,
|
||||
const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
|
||||
std::vector<Bonus> bonusesToAdd;
|
||||
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;
|
||||
};
|
||||
|
@ -53,7 +53,7 @@ struct EnemyInfo
|
||||
{}
|
||||
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;
|
||||
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)
|
||||
{
|
||||
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));
|
||||
print("activeStack called for " + stack->nodeName());
|
||||
auto dists = cb->battleGetDistances(stack);
|
||||
auto dists = cb->battleGetDistances(stack, stack->getPosition());
|
||||
std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
|
||||
|
||||
if(stack->type->idNumber == CreatureID::CATAPULT)
|
||||
{
|
||||
BattleAction attack;
|
||||
static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
|
||||
|
||||
attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
|
||||
attack.actionType = Battle::CATAPULT;
|
||||
attack.additionalInfo = 0;
|
||||
auto seletectedHex = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
|
||||
attack.aimToHex(seletectedHex);
|
||||
attack.actionType = EActionType::CATAPULT;
|
||||
attack.side = side;
|
||||
attack.stackNumber = stack->ID;
|
||||
|
||||
@ -134,13 +133,13 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
|
||||
std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -176,16 +175,16 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
|
||||
else if(enemiesReachable.size())
|
||||
{
|
||||
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
|
||||
{
|
||||
assert(enemiesUnreachable.size());
|
||||
const EnemyInfo &ei= *std::min_element(enemiesUnreachable.begin(), enemiesUnreachable.end(), std::bind(isCloser, _1, _2, std::ref(dists)));
|
||||
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");
|
||||
}
|
||||
|
||||
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
|
||||
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog)
|
||||
{
|
||||
print("battleStacksAttacked called");
|
||||
}
|
||||
@ -243,31 +242,11 @@ void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2
|
||||
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)
|
||||
{
|
||||
print("battleCatapultAttacked called");
|
||||
}
|
||||
|
||||
void CStupidAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
|
||||
{
|
||||
print("battleStacksRemoved called");
|
||||
}
|
||||
|
||||
void CStupidAI::print(const std::string &text) const
|
||||
{
|
||||
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)
|
||||
{
|
||||
assert(destination.isValid());
|
||||
auto avHexes = cb->battleGetAvailableHexes(stack, false);
|
||||
auto reachability = cb->getReachability(stack);
|
||||
auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
|
||||
|
||||
if(vstd::contains(avHexes, destination))
|
||||
return BattleAction::makeMove(stack, destination);
|
||||
|
@ -27,7 +27,7 @@ public:
|
||||
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
|
||||
|
||||
void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, const std::vector<MetaString> & battleLog) override; //called when stack receives damage (after battleAttack())
|
||||
void battleEnd(const BattleResult *br) override;
|
||||
//void battleResultsApplied() override; //called when all effects of last battle are applied
|
||||
void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
|
||||
@ -37,11 +37,7 @@ public:
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
|
||||
//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 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 battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
|
||||
|
||||
BattleAction goTowards(const CStack * stack, BattleHex hex );
|
||||
|
||||
|
@ -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};
|
||||
static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
|
||||
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};
|
||||
static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
|
||||
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
|
||||
@ -1416,7 +1416,7 @@ void VCAI::buildStructure(const CGTownInstance * t)
|
||||
}
|
||||
|
||||
//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;
|
||||
if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
|
||||
return;
|
||||
|
@ -174,7 +174,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
|
||||
int CBattleCallback::battleMakeAction(BattleAction* action)
|
||||
{
|
||||
assert(action->actionType == Battle::HERO_SPELL);
|
||||
assert(action->actionType == EActionType::HERO_SPELL);
|
||||
MakeCustomAction mca(*action);
|
||||
sendRequest(&mca);
|
||||
return 0;
|
||||
@ -279,9 +279,11 @@ void CCallback::buildBoat( const IShipyard *obj )
|
||||
sendRequest(&bb);
|
||||
}
|
||||
|
||||
CCallback::CCallback( CGameState * GS, boost::optional<PlayerColor> Player, CClient *C )
|
||||
:CBattleCallback(GS, Player, C)
|
||||
CCallback::CCallback(CGameState * GS, boost::optional<PlayerColor> Player, CClient * C)
|
||||
: CBattleCallback(Player, C)
|
||||
{
|
||||
gs = GS;
|
||||
|
||||
waitTillRealize = false;
|
||||
unlockGsWhenWaiting = false;
|
||||
}
|
||||
@ -367,9 +369,8 @@ void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver>
|
||||
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;
|
||||
cl = C;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class CGameState;
|
||||
struct CPath;
|
||||
class CGObjectInstance;
|
||||
class CArmedInstance;
|
||||
struct BattleAction;
|
||||
class BattleAction;
|
||||
class CGTownInstance;
|
||||
struct lua_State;
|
||||
class CClient;
|
||||
@ -85,10 +85,9 @@ class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
|
||||
protected:
|
||||
int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied)
|
||||
CClient *cl;
|
||||
//virtual bool hasAccess(int playerId) const;
|
||||
|
||||
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
|
||||
bool battleMakeTacticAction(BattleAction * action) override; // performs tactic phase actions
|
||||
|
||||
|
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png
Normal file
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/defendBig.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png
Normal file
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/defendSmall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 733 B |
8
Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json
Normal file
8
Mods/vcmi/Sprites/vcmi/battleQueue/statesBig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"basepath": "vcmi/battleQueue/",
|
||||
"images" :
|
||||
[
|
||||
{ "frame" : 0, "file" : "defendBig"},
|
||||
{ "frame" : 1, "file" : "waitBig"}
|
||||
]
|
||||
}
|
8
Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json
Normal file
8
Mods/vcmi/Sprites/vcmi/battleQueue/statesSmall.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"basepath": "vcmi/battleQueue/",
|
||||
"images" :
|
||||
[
|
||||
{ "frame" : 0, "file" : "defendSmall"},
|
||||
{ "frame" : 1, "file" : "waitSmall"}
|
||||
]
|
||||
}
|
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png
Normal file
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/waitBig.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png
Normal file
BIN
Mods/vcmi/Sprites/vcmi/battleQueue/waitSmall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 759 B |
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
@ -714,6 +714,46 @@ void processCommand(const std::string &message)
|
||||
std::cout << "\rExtracting done :)\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")
|
||||
{
|
||||
std::cout << "Command accepted.\t";
|
||||
|
@ -45,7 +45,6 @@ set(client_SRCS
|
||||
|
||||
CBitmapHandler.cpp
|
||||
CreatureCostBox.cpp
|
||||
CDefHandler.cpp
|
||||
CGameInfo.cpp
|
||||
Client.cpp
|
||||
CMessage.cpp
|
||||
@ -103,7 +102,6 @@ set(client_HEADERS
|
||||
|
||||
CBitmapHandler.h
|
||||
CreatureCostBox.h
|
||||
CDefHandler.h
|
||||
CGameInfo.h
|
||||
Client.h
|
||||
CMessage.h
|
||||
|
@ -42,7 +42,8 @@
|
||||
#include "../lib/JsonNode.h"
|
||||
#include "CMusicHandler.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/VCMIDirs.h"
|
||||
#include "mapHandler.h"
|
||||
@ -695,80 +696,91 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
}
|
||||
|
||||
|
||||
void CPlayerInterface::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
|
||||
void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
for (auto & healedStack : healedStacks)
|
||||
for(auto & info : units)
|
||||
{
|
||||
const CStack * healed = cb->battleGetStackByID(healedStack.first);
|
||||
if (battleInt->creAnims[healed->ID]->isDead())
|
||||
switch(info.operation)
|
||||
{
|
||||
//stack has been resurrected
|
||||
battleInt->creAnims[healed->ID]->setType(CCreatureAnim::HOLDING);
|
||||
case UnitChanges::EOperation::RESET_STATE:
|
||||
{
|
||||
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);
|
||||
const CStack * defender = cb->battleGetStackByID(lifeDrainFrom, false);
|
||||
|
||||
if(attacker && defender)
|
||||
if(change.operation == BattleChanges::EOperation::ADD)
|
||||
{
|
||||
battleInt->displayEffect(52, attacker->position); //TODO: transparency
|
||||
CCS->soundh->playSound(soundBase::DRAINLIF);
|
||||
|
||||
MetaString text;
|
||||
attacker->addText(text, MetaString::GENERAL_TXT, 361);
|
||||
attacker->addNameReplacement(text, false);
|
||||
text.addReplacement(healedStacks[0].second);
|
||||
defender->addNameReplacement(text, true);
|
||||
battleInt->console->addText(text.toString());
|
||||
auto instance = cb->battleGetObstacleByID(change.id);
|
||||
if(instance)
|
||||
battleInt->obstaclePlaced(*instance);
|
||||
else
|
||||
logNetwork->error("Invalid obstacle instance %d", change.id);
|
||||
}
|
||||
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)
|
||||
{
|
||||
MetaString text;
|
||||
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);
|
||||
if(needUpdate)
|
||||
//update accessible hexes
|
||||
battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
|
||||
@ -779,17 +791,6 @@ void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & 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
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
@ -864,9 +865,9 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
|
||||
BattleAction ret = *(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->trace("Canceled command for %s", stackName);
|
||||
}
|
||||
@ -932,37 +933,36 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
|
||||
RETURN_IF_QUICK_COMBAT;
|
||||
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;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
std::vector<StackAttackedInfo> arg;
|
||||
for (auto & elem : bsa)
|
||||
for(auto & elem : bsa)
|
||||
{
|
||||
const CStack *defender = cb->battleGetStackByID(elem.stackAttacked, false);
|
||||
const CStack *attacker = cb->battleGetStackByID(elem.attackerID, false);
|
||||
if (elem.isEffect())
|
||||
const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false);
|
||||
const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false);
|
||||
if(elem.isEffect())
|
||||
{
|
||||
if (defender && !elem.isSecondary())
|
||||
battleInt->displayEffect(elem.effect, defender->position);
|
||||
if(defender && !elem.isSecondary())
|
||||
battleInt->displayEffect(elem.effect, defender->getPosition());
|
||||
}
|
||||
if (elem.isSpell())
|
||||
if(elem.isSpell())
|
||||
{
|
||||
if (defender)
|
||||
battleInt->displaySpellEffect(elem.spellID, defender->position);
|
||||
if(defender)
|
||||
battleInt->displaySpellEffect(elem.spellID, defender->getPosition());
|
||||
}
|
||||
//FIXME: why action is deleted during enchanter cast?
|
||||
bool remoteAttack = false;
|
||||
|
||||
if (LOCPLINT->curAction)
|
||||
remoteAttack |= LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK;
|
||||
if(LOCPLINT->curAction)
|
||||
remoteAttack |= LOCPLINT->curAction->actionType != EActionType::WALK_AND_ATTACK;
|
||||
|
||||
StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
|
||||
arg.push_back(to_put);
|
||||
}
|
||||
|
||||
battleInt->stacksAreAttacked(arg);
|
||||
battleInt->stacksAreAttacked(arg, battleLog);
|
||||
}
|
||||
void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||
{
|
||||
@ -982,13 +982,13 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||
if(ba->lucky()) //lucky hit
|
||||
{
|
||||
battleInt->console->addText(attacker->formatGeneralMessage(-45));
|
||||
battleInt->displayEffect(18, attacker->position);
|
||||
battleInt->displayEffect(18, attacker->getPosition());
|
||||
CCS->soundh->playSound(soundBase::GOODLUCK);
|
||||
}
|
||||
if(ba->unlucky()) //unlucky hit
|
||||
{
|
||||
battleInt->console->addText(attacker->formatGeneralMessage(-44));
|
||||
battleInt->displayEffect(48, attacker->position);
|
||||
battleInt->displayEffect(48, attacker->getPosition());
|
||||
CCS->soundh->playSound(soundBase::BADLUCK);
|
||||
}
|
||||
if(ba->deathBlow())
|
||||
@ -997,12 +997,23 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||
for(auto & elem : ba->bsa)
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||
battleInt->displayEffect(73, attacked->position);
|
||||
battleInt->displayEffect(73, attacked->getPosition());
|
||||
}
|
||||
CCS->soundh->playSound(soundBase::deathBlow);
|
||||
}
|
||||
|
||||
battleInt->displayCustomEffects(ba->customEffects);
|
||||
|
||||
battleInt->waitForAnims();
|
||||
|
||||
auto actionTarget = curAction->getTarget(cb.get());
|
||||
|
||||
if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot()))
|
||||
{
|
||||
logNetwork->error("Invalid current action: no destination.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(ba->shot())
|
||||
{
|
||||
for(auto & elem : ba->bsa)
|
||||
@ -1010,17 +1021,22 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||
if(!elem.isSecondary()) //display projectile only for primary target
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||
battleInt->stackAttacking(attacker, attacked->position, attacked, true);
|
||||
battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto attackFrom = actionTarget.at(0).hexValue;
|
||||
auto attackTarget = actionTarget.at(1).hexValue;
|
||||
|
||||
//TODO: use information from BattleAttack but not curAction
|
||||
|
||||
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 distm = BattleHex::getDistance(curAction->destinationTile - 1, attacker->position);
|
||||
int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition());
|
||||
int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition());
|
||||
|
||||
if(distp < distm)
|
||||
shift = 1;
|
||||
@ -1028,25 +1044,21 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
|
||||
shift = -1;
|
||||
}
|
||||
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
|
||||
|
||||
if(ba->spellLike())
|
||||
{
|
||||
//TODO: use information from BattleAttack but not curAction
|
||||
|
||||
auto destination = actionTarget.at(0).hexValue;
|
||||
//display hit animation
|
||||
SpellID spellID = ba->spellID;
|
||||
battleInt->displaySpellHit(spellID, 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)
|
||||
{
|
||||
|
@ -212,15 +212,12 @@ public:
|
||||
void battleSpellCast(const BattleSpellCast *sc) override;
|
||||
void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
|
||||
void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
|
||||
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, 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 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 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 battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog) override;
|
||||
void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
|
||||
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 yourTacticPhase(int distance) override;
|
||||
|
||||
|
@ -3455,7 +3455,8 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
|
||||
//get header
|
||||
std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
|
||||
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;
|
||||
names[1] = settings["general"]["playerName"].String();
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "../lib/VCMI_Lib.h"
|
||||
#include "../lib/VCMIDirs.h"
|
||||
#include "../lib/mapping/CMap.h"
|
||||
#include "../lib/mapping/CMapService.h"
|
||||
#include "../lib/JsonNode.h"
|
||||
#include "mapHandler.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
@ -153,7 +154,7 @@ void CClient::waitForMoveAndSend(PlayerColor color)
|
||||
setThreadName("CClient::waitForMoveAndSend");
|
||||
assert(vstd::contains(battleints, color));
|
||||
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());
|
||||
MakeAction temp_action(ba);
|
||||
@ -413,10 +414,11 @@ void CClient::newGame( CConnection *con, StartInfo *si )
|
||||
c.disableSmartPointerSerialization();
|
||||
|
||||
// Initialize game state
|
||||
CMapService mapService;
|
||||
gs = new CGameState();
|
||||
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());
|
||||
|
||||
// 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)
|
||||
{
|
||||
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;
|
||||
battleInterface->init(cbc);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class CGameState;
|
||||
class CGameInterface;
|
||||
class CConnection;
|
||||
class CCallback;
|
||||
struct BattleAction;
|
||||
class BattleAction;
|
||||
struct SharedMemory;
|
||||
class CClient;
|
||||
class CScriptingModule;
|
||||
|
@ -645,11 +645,6 @@ void BattleTriggerEffect::applyCl(CClient * cl)
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, *this);
|
||||
}
|
||||
|
||||
void BattleObstaclePlaced::applyCl(CClient * cl)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclePlaced, *obstacle);
|
||||
}
|
||||
|
||||
void BattleUpdateGateState::applyFirstCl(CClient * cl)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleGateStateChanged, state);
|
||||
@ -667,30 +662,14 @@ void BattleStackMoved::applyFirstCl(CClient *cl)
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa, battleLog);
|
||||
}
|
||||
|
||||
void StartAction::applyFirstCl(CClient *cl)
|
||||
@ -712,7 +691,7 @@ void SetStackEffect::applyCl(CClient *cl)
|
||||
|
||||
void StacksInjured::applyCl(CClient *cl)
|
||||
{
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks, battleLog);
|
||||
}
|
||||
|
||||
void BattleResultsApplied::applyCl(CClient *cl)
|
||||
@ -722,20 +701,15 @@ void BattleResultsApplied::applyCl(CClient *cl)
|
||||
callInterfaceIfPresent(cl, PlayerColor::SPECTATOR, &IGameEventsReceiver::battleResultsApplied);
|
||||
}
|
||||
|
||||
void StacksHealedOrResurrected::applyCl(CClient * cl)
|
||||
void BattleUnitsChanged::applyCl(CClient * cl)
|
||||
{
|
||||
std::vector<std::pair<ui32, ui32> > shiftedHealed;
|
||||
for(auto & elem : healedStacks)
|
||||
{
|
||||
shiftedHealed.push_back(std::make_pair(elem.stackId, (ui32)elem.delta));
|
||||
}
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksHealedRes, shiftedHealed, lifeDrain, tentHealing, drainedFrom);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects, battleLog);
|
||||
}
|
||||
|
||||
void ObstaclesRemoved::applyCl(CClient *cl)
|
||||
void BattleObstaclesChanged::applyCl(CClient *cl)
|
||||
{
|
||||
//inform interfaces about removed obstacles
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesRemoved, obstacles);
|
||||
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesChanged, changes);
|
||||
}
|
||||
|
||||
void CatapultAttack::applyCl(CClient *cl)
|
||||
@ -744,17 +718,6 @@ void CatapultAttack::applyCl(CClient *cl)
|
||||
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)
|
||||
{
|
||||
return cl->gs;
|
||||
|
@ -104,8 +104,6 @@
|
||||
<Unit filename="../CCallback.h" />
|
||||
<Unit filename="CBitmapHandler.cpp" />
|
||||
<Unit filename="CBitmapHandler.h" />
|
||||
<Unit filename="CDefHandler.cpp" />
|
||||
<Unit filename="CDefHandler.h" />
|
||||
<Unit filename="CGameInfo.cpp" />
|
||||
<Unit filename="CGameInfo.h" />
|
||||
<Unit filename="CMT.cpp" />
|
||||
|
@ -138,7 +138,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
|
||||
dest(_dest), attackedStack(defender), attackingStack(attacker)
|
||||
{
|
||||
assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n");
|
||||
attackingStackPosBeforeReturn = attackingStack->position;
|
||||
attackingStackPosBeforeReturn = attackingStack->getPosition();
|
||||
}
|
||||
|
||||
CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
|
||||
@ -184,9 +184,9 @@ bool CDefenceAnimation::init()
|
||||
|
||||
|
||||
//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;
|
||||
}
|
||||
//unit reversed
|
||||
@ -226,11 +226,10 @@ std::string CDefenceAnimation::getMySound()
|
||||
{
|
||||
if(killed)
|
||||
return battle_sound(stack->getCreature(), killed);
|
||||
|
||||
if (vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
|
||||
else if(stack->defendingAnim)
|
||||
return battle_sound(stack->getCreature(), defend);
|
||||
|
||||
return battle_sound(stack->getCreature(), wince);
|
||||
else
|
||||
return battle_sound(stack->getCreature(), wince);
|
||||
}
|
||||
|
||||
CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
|
||||
@ -243,10 +242,10 @@ CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
|
||||
return CCreatureAnim::DEATH;
|
||||
}
|
||||
|
||||
if(vstd::contains(stack->state, EBattleStackState::DEFENDING_ANIM))
|
||||
if(stack->defendingAnim)
|
||||
return CCreatureAnim::DEFENCE;
|
||||
|
||||
return CCreatureAnim::HITTED;
|
||||
else
|
||||
return CCreatureAnim::HITTED;
|
||||
}
|
||||
|
||||
void CDefenceAnimation::startAnimation()
|
||||
@ -324,7 +323,7 @@ bool CMeleeAttackAnimation::init()
|
||||
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)
|
||||
{
|
||||
@ -366,7 +365,7 @@ bool CMeleeAttackAnimation::init()
|
||||
int mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn, dest);
|
||||
if(mutPos == -1 && attackingStack->doubleWide())
|
||||
{
|
||||
mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->position);
|
||||
mutPos = BattleHex::mutualPosition(attackingStackPosBeforeReturn + revShiftattacker, attackedStack->getPosition());
|
||||
}
|
||||
if (mutPos == -1 && attackedStack->doubleWide())
|
||||
{
|
||||
@ -558,7 +557,7 @@ CMovementAnimation::CMovementAnimation(CBattleInterface *_owner, const CStack *_
|
||||
: CBattleStackAnimation(_owner, _stack),
|
||||
destTiles(_destTiles),
|
||||
curentMoveIndex(0),
|
||||
oldPos(stack->position),
|
||||
oldPos(stack->getPosition()),
|
||||
begX(0), begY(0),
|
||||
distanceX(0), distanceY(0),
|
||||
timeToMove(0.0),
|
||||
@ -731,16 +730,18 @@ bool CShootingAnimation::init()
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
// opponent must face attacker ( = different directions) before he can be attacked
|
||||
if (attackingStack && attackedStack &&
|
||||
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
|
||||
return false;
|
||||
//FIXME: this cause freeze
|
||||
|
||||
// if (attackingStack && attackedStack &&
|
||||
// owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
|
||||
// return false;
|
||||
|
||||
// Create the projectile animation
|
||||
|
||||
@ -780,7 +781,7 @@ bool CShootingAnimation::init()
|
||||
int multiplier = spi.reverse ? -1 : 1;
|
||||
|
||||
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;
|
||||
|
||||
// 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)
|
||||
{
|
||||
if(!dest_.isValid() && defender)
|
||||
dest = defender->position;
|
||||
dest = defender->getPosition();
|
||||
}
|
||||
|
||||
bool CCastAnimation::init()
|
||||
@ -937,17 +938,17 @@ bool CCastAnimation::init()
|
||||
//reverse unit if necessary
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
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);
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@ -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)
|
||||
: CBattleAnimation(_owner),
|
||||
destTile(BattleHex::INVALID),
|
||||
customAnim(_customAnim),
|
||||
x(_x),
|
||||
y(_y),
|
||||
dx(_dx),
|
||||
@ -1053,13 +1053,29 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo
|
||||
Vflip(_Vflip),
|
||||
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)
|
||||
: CBattleAnimation(_owner),
|
||||
destTile(_destTile),
|
||||
customAnim(_customAnim),
|
||||
x(-1),
|
||||
y(-1),
|
||||
dx(0),
|
||||
@ -1067,24 +1083,18 @@ CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _custo
|
||||
Vflip(_Vflip),
|
||||
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()
|
||||
{
|
||||
if(!isEarliest(true))
|
||||
return false;
|
||||
|
||||
if(customAnim.empty())
|
||||
{
|
||||
endAnim();
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
if(Vflip)
|
||||
|
@ -238,7 +238,7 @@ class CEffectAnimation : public CBattleAnimation
|
||||
{
|
||||
private:
|
||||
BattleHex destTile;
|
||||
std::string customAnim;
|
||||
std::shared_ptr<CAnimation> customAnim;
|
||||
int x, y, dx, dy;
|
||||
bool Vflip;
|
||||
bool alignToBottom;
|
||||
@ -248,6 +248,9 @@ public:
|
||||
void endAnim() override;
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx = 0, int _dy = 0);
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
|
||||
virtual ~CEffectAnimation(){};
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@
|
||||
class CLabel;
|
||||
class CCreatureSet;
|
||||
class CGHeroInstance;
|
||||
class CDefHandler;
|
||||
class CStack;
|
||||
class CCallback;
|
||||
class CButton;
|
||||
@ -30,7 +29,7 @@ struct BattleSpellCast;
|
||||
struct CObstacleInstance;
|
||||
template <typename T> struct CondSh;
|
||||
struct SetStackEffect;
|
||||
struct BattleAction;
|
||||
class BattleAction;
|
||||
class CGTownInstance;
|
||||
struct CatapultAttack;
|
||||
struct CatapultProjectileInfo;
|
||||
@ -46,15 +45,16 @@ struct ProjectileInfo;
|
||||
class CClickableHex;
|
||||
struct BattleHex;
|
||||
struct InfoAboutHero;
|
||||
struct BattleAction;
|
||||
class CBattleGameInterface;
|
||||
struct CustomEffectInfo;
|
||||
class CAnimation;
|
||||
class IImage;
|
||||
|
||||
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
|
||||
struct StackAttackedInfo
|
||||
{
|
||||
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
|
||||
const CStack *attacker; //attacking stack
|
||||
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, CDefHandler *> idToObstacle; //obstacles located on the battlefield
|
||||
std::map<int, SDL_Surface *> idToAbsoluteObstacle; //obstacles located on the battlefield
|
||||
|
||||
//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<std::string, std::shared_ptr<CAnimation>> animationsCache;
|
||||
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
|
||||
|
||||
std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
|
||||
ui8 animCount;
|
||||
@ -185,7 +178,9 @@ private:
|
||||
void printConsoleAttacked(const CStack *defender, int dmg, int killed, const CStack *attacker, bool Multiple);
|
||||
|
||||
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 isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
|
||||
|
||||
@ -259,12 +254,14 @@ private:
|
||||
BattleObjectsByHex sortObjectsByHex();
|
||||
void updateBattleAnimations();
|
||||
|
||||
SDL_Surface *getObstacleImage(const CObstacleInstance &oi);
|
||||
Point getObstaclePosition(SDL_Surface *image, const CObstacleInstance &obstacle);
|
||||
IImage * getObstacleImage(const CObstacleInstance & oi);
|
||||
|
||||
Point getObstaclePosition(IImage * image, const CObstacleInstance & obstacle);
|
||||
|
||||
void redrawBackgroundWithHexes(const CStack *activeStack);
|
||||
/** 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);
|
||||
public:
|
||||
@ -329,12 +326,12 @@ public:
|
||||
|
||||
//call-ins
|
||||
void startAction(const BattleAction* action);
|
||||
void newStack(const CStack *stack); //new stack appeared on battlefield
|
||||
void stackRemoved(int stackID); //stack disappeared from batlefiled
|
||||
void unitAdded(const CStack * stack); //new stack appeared on battlefield
|
||||
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
|
||||
void stackActivated(const CStack *stack); //active stack has been changed
|
||||
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
|
||||
void waitForAnims();
|
||||
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
|
||||
void 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 newRoundFirst( int 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 battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
|
||||
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
|
||||
|
||||
void displayBattleLog(const std::vector<MetaString> & battleLog);
|
||||
void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
|
||||
|
||||
void displayEffect(ui32 effect, BattleHex destTile); //displays custom effect on the battlefield
|
||||
|
||||
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
|
||||
@ -370,7 +371,7 @@ public:
|
||||
|
||||
void gateStateChanged(const EGateState state);
|
||||
|
||||
void initStackProjectile(const CStack *stack);
|
||||
void initStackProjectile(const CStack * stack);
|
||||
|
||||
const CGHeroInstance *currentHero() const;
|
||||
InfoAboutHero enemyHero() const;
|
||||
|
@ -203,7 +203,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
|
||||
if(!myHero || down || !myOwner->myTurn)
|
||||
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
|
||||
{
|
||||
@ -505,9 +505,9 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
|
||||
assert(cbi);
|
||||
|
||||
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
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
:embedded(Embedded), owner(_owner)
|
||||
: embedded(Embedded),
|
||||
owner(_owner)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
||||
if(embedded)
|
||||
{
|
||||
bg = nullptr;
|
||||
pos.w = QUEUE_SIZE * 37;
|
||||
pos.h = 46;
|
||||
pos.x = screen->w/2 - pos.w/2;
|
||||
pos.y = (screen->h - 600)/2 + 10;
|
||||
|
||||
icons = std::make_shared<CAnimation>("CPRSMALL");
|
||||
stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
|
||||
}
|
||||
else
|
||||
{
|
||||
bg = BitmapHandler::loadBitmap("DIBOXBCK");
|
||||
pos.w = 800;
|
||||
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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
if(bg)
|
||||
size_t boxIndex = 0;
|
||||
|
||||
for(size_t turn = 0; turn < queueData.size() && boxIndex < stackBoxes.size(); turn++)
|
||||
{
|
||||
SDL_SetClipRect(to, &pos);
|
||||
CSDL_Ext::fillTexture(to, bg);
|
||||
SDL_SetClipRect(to, nullptr);
|
||||
for(size_t unitIndex = 0; unitIndex < queueData[turn].size() && boxIndex < stackBoxes.size(); boxIndex++, unitIndex++)
|
||||
stackBoxes[boxIndex]->setStack(queueData[turn][unitIndex], turn);
|
||||
}
|
||||
|
||||
for(; boxIndex < stackBoxes.size(); boxIndex++)
|
||||
stackBoxes[boxIndex]->setStack(nullptr);
|
||||
}
|
||||
|
||||
void CStackQueue::StackBox::showAll(SDL_Surface * to)
|
||||
{
|
||||
assert(stack);
|
||||
bg->colorize(stack->owner);
|
||||
CIntObject::showAll(to);
|
||||
|
||||
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)
|
||||
CStackQueue::StackBox::StackBox(CStackQueue * owner)
|
||||
: bg(nullptr),
|
||||
icon(nullptr),
|
||||
amount(nullptr),
|
||||
stateIcon(nullptr)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
||||
bg = new CPicture(small ? "StackQueueSmall" : "StackQueueLarge" );
|
||||
|
||||
if (small)
|
||||
{
|
||||
icon = new CAnimImage("CPRSMALL", 0, 0, 5, 2);
|
||||
}
|
||||
else
|
||||
icon = new CAnimImage("TWCRPORT", 0, 0, 9, 1);
|
||||
bg = new CPicture(owner->embedded ? "StackQueueSmall" : "StackQueueLarge" );
|
||||
|
||||
pos.w = bg->pos.w;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ class CToggleGroup;
|
||||
class CLabel;
|
||||
struct BattleResult;
|
||||
class CStack;
|
||||
namespace battle
|
||||
{
|
||||
class Unit;
|
||||
}
|
||||
class CAnimImage;
|
||||
class CPlayerInterface;
|
||||
|
||||
@ -141,27 +145,23 @@ class CStackQueue : public CIntObject
|
||||
public:
|
||||
CPicture * bg;
|
||||
CAnimImage * icon;
|
||||
const CStack *stack;
|
||||
bool small;
|
||||
CLabel * amount;
|
||||
CAnimImage * stateIcon;
|
||||
|
||||
void showAll(SDL_Surface * to) override;
|
||||
void setStack(const CStack *nStack);
|
||||
StackBox(bool small);
|
||||
void setStack(const battle::Unit * nStack, size_t turn = 0);
|
||||
StackBox(CStackQueue * owner);
|
||||
};
|
||||
|
||||
public:
|
||||
static const int QUEUE_SIZE = 10;
|
||||
const bool embedded;
|
||||
std::vector<const CStack *> stacksSorted;
|
||||
std::vector<StackBox *> stackBoxes;
|
||||
|
||||
SDL_Surface * bg;
|
||||
|
||||
CBattleInterface * owner;
|
||||
|
||||
std::shared_ptr<CAnimation> icons;
|
||||
std::shared_ptr<CAnimation> stateIcons;
|
||||
|
||||
CStackQueue(bool Embedded, CBattleInterface * _owner);
|
||||
~CStackQueue();
|
||||
void update();
|
||||
void showAll(SDL_Surface *to) override;
|
||||
void blitBg(SDL_Surface * to);
|
||||
};
|
||||
|
@ -1641,11 +1641,11 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
|
||||
new CLabel(78, 11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl);
|
||||
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
values.push_back(new LabeledValue(sizes, CGI->generaltexth->allTexts[388], CGI->generaltexth->fcommands[3], getMyCreature()->MaxHealth()));
|
||||
sizes.y+=21;
|
||||
|
@ -219,7 +219,7 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
|
||||
|
||||
int dmgMultiply = 1;
|
||||
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);
|
||||
|
||||
@ -230,9 +230,9 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
|
||||
|
||||
if(battleStack != nullptr) // in battle
|
||||
{
|
||||
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), battleStack->Attack());
|
||||
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), battleStack->Defense());
|
||||
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, battleStack->getMaxDamage() * dmgMultiply);
|
||||
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->getDefence(battleStack->isShooter()), battleStack->getDefence(battleStack->isShooter()));
|
||||
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::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 caster = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
|
||||
|
||||
printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->Attack(), parent->info->stackNode->Attack());
|
||||
printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->Defense(false), parent->info->stackNode->Defense());
|
||||
printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply);
|
||||
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->getDefence(shooter), parent->info->stackNode->getDefence(shooter));
|
||||
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::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed());
|
||||
|
||||
|
@ -61,6 +61,11 @@ const TBonusListPtr CHeroWithMaybePickedArtifact::getAllBonuses(const CSelector
|
||||
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)
|
||||
: hero(Hero), cww(Cww)
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
};
|
||||
|
||||
//helper class for calculating values of hero bonuses without bonuses from picked up artifact
|
||||
class CHeroWithMaybePickedArtifact : public IBonusBearer
|
||||
class CHeroWithMaybePickedArtifact : public virtual IBonusBearer
|
||||
{
|
||||
public:
|
||||
const CGHeroInstance *hero;
|
||||
@ -47,6 +47,8 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
int64_t getTreeVersion() const override;
|
||||
};
|
||||
|
||||
class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts
|
||||
|
@ -34,14 +34,13 @@
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CGeneralTextHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/Problem.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)
|
||||
{
|
||||
@ -125,7 +124,7 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
|
||||
|
||||
++sitesPerOurTab[4];
|
||||
|
||||
spell->forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop)
|
||||
spell->forEachSchool([&sitesPerOurTab](const spells::SchoolInfo & school, bool & stop)
|
||||
{
|
||||
++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));
|
||||
return;
|
||||
}
|
||||
//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
|
||||
if((mySpell->isCombatSpell() && !owner->myInt->battleInt)
|
||||
|| (mySpell->isAdventureSpell() && (owner->myInt->battleInt || owner->myInt->castleInt)))
|
||||
|
||||
//anything that is not combat spell is adventure spell
|
||||
//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));
|
||||
owner->myInt->showInfoDialog(mySpell->getLevelInfo(schoolLevel).description, hlp);
|
||||
logGlobal->error("Spell have invalid flags");
|
||||
return;
|
||||
}
|
||||
|
||||
//we will cast a spell
|
||||
if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open
|
||||
const bool inCombat = owner->myInt->battleInt != nullptr;
|
||||
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);
|
||||
switch (problem)
|
||||
std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell->id, 0));
|
||||
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();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
|
||||
{
|
||||
std::string text = CGI->generaltexth->allTexts[538], elemental, caster;
|
||||
const PlayerColor player = owner->myInt->playerID;
|
||||
|
||||
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);
|
||||
}
|
||||
owner->myInt->battleInt->castThisSpell(mySpell->id);
|
||||
owner->fexitb();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::string> texts;
|
||||
problem.getAll(texts);
|
||||
if(!texts.empty())
|
||||
owner->myInt->showInfoDialog(texts.front());
|
||||
else
|
||||
owner->myInt->showInfoDialog("Unknown problem with this spell, no more information available.");
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
auto guard = vstd::makeScopeGuard([this]()
|
||||
@ -639,9 +583,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
|
||||
owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
|
||||
});
|
||||
|
||||
if(mySpell->getTargetType() == CSpell::LOCATION)
|
||||
if(mySpell->getTargetType() == spells::AimType::LOCATION)
|
||||
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);
|
||||
else
|
||||
logGlobal->error("Invalid spell target type");
|
||||
@ -654,7 +598,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
|
||||
if(mySpell && down)
|
||||
{
|
||||
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
|
||||
dmgInfo = "";
|
||||
else
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "../CreatureCostBox.h"
|
||||
#include "QuickRecruitmentWindow.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
|
||||
void CreaturePurchaseCard::initButtons()
|
||||
{
|
||||
|
@ -15,7 +15,8 @@
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../../CCallback.h"
|
||||
#include "../CreatureCostBox.h"
|
||||
#include "../lib/ResourceSet.h"
|
||||
#include "../../lib/ResourceSet.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "CreaturePurchaseCard.h"
|
||||
|
||||
|
||||
|
@ -196,7 +196,7 @@
|
||||
"type" : "object",
|
||||
"additionalProperties" : false,
|
||||
"default": {},
|
||||
"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue" ],
|
||||
"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize" ],
|
||||
"properties" : {
|
||||
"animationSpeed" : {
|
||||
"type" : "number",
|
||||
@ -217,6 +217,11 @@
|
||||
"showQueue" : {
|
||||
"type" : "boolean",
|
||||
"default" : true
|
||||
},
|
||||
"queueSize" : {
|
||||
"type" : "string",
|
||||
"default" : "auto",
|
||||
"enum" : [ "auto", "small", "big" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -97,6 +97,12 @@
|
||||
"$ref" : "vcmi:bonus"
|
||||
}
|
||||
},
|
||||
"battleEffects":{
|
||||
"type": "object",
|
||||
"additionalProperties" : {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"targetModifier":{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@ -246,7 +252,10 @@
|
||||
"$ref" : "#/definitions/flags",
|
||||
"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"},
|
||||
|
||||
"graphics":{
|
||||
|
@ -16,7 +16,7 @@
|
||||
"notActive" : {
|
||||
"val" : 0,
|
||||
"type" : "NOT_ACTIVE",
|
||||
"subtype" : 62,
|
||||
"subtype" : "spell.stoneGaze",
|
||||
"duration" : [
|
||||
"UNTIL_BEING_ATTACKED",
|
||||
"N_TURNS"
|
||||
@ -43,11 +43,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"indifferent": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"poison" : {
|
||||
@ -80,15 +82,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bind" : {
|
||||
@ -149,15 +151,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"paralyze" : {
|
||||
@ -178,7 +180,7 @@
|
||||
"notActive" : {
|
||||
"val" : 0,
|
||||
"type" : "NOT_ACTIVE",
|
||||
"subtype" : 74,
|
||||
"subtype" : "spell.paralyze",
|
||||
"duration" : [
|
||||
"UNTIL_BEING_ATTACKED",
|
||||
"N_TURNS"
|
||||
@ -195,11 +197,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"age" : {
|
||||
@ -226,15 +230,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deathCloud" : {
|
||||
@ -249,18 +253,23 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0-1"
|
||||
"range" : "0-1",
|
||||
"battleEffects":{
|
||||
"damage":{
|
||||
"type":"core:damage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"indifferent": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"thunderbolt" : {
|
||||
@ -275,12 +284,16 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0"
|
||||
"range" : "0",
|
||||
"battleEffects":{
|
||||
"damage":{
|
||||
"type":"core:damage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
}
|
||||
},
|
||||
@ -296,6 +309,15 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"battleEffects":{
|
||||
"dispel":{
|
||||
"type":"core:dispel",
|
||||
"ignoreImmunity" : true,
|
||||
"dispelNegative":false,
|
||||
"dispelNeutral":false,
|
||||
"dispelPositive":true
|
||||
}
|
||||
},
|
||||
"range" : "0"
|
||||
}
|
||||
},
|
||||
@ -316,41 +338,50 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"battleEffects":{
|
||||
"destruction":{
|
||||
"type":"core:damage",
|
||||
"killByCount": true
|
||||
}
|
||||
},
|
||||
"range" : "0"
|
||||
}
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"UNDEAD": true,
|
||||
"SIEGE_WEAPON": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"indifferent": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "absolute",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"acidBreath" : {
|
||||
"index" : 80,
|
||||
"targetType": "NO_TARGET",
|
||||
|
||||
"animation":{
|
||||
//???
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ACID"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"cumulativeEffects" : {
|
||||
"primarySkill" : {
|
||||
"val" : -3,
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.defence",
|
||||
"duration" : "PERMANENT",
|
||||
"valueType" : "ADDITIVE_VALUE"
|
||||
"battleEffects":{
|
||||
"timed":{
|
||||
"type":"core:timed",
|
||||
"cumulative":true,
|
||||
"ignoreImmunity" : true,
|
||||
"bonus" :{
|
||||
"primarySkill" : {
|
||||
"val" : -3,
|
||||
"type" : "PRIMARY_SKILL",
|
||||
"subtype" : "primSkill.defence",
|
||||
"duration" : "PERMANENT",
|
||||
"valueType" : "ADDITIVE_VALUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -369,6 +400,12 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"battleEffects":{
|
||||
"damage":{
|
||||
"type":"core:damage",
|
||||
"ignoreImmunity" : true
|
||||
}
|
||||
},
|
||||
"range" : "0"
|
||||
}
|
||||
},
|
||||
@ -376,8 +413,10 @@
|
||||
"damage": true,
|
||||
"indifferent": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"magicArrow" : {
|
||||
"index" : 15,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"projectile": [
|
||||
{"minimumAngle": 0 ,"defName":"C20SPX4"},
|
||||
@ -19,22 +19,26 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"iceBolt" : {
|
||||
"index" : 16,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"projectile": [
|
||||
{"minimumAngle": 0 ,"defName":"C08SPW4"},
|
||||
@ -44,54 +48,62 @@
|
||||
{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
|
||||
],
|
||||
"hit":["C08SPW5"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ICERAY"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lightningBolt" : {
|
||||
"index" : 17,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "LIGHTBLT"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"implosion" : {
|
||||
"index" : 18,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C05SPE0"]
|
||||
},
|
||||
@ -101,245 +113,294 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chainLightning" : {
|
||||
"index" : 19,
|
||||
"targetType": "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "CHAINLTE"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {
|
||||
"type" : "core:damage",
|
||||
"chainFactor" : 0.5,
|
||||
"chainLength" : 4
|
||||
}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
},
|
||||
"advanced" : {
|
||||
"battleEffects" : {
|
||||
"directDamage" : {
|
||||
"chainLength" : 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert" : {
|
||||
"battleEffects" : {
|
||||
"directDamage" : {
|
||||
"chainLength" : 5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
}
|
||||
},
|
||||
"frostRing" : {
|
||||
"index" : 20,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
|
||||
"animation":{
|
||||
"hit":["C07SPW"] //C07SPW0 ???
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FROSTING"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "1",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fireball" : {
|
||||
"index" : 21,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
|
||||
"animation":{
|
||||
"hit":["C13SPF"] //C13SPF0 ???
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SPONTCOMB"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0,1",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inferno" : {
|
||||
"index" : 22,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
|
||||
"animation":{
|
||||
"hit":["C04SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "FIREBLST"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0-2",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meteorShower" : {
|
||||
"index" : 23,
|
||||
"targetType": "LOCATION",
|
||||
|
||||
|
||||
"animation":{
|
||||
"hit":["C08SPE0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "METEOR"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0,1",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deathRipple" : {
|
||||
"index" : 24,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C04SPE0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "DEATHRIP"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true,
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"destroyUndead" : {
|
||||
"index" : 25,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"affect":["C14SPA0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "SACBRETH"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"absoluteLimit" : {
|
||||
"UNDEAD": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"allOf" : {
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
},
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"armageddon" : {
|
||||
"index" : 26,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"hit":["C06SPF0"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "ARMGEDN"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":false}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"titanBolt" : {
|
||||
"index" : 57,
|
||||
"targetType" : "CREATURE",
|
||||
|
||||
|
||||
"animation":{
|
||||
"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
|
||||
},
|
||||
},
|
||||
"sounds": {
|
||||
"cast": "LIGHTBLT"
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects" : {
|
||||
"directDamage" : {"type":"core:damage"}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"damage": true,
|
||||
"offensive": true,
|
||||
"negative": true,
|
||||
"special": true
|
||||
}
|
||||
|
@ -8,7 +8,42 @@
|
||||
},
|
||||
"levels" : {
|
||||
"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" : {
|
||||
@ -24,15 +59,57 @@
|
||||
},
|
||||
"levels" : {
|
||||
"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" : {
|
||||
"damage": true,
|
||||
"indifferent": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"forceField" : {
|
||||
@ -47,6 +124,61 @@
|
||||
"range" : "0",
|
||||
"targetModifier":{
|
||||
"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",
|
||||
"targetModifier":{
|
||||
"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,
|
||||
"indifferent": true
|
||||
},
|
||||
"immunity" : {
|
||||
"DIRECT_DAMAGE_IMMUNITY": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"earthquake" : {
|
||||
@ -87,7 +273,27 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"targetModifier":{"smart":true},
|
||||
"battleEffects":{
|
||||
"catapult":{
|
||||
"type":"core:catapult",
|
||||
"targetsToAttack": 2
|
||||
}
|
||||
},
|
||||
"range" : "X"
|
||||
},
|
||||
"advanced":{
|
||||
"battleEffects":{
|
||||
"catapult":{
|
||||
"targetsToAttack": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"battleEffects":{
|
||||
"catapult":{
|
||||
"targetsToAttack": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -107,7 +313,19 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"targetModifier":{"smart":true},
|
||||
"targetModifier":{
|
||||
"smart":true
|
||||
},
|
||||
"battleEffects":{
|
||||
"dispel":{
|
||||
"type":"core:dispel",
|
||||
"optional":false,
|
||||
"ignoreImmunity" : true,
|
||||
"dispelNegative":true,
|
||||
"dispelNeutral":true,
|
||||
"dispelPositive":true
|
||||
}
|
||||
},
|
||||
"range" : "0"
|
||||
},
|
||||
"advanced":{
|
||||
@ -115,6 +333,16 @@
|
||||
},
|
||||
"expert":{
|
||||
"targetModifier":{"smart":false},
|
||||
"battleEffects":{
|
||||
"dispel":{
|
||||
"optional":true
|
||||
},
|
||||
"removeObstacle":{
|
||||
"optional":true,
|
||||
"type":"core:removeObstacle",
|
||||
"removeAllSpells" : true
|
||||
}
|
||||
},
|
||||
"range" : "X"
|
||||
}
|
||||
},
|
||||
@ -135,6 +363,21 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"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"
|
||||
},
|
||||
"expert":{
|
||||
@ -158,17 +401,49 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"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}
|
||||
},
|
||||
"advanced":{
|
||||
"battleEffects":{
|
||||
"heal":{
|
||||
"healPower":"permanent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"battleEffects":{
|
||||
"heal":{
|
||||
"healPower":"permanent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"rising": true,
|
||||
"positive": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"UNDEAD": true,
|
||||
"SIEGE_WEAPON": true,
|
||||
"NON_LIVING": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "absolute",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"animateDead" : {
|
||||
@ -184,6 +459,14 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects":{
|
||||
"heal":{
|
||||
"type":"core:heal",
|
||||
"healLevel":"resurrect",
|
||||
"healPower":"permanent",
|
||||
"minFullUnits" : 1
|
||||
}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
@ -191,8 +474,10 @@
|
||||
"rising": true,
|
||||
"positive": true
|
||||
},
|
||||
"absoluteLimit" : {
|
||||
"UNDEAD": true
|
||||
"targetCondition" : {
|
||||
"allOf" : {
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sacrifice" : {
|
||||
@ -208,6 +493,14 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects":{
|
||||
"sacrifice":{
|
||||
"type":"core:sacrifice",
|
||||
"healLevel":"resurrect",
|
||||
"healPower":"permanent",
|
||||
"minFullUnits" : 0
|
||||
}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
@ -215,10 +508,12 @@
|
||||
"rising": true,
|
||||
"positive": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.NON_LIVING" : "absolute",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"teleport" : {
|
||||
@ -231,14 +526,21 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects":{
|
||||
"teleport":{
|
||||
"type":"core:teleport"
|
||||
}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removeObstacle" : {
|
||||
@ -252,7 +554,28 @@
|
||||
},
|
||||
"levels" : {
|
||||
"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" : {
|
||||
@ -271,14 +594,36 @@
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"battleEffects":{
|
||||
"clone":{
|
||||
"maxTier":5,
|
||||
"type":"core:clone"
|
||||
}
|
||||
},
|
||||
"targetModifier":{"smart":true}
|
||||
},
|
||||
"advanced":{
|
||||
"battleEffects":{
|
||||
"clone":{
|
||||
"maxTier":6
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"battleEffects":{
|
||||
"clone":{
|
||||
"maxTier":1000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fireElemental" : {
|
||||
@ -292,7 +637,15 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X"
|
||||
"range" : "X",
|
||||
"battleEffects":{
|
||||
"summon":{
|
||||
"exclusive":true,
|
||||
"id":"fireElemental",
|
||||
"permanent":false,
|
||||
"type":"core:summon"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -310,7 +663,15 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X"
|
||||
"range" : "X",
|
||||
"battleEffects":{
|
||||
"summon":{
|
||||
"exclusive":true,
|
||||
"id":"earthElemental",
|
||||
"permanent":false,
|
||||
"type":"core:summon"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -328,7 +689,15 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X"
|
||||
"range" : "X",
|
||||
"battleEffects":{
|
||||
"summon":{
|
||||
"exclusive":true,
|
||||
"id":"waterElemental",
|
||||
"permanent":false,
|
||||
"type":"core:summon"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
@ -346,7 +715,15 @@
|
||||
},
|
||||
"levels" : {
|
||||
"base":{
|
||||
"range" : "X"
|
||||
"range" : "X",
|
||||
"battleEffects":{
|
||||
"summon":{
|
||||
"exclusive":true,
|
||||
"id":"airElemental",
|
||||
"permanent":false,
|
||||
"type":"core:summon"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
|
@ -108,11 +108,24 @@
|
||||
"spellDamageReduction" : {
|
||||
"type" : "SPELL_DAMAGE_REDUCTION",
|
||||
"subtype" : 0,
|
||||
"duration" : "N_TURNS"
|
||||
"duration" : "N_TURNS",
|
||||
"val" : 30
|
||||
}
|
||||
}
|
||||
},
|
||||
"advanced" : {
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
},
|
||||
"range" : "X"
|
||||
}
|
||||
},
|
||||
@ -138,11 +151,24 @@
|
||||
"spellDamageReduction" : {
|
||||
"type" : "SPELL_DAMAGE_REDUCTION",
|
||||
"subtype" : 1,
|
||||
"duration" : "N_TURNS"
|
||||
"duration" : "N_TURNS",
|
||||
"val" : 30
|
||||
}
|
||||
}
|
||||
},
|
||||
"advanced" : {
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
},
|
||||
"range" : "X"
|
||||
}
|
||||
},
|
||||
@ -168,11 +194,24 @@
|
||||
"spellDamageReduction" : {
|
||||
"type" : "SPELL_DAMAGE_REDUCTION",
|
||||
"subtype" : 2,
|
||||
"duration" : "N_TURNS"
|
||||
"duration" : "N_TURNS",
|
||||
"val" : 30
|
||||
}
|
||||
}
|
||||
},
|
||||
"advanced" : {
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
},
|
||||
"range" : "X"
|
||||
}
|
||||
},
|
||||
@ -198,11 +237,24 @@
|
||||
"spellDamageReduction" : {
|
||||
"type" : "SPELL_DAMAGE_REDUCTION",
|
||||
"subtype" : 3,
|
||||
"duration" : "N_TURNS"
|
||||
"duration" : "N_TURNS",
|
||||
"val" : 30
|
||||
}
|
||||
}
|
||||
},
|
||||
"advanced" : {
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"effects" : {
|
||||
"spellDamageReduction" : {
|
||||
"val" : 50
|
||||
}
|
||||
},
|
||||
"range" : "X"
|
||||
}
|
||||
},
|
||||
@ -224,26 +276,47 @@
|
||||
"base":{
|
||||
"range" : "0",
|
||||
"targetModifier":{"smart":true},
|
||||
"effects" : {
|
||||
"levelSpellImmunity" : {
|
||||
"val" : 3,
|
||||
"type" : "LEVEL_SPELL_IMMUNITY",
|
||||
"valueType" : "INDEPENDENT_MAX",
|
||||
"duration" : "N_TURNS"
|
||||
"battleEffects":{
|
||||
"spellImmunity":{
|
||||
"type":"core:timed",
|
||||
"bonus":{
|
||||
"levelSpellImmunity":{
|
||||
"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":{
|
||||
"effects" : {
|
||||
"levelSpellImmunity" : {
|
||||
"val" : 4
|
||||
"battleEffects":{
|
||||
"spellImmunity":{
|
||||
"bonus" :{
|
||||
"levelSpellImmunity":{
|
||||
"val" : 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"expert":{
|
||||
"effects" : {
|
||||
"levelSpellImmunity" : {
|
||||
"val" : 5
|
||||
"battleEffects":{
|
||||
"spellImmunity":{
|
||||
"bonus":{
|
||||
"levelSpellImmunity":{
|
||||
"val" : 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -310,12 +383,14 @@
|
||||
"counters" : {
|
||||
"spell.curse": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"curse" : {
|
||||
@ -350,12 +425,14 @@
|
||||
"counters" : {
|
||||
"spell.bless": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bloodlust" : {
|
||||
@ -401,11 +478,13 @@
|
||||
"counters" : {
|
||||
"spell.weakness": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"precision" : {
|
||||
@ -448,11 +527,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteLimit" : {
|
||||
"SHOOTER": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"allOf" : {
|
||||
"bonus.SHOOTER" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weakness" : {
|
||||
@ -697,16 +778,16 @@
|
||||
"counters" : {
|
||||
"spell.sorrow":true
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true,
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.MIND_IMMUNITY" : "normal",
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorrow" : {
|
||||
@ -750,16 +831,16 @@
|
||||
"counters" : {
|
||||
"spell.mirth":true
|
||||
},
|
||||
"absoluteImmunity":{
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true,
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.MIND_IMMUNITY" : "normal",
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fortune" : {
|
||||
@ -894,11 +975,13 @@
|
||||
"counters" : {
|
||||
"spell.slow": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"slow" : {
|
||||
@ -941,15 +1024,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"counters" : {
|
||||
"spell.haste":true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"slayer" : {
|
||||
@ -1024,8 +1108,7 @@
|
||||
"inFrenzy" : {
|
||||
"type" : "IN_FRENZY",
|
||||
"val" : 100,
|
||||
"duration" : "N_TURNS",
|
||||
"turns" : 1
|
||||
"duration" : "UNTIL_ATTACK"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1044,11 +1127,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"counterstrike" : {
|
||||
@ -1089,11 +1174,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"flags" : {
|
||||
"positive": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON" : "absolute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"berserk" : {
|
||||
@ -1127,16 +1214,16 @@
|
||||
"counters" : {
|
||||
"spell.hypnotize": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.MIND_IMMUNITY" : "normal",
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hypnotize" : {
|
||||
@ -1192,13 +1279,16 @@
|
||||
"counters" : {
|
||||
"spell.berserk": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
"targetCondition" : {
|
||||
"allOf" : {
|
||||
"healthValueSpecial" : "absolute"
|
||||
},
|
||||
"noneOf" : {
|
||||
"bonus.SIEGE_WEAPON":"absolute",
|
||||
"bonus.MIND_IMMUNITY":"normal",
|
||||
"bonus.UNDEAD":"normal",
|
||||
"bonus.NON_LIVING":"normal"
|
||||
}
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
@ -1255,19 +1345,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteLimit" : {
|
||||
"SHOOTER": true
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
"UNDEAD": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"allOf" : {
|
||||
"bonus.SHOOTER" : "absolute"
|
||||
},
|
||||
"noneOf" : {
|
||||
"bonus.MIND_IMMUNITY" : "normal",
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "normal"
|
||||
}
|
||||
}
|
||||
},
|
||||
"blind" : {
|
||||
@ -1316,16 +1406,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"absoluteImmunity" : {
|
||||
"SIEGE_WEAPON": true,
|
||||
"UNDEAD": true
|
||||
},
|
||||
"immunity" : {
|
||||
"MIND_IMMUNITY": true,
|
||||
"NON_LIVING": true
|
||||
},
|
||||
"flags" : {
|
||||
"negative": true
|
||||
},
|
||||
"targetCondition" : {
|
||||
"noneOf" : {
|
||||
"bonus.MIND_IMMUNITY" : "normal",
|
||||
"bonus.NON_LIVING" : "normal",
|
||||
"bonus.SIEGE_WEAPON" : "absolute",
|
||||
"bonus.UNDEAD" : "absolute"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
58
include/vstd/RNG.h
Normal file
58
include/vstd/RNG.h
Normal 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)()]);
|
||||
}
|
||||
}
|
||||
}
|
@ -728,20 +728,6 @@ void CArtHandler::afterLoadFinalization()
|
||||
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()
|
||||
{
|
||||
init();
|
||||
@ -1407,7 +1393,7 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map)
|
||||
for(const ArtSlotInfo & info : artifactsInBackpack)
|
||||
backpackTemp.push_back(info.artifact->artType->id);
|
||||
}
|
||||
handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp, &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact);
|
||||
handler.serializeIdArray(NArtifactPosition::backpack, backpackTemp);
|
||||
if(!handler.saving)
|
||||
{
|
||||
for(const ArtifactID & artifactID : backpackTemp)
|
||||
@ -1441,12 +1427,12 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa
|
||||
if(info != nullptr && !info->locked)
|
||||
{
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -271,12 +271,6 @@ public:
|
||||
|
||||
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)
|
||||
{
|
||||
h & artifacts;
|
||||
|
@ -479,20 +479,6 @@ std::vector<bool> CCreatureHandler::getDefaultAllowed() const
|
||||
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()
|
||||
{
|
||||
if (VLC->modh->modules.STACK_EXP) //reading default stack experience bonuses
|
||||
|
@ -259,12 +259,6 @@ public:
|
||||
|
||||
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)
|
||||
{
|
||||
//TODO: should be optimized, not all these informations needs to be serialized (same for ccreature)
|
||||
|
@ -189,14 +189,14 @@ int CGameInfoCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * ca
|
||||
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);
|
||||
|
||||
ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
|
||||
|
||||
if (hero) //we see hero's spellbook
|
||||
return sp->calculateDamage(hero, nullptr, hero->getEffectLevel(sp), hero->getEffectPower(sp));
|
||||
if(hero) //we see hero's spellbook
|
||||
return sp->calculateDamage(hero);
|
||||
else
|
||||
return 0; //mage guild
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "int3.h"
|
||||
#include "ResourceSet.h" // for Res::ERes
|
||||
#include "battle/CPlayerBattleCallback.h"
|
||||
|
||||
@ -28,13 +29,15 @@ class CGTeleport;
|
||||
class CMapHeader;
|
||||
struct TeamState;
|
||||
struct QuestInfo;
|
||||
class int3;
|
||||
struct ShashInt3;
|
||||
class CGameState;
|
||||
|
||||
|
||||
class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
|
||||
{
|
||||
protected:
|
||||
CGameState * gs;
|
||||
|
||||
CGameInfoCallback();
|
||||
CGameInfoCallback(CGameState *GS, boost::optional<PlayerColor> Player);
|
||||
bool hasAccess(boost::optional<PlayerColor> playerId) const;
|
||||
@ -72,7 +75,7 @@ public:
|
||||
int getHeroCount(PlayerColor player, bool includeGarrisoned) 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 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 CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
|
||||
|
||||
|
@ -134,7 +134,7 @@ std::shared_ptr<CScriptingModule> CDynLibHandler::getNewScriptingModule(std::str
|
||||
BattleAction CGlobalAI::activeStack(const CStack * stack)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.actionType = Battle::DEFEND;
|
||||
ba.actionType = EActionType::DEFEND;
|
||||
ba.stackNumber = stack->ID;
|
||||
return ba;
|
||||
}
|
||||
@ -164,9 +164,9 @@ void CAdventureAI::battleStart(const CCreatureSet * army1, const CCreatureSet *
|
||||
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)
|
||||
@ -189,19 +189,9 @@ void CAdventureAI::battleStacksEffectsSet(const SetStackEffect & sse)
|
||||
battleAI->battleStacksEffectsSet(sse);
|
||||
}
|
||||
|
||||
void CAdventureAI::battleStacksRemoved(const BattleStacksRemoved & bsr)
|
||||
void CAdventureAI::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
|
||||
{
|
||||
battleAI->battleStacksRemoved(bsr);
|
||||
}
|
||||
|
||||
void CAdventureAI::battleObstaclesRemoved(const std::set<si32> & removedObstacles)
|
||||
{
|
||||
battleAI->battleObstaclesRemoved(removedObstacles);
|
||||
}
|
||||
|
||||
void CAdventureAI::battleNewStackAppeared(const CStack * stack)
|
||||
{
|
||||
battleAI->battleNewStackAppeared(stack);
|
||||
battleAI->battleObstaclesChanged(obstacles);
|
||||
}
|
||||
|
||||
void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
|
||||
@ -225,10 +215,9 @@ void CAdventureAI::battleEnd(const BattleResult * br)
|
||||
battleAI.reset();
|
||||
}
|
||||
|
||||
void CAdventureAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain,
|
||||
bool tentHeal, si32 lifeDrainFrom)
|
||||
void CAdventureAI::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog)
|
||||
{
|
||||
battleAI->battleStacksHealedRes(healedStacks, lifeDrain, tentHeal, lifeDrainFrom);
|
||||
battleAI->battleUnitsChanged(units, customEffects, battleLog);
|
||||
}
|
||||
|
||||
BattleAction CAdventureAI::activeStack(const CStack * stack)
|
||||
|
@ -43,7 +43,6 @@ struct Bonus;
|
||||
struct PackageApplied;
|
||||
struct SetObjectProperty;
|
||||
struct CatapultAttack;
|
||||
struct BattleStacksRemoved;
|
||||
struct StackLocation;
|
||||
class CStackInstance;
|
||||
class CCommanderInstance;
|
||||
@ -134,20 +133,17 @@ public:
|
||||
virtual void battleNewRound(int round) 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 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 battleNewRoundFirst(int round) override;
|
||||
virtual void actionFinished(const BattleAction &action) override;
|
||||
virtual void battleStacksEffectsSet(const SetStackEffect & sse) override;
|
||||
//virtual void battleTriggerEffect(const BattleTriggerEffect & bte);
|
||||
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 battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
|
||||
virtual void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
|
||||
virtual void battleAttack(const BattleAttack *ba) override;
|
||||
virtual void battleSpellCast(const BattleSpellCast *sc) 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 loadGame(BinaryDeserializer & h, const int version) override; //loading
|
||||
|
@ -33,7 +33,6 @@
|
||||
#include "rmg/CMapGenerator.h"
|
||||
#include "CStopWatch.h"
|
||||
#include "mapping/CMapEditManager.h"
|
||||
#include "mapping/CMapService.h"
|
||||
#include "serializer/CTypeList.h"
|
||||
#include "serializer/CMemorySerializer.h"
|
||||
#include "VCMIDirs.h"
|
||||
@ -703,7 +702,7 @@ CGameState::~CGameState()
|
||||
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);
|
||||
getRandomGenerator().setSeed(si->seedToBeUsed);
|
||||
@ -714,10 +713,10 @@ void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
|
||||
switch(scenarioOps->mode)
|
||||
{
|
||||
case StartInfo::NEW_GAME:
|
||||
initNewGame(allowSavingRandomMap);
|
||||
initNewGame(mapService, allowSavingRandomMap);
|
||||
break;
|
||||
case StartInfo::CAMPAIGN:
|
||||
initCampaign();
|
||||
initCampaign(mapService);
|
||||
break;
|
||||
default:
|
||||
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())
|
||||
{
|
||||
@ -800,7 +799,7 @@ void CGameState::initNewGame(bool allowSavingRandomMap)
|
||||
const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed );
|
||||
const auto fullPath = path / fileName;
|
||||
|
||||
CMapService::saveMap(randomMap, fullPath);
|
||||
mapService->saveMap(randomMap, fullPath);
|
||||
|
||||
logGlobal->info("Random map has been saved to:");
|
||||
logGlobal->info(fullPath.string());
|
||||
@ -841,11 +840,11 @@ void CGameState::initNewGame(bool allowSavingRandomMap)
|
||||
{
|
||||
logGlobal->info("Open map file: %s", scenarioOps->mapname);
|
||||
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());
|
||||
auto campaign = scenarioOps->campState;
|
||||
@ -857,7 +856,7 @@ void CGameState::initCampaign()
|
||||
|
||||
std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
|
||||
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()
|
||||
|
@ -26,7 +26,6 @@ class CTown;
|
||||
class CCallback;
|
||||
class IGameCallback;
|
||||
class CCreatureSet;
|
||||
class CStack;
|
||||
class CQuest;
|
||||
class CGHeroInstance;
|
||||
class CGTownInstance;
|
||||
@ -54,6 +53,7 @@ class CQuest;
|
||||
class CCampaignScenario;
|
||||
struct EventCondition;
|
||||
class CScenarioTravel;
|
||||
class IMapService;
|
||||
|
||||
namespace boost
|
||||
{
|
||||
@ -127,7 +127,7 @@ struct UpgradeInfo
|
||||
UpgradeInfo(){oldID = CreatureID::NONE;};
|
||||
};
|
||||
|
||||
struct BattleInfo;
|
||||
class BattleInfo;
|
||||
|
||||
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult);
|
||||
|
||||
@ -152,7 +152,7 @@ public:
|
||||
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)
|
||||
PlayerColor currentPlayer; //ID of player currently having turn
|
||||
@ -245,8 +245,8 @@ private:
|
||||
|
||||
// ----- initialization -----
|
||||
|
||||
void initNewGame(bool allowSavingRandomMap);
|
||||
void initCampaign();
|
||||
void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
|
||||
void initCampaign(const IMapService * mapService);
|
||||
void checkMapChecksum();
|
||||
void initGrailPosition();
|
||||
void initRandomFactionsForPlayers();
|
||||
|
@ -10,14 +10,19 @@ set(lib_SRCS
|
||||
battle/BattleAttackInfo.cpp
|
||||
battle/BattleHex.cpp
|
||||
battle/BattleInfo.cpp
|
||||
battle/BattleProxy.cpp
|
||||
battle/CBattleInfoCallback.cpp
|
||||
battle/CBattleInfoEssentials.cpp
|
||||
battle/CCallbackBase.cpp
|
||||
battle/CObstacleInstance.cpp
|
||||
battle/CPlayerBattleCallback.cpp
|
||||
battle/CUnitState.cpp
|
||||
battle/Destination.cpp
|
||||
battle/IBattleState.cpp
|
||||
battle/ReachabilityInfo.cpp
|
||||
battle/SideInBattle.cpp
|
||||
battle/SiegeInfo.cpp
|
||||
battle/Unit.cpp
|
||||
|
||||
filesystem/AdapterLoaders.cpp
|
||||
filesystem/CArchiveLoader.cpp
|
||||
@ -93,12 +98,29 @@ set(lib_SRCS
|
||||
|
||||
spells/AdventureSpellMechanics.cpp
|
||||
spells/BattleSpellMechanics.cpp
|
||||
spells/CDefaultSpellMechanics.cpp
|
||||
spells/CreatureSpellMechanics.cpp
|
||||
spells/CSpellHandler.cpp
|
||||
spells/ISpellMechanics.cpp
|
||||
spells/Problem.cpp
|
||||
spells/TargetCondition.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
|
||||
CArtHandler.cpp
|
||||
CBonusTypeHandler.cpp
|
||||
@ -143,14 +165,20 @@ set(lib_HEADERS
|
||||
battle/BattleAttackInfo.h
|
||||
battle/BattleHex.h
|
||||
battle/BattleInfo.h
|
||||
battle/BattleProxy.h
|
||||
battle/CBattleInfoCallback.h
|
||||
battle/CBattleInfoEssentials.h
|
||||
battle/CCallbackBase.h
|
||||
battle/CObstacleInstance.h
|
||||
battle/CPlayerBattleCallback.h
|
||||
battle/CUnitState.h
|
||||
battle/Destination.h
|
||||
battle/IBattleState.h
|
||||
battle/IUnitInfo.h
|
||||
battle/ReachabilityInfo.h
|
||||
battle/SideInBattle.h
|
||||
battle/SiegeInfo.h
|
||||
battle/Unit.h
|
||||
|
||||
filesystem/AdapterLoaders.h
|
||||
filesystem/CArchiveLoader.h
|
||||
@ -227,14 +255,31 @@ set(lib_HEADERS
|
||||
|
||||
spells/AdventureSpellMechanics.h
|
||||
spells/BattleSpellMechanics.h
|
||||
spells/CDefaultSpellMechanics.h
|
||||
spells/CreatureSpellMechanics.h
|
||||
spells/CSpellHandler.h
|
||||
spells/ISpellMechanics.h
|
||||
spells/Magic.h
|
||||
spells/SpellMechanics.h
|
||||
spells/Problem.h
|
||||
spells/TargetCondition.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
|
||||
CAndroidVMHelper.h
|
||||
CArtHandler.h
|
||||
|
@ -316,7 +316,7 @@ void CIdentifierStorage::finalize()
|
||||
state = FINISHED;
|
||||
}
|
||||
|
||||
CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName):
|
||||
ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName):
|
||||
handler(handler),
|
||||
objectName(objectName),
|
||||
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;
|
||||
JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
|
||||
@ -362,7 +362,7 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate)
|
||||
bool ContentTypeHandler::loadMod(std::string modName, bool validate)
|
||||
{
|
||||
ModInfo & modInfo = modData[modName];
|
||||
bool result = true;
|
||||
@ -387,42 +387,47 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool vali
|
||||
// try to add H3 object data
|
||||
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);
|
||||
JsonUtils::merge(originalData[index], data);
|
||||
performValidate(originalData[index],name);
|
||||
handler->loadObject(modName, name, originalData[index], index);
|
||||
std::swap(originalData[index], data);
|
||||
originalData[index].clear(); // do not use same data twice (same ID)
|
||||
}
|
||||
else
|
||||
{
|
||||
logMod->debug("no original data in loadMod(%s) at index %d", name, index);
|
||||
performValidate(data, name);
|
||||
handler->loadObject(modName, name, data, index);
|
||||
logMod->warn("no original data in loadMod(%s) at index %d", name, 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;
|
||||
}
|
||||
|
||||
|
||||
void CContentHandler::ContentTypeHandler::loadCustom()
|
||||
void ContentTypeHandler::loadCustom()
|
||||
{
|
||||
handler->loadCustom();
|
||||
}
|
||||
|
||||
void CContentHandler::ContentTypeHandler::afterLoadFinalization()
|
||||
void ContentTypeHandler::afterLoadFinalization()
|
||||
{
|
||||
handler->afterLoadFinalization();
|
||||
}
|
||||
|
||||
CContentHandler::CContentHandler()
|
||||
{
|
||||
}
|
||||
|
||||
void CContentHandler::init()
|
||||
{
|
||||
handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass")));
|
||||
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);
|
||||
}
|
||||
|
||||
const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const
|
||||
{
|
||||
return handlers.at(name);
|
||||
}
|
||||
|
||||
static JsonNode loadModSettings(std::string 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)
|
||||
{
|
||||
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);
|
||||
std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
|
||||
CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
|
||||
if (!parent.empty()) // this is submod, add parent to dependencies
|
||||
mod.dependencies.insert(parent);
|
||||
|
||||
if (CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
|
||||
{
|
||||
CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
|
||||
if (!parent.empty()) // this is submod, add parent to dependecies
|
||||
mod.dependencies.insert(parent);
|
||||
allMods[modFullName] = mod;
|
||||
if (mod.enabled && enableMods)
|
||||
activeMods.push_back(modFullName);
|
||||
|
||||
allMods[modFullName] = mod;
|
||||
if (mod.enabled && enableMods)
|
||||
activeMods.push_back(modFullName);
|
||||
|
||||
loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
|
||||
}
|
||||
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.name = "Original game files";
|
||||
@ -941,9 +962,10 @@ void CModHandler::load()
|
||||
{
|
||||
CStopWatch totalTime, timer;
|
||||
|
||||
CContentHandler content;
|
||||
logMod->info("\tInitializing content handler: %d ms", timer.getDiff());
|
||||
|
||||
content.init();
|
||||
|
||||
for(const TModID & modName : activeMods)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
void CModHandler::afterLoad()
|
||||
void CModHandler::afterLoad(bool onlyEssential)
|
||||
{
|
||||
JsonNode modSettings;
|
||||
for (auto & modEntry : allMods)
|
||||
@ -987,8 +1009,12 @@ void CModHandler::afterLoad()
|
||||
}
|
||||
modSettings["core"] = coreMod.saveLocalData();
|
||||
|
||||
FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
|
||||
file << modSettings.toJson();
|
||||
if(!onlyEssential)
|
||||
{
|
||||
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)
|
||||
@ -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)
|
||||
{
|
||||
auto p = splitString(identifier, ':');
|
||||
if(type == "")
|
||||
logGlobal->error("Full identifier (%s %s) requires type name", scope, identifier);
|
||||
|
||||
if(p.first != "")
|
||||
return p.first + ":" + type + "." + p.second;//ignore type if identifier is scoped
|
||||
std::string actualScope = scope;
|
||||
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
|
||||
return scope == "" ? (identifier == "" ? type : type + "." + identifier) : scope + ":" + type + "." + identifier;
|
||||
{
|
||||
return actualName == "" ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName;
|
||||
}
|
||||
}
|
||||
|
@ -108,40 +108,38 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/// class used to load all game data into handlers. Used only during loading
|
||||
class CContentHandler
|
||||
/// internal type to handle loading of one data type (e.g. artifacts, creatures)
|
||||
class DLL_LINKAGE ContentTypeHandler
|
||||
{
|
||||
/// internal type to handle loading of one data type (e.g. artifacts, creatures)
|
||||
class ContentTypeHandler
|
||||
public:
|
||||
struct ModInfo
|
||||
{
|
||||
struct ModInfo
|
||||
{
|
||||
/// mod data from this mod and for this mod
|
||||
JsonNode modData;
|
||||
/// 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();
|
||||
/// mod data from this mod and for this mod
|
||||
JsonNode modData;
|
||||
/// 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;
|
||||
|
||||
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.
|
||||
bool preloadModData(std::string modName, JsonNode modConfig, bool validate);
|
||||
|
||||
@ -150,9 +148,10 @@ class CContentHandler
|
||||
|
||||
std::map<std::string, ContentTypeHandler> handlers;
|
||||
public:
|
||||
/// fully initialize object. Will cause reading of H3 config files
|
||||
CContentHandler();
|
||||
|
||||
void init();
|
||||
|
||||
/// preloads all data from fileList as data from modName.
|
||||
void preloadData(CModInfo & mod);
|
||||
|
||||
@ -163,6 +162,8 @@ public:
|
||||
|
||||
/// all data was loaded, time for final validation / integration
|
||||
void afterLoadFinalization();
|
||||
|
||||
const ContentTypeHandler & operator[] (const std::string & name) const;
|
||||
};
|
||||
|
||||
typedef std::string TModID;
|
||||
@ -246,14 +247,17 @@ class DLL_LINKAGE CModHandler
|
||||
std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
|
||||
|
||||
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:
|
||||
|
||||
CIdentifierStorage identifiers;
|
||||
|
||||
CContentHandler content; //(!)Do not serialize
|
||||
|
||||
/// receives list of available mods and trying to load mod.json from all of them
|
||||
void initializeConfig();
|
||||
void loadMods();
|
||||
void loadMods(bool onlyEssential = false);
|
||||
void loadModFilesystems();
|
||||
|
||||
CModInfo & getModData(TModID modId);
|
||||
@ -264,7 +268,7 @@ public:
|
||||
|
||||
/// load content from all available mods
|
||||
void load();
|
||||
void afterLoad();
|
||||
void afterLoad(bool onlyEssential);
|
||||
|
||||
struct DLL_LINKAGE hardcodedFeatures
|
||||
{
|
||||
|
@ -32,7 +32,12 @@ void CRandomGenerator::resetSeed()
|
||||
|
||||
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)
|
||||
@ -50,7 +55,7 @@ int CRandomGenerator::nextInt()
|
||||
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));
|
||||
}
|
||||
|
@ -10,16 +10,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
typedef std::mt19937 TGenerator;
|
||||
typedef std::uniform_int_distribution<int> TIntDist;
|
||||
typedef std::uniform_int_distribution<int64_t> TInt64Dist;
|
||||
typedef std::uniform_real_distribution<double> TRealDist;
|
||||
typedef std::function<int()> TRandI;
|
||||
typedef std::function<double()> TRand;
|
||||
|
||||
/// 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
|
||||
/// 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:
|
||||
/// 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();
|
||||
/// requires: lower <= upper
|
||||
TRandI getIntRange(int lower, int upper);
|
||||
|
||||
|
||||
vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override;
|
||||
|
||||
/// Generates an integer between 0 and upper.
|
||||
/// requires: 0 <= upper
|
||||
int nextInt(int upper);
|
||||
|
||||
/// requires: lower <= upper
|
||||
int nextInt(int lower, int upper);
|
||||
|
||||
|
||||
/// Generates an integer between 0 and the maximum value it can hold.
|
||||
int nextInt();
|
||||
|
||||
/// Generate several double/real numbers within the same range.
|
||||
/// e.g.: auto a = gen.getDoubleRange(4.5,10.2); a(); a(); a();
|
||||
/// requires: lower <= upper
|
||||
TRand getDoubleRange(double lower, double upper);
|
||||
|
||||
vstd::TRand getDoubleRange(double lower, double upper) override;
|
||||
|
||||
/// Generates a double between 0 and upper.
|
||||
/// requires: 0 <= 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)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,6 @@
|
||||
#include "CModHandler.h"
|
||||
#include "StringConstants.h"
|
||||
|
||||
#include "CStack.h"
|
||||
#include "battle/BattleInfo.h"
|
||||
#include "battle/CBattleInfoCallback.h"
|
||||
|
||||
///CSkill
|
||||
CSkill::LevelInfo::LevelInfo()
|
||||
{
|
||||
@ -158,7 +154,7 @@ const std::string & CSkillHandler::skillName(int skill) const
|
||||
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;
|
||||
|
||||
|
@ -83,5 +83,5 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
CSkill * loadFromJson(const JsonNode & json, const std::string & identifier) override;
|
||||
CSkill * loadFromJson(const JsonNode & json, const std::string & identifier, size_t index) override;
|
||||
};
|
||||
|
824
lib/CStack.cpp
824
lib/CStack.cpp
@ -9,326 +9,34 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CStack.h"
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
#include "CGeneralTextHandler.h"
|
||||
#include "battle/BattleInfo.h"
|
||||
#include "spells/CSpellHandler.h"
|
||||
#include "CRandomGenerator.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(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S):
|
||||
base(Base), ID(I), owner(O), slot(S), side(Side),
|
||||
counterAttacks(this), shots(this), casts(this), health(this), cloneID(-1),
|
||||
position()
|
||||
CStack::CStack(const CStackInstance * Base, PlayerColor O, int I, ui8 Side, SlotID S)
|
||||
: CBonusSystemNode(STACK_BATTLE),
|
||||
CUnitState(),
|
||||
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(); //???
|
||||
setNodeType(STACK_BATTLE);
|
||||
}
|
||||
|
||||
CStack::CStack():
|
||||
counterAttacks(this), shots(this), casts(this), health(this)
|
||||
{
|
||||
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()
|
||||
CStack::CStack()
|
||||
: CBonusSystemNode(STACK_BATTLE),
|
||||
CUnitState()
|
||||
{
|
||||
base = nullptr;
|
||||
type = nullptr;
|
||||
@ -337,14 +45,32 @@ void CStack::init()
|
||||
owner = PlayerColor::NEUTRAL;
|
||||
slot = SlotID(255);
|
||||
side = 1;
|
||||
position = BattleHex();
|
||||
cloneID = -1;
|
||||
initialPosition = BattleHex();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
battle = battleInfo;
|
||||
cloneID = -1;
|
||||
assert(type);
|
||||
|
||||
exportBonuses();
|
||||
@ -359,185 +85,35 @@ void CStack::localInit(BattleInfo * battleInfo)
|
||||
attachTo(const_cast<CCreature *>(type));
|
||||
}
|
||||
|
||||
shots.reset();
|
||||
counterAttacks.reset();
|
||||
casts.reset();
|
||||
health.init();
|
||||
CUnitState::localInit(this);
|
||||
position = initialPosition;
|
||||
}
|
||||
|
||||
ui32 CStack::level() const
|
||||
{
|
||||
if(base)
|
||||
return base->getLevel(); //creatture or commander
|
||||
return base->getLevel(); //creature or commander
|
||||
else
|
||||
return std::max(1, (int)getCreature()->level); //war machine, clone etc
|
||||
}
|
||||
|
||||
si32 CStack::magicResistance() const
|
||||
{
|
||||
si32 magicResistance;
|
||||
if(base) //TODO: make war machines receive aura of magic resistance
|
||||
si32 magicResistance = IBonusBearer::magicResistance();
|
||||
|
||||
si32 auraBonus = 0;
|
||||
|
||||
for(auto one : battle->battleAdjacentUnits(this))
|
||||
{
|
||||
magicResistance = base->magicResistance();
|
||||
int auraBonus = 0;
|
||||
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);
|
||||
if(one->unitOwner() == owner)
|
||||
vstd::amax(auraBonus, one->valOfBonuses(Bonus::SPELL_RESISTANCE_AURA)); //max value
|
||||
}
|
||||
else
|
||||
magicResistance = type->magicResistance();
|
||||
magicResistance += auraBonus;
|
||||
vstd::amin(magicResistance, 100);
|
||||
|
||||
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
|
||||
{
|
||||
if(doubleWide())
|
||||
@ -595,7 +171,8 @@ const CGHeroInstance * CStack::getMyHero() const
|
||||
std::string CStack::nodeName() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Battle stack [" << ID << "]: " << health.getCount() << " creatures of ";
|
||||
oss << owner.getStr();
|
||||
oss << " battle stack [" << ID << "]: " << getCount() << " of ";
|
||||
if(type)
|
||||
oss << type->namePl;
|
||||
else
|
||||
@ -607,156 +184,95 @@ std::string CStack::nodeName() const
|
||||
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())
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
customState->damage(bsa.damageAmount);
|
||||
|
||||
return res;
|
||||
}
|
||||
bsa.killedAmount = initialCount - customState->getCount();
|
||||
|
||||
CHealth CStack::healthAfterHealed(int32_t & toHeal, EHealLevel level, EHealPower power) const
|
||||
{
|
||||
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())
|
||||
if(!customState->alive() && customState->isClone())
|
||||
{
|
||||
bsa.flags |= BattleStackAttacked::CLONE_KILLED;
|
||||
return; // no rebirth I believe
|
||||
}
|
||||
|
||||
if(afterAttack.available() <= 0) //stack killed
|
||||
else if(!customState->alive()) //stack killed
|
||||
{
|
||||
bsa.flags |= BattleStackAttacked::KILLED;
|
||||
|
||||
int resurrectFactor = valOfBonuses(Bonus::REBIRTH);
|
||||
if(resurrectFactor > 0 && canCast()) //there must be casts left
|
||||
{
|
||||
int resurrectedStackCount = baseAmount * resurrectFactor / 100;
|
||||
auto resurrectValue = customState->valOfBonuses(Bonus::REBIRTH);
|
||||
|
||||
// last stack has proportional chance to rebirth
|
||||
//FIXME: diff is always 0
|
||||
auto diff = baseAmount * resurrectFactor / 100.0 - resurrectedStackCount;
|
||||
if(diff > rand.nextDouble(0, 0.99))
|
||||
if(resurrectValue > 0 && customState->canCast()) //there must be casts left
|
||||
{
|
||||
double resurrectFactor = resurrectValue / 100;
|
||||
|
||||
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
|
||||
vstd::amax(resurrectedStackCount, 1);
|
||||
vstd::amax(resurrectedCount, 1);
|
||||
}
|
||||
|
||||
if(resurrectedStackCount > 0)
|
||||
if(resurrectedCount > 0)
|
||||
{
|
||||
customState->casts.use();
|
||||
bsa.flags |= BattleStackAttacked::REBIRTH;
|
||||
//TODO: use StackHealedOrResurrected
|
||||
bsa.newHealth.firstHPleft = MaxHealth();
|
||||
bsa.newHealth.fullUnits = resurrectedStackCount - 1;
|
||||
bsa.newHealth.resurrected = 0; //TODO: add one-battle rebirth?
|
||||
int64_t toHeal = customState->MaxHealth() * resurrectedCount;
|
||||
//TODO: add one-battle rebirth?
|
||||
customState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
|
||||
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())
|
||||
attackerPos = attacker->position;
|
||||
attackerPos = attacker->getPosition();
|
||||
if(!defenderPos.isValid())
|
||||
defenderPos = defender->position;
|
||||
defenderPos = defender->getPosition();
|
||||
|
||||
return
|
||||
(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0)//front <=> 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
|
||||
&& 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
|
||||
&& 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
|
||||
{
|
||||
return (health.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;
|
||||
return (getCount() == 1) ? type->nameSing : type->namePl; //War machines can't use base
|
||||
}
|
||||
|
||||
bool CStack::canBeHealed() const
|
||||
@ -766,75 +282,9 @@ bool CStack::canBeHealed() const
|
||||
&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
|
||||
}
|
||||
|
||||
void CStack::makeGhost()
|
||||
const CCreature * CStack::unitType() const
|
||||
{
|
||||
state.erase(EBattleStackState::ALIVE);
|
||||
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();
|
||||
return type;
|
||||
}
|
||||
|
||||
int32_t CStack::unitBaseAmount() const
|
||||
@ -842,46 +292,60 @@ int32_t CStack::unitBaseAmount() const
|
||||
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))
|
||||
serial = VLC->generaltexth->pluralText(serial, health.getCount());
|
||||
else if(plural)
|
||||
serial = VLC->generaltexth->pluralText(serial, 2);
|
||||
else
|
||||
serial = VLC->generaltexth->pluralText(serial, 1);
|
||||
bool hasAmmoCart = false;
|
||||
|
||||
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))
|
||||
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);
|
||||
return battle->battleGetOwner(unit);
|
||||
}
|
||||
|
||||
std::string CStack::formatGeneralMessage(const int32_t baseTextId) const
|
||||
uint32_t CStack::unitId() const
|
||||
{
|
||||
const int32_t textId = VLC->generaltexth->pluralText(baseTextId, health.getCount());
|
||||
|
||||
MetaString text;
|
||||
text.addTxt(MetaString::GENERAL_TXT, textId);
|
||||
text.addCreReplacement(type->idNumber, health.getCount());
|
||||
|
||||
return text.toString();
|
||||
return ID;
|
||||
}
|
||||
|
||||
void CStack::setHealth(const CHealthInfo & value)
|
||||
ui8 CStack::unitSide() const
|
||||
{
|
||||
health.reset();
|
||||
health.fromInfo(value);
|
||||
return side;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
238
lib/CStack.h
238
lib/CStack.h
@ -9,179 +9,42 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "JsonNode.h"
|
||||
#include "HeroBonus.h"
|
||||
#include "CCreatureHandler.h" //todo: remove
|
||||
#include "battle/BattleHex.h"
|
||||
#include "CCreatureHandler.h"
|
||||
#include "mapObjects/CGHeroInstance.h" // for commander serialization
|
||||
|
||||
#include "battle/CUnitState.h"
|
||||
|
||||
struct BattleStackAttacked;
|
||||
struct BattleInfo;
|
||||
class CStack;
|
||||
class CHealthInfo;
|
||||
class BattleInfo;
|
||||
|
||||
template <typename Quantity>
|
||||
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
|
||||
class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, public battle::IUnitEnvironment
|
||||
{
|
||||
public:
|
||||
const CStackInstance * base; //garrison slot from which stack originates (nullptr for war machines, summoned cres, etc)
|
||||
|
||||
ui32 ID; //unique ID of stack
|
||||
ui32 baseAmount;
|
||||
const CCreature * type;
|
||||
ui32 baseAmount;
|
||||
|
||||
PlayerColor owner; //owner - player color (255 for neutrals)
|
||||
SlotID slot; //slot - position in garrison (may be 255 for neutrals/called creatures)
|
||||
ui8 side;
|
||||
BattleHex position; //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;
|
||||
BattleHex initialPosition; //position on battlefield; -2 - keep, -3 - lower tower, -4 - upper tower
|
||||
|
||||
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();
|
||||
~CStack();
|
||||
|
||||
int32_t getKilled() const;
|
||||
int32_t getCount() const;
|
||||
int32_t getFirstHPleft() const;
|
||||
const CCreature * getCreature() const;
|
||||
const CCreature * getCreature() const; //deprecated
|
||||
|
||||
std::string nodeName() const override;
|
||||
|
||||
void init(); //set initial (invalid) values
|
||||
void localInit(BattleInfo * battleInfo);
|
||||
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
|
||||
|
||||
@ -190,68 +53,32 @@ public:
|
||||
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
|
||||
|
||||
static bool isMeleeAttackPossible(const CStack * attacker, const CStack * 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
|
||||
static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID);
|
||||
|
||||
BattleHex::EDir destShiftDir() const;
|
||||
|
||||
CHealth healthAfterAttacked(int32_t & damage) const;
|
||||
CHealth healthAfterAttacked(int32_t & damage, const CHealth & customHealth) const;
|
||||
void prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand) const; //requires bsa.damageAmout filled
|
||||
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;
|
||||
|
||||
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;
|
||||
const CCreature * unitType() 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;
|
||||
void addNameReplacement(MetaString & text, const boost::logic::tribool & plural = boost::logic::indeterminate) const;
|
||||
std::string formatGeneralMessage(const int32_t baseTextId) const;
|
||||
std::string getDescription() const override;
|
||||
|
||||
///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 makeGhost();
|
||||
void setHealth(const CHealthInfo & value);
|
||||
void setHealth(const CHealth & value);
|
||||
void spendMana(const spells::PacketSender * server, const int spellCost) const override;
|
||||
|
||||
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());
|
||||
h & static_cast<CBonusSystemNode&>(*this);
|
||||
h & type;
|
||||
@ -260,12 +87,7 @@ public:
|
||||
h & owner;
|
||||
h & slot;
|
||||
h & side;
|
||||
h & position;
|
||||
h & state;
|
||||
h & shots;
|
||||
h & casts;
|
||||
h & counterAttacks;
|
||||
h & health;
|
||||
h & initialPosition;
|
||||
|
||||
const CArmedInstance * army = (base ? base->armyObj : nullptr);
|
||||
SlotID extSlot = (base ? base->armyObj->findStack(base) : SlotID());
|
||||
@ -279,6 +101,7 @@ public:
|
||||
{
|
||||
h & army;
|
||||
h & extSlot;
|
||||
|
||||
if(extSlot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
|
||||
{
|
||||
auto hero = dynamic_cast<const CGHeroInstance *>(army);
|
||||
@ -300,17 +123,8 @@ public:
|
||||
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:
|
||||
const BattleInfo * battle; //do not serialize
|
||||
};
|
||||
|
@ -25,13 +25,11 @@ CThreadHelper::CThreadHelper(std::vector<std::function<void()> > *Tasks, int Thr
|
||||
void CThreadHelper::run()
|
||||
{
|
||||
boost::thread_group grupa;
|
||||
std::vector<boost::thread *> thr;
|
||||
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();
|
||||
|
||||
for(auto thread : thr)
|
||||
delete thread;
|
||||
//thread group deletes threads, do not free manually
|
||||
}
|
||||
void CThreadHelper::processTasks()
|
||||
{
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "CSkillHandler.h"
|
||||
#include "StringConstants.h"
|
||||
#include "CGeneralTextHandler.h"
|
||||
#include "CModHandler.h"
|
||||
|
||||
const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
|
||||
const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
|
||||
@ -51,11 +52,39 @@ const CArtifact * ArtifactID::toArtifact() const
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
if(num < 0 || num >= VLC->spellh->objects.size())
|
||||
@ -66,6 +95,20 @@ const CSpell * SpellID::toSpell() const
|
||||
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
|
||||
{
|
||||
return VLC->skillh->objects.at(*this);
|
||||
@ -110,26 +153,26 @@ std::string PlayerColor::getStrCap(bool L10n) const
|
||||
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"},
|
||||
{Battle::INVALID, "Invalid"},
|
||||
{Battle::NO_ACTION, "No action"},
|
||||
{Battle::HERO_SPELL, "Hero spell"},
|
||||
{Battle::WALK, "Walk"},
|
||||
{Battle::DEFEND, "Defend"},
|
||||
{Battle::RETREAT, "Retreat"},
|
||||
{Battle::SURRENDER, "Surrender"},
|
||||
{Battle::WALK_AND_ATTACK, "Walk and attack"},
|
||||
{Battle::SHOOT, "Shoot"},
|
||||
{Battle::WAIT, "Wait"},
|
||||
{Battle::CATAPULT, "Catapult"},
|
||||
{Battle::MONSTER_SPELL, "Monster spell"},
|
||||
{Battle::BAD_MORALE, "Bad morale"},
|
||||
{Battle::STACK_HEAL, "Stack heal"},
|
||||
{Battle::DAEMON_SUMMONING, "Daemon summoning"}
|
||||
{EActionType::END_TACTIC_PHASE, "End tactic phase"},
|
||||
{EActionType::INVALID, "Invalid"},
|
||||
{EActionType::NO_ACTION, "No action"},
|
||||
{EActionType::HERO_SPELL, "Hero spell"},
|
||||
{EActionType::WALK, "Walk"},
|
||||
{EActionType::DEFEND, "Defend"},
|
||||
{EActionType::RETREAT, "Retreat"},
|
||||
{EActionType::SURRENDER, "Surrender"},
|
||||
{EActionType::WALK_AND_ATTACK, "Walk and attack"},
|
||||
{EActionType::SHOOT, "Shoot"},
|
||||
{EActionType::WAIT, "Wait"},
|
||||
{EActionType::CATAPULT, "Catapult"},
|
||||
{EActionType::MONSTER_SPELL, "Monster spell"},
|
||||
{EActionType::BAD_MORALE, "Bad morale"},
|
||||
{EActionType::STACK_HEAL, "Stack heal"},
|
||||
{EActionType::DAEMON_SUMMONING, "Daemon summoning"}
|
||||
};
|
||||
|
||||
auto it = actionTypeToString.find(actionType);
|
||||
|
@ -15,10 +15,6 @@ namespace GameConstants
|
||||
{
|
||||
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 MAX_HEROES_PER_PLAYER = 8;
|
||||
@ -443,27 +439,15 @@ namespace 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,
|
||||
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,
|
||||
MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all
|
||||
NOT_DECIDED,
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
enum SecondarySkills {ATTACK, DEFENSE, HEALTH, DAMAGE, SPEED, SPELL_POWER, CASTS, RESISTANCE};
|
||||
@ -518,8 +482,6 @@ namespace EWallState
|
||||
DESTROYED,
|
||||
DAMAGED,
|
||||
INTACT
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@ -778,30 +740,27 @@ namespace Date
|
||||
};
|
||||
}
|
||||
|
||||
namespace Battle
|
||||
enum class EActionType : int32_t
|
||||
{
|
||||
enum ActionType
|
||||
{
|
||||
CANCEL = -3,
|
||||
END_TACTIC_PHASE = -2,
|
||||
INVALID = -1,
|
||||
NO_ACTION = 0,
|
||||
HERO_SPELL,
|
||||
WALK, DEFEND,
|
||||
RETREAT,
|
||||
SURRENDER,
|
||||
WALK_AND_ATTACK,
|
||||
SHOOT,
|
||||
WAIT,
|
||||
CATAPULT,
|
||||
MONSTER_SPELL,
|
||||
BAD_MORALE,
|
||||
STACK_HEAL,
|
||||
DAEMON_SUMMONING
|
||||
};
|
||||
}
|
||||
CANCEL = -3,
|
||||
END_TACTIC_PHASE = -2,
|
||||
INVALID = -1,
|
||||
NO_ACTION = 0,
|
||||
HERO_SPELL,
|
||||
WALK, DEFEND,
|
||||
RETREAT,
|
||||
SURRENDER,
|
||||
WALK_AND_ATTACK,
|
||||
SHOOT,
|
||||
WAIT,
|
||||
CATAPULT,
|
||||
MONSTER_SPELL,
|
||||
BAD_MORALE,
|
||||
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
|
||||
{
|
||||
@ -969,6 +928,10 @@ public:
|
||||
|
||||
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)
|
||||
|
||||
EArtifactID num;
|
||||
@ -1017,6 +980,10 @@ public:
|
||||
ID_LIKE_CLASS_COMMON(CreatureID, ECreatureID)
|
||||
|
||||
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)
|
||||
@ -1060,6 +1027,10 @@ public:
|
||||
ID_LIKE_CLASS_COMMON(SpellID, ESpellID)
|
||||
|
||||
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)
|
||||
@ -1108,7 +1079,7 @@ enum class EHealPower : ui8
|
||||
// Typedef declarations
|
||||
typedef ui8 TFaction;
|
||||
typedef si64 TExpType;
|
||||
typedef std::pair<ui32, ui32> TDmgRange;
|
||||
typedef std::pair<si64, si64> TDmgRange;
|
||||
typedef si32 TBonusSubtype;
|
||||
typedef si32 TQuantity;
|
||||
|
||||
|
@ -81,20 +81,59 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
|
||||
}; //untested
|
||||
|
||||
///CBonusProxy
|
||||
CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector):
|
||||
cachedLast(0), target(Target), selector(Selector), data()
|
||||
CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector)
|
||||
: 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
|
||||
{
|
||||
if(CBonusSystemNode::treeChanged != cachedLast || !data)
|
||||
if(target->getTreeVersion() != cachedLast || !data)
|
||||
{
|
||||
//TODO: support limiters
|
||||
data = target->getAllBonuses(selector, nullptr);
|
||||
data = target->getAllBonuses(selector, Selector::all);
|
||||
data->eliminateDuplicates();
|
||||
cachedLast = CBonusSystemNode::treeChanged;
|
||||
cachedLast = target->getTreeVersion();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
@ -104,7 +143,7 @@ const BonusList * CBonusProxy::operator->() const
|
||||
return get().get();
|
||||
}
|
||||
|
||||
int CBonusSystemNode::treeChanged = 1;
|
||||
std::atomic<int32_t> CBonusSystemNode::treeChanged(1);
|
||||
const bool CBonusSystemNode::cachingEnabled = true;
|
||||
|
||||
BonusList::BonusList(bool BelongsToTree) : belongsToTree(BelongsToTree)
|
||||
@ -408,48 +447,44 @@ int IBonusBearer::LuckVal() const
|
||||
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
|
||||
{
|
||||
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;
|
||||
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());
|
||||
const std::string cachingStr = "type_PRIMARY_SKILLs_ATTACK";
|
||||
|
||||
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;
|
||||
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());
|
||||
const std::string cachingStr = "type_PRIMARY_SKILLs_DEFENSE";
|
||||
|
||||
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
|
||||
@ -461,13 +496,7 @@ si32 IBonusBearer::manaLimit() const
|
||||
|
||||
int IBonusBearer::getPrimSkillLevel(PrimarySkill::PrimarySkill id) const
|
||||
{
|
||||
int ret = 0;
|
||||
if(id == PrimarySkill::ATTACK)
|
||||
ret = Attack();
|
||||
else if(id == PrimarySkill::DEFENSE)
|
||||
ret = Defense();
|
||||
else
|
||||
ret = valOfBonuses(Bonus::PRIMARY_SKILL, id);
|
||||
int 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
|
||||
return ret;
|
||||
@ -478,7 +507,7 @@ si32 IBonusBearer::magicResistance() const
|
||||
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
|
||||
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
|
||||
{
|
||||
auto bonuses = getAllBonuses(Selector::all, Selector::all);
|
||||
return bonuses->getFirst(selector);
|
||||
auto bonuses = getAllBonuses(selector, Selector::all);
|
||||
return bonuses->getFirst(Selector::all);
|
||||
}
|
||||
|
||||
std::shared_ptr<Bonus> CBonusSystemNode::getBonusLocalFirst(const CSelector &selector)
|
||||
@ -657,7 +686,19 @@ const TBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelecto
|
||||
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++;
|
||||
}
|
||||
|
||||
int64_t CBonusSystemNode::getTreeVersion() const
|
||||
{
|
||||
int64_t ret = treeChanged;
|
||||
return ret << 32;
|
||||
}
|
||||
|
||||
int NBonus::valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype)
|
||||
{
|
||||
if(obj)
|
||||
|
@ -64,16 +64,21 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CBonusProxy : public boost::noncopyable
|
||||
class DLL_LINKAGE CBonusProxy
|
||||
{
|
||||
public:
|
||||
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;
|
||||
|
||||
const BonusList * operator->() const;
|
||||
private:
|
||||
mutable int cachedLast;
|
||||
mutable int64_t cachedLast;
|
||||
const IBonusBearer * target;
|
||||
CSelector selector;
|
||||
mutable TBonusListPtr data;
|
||||
@ -607,12 +612,15 @@ public:
|
||||
bool hasBonusFrom(Bonus::BonusSource source, ui32 sourceID) const;
|
||||
|
||||
//various hlp functions for non-trivial values
|
||||
ui32 getMinDamage() const; //used for stacks and creatures only
|
||||
ui32 getMaxDamage() const;
|
||||
//used for stacks and creatures only
|
||||
|
||||
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 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
|
||||
bool isLiving() const; //non-undead, non-non living or alive
|
||||
virtual si32 magicResistance() const;
|
||||
@ -620,9 +628,11 @@ public:
|
||||
|
||||
si32 manaLimit() const; //maximum mana value for this hero (basically 10*knowledge)
|
||||
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:
|
||||
enum ENodeTypes
|
||||
@ -642,8 +652,8 @@ private:
|
||||
|
||||
static const bool cachingEnabled;
|
||||
mutable BonusList cachedBonuses;
|
||||
mutable int cachedLast;
|
||||
static int treeChanged;
|
||||
mutable int64_t cachedLast;
|
||||
static std::atomic<int32_t> treeChanged;
|
||||
|
||||
// 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:
|
||||
@ -656,6 +666,7 @@ private:
|
||||
|
||||
public:
|
||||
explicit CBonusSystemNode();
|
||||
explicit CBonusSystemNode(ENodeTypes NodeType);
|
||||
CBonusSystemNode(CBonusSystemNode && other);
|
||||
virtual ~CBonusSystemNode();
|
||||
|
||||
@ -711,6 +722,8 @@ public:
|
||||
|
||||
static void treeHasChanged();
|
||||
|
||||
int64_t getTreeVersion() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
// h & bonuses;
|
||||
|
@ -19,6 +19,6 @@ class IBonusTypeHandler
|
||||
public:
|
||||
virtual ~IBonusTypeHandler(){};
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
@ -32,12 +32,11 @@ struct Bonus;
|
||||
class IMarket;
|
||||
struct SetObjectProperty;
|
||||
struct PackageApplied;
|
||||
struct BattleAction;
|
||||
class BattleAction;
|
||||
struct BattleStackAttacked;
|
||||
struct BattleResult;
|
||||
struct BattleSpellCast;
|
||||
struct CatapultAttack;
|
||||
struct BattleStacksRemoved;
|
||||
class CStack;
|
||||
class CCreatureSet;
|
||||
struct BattleAttack;
|
||||
@ -47,6 +46,10 @@ class CComponent;
|
||||
struct CObstacleInstance;
|
||||
struct CPackForServer;
|
||||
class EVictoryLossCheckResult;
|
||||
struct MetaString;
|
||||
struct CustomEffectInfo;
|
||||
class ObstacleChanges;
|
||||
class UnitChanges;
|
||||
|
||||
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 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 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 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
|
||||
@ -64,12 +67,9 @@ public:
|
||||
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 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 battleNewStackAppeared(const CStack * stack){}; //not called at the beginning of a battle or by resurrection; called eg. when elemental is summoned
|
||||
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 battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects, const std::vector<MetaString> & battleLog){};
|
||||
virtual void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles){};
|
||||
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){};
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include "../lib/ConstTransitivePtr.h"
|
||||
#include "VCMI_Lib.h"
|
||||
//#include "CModHandler.h"
|
||||
|
||||
class JsonNode;
|
||||
|
||||
@ -69,8 +68,7 @@ public:
|
||||
void loadObject(std::string scope, std::string name, const JsonNode & data) override
|
||||
{
|
||||
auto type_name = getTypeName();
|
||||
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
|
||||
object->id = _ObjectID(objects.size());
|
||||
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), objects.size());
|
||||
|
||||
objects.push_back(object);
|
||||
|
||||
@ -79,8 +77,7 @@ public:
|
||||
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override
|
||||
{
|
||||
auto type_name = getTypeName();
|
||||
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
|
||||
object->id = _ObjectID(index);
|
||||
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index);
|
||||
|
||||
assert(objects[index] == nullptr); // ensure that this id was not loaded before
|
||||
objects[index] = object;
|
||||
@ -101,7 +98,7 @@ public:
|
||||
return objects[raw_id];
|
||||
}
|
||||
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;
|
||||
public: //todo: make private
|
||||
std::vector<ConstTransitivePtr<_Object>> objects;
|
||||
|
@ -305,7 +305,7 @@ namespace JsonDetail
|
||||
return node.Bool();
|
||||
}
|
||||
};
|
||||
} // namespace JsonDetail
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
Type JsonNode::convertTo() const
|
||||
|
254
lib/NetPacks.h
254
lib/NetPacks.h
@ -12,7 +12,6 @@
|
||||
#include "NetPacksBase.h"
|
||||
|
||||
#include "battle/BattleAction.h"
|
||||
#include "JsonNode.h"
|
||||
#include "mapObjects/CGHeroInstance.h"
|
||||
#include "ConstTransitivePtr.h"
|
||||
#include "int3.h"
|
||||
@ -23,10 +22,6 @@
|
||||
|
||||
#include "spells/ViewSpellInt.h"
|
||||
|
||||
class CClient;
|
||||
class CGameState;
|
||||
class CGameHandler;
|
||||
class CConnection;
|
||||
class CCampaignState;
|
||||
class CArtifact;
|
||||
class CSelectionScreen;
|
||||
@ -37,45 +32,7 @@ struct ArtSlotInfo;
|
||||
struct QuestInfo;
|
||||
class CMapInfo;
|
||||
struct StartInfo;
|
||||
|
||||
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);
|
||||
};
|
||||
class IBattleState;
|
||||
|
||||
struct Query : public CPackForClient
|
||||
{
|
||||
@ -1351,7 +1308,7 @@ struct MapObjectSelectDialog : public Query
|
||||
}
|
||||
};
|
||||
|
||||
struct BattleInfo;
|
||||
class BattleInfo;
|
||||
struct BattleStart : public CPackForClient
|
||||
{
|
||||
BattleStart()
|
||||
@ -1440,12 +1397,16 @@ struct BattleStackMoved : public CPackForClient
|
||||
{
|
||||
ui32 stack;
|
||||
std::vector<BattleHex> tilesToMove;
|
||||
ui8 distance, teleporting;
|
||||
int distance;
|
||||
bool teleporting;
|
||||
BattleStackMoved()
|
||||
:stack(0), distance(0), teleporting(0)
|
||||
: stack(0),
|
||||
distance(0),
|
||||
teleporting(false)
|
||||
{};
|
||||
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)
|
||||
{
|
||||
h & stack;
|
||||
@ -1454,52 +1415,46 @@ struct BattleStackMoved : public CPackForClient
|
||||
}
|
||||
};
|
||||
|
||||
struct StacksHealedOrResurrected : public CPackForClient
|
||||
struct BattleUnitsChanged : public CPackForClient
|
||||
{
|
||||
StacksHealedOrResurrected()
|
||||
:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false)
|
||||
{}
|
||||
BattleUnitsChanged(){}
|
||||
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
DLL_LINKAGE void applyBattle(IBattleState * battleState);
|
||||
void applyCl(CClient *cl);
|
||||
|
||||
std::vector<CHealthInfo> healedStacks;
|
||||
bool lifeDrain; //if true, this heal is an effect of life drain or soul steal
|
||||
bool tentHealing; //if true, than it's healing via First Aid Tent
|
||||
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
|
||||
std::vector<UnitChanges> changedStacks;
|
||||
std::vector<MetaString> battleLog;
|
||||
std::vector<CustomEffectInfo> customEffects;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
template <typename Handler> void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & healedStacks;
|
||||
h & lifeDrain;
|
||||
h & tentHealing;
|
||||
h & drainedFrom;
|
||||
h & cure;
|
||||
h & changedStacks;
|
||||
h & battleLog;
|
||||
h & customEffects;
|
||||
}
|
||||
};
|
||||
|
||||
struct BattleStackAttacked : public CPackForClient
|
||||
struct BattleStackAttacked
|
||||
{
|
||||
BattleStackAttacked():
|
||||
stackAttacked(0), attackerID(0),
|
||||
killedAmount(0), damageAmount(0),
|
||||
newHealth(),
|
||||
newState(),
|
||||
flags(0), effect(0), spellID(SpellID::NONE)
|
||||
{};
|
||||
void applyFirstCl(CClient * cl);
|
||||
//void applyCl(CClient *cl);
|
||||
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
DLL_LINKAGE void applyBattle(IBattleState * battleState);
|
||||
|
||||
ui32 stackAttacked, attackerID;
|
||||
ui32 killedAmount;
|
||||
si32 damageAmount;
|
||||
CHealthInfo newHealth;
|
||||
int64_t damageAmount;
|
||||
UnitChanges newState;
|
||||
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 effect; //set only if flag 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
|
||||
{
|
||||
@ -1526,20 +1481,15 @@ struct BattleStackAttacked : public CPackForClient
|
||||
{
|
||||
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)
|
||||
{
|
||||
h & stackAttacked;
|
||||
h & attackerID;
|
||||
h & newHealth;
|
||||
h & newState;
|
||||
h & flags;
|
||||
h & killedAmount;
|
||||
h & damageAmount;
|
||||
h & effect;
|
||||
h & healedStacks;
|
||||
h & spellID;
|
||||
}
|
||||
bool operator<(const BattleStackAttacked &b) const
|
||||
@ -1557,6 +1507,8 @@ struct BattleAttack : public CPackForClient
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
|
||||
BattleUnitsChanged attackerChanges;
|
||||
|
||||
std::vector<BattleStackAttacked> bsa;
|
||||
ui32 stackAttacking;
|
||||
ui32 flags; //uses Eflags (below)
|
||||
@ -1564,6 +1516,9 @@ struct BattleAttack : public CPackForClient
|
||||
|
||||
SpellID spellID; //for SPELL_LIKE
|
||||
|
||||
std::vector<MetaString> battleLog;
|
||||
std::vector<CustomEffectInfo> customEffects;
|
||||
|
||||
bool shot() const//distance attack - decrease number of shots
|
||||
{
|
||||
return flags & SHOT;
|
||||
@ -1598,6 +1553,9 @@ struct BattleAttack : public CPackForClient
|
||||
h & stackAttacking;
|
||||
h & flags;
|
||||
h & spellID;
|
||||
h & battleLog;
|
||||
h & customEffects;
|
||||
h & attackerChanges;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1627,24 +1585,9 @@ struct EndAction : 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()
|
||||
{
|
||||
side = 0;
|
||||
id = 0;
|
||||
skill = 0;
|
||||
manaGained = 0;
|
||||
casterStack = -1;
|
||||
castByHero = true;
|
||||
@ -1655,11 +1598,10 @@ struct BattleSpellCast : public CPackForClient
|
||||
|
||||
bool activeCast;
|
||||
ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
|
||||
ui32 id; //id of spell
|
||||
ui8 skill; //caster's skill level
|
||||
SpellID spellID; //id of spell
|
||||
ui8 manaGained; //mana channeling ability
|
||||
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)
|
||||
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
|
||||
@ -1668,8 +1610,7 @@ struct BattleSpellCast : public CPackForClient
|
||||
template <typename Handler> void serialize(Handler &h, const int version)
|
||||
{
|
||||
h & side;
|
||||
h & id;
|
||||
h & skill;
|
||||
h & spellID;
|
||||
h & manaGained;
|
||||
h & tile;
|
||||
h & customEffects;
|
||||
@ -1684,27 +1625,20 @@ struct BattleSpellCast : public CPackForClient
|
||||
struct SetStackEffect : public CPackForClient
|
||||
{
|
||||
SetStackEffect(){};
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
DLL_LINKAGE void applyBattle(IBattleState * battleState);
|
||||
void applyCl(CClient * cl);
|
||||
|
||||
std::vector<ui32> stacks; //affected stacks (IDs)
|
||||
|
||||
//regular effects
|
||||
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<std::pair<ui32, std::vector<Bonus>>> toAdd;
|
||||
std::vector<std::pair<ui32, std::vector<Bonus>>> toUpdate;
|
||||
std::vector<std::pair<ui32, std::vector<Bonus>>> toRemove;
|
||||
|
||||
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 & effect;
|
||||
h & uniqueBonuses;
|
||||
h & cumulativeEffects;
|
||||
h & cumulativeUniqueBonuses;
|
||||
h & toAdd;
|
||||
h & toUpdate;
|
||||
h & toRemove;
|
||||
h & battleLog;
|
||||
}
|
||||
};
|
||||
@ -1712,13 +1646,18 @@ struct SetStackEffect : public CPackForClient
|
||||
struct StacksInjured : public CPackForClient
|
||||
{
|
||||
StacksInjured(){}
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
DLL_LINKAGE void applyBattle(IBattleState * battleState);
|
||||
|
||||
void applyCl(CClient * cl);
|
||||
|
||||
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 & 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);
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
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 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 & attackedPart;
|
||||
@ -1772,9 +1710,9 @@ struct ELF_VISIBILITY CatapultAttack : public CPackForClient
|
||||
DLL_LINKAGE CatapultAttack();
|
||||
DLL_LINKAGE ~CatapultAttack();
|
||||
|
||||
DLL_LINKAGE void applyGs(CGameState *gs);
|
||||
void applyCl(CClient *cl);
|
||||
DLL_LINKAGE std::string toString() const override;
|
||||
DLL_LINKAGE void applyGs(CGameState * gs);
|
||||
DLL_LINKAGE void applyBattle(IBattleState * battleState);
|
||||
void applyCl(CClient * cl);
|
||||
|
||||
std::vector< AttackInfo > attackedParts;
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
BattleUpdateGateState():state(EGateState::NONE){};
|
||||
|
@ -9,7 +9,10 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class CClient;
|
||||
class CGameState;
|
||||
class CGameHandler;
|
||||
class CConnection;
|
||||
class CStackBasicDescriptor;
|
||||
class CGHeroInstance;
|
||||
class CStackInstance;
|
||||
@ -17,9 +20,11 @@ class CArmedInstance;
|
||||
class CArtifactSet;
|
||||
class CBonusSystemNode;
|
||||
struct ArtSlotInfo;
|
||||
class BattleInfo;
|
||||
|
||||
#include "ConstTransitivePtr.h"
|
||||
#include "GameConstants.h"
|
||||
#include "JsonNode.h"
|
||||
|
||||
struct DLL_LINKAGE CPack
|
||||
{
|
||||
@ -31,11 +36,49 @@ struct DLL_LINKAGE CPack
|
||||
logNetwork->error("CPack serialized... this should not happen!");
|
||||
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
|
||||
{
|
||||
@ -196,25 +239,104 @@ struct ArtifactLocation
|
||||
}
|
||||
};
|
||||
|
||||
class CHealthInfo
|
||||
///custom effect (resistance, reflection, etc)
|
||||
struct CustomEffectInfo
|
||||
{
|
||||
public:
|
||||
CHealthInfo():
|
||||
stackId(0), delta(0), firstHPleft(0), fullUnits(0), resurrected(0)
|
||||
CustomEffectInfo()
|
||||
:effect(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)
|
||||
{
|
||||
h & stackId;
|
||||
h & delta;
|
||||
h & firstHPleft;
|
||||
h & fullUnits;
|
||||
h & resurrected;
|
||||
h & id;
|
||||
h & healthDelta;
|
||||
h & data;
|
||||
h & operation;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
@ -32,11 +32,6 @@
|
||||
#undef max
|
||||
|
||||
|
||||
std::ostream & operator<<(std::ostream & out, const CPack * pack)
|
||||
{
|
||||
return out << (pack? pack->toString() : "<nullptr>");
|
||||
}
|
||||
|
||||
DLL_LINKAGE void SetResources::applyGs(CGameState *gs)
|
||||
{
|
||||
assert(player < PlayerColor::PLAYER_LIMIT);
|
||||
@ -1232,50 +1227,12 @@ DLL_LINKAGE void BattleStart::applyGs(CGameState *gs)
|
||||
|
||||
DLL_LINKAGE void BattleNextRound::applyGs(CGameState *gs)
|
||||
{
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
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();
|
||||
gs->curB->nextRound(round);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BattleSetActiveStack::applyGs(CGameState *gs)
|
||||
{
|
||||
gs->curB->activeStack = 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);
|
||||
gs->curB->nextTurn(stack);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
|
||||
@ -1286,15 +1243,14 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
|
||||
{
|
||||
case Bonus::HP_REGENERATION:
|
||||
{
|
||||
int32_t toHeal = val;
|
||||
CHealth health = st->healthAfterHealed(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
|
||||
st->setHealth(health);
|
||||
int64_t toHeal = val;
|
||||
st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
|
||||
break;
|
||||
}
|
||||
case Bonus::MANA_DRAIN:
|
||||
{
|
||||
CGHeroInstance * h = gs->getHero(ObjectInstanceID(additionalInfo));
|
||||
st->state.insert (EBattleStackState::DRAINED_MANA);
|
||||
st->drainedMana = true;
|
||||
h->mana -= val;
|
||||
vstd::amax(h->mana, 0);
|
||||
break;
|
||||
@ -1310,18 +1266,13 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs(CGameState *gs)
|
||||
case Bonus::ENCHANTER:
|
||||
break;
|
||||
case Bonus::FEAR:
|
||||
st->state.insert(EBattleStackState::FEAR);
|
||||
st->fear = true;
|
||||
break;
|
||||
default:
|
||||
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)
|
||||
{
|
||||
if(gs->curB)
|
||||
@ -1330,15 +1281,6 @@ DLL_LINKAGE void BattleUpdateGateState::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)
|
||||
delete elem;
|
||||
|
||||
@ -1373,90 +1315,24 @@ void BattleResult::applyGs(CGameState *gs)
|
||||
gs->curB.dellNull();
|
||||
}
|
||||
|
||||
void BattleStackMoved::applyGs(CGameState *gs)
|
||||
DLL_LINKAGE void BattleStackMoved::applyGs(CGameState *gs)
|
||||
{
|
||||
CStack *s = gs->curB->getStack(stack);
|
||||
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;
|
||||
applyBattle(gs->curB);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState *gs)
|
||||
DLL_LINKAGE void BattleStackMoved::applyBattle(IBattleState * battleState)
|
||||
{
|
||||
CStack * at = gs->curB->getStack(stackAttacked);
|
||||
assert(at);
|
||||
at->popBonuses(Bonus::UntilBeingAttacked);
|
||||
battleState->moveUnit(stack, tilesToMove.back());
|
||||
}
|
||||
|
||||
if(willRebirth())
|
||||
at->health.reset();//kill stack first
|
||||
else
|
||||
at->setHealth(newHealth);
|
||||
DLL_LINKAGE void BattleStackAttacked::applyGs(CGameState * gs)
|
||||
{
|
||||
applyBattle(gs->curB);
|
||||
}
|
||||
|
||||
if(killed())
|
||||
{
|
||||
at->state -= EBattleStackState::ALIVE;
|
||||
|
||||
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 BattleStackAttacked::applyBattle(IBattleState * battleState)
|
||||
{
|
||||
battleState->setUnitState(newState.id, newState.data, newState.healthDelta);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
|
||||
@ -1464,11 +1340,7 @@ DLL_LINKAGE void BattleAttack::applyGs(CGameState * gs)
|
||||
CStack * attacker = gs->curB->getStack(stackAttacking);
|
||||
assert(attacker);
|
||||
|
||||
if(counter())
|
||||
attacker->counterAttacks.use();
|
||||
|
||||
if(shot())
|
||||
attacker->shots.use();
|
||||
attackerChanges.applyGs(gs);
|
||||
|
||||
for(BattleStackAttacked & stackAttacked : bsa)
|
||||
stackAttacked.applyGs(gs);
|
||||
@ -1480,7 +1352,7 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs)
|
||||
{
|
||||
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;
|
||||
return;
|
||||
@ -1493,216 +1365,130 @@ DLL_LINKAGE void StartAction::applyGs(CGameState *gs)
|
||||
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);
|
||||
}
|
||||
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)
|
||||
{
|
||||
case Battle::DEFEND:
|
||||
st->state -= EBattleStackState::DEFENDING_ANIM;
|
||||
st->state.insert(EBattleStackState::DEFENDING);
|
||||
st->state.insert(EBattleStackState::DEFENDING_ANIM);
|
||||
case EActionType::DEFEND:
|
||||
st->waiting = false;
|
||||
st->defending = true;
|
||||
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;
|
||||
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...
|
||||
st->state -= EBattleStackState::DEFENDING_ANIM;
|
||||
st->state.insert(EBattleStackState::MOVED);
|
||||
st->waiting = false;
|
||||
st->defendingAnim = false;
|
||||
st->movedThisRound = true;
|
||||
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)
|
||||
{
|
||||
assert(gs->curB);
|
||||
|
||||
const CSpell * spell = SpellID(id).toSpell();
|
||||
|
||||
spell->applyBattle(gs->curB, this);
|
||||
}
|
||||
|
||||
void actualizeEffect(CStack * s, const Bonus & ef)
|
||||
{
|
||||
for(auto stackBonus : s->getBonusList()) //TODO: optimize
|
||||
if(castByHero)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if(effect.empty() && cumulativeEffects.empty())
|
||||
{
|
||||
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);
|
||||
}
|
||||
applyBattle(gs->curB);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
applyBattle(gs->curB);
|
||||
}
|
||||
|
||||
DLL_LINKAGE void StacksInjured::applyBattle(IBattleState * battleState)
|
||||
{
|
||||
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);
|
||||
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))
|
||||
switch(elem.operation)
|
||||
{
|
||||
logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->position.hex);
|
||||
return; //position is already occupied
|
||||
}
|
||||
|
||||
//applying changes
|
||||
bool resurrected = !changedStack->alive(); //indicates if stack is resurrected or just healed
|
||||
if(resurrected)
|
||||
{
|
||||
if(auto totalHealth = changedStack->health.available())
|
||||
logGlobal->warn("Dead stack %s with positive total HP %d", changedStack->nodeName(), totalHealth);
|
||||
|
||||
changedStack->state.insert(EBattleStackState::ALIVE);
|
||||
}
|
||||
|
||||
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);
|
||||
case BattleChanges::EOperation::RESET_STATE:
|
||||
battleState->setUnitState(elem.id, elem.data, elem.healthDelta);
|
||||
break;
|
||||
case BattleChanges::EOperation::REMOVE:
|
||||
battleState->removeUnit(elem.id);
|
||||
break;
|
||||
case BattleChanges::EOperation::ADD:
|
||||
battleState->addUnit(elem.id, elem.data);
|
||||
break;
|
||||
default:
|
||||
logNetwork->error("Unknown unit operation %d", (int)elem.operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if(gs->curB->obstacles[i]->uniqueID == rem_obst) //remove this obstacle
|
||||
{
|
||||
gs->curB->obstacles.erase(gs->curB->obstacles.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
case BattleChanges::EOperation::REMOVE:
|
||||
battleState->removeObstacle(change.id);
|
||||
break;
|
||||
case BattleChanges::EOperation::ADD:
|
||||
battleState->addObstacle(change);
|
||||
break;
|
||||
default:
|
||||
logNetwork->error("Unknown obstacle operation %d", (int)change.operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DLL_LINKAGE CatapultAttack::CatapultAttack()
|
||||
{
|
||||
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
|
||||
{
|
||||
for(const auto &it :attackedParts)
|
||||
{
|
||||
gs->curB->si.wallState[it.attackedPart] =
|
||||
SiegeInfo::applyDamage(EWallState::EWallState(gs->curB->si.wallState[it.attackedPart]), it.damageDealt);
|
||||
}
|
||||
}
|
||||
if(gs->curB)
|
||||
applyBattle(gs->curB);
|
||||
}
|
||||
|
||||
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'}")
|
||||
% destinationTile % static_cast<int>(attackedPart) % static_cast<int>(damageDealt));
|
||||
}
|
||||
|
||||
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)
|
||||
auto town = battleState->getDefendedTown();
|
||||
if(!town)
|
||||
return;
|
||||
|
||||
while(!stackIDs.empty())
|
||||
{
|
||||
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!");
|
||||
if(town->fortLevel() == CGTownInstance::NONE)
|
||||
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)
|
||||
@ -1837,7 +1550,7 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState * gs)
|
||||
}
|
||||
case CLONED:
|
||||
{
|
||||
stack->state.insert(EBattleStackState::CLONED);
|
||||
stack->cloned = true;
|
||||
break;
|
||||
}
|
||||
case HAS_CLONE:
|
||||
|
@ -33,13 +33,13 @@
|
||||
|
||||
LibClasses * VLC = nullptr;
|
||||
|
||||
DLL_LINKAGE void preinitDLL(CConsoleHandler *Console)
|
||||
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential)
|
||||
{
|
||||
console = Console;
|
||||
VLC = new LibClasses();
|
||||
try
|
||||
{
|
||||
VLC->loadFilesystem();
|
||||
VLC->loadFilesystem(onlyEssential);
|
||||
}
|
||||
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
|
||||
@ -58,7 +58,7 @@ const IBonusTypeHandler * LibClasses::getBth() const
|
||||
return bth;
|
||||
}
|
||||
|
||||
void LibClasses::loadFilesystem()
|
||||
void LibClasses::loadFilesystem(bool onlyEssential)
|
||||
{
|
||||
CStopWatch totalTime;
|
||||
CStopWatch loadTime;
|
||||
@ -72,7 +72,7 @@ void LibClasses::loadFilesystem()
|
||||
modh = new CModHandler();
|
||||
logGlobal->info("\tMod handler: %d ms", loadTime.getDiff());
|
||||
|
||||
modh->loadMods();
|
||||
modh->loadMods(onlyEssential);
|
||||
modh->loadModFilesystems();
|
||||
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);
|
||||
}
|
||||
|
||||
void LibClasses::init()
|
||||
void LibClasses::init(bool onlyEssential)
|
||||
{
|
||||
CStopWatch pomtime, totalTime;
|
||||
|
||||
@ -124,7 +124,7 @@ void LibClasses::init()
|
||||
|
||||
modh->load();
|
||||
|
||||
modh->afterLoad();
|
||||
modh->afterLoad(onlyEssential);
|
||||
|
||||
//FIXME: make sure that everything is ok after game restart
|
||||
//TODO: This should be done every time mod config changes
|
||||
|
@ -53,11 +53,11 @@ public:
|
||||
|
||||
LibClasses(); //c-tor, loads .lods and NULLs handlers
|
||||
~LibClasses();
|
||||
void init(); //uses standard config file
|
||||
void init(bool onlyEssential); //uses standard config file
|
||||
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)
|
||||
@ -85,6 +85,6 @@ public:
|
||||
|
||||
extern DLL_LINKAGE LibClasses * VLC;
|
||||
|
||||
DLL_LINKAGE void preinitDLL(CConsoleHandler *Console);
|
||||
DLL_LINKAGE void loadDLLClasses();
|
||||
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false);
|
||||
DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false);
|
||||
|
||||
|
@ -17,8 +17,10 @@
|
||||
<Option run_host_application_in_terminal="1" />
|
||||
<Option createStaticLib="1" />
|
||||
<Compiler>
|
||||
<Add option="-g" />
|
||||
<Add option="-Og" />
|
||||
<Add option="-g" />
|
||||
<Add directory="$(#zlib.include)" />
|
||||
<Add directory="lib/" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lws2_32" />
|
||||
@ -33,6 +35,8 @@
|
||||
<Add option="-liconv" />
|
||||
<Add option="-ldbghelp" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
<Add directory="$(#sdl2.lib)" />
|
||||
<Add directory="$(#zlib.lib)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Release-win32">
|
||||
@ -45,6 +49,8 @@
|
||||
<Compiler>
|
||||
<Add option="-fomit-frame-pointer" />
|
||||
<Add option="-O2" />
|
||||
<Add directory="$(#zlib.include)" />
|
||||
<Add directory="lib/" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-s" />
|
||||
@ -59,6 +65,8 @@
|
||||
<Add option="-lboost_date_time$(#boost.libsuffix)" />
|
||||
<Add option="-liconv" />
|
||||
<Add directory="$(#boost.lib32)" />
|
||||
<Add directory="$(#sdl2.lib)" />
|
||||
<Add directory="$(#zlib.lib)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
<Target title="Debug-win64">
|
||||
@ -74,6 +82,7 @@
|
||||
<Add option="-Og" />
|
||||
<Add option="-g" />
|
||||
<Add directory="$(#zlib64.include)" />
|
||||
<Add directory="lib/" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add option="-lws2_32" />
|
||||
@ -87,8 +96,8 @@
|
||||
<Add option="-lboost_date_time$(#boost.libsuffix)" />
|
||||
<Add option="-liconv" />
|
||||
<Add option="-ldbghelp" />
|
||||
<Add directory="$(#sdl2.lib64)" />
|
||||
<Add directory="$(#boost.lib64)" />
|
||||
<Add directory="$(#sdl2.lib64)" />
|
||||
<Add directory="$(#zlib64.lib)" />
|
||||
</Linker>
|
||||
</Target>
|
||||
@ -113,17 +122,15 @@
|
||||
<Add option="-DVCMI_NO_EXTRA_VERSION" />
|
||||
<Add directory="." />
|
||||
<Add directory="$(#sdl2.include)" />
|
||||
<Add directory="$(#zlib.include)" />
|
||||
<Add directory="../include" />
|
||||
</Compiler>
|
||||
<Linker>
|
||||
<Add directory="../" />
|
||||
<Add directory="$(#sdl2.lib)" />
|
||||
<Add directory="$(#zlib.lib)" />
|
||||
</Linker>
|
||||
<Unit filename="../Global.h" />
|
||||
<Unit filename="../Version.h" />
|
||||
<Unit filename="../include/vstd/CLoggerBase.h" />
|
||||
<Unit filename="../include/vstd/RNG.h" />
|
||||
<Unit filename="AI_Base.h" />
|
||||
<Unit filename="CArtHandler.cpp" />
|
||||
<Unit filename="CArtHandler.h" />
|
||||
@ -216,6 +223,8 @@
|
||||
<Unit filename="battle/BattleHex.h" />
|
||||
<Unit filename="battle/BattleInfo.cpp" />
|
||||
<Unit filename="battle/BattleInfo.h" />
|
||||
<Unit filename="battle/BattleProxy.cpp" />
|
||||
<Unit filename="battle/BattleProxy.h" />
|
||||
<Unit filename="battle/CBattleInfoCallback.cpp" />
|
||||
<Unit filename="battle/CBattleInfoCallback.h" />
|
||||
<Unit filename="battle/CBattleInfoEssentials.cpp" />
|
||||
@ -226,12 +235,21 @@
|
||||
<Unit filename="battle/CObstacleInstance.h" />
|
||||
<Unit filename="battle/CPlayerBattleCallback.cpp" />
|
||||
<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.h" />
|
||||
<Unit filename="battle/SideInBattle.cpp" />
|
||||
<Unit filename="battle/SideInBattle.h" />
|
||||
<Unit filename="battle/SiegeInfo.cpp" />
|
||||
<Unit filename="battle/SiegeInfo.h" />
|
||||
<Unit filename="battle/Unit.cpp" />
|
||||
<Unit filename="battle/Unit.h" />
|
||||
<Unit filename="filesystem/AdapterLoaders.cpp" />
|
||||
<Unit filename="filesystem/AdapterLoaders.h" />
|
||||
<Unit filename="filesystem/CArchiveLoader.cpp" />
|
||||
@ -362,21 +380,55 @@
|
||||
<Unit filename="serializer/JsonSerializeFormat.h" />
|
||||
<Unit filename="serializer/JsonSerializer.cpp" />
|
||||
<Unit filename="serializer/JsonSerializer.h" />
|
||||
<Unit filename="serializer/JsonTreeSerializer.h" />
|
||||
<Unit filename="spells/AdventureSpellMechanics.cpp" />
|
||||
<Unit filename="spells/AdventureSpellMechanics.h" />
|
||||
<Unit filename="spells/BattleSpellMechanics.cpp" />
|
||||
<Unit filename="spells/BattleSpellMechanics.h" />
|
||||
<Unit filename="spells/CDefaultSpellMechanics.cpp" />
|
||||
<Unit filename="spells/CDefaultSpellMechanics.h" />
|
||||
<Unit filename="spells/CSpellHandler.cpp" />
|
||||
<Unit filename="spells/CSpellHandler.h" />
|
||||
<Unit filename="spells/CreatureSpellMechanics.cpp" />
|
||||
<Unit filename="spells/CreatureSpellMechanics.h" />
|
||||
<Unit filename="spells/ISpellMechanics.cpp" />
|
||||
<Unit filename="spells/ISpellMechanics.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.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" />
|
||||
<Extensions>
|
||||
<code_completion />
|
||||
|
@ -222,7 +222,18 @@
|
||||
<ClCompile Include="spells\BattleSpellMechanics.cpp" />
|
||||
<ClCompile Include="spells\CreatureSpellMechanics.cpp" />
|
||||
<ClCompile Include="spells\CDefaultSpellMechanics.cpp" />
|
||||
<ClCompile Include="spells\Problem.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\CArchiveLoader.cpp" />
|
||||
<ClCompile Include="filesystem\CBinaryReader.cpp" />
|
||||
@ -429,7 +440,18 @@
|
||||
<ClInclude Include="spells\ISpellMechanics.h" />
|
||||
<ClInclude Include="spells\Magic.h" />
|
||||
<ClInclude Include="spells\SpellMechanics.h" />
|
||||
<ClInclude Include="spells\Problem.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="StdInc.h" />
|
||||
<ClInclude Include="StringConstants.h" />
|
||||
|
@ -9,29 +9,31 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "AccessibilityInfo.h"
|
||||
#include "../CStack.h"
|
||||
#include "Unit.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
|
||||
{
|
||||
// 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
|
||||
if(!hex.isValid())
|
||||
auto otherHex = battle::Unit::occupiedHex(tile, doubleWide, side);
|
||||
if(!otherHex.isValid())
|
||||
return false;
|
||||
// If we're no defender which step on gate and the hex isn't accessible, then the tile
|
||||
// isn't accessible
|
||||
else if(at(hex) != EAccessibility::ACCESSIBLE &&
|
||||
!(at(hex) == EAccessibility::GATE && side == BattleSide::DEFENDER))
|
||||
{
|
||||
if(at(otherHex) != EAccessibility::ACCESSIBLE && !(at(otherHex) == EAccessibility::GATE && side == BattleSide::DEFENDER))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -11,7 +11,10 @@
|
||||
#include "BattleHex.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.
|
||||
enum class EAccessibility
|
||||
@ -30,6 +33,6 @@ typedef std::array<EAccessibility, GameConstants::BFIELD_SIZE> TAccessibilityArr
|
||||
|
||||
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
|
||||
};
|
||||
|
@ -10,77 +10,77 @@
|
||||
|
||||
#include "StdInc.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():
|
||||
side(-1),
|
||||
stackNumber(-1),
|
||||
actionType(INVALID),
|
||||
destinationTile(-1),
|
||||
additionalInfo(-1),
|
||||
selectedStack(-1)
|
||||
actionType(EActionType::INVALID),
|
||||
actionSubtype(-1)
|
||||
{
|
||||
}
|
||||
|
||||
BattleAction BattleAction::makeHeal(const CStack * healer, const CStack * healed)
|
||||
BattleAction BattleAction::makeHeal(const battle::Unit * healer, const battle::Unit * healed)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = healer->side;
|
||||
ba.actionType = STACK_HEAL;
|
||||
ba.stackNumber = healer->ID;
|
||||
ba.destinationTile = healed->position;
|
||||
ba.side = healer->unitSide();
|
||||
ba.actionType = EActionType::STACK_HEAL;
|
||||
ba.stackNumber = healer->unitId();
|
||||
ba.aimToUnit(healed);
|
||||
return ba;
|
||||
}
|
||||
|
||||
BattleAction BattleAction::makeDefend(const CStack * stack)
|
||||
BattleAction BattleAction::makeDefend(const battle::Unit * stack)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = stack->side;
|
||||
ba.actionType = DEFEND;
|
||||
ba.stackNumber = stack->ID;
|
||||
ba.side = stack->unitSide();
|
||||
ba.actionType = EActionType::DEFEND;
|
||||
ba.stackNumber = stack->unitId();
|
||||
return ba;
|
||||
}
|
||||
|
||||
|
||||
BattleAction BattleAction::makeMeleeAttack(const CStack * stack, const CStack * attacked, BattleHex attackFrom)
|
||||
BattleAction BattleAction::makeMeleeAttack(const battle::Unit * stack, BattleHex destination, BattleHex attackFrom, bool returnAfterAttack)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = stack->side;
|
||||
ba.actionType = WALK_AND_ATTACK;
|
||||
ba.stackNumber = stack->ID;
|
||||
ba.destinationTile = attackFrom;
|
||||
ba.additionalInfo = attacked->position;
|
||||
return ba;
|
||||
|
||||
}
|
||||
BattleAction BattleAction::makeWait(const CStack * stack)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = stack->side;
|
||||
ba.actionType = WAIT;
|
||||
ba.stackNumber = stack->ID;
|
||||
ba.side = stack->unitSide(); //FIXME: will it fail if stack mind controlled?
|
||||
ba.actionType = EActionType::WALK_AND_ATTACK;
|
||||
ba.stackNumber = stack->unitId();
|
||||
ba.aimToHex(attackFrom);
|
||||
ba.aimToHex(destination);
|
||||
if(returnAfterAttack && stack->hasBonusOfType(Bonus::RETURN_AFTER_STRIKE))
|
||||
ba.aimToHex(stack->getPosition());
|
||||
return ba;
|
||||
}
|
||||
|
||||
BattleAction BattleAction::makeShotAttack(const CStack * shooter, const CStack * target)
|
||||
BattleAction BattleAction::makeWait(const battle::Unit * stack)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = shooter->side;
|
||||
ba.actionType = SHOOT;
|
||||
ba.stackNumber = shooter->ID;
|
||||
ba.destinationTile = target->position;
|
||||
ba.side = stack->unitSide();
|
||||
ba.actionType = EActionType::WAIT;
|
||||
ba.stackNumber = stack->unitId();
|
||||
return ba;
|
||||
}
|
||||
|
||||
BattleAction BattleAction::makeMove(const CStack * stack, BattleHex dest)
|
||||
BattleAction BattleAction::makeShotAttack(const battle::Unit * shooter, const battle::Unit * target)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = stack->side;
|
||||
ba.actionType = WALK;
|
||||
ba.stackNumber = stack->ID;
|
||||
ba.destinationTile = dest;
|
||||
ba.side = shooter->unitSide();
|
||||
ba.actionType = EActionType::SHOOT;
|
||||
ba.stackNumber = shooter->unitId();
|
||||
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;
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ BattleAction BattleAction::makeEndOFTacticPhase(ui8 side)
|
||||
{
|
||||
BattleAction ba;
|
||||
ba.side = side;
|
||||
ba.actionType = END_TACTIC_PHASE;
|
||||
ba.actionType = EActionType::END_TACTIC_PHASE;
|
||||
return ba;
|
||||
}
|
||||
|
||||
@ -97,11 +97,74 @@ std::string BattleAction::toString() const
|
||||
std::stringstream actionTypeStream;
|
||||
actionTypeStream << actionType;
|
||||
|
||||
boost::format fmt("{BattleAction: side '%d', stackNumber '%d', actionType '%s', destinationTile '%s', additionalInfo '%d', selectedStack '%d'}");
|
||||
fmt % static_cast<int>(side) % stackNumber % actionTypeStream.str() % destinationTile % additionalInfo % selectedStack;
|
||||
std::stringstream targetStream;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
os << ba.toString();
|
||||
|
@ -8,42 +8,67 @@
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include "BattleHex.h"
|
||||
#include "Destination.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
|
||||
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,
|
||||
Battle::ActionType 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
|
||||
EActionType actionType; //use ActionType enum for values
|
||||
|
||||
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 & stackNumber;
|
||||
h & actionType;
|
||||
h & destinationTile;
|
||||
h & additionalInfo;
|
||||
h & selectedStack;
|
||||
h & actionSubtype;
|
||||
h & target;
|
||||
}
|
||||
private:
|
||||
|
||||
BattleAction();
|
||||
struct DestinationInfo
|
||||
{
|
||||
int32_t unitValue;
|
||||
BattleHex hexValue;
|
||||
|
||||
static BattleAction makeHeal(const CStack * healer, const CStack * healed);
|
||||
static BattleAction makeDefend(const CStack * stack);
|
||||
static BattleAction makeWait(const CStack * stack);
|
||||
static BattleAction makeMeleeAttack(const CStack * stack, const CStack * attacked, BattleHex attackFrom = BattleHex::INVALID);
|
||||
static BattleAction makeShotAttack(const CStack * shooter, const CStack * target);
|
||||
static BattleAction makeMove(const CStack * stack, BattleHex dest);
|
||||
static BattleAction makeEndOFTacticPhase(ui8 side);
|
||||
template <typename Handler> void serialize(Handler & h, const int version)
|
||||
{
|
||||
h & unitValue;
|
||||
h & hexValue;
|
||||
}
|
||||
};
|
||||
|
||||
std::string toString() const;
|
||||
std::vector<DestinationInfo> target;
|
||||
};
|
||||
|
||||
DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleAction & ba); //todo: remove
|
||||
|
@ -9,40 +9,29 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleAttackInfo.h"
|
||||
#include "CUnitState.h"
|
||||
|
||||
|
||||
BattleAttackInfo::BattleAttackInfo(const CStack * Attacker, const CStack * Defender, bool Shooting):
|
||||
attackerHealth(Attacker->health), defenderHealth(Defender->health)
|
||||
BattleAttackInfo::BattleAttackInfo(const battle::Unit * Attacker, const battle::Unit * Defender, bool Shooting)
|
||||
: attacker(Attacker),
|
||||
defender(Defender)
|
||||
{
|
||||
attacker = Attacker;
|
||||
defender = Defender;
|
||||
|
||||
attackerBonuses = Attacker;
|
||||
defenderBonuses = Defender;
|
||||
|
||||
attackerPosition = Attacker->position;
|
||||
defenderPosition = Defender->position;
|
||||
|
||||
shooting = Shooting;
|
||||
chargedFields = 0;
|
||||
|
||||
luckyHit = false;
|
||||
unluckyHit = false;
|
||||
deathBlow = false;
|
||||
ballistaDoubleDamage = false;
|
||||
additiveBonus = 0.0;
|
||||
multBonus = 1.0;
|
||||
}
|
||||
|
||||
BattleAttackInfo BattleAttackInfo::reverse() const
|
||||
{
|
||||
BattleAttackInfo ret = *this;
|
||||
|
||||
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.chargedFields = 0;
|
||||
ret.luckyHit = ret.ballistaDoubleDamage = ret.deathBlow = false;
|
||||
|
||||
ret.additiveBonus = 0.0;
|
||||
ret.multBonus = 1.0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -8,27 +8,24 @@
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include "BattleHex.h"
|
||||
#include "../CStack.h"
|
||||
|
||||
class IBonusBearer;
|
||||
namespace battle
|
||||
{
|
||||
class Unit;
|
||||
class CUnitState;
|
||||
}
|
||||
|
||||
struct DLL_LINKAGE BattleAttackInfo
|
||||
{
|
||||
const IBonusBearer *attackerBonuses, *defenderBonuses;
|
||||
const CStack *attacker, *defender;
|
||||
BattleHex attackerPosition, defenderPosition;
|
||||
|
||||
CHealth attackerHealth, defenderHealth;
|
||||
const battle::Unit * attacker;
|
||||
const battle::Unit * defender;
|
||||
|
||||
bool shooting;
|
||||
int chargedFields;
|
||||
|
||||
bool luckyHit;
|
||||
bool unluckyHit;
|
||||
bool deathBlow;
|
||||
bool ballistaDoubleDamage;
|
||||
double additiveBonus;
|
||||
double multBonus;
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -9,7 +9,6 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleHex.h"
|
||||
#include "../GameConstants.h"
|
||||
|
||||
BattleHex::BattleHex() : hex(INVALID) {}
|
||||
|
||||
@ -53,7 +52,11 @@ void BattleHex::setY(si16 y)
|
||||
void BattleHex::setXY(si16 x, si16 y, bool 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;
|
||||
}
|
||||
|
||||
@ -129,6 +132,7 @@ BattleHex BattleHex::operator+(BattleHex::EDir dir) const
|
||||
std::vector<BattleHex> BattleHex::neighbouringTiles() const
|
||||
{
|
||||
std::vector<BattleHex> ret;
|
||||
ret.reserve(6);
|
||||
for(EDir dir = EDir(0); dir <= EDir(5); dir = EDir(dir+1))
|
||||
checkAndPush(cloneInDirection(dir, false), 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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -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;
|
||||
|
||||
// for battle stacks' positions
|
||||
@ -67,6 +74,11 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
|
||||
{
|
||||
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);
|
||||
|
@ -16,48 +16,7 @@
|
||||
#include "../mapObjects/CGTownInstance.h"
|
||||
#include "../CGeneralTextHandler.h"
|
||||
|
||||
const CStack * BattleInfo::getNextStack() const
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
///BattleInfo
|
||||
std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, BattleHex dest, const CStack * 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]);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
assert((owner >= PlayerColor::PLAYER_LIMIT) ||
|
||||
(base.armyObj && base.armyObj->tempOwner == owner));
|
||||
|
||||
auto ret = new CStack(&base, owner, stackID, side, slot);
|
||||
ret->position = getAvaliableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found?
|
||||
ret->state.insert(EBattleStackState::ALIVE); //alive state indication
|
||||
auto ret = new CStack(&base, owner, id, side, slot);
|
||||
ret->initialPosition = getAvaliableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found?
|
||||
stacks.push_back(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;
|
||||
auto ret = new CStack(&base, owner, stackID, side, slot);
|
||||
ret->position = position;
|
||||
ret->state.insert(EBattleStackState::ALIVE); //alive state indication
|
||||
auto ret = new CStack(&base, owner, id, side, slot);
|
||||
ret->initialPosition = position;
|
||||
stacks.push_back(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -436,7 +368,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
|
||||
CreatureID cre = warMachineArt->artType->warMachine;
|
||||
|
||||
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())
|
||||
pos += side ? BattleHex::LEFT : BattleHex::RIGHT;
|
||||
|
||||
CStack * stack = curB->generateNewStack(*i->second, side, i->first, pos);
|
||||
stacks.push_back(stack);
|
||||
curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,9 +422,7 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
|
||||
{
|
||||
if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive)
|
||||
{
|
||||
CStack * stack = curB->generateNewStack (*heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER,
|
||||
creatureBank ? commanderBank[i] : commanderField[i]);
|
||||
stacks.push_back(stack);
|
||||
curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, creatureBank ? commanderBank[i] : commanderField[i]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -501,16 +430,14 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
|
||||
if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL)
|
||||
{
|
||||
// keep tower
|
||||
CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2);
|
||||
stacks.push_back(stack);
|
||||
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2);
|
||||
|
||||
if (curB->town->fortLevel() >= CGTownInstance::CASTLE)
|
||||
{
|
||||
// lower tower + upper tower
|
||||
CStack * stack = curB->generateNewStack(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);
|
||||
stacks.push_back(stack);
|
||||
curB->generateNewStack(curB->nextUnitId(), 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, -3);
|
||||
}
|
||||
|
||||
//moat
|
||||
@ -523,10 +450,6 @@ BattleInfo * BattleInfo::setupBattle(int3 tile, ETerrainType terrain, BFieldType
|
||||
|
||||
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 good = std::make_shared<CreatureAlignmentLimiter>(EAlignment::GOOD);
|
||||
auto evil = std::make_shared<CreatureAlignmentLimiter>(EAlignment::EVIL);
|
||||
@ -660,11 +583,6 @@ const CGHeroInstance * BattleInfo::getHero(PlayerColor player) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PlayerColor BattleInfo::theOtherPlayer(PlayerColor player) const
|
||||
{
|
||||
return sides[!whatSide(player)].color;
|
||||
}
|
||||
|
||||
ui8 BattleInfo::whatSide(PlayerColor player) const
|
||||
{
|
||||
for(int i = 0; i < sides.size(); i++)
|
||||
@ -675,29 +593,6 @@ ui8 BattleInfo::whatSide(PlayerColor player) const
|
||||
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)
|
||||
{
|
||||
static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap =
|
||||
@ -728,7 +623,7 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
|
||||
}
|
||||
|
||||
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),
|
||||
tacticsSide(0), tacticDistance(0)
|
||||
{
|
||||
@ -736,6 +631,416 @@ BattleInfo::BattleInfo()
|
||||
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
|
||||
{
|
||||
return const_cast<CArmedInstance*>(CBattleInfoEssentials::battleGetArmyObject(side));
|
||||
@ -746,30 +1051,29 @@ CGHeroInstance * BattleInfo::battleGetFightingHero(ui8 side) const
|
||||
return const_cast<CGHeroInstance*>(CBattleInfoEssentials::battleGetFightingHero(side));
|
||||
}
|
||||
|
||||
|
||||
bool CMP_stack::operator()(const CStack* a, const CStack* b)
|
||||
bool CMP_stack::operator()(const battle::Unit * a, const battle::Unit * b)
|
||||
{
|
||||
switch(phase)
|
||||
{
|
||||
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
|
||||
{
|
||||
int as = a->Speed(turn), bs = b->Speed(turn);
|
||||
int as = a->getInitiative(turn), bs = b->getInitiative(turn);
|
||||
if(as != bs)
|
||||
return as > bs;
|
||||
else
|
||||
return a->slot < b->slot;
|
||||
return a->unitSlot() < b->unitSlot(); //FIXME: what about summoned stacks?
|
||||
}
|
||||
case 2: //fastest last, upper slot first
|
||||
//TODO: should be replaced with order of receiving morale!
|
||||
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)
|
||||
return as < bs;
|
||||
else
|
||||
return a->slot < b->slot;
|
||||
return a->unitSlot() < b->unitSlot();
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user