1
0
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:
AlexVinS 2017-07-20 07:08:49 +03:00
parent ff2d01a03d
commit 0b70baa95e
256 changed files with 20904 additions and 7964 deletions

View File

@ -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;

View File

@ -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);
};

View File

@ -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" />

View File

@ -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()

View File

@ -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
};

View File

@ -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
)

View File

@ -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();
}

View File

@ -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;
};

View File

@ -0,0 +1,21 @@
/*
* PossibleSpellcast.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "PossibleSpellcast.h"
PossibleSpellcast::PossibleSpellcast()
: spell(nullptr),
dest(),
value(0)
{
}
PossibleSpellcast::~PossibleSpellcast() = default;

View File

@ -0,0 +1,29 @@
/*
* PossibleSpellcast.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/battle/Destination.h"
#include "../../lib/spells/Magic.h"
class CSpell;
class PossibleSpellcast
{
public:
using ValueMap = std::map<uint32_t, int64_t>;
const CSpell * spell;
spells::Target dest;
int64_t value;
PossibleSpellcast();
virtual ~PossibleSpellcast();
};

View File

@ -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())

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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 );

View File

@ -1359,7 +1359,7 @@ static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingI
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
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;

View File

@ -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;
}

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

View File

@ -0,0 +1,8 @@
{
"basepath": "vcmi/battleQueue/",
"images" :
[
{ "frame" : 0, "file" : "defendBig"},
{ "frame" : 1, "file" : "waitBig"}
]
}

View File

@ -0,0 +1,8 @@
{
"basepath": "vcmi/battleQueue/",
"images" :
[
{ "frame" : 0, "file" : "defendSmall"},
{ "frame" : 1, "file" : "waitSmall"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

View File

@ -1,356 +0,0 @@
/*
* CDefHandler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "SDL.h"
#include "CDefHandler.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/VCMI_Lib.h"
#include "CBitmapHandler.h"
#include "gui/SDL_Extensions.h"
#ifdef unused
static long long pow(long long a, int b)
{
if (!b) return 1;
long c = a;
while (--b)
a*=c;
return a;
}
#endif
CDefHandler::CDefHandler()
{
}
CDefHandler::~CDefHandler()
{
for (auto & elem : ourImages)
{
if (elem.bitmap)
{
SDL_FreeSurface(elem.bitmap);
elem.bitmap=nullptr;
}
}
}
void CDefHandler::openFromMemory(ui8 *table, const std::string & name)
{
SDL_Color palette[256];
SDefEntry &de = * reinterpret_cast<SDefEntry *>(table);
ui8 *p;
defName = name;
DEFType = read_le_u32(&de.DEFType);
width = read_le_u32(&de.width);
height = read_le_u32(&de.height);
ui32 totalBlocks = read_le_u32(&de.totalBlocks);
for (ui32 it=0;it<256;it++)
{
palette[it].r = de.palette[it].R;
palette[it].g = de.palette[it].G;
palette[it].b = de.palette[it].B;
palette[it].a = SDL_ALPHA_OPAQUE;
}
// The SDefEntryBlock starts just after the SDefEntry
p = reinterpret_cast<ui8 *>(&de);
p += sizeof(de);
int totalEntries=0;
for (ui32 z=0; z<totalBlocks; z++)
{
SDefEntryBlock &block = * reinterpret_cast<SDefEntryBlock *>(p);
ui32 totalInBlock;
totalInBlock = read_le_u32(&block.totalInBlock);
for (ui32 j=SEntries.size(); j<totalEntries+totalInBlock; j++)
SEntries.push_back(SEntry());
p = block.data;
for (ui32 j=0; j<totalInBlock; j++)
{
char Buffer[13];
memcpy(Buffer, p, 12);
Buffer[12] = 0;
SEntries[totalEntries+j].name=Buffer;
p += 13;
}
for (ui32 j=0; j<totalInBlock; j++)
{
SEntries[totalEntries+j].offset = read_le_u32(p);
p += 4;
}
//totalEntries+=totalInBlock;
for(ui32 hh=0; hh<totalInBlock; ++hh)
{
SEntries[totalEntries].group = z;
++totalEntries;
}
}
for(auto & elem : SEntries)
{
elem.name = elem.name.substr(0, elem.name.find('.')+4);
}
//RWEntries = new ui32[height];
for(ui32 i=0; i < SEntries.size(); ++i)
{
Cimage nimg;
nimg.bitmap = getSprite(i, table, palette);
nimg.imName = SEntries[i].name;
nimg.groupNumber = SEntries[i].group;
ourImages.push_back(nimg);
}
}
SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const
{
SDL_Surface * ret=nullptr;
ui32 BaseOffset,
SpriteWidth, SpriteHeight, //format of sprite
TotalRowLength, // length of read segment
add, FullHeight,FullWidth,
RowAdd,
defType2;
int LeftMargin, RightMargin, TopMargin, BottomMargin;
ui8 SegmentType;
BaseOffset = SEntries[SIndex].offset;
SSpriteDef sd = * reinterpret_cast<const SSpriteDef *>(FDef + BaseOffset);
defType2 = read_le_u32(&sd.defType2);
FullWidth = read_le_u32(&sd.FullWidth);
FullHeight = read_le_u32(&sd.FullHeight);
SpriteWidth = read_le_u32(&sd.SpriteWidth);
SpriteHeight = read_le_u32(&sd.SpriteHeight);
LeftMargin = read_le_u32(&sd.LeftMargin);
TopMargin = read_le_u32(&sd.TopMargin);
RightMargin = FullWidth - SpriteWidth - LeftMargin;
BottomMargin = FullHeight - SpriteHeight - TopMargin;
//if(LeftMargin + RightMargin < 0)
// SpriteWidth += LeftMargin + RightMargin; //ugly construction... TODO: check how to do it nicer
if(LeftMargin<0)
SpriteWidth+=LeftMargin;
if(RightMargin<0)
SpriteWidth+=RightMargin;
// Note: this looks bogus because we allocate only FullWidth, not FullWidth+add
add = 4 - FullWidth%4;
if (add==4)
add=0;
ret = SDL_CreateRGBSurface(SDL_SWSURFACE, FullWidth, FullHeight, 8, 0, 0, 0, 0);
if(nullptr == ret)
{
logGlobal->error("%s: Unable to create surface", __FUNCTION__);
logGlobal->error("%dX%d", FullWidth, FullHeight);
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
BaseOffset += sizeof(SSpriteDef);
int BaseOffsetor = BaseOffset;
SDL_Palette * p = SDL_AllocPalette(256);
SDL_SetPaletteColors(p, palette, 0, 256);
SDL_SetSurfacePalette(ret, p);
SDL_FreePalette(p);
int ftcp=0;
// If there's a margin anywhere, just blank out the whole surface.
if (TopMargin > 0 || BottomMargin > 0 || LeftMargin > 0 || RightMargin > 0) {
memset( reinterpret_cast<char*>(ret->pixels), 0, FullHeight*FullWidth);
}
// Skip top margin
if (TopMargin > 0)
ftcp += TopMargin*(FullWidth+add);
switch(defType2)
{
case 0:
{
for (ui32 i=0;i<SpriteHeight;i++)
{
if (LeftMargin>0)
ftcp += LeftMargin;
memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, &FDef[BaseOffset], SpriteWidth);
ftcp += SpriteWidth;
BaseOffset += SpriteWidth;
if (RightMargin>0)
ftcp += RightMargin;
}
}
break;
case 1:
{
const ui32 * RWEntriesLoc = reinterpret_cast<const ui32 *>(FDef+BaseOffset);
BaseOffset += sizeof(int) * SpriteHeight;
for (ui32 i=0;i<SpriteHeight;i++)
{
BaseOffset=BaseOffsetor + read_le_u32(RWEntriesLoc + i);
if (LeftMargin>0)
ftcp += LeftMargin;
TotalRowLength=0;
do
{
ui32 SegmentLength;
SegmentType=FDef[BaseOffset++];
SegmentLength=FDef[BaseOffset++] + 1;
if (SegmentType==0xFF)
{
memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, FDef + BaseOffset, SegmentLength);
BaseOffset+=SegmentLength;
}
else
{
memset(reinterpret_cast<char*>(ret->pixels)+ftcp, SegmentType, SegmentLength);
}
ftcp += SegmentLength;
TotalRowLength += SegmentLength;
}while(TotalRowLength<SpriteWidth);
RowAdd=SpriteWidth-TotalRowLength;
if (RightMargin>0)
ftcp += RightMargin;
if (add>0)
ftcp += add+RowAdd;
}
}
break;
case 2:
{
BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor);
for (ui32 i=0;i<SpriteHeight;i++)
{
//BaseOffset = BaseOffsetor+RWEntries[i];
if (LeftMargin>0)
ftcp += LeftMargin;
TotalRowLength=0;
do
{
SegmentType=FDef[BaseOffset++];
ui8 code = SegmentType / 32;
ui8 value = (SegmentType & 31) + 1;
if(code==7)
{
memcpy(reinterpret_cast<char*>(ret->pixels)+ftcp, &FDef[BaseOffset], value);
ftcp += value;
BaseOffset += value;
}
else
{
memset(reinterpret_cast<char*>(ret->pixels)+ftcp, code, value);
ftcp += value;
}
TotalRowLength+=value;
} while(TotalRowLength<SpriteWidth);
if (RightMargin>0)
ftcp += RightMargin;
RowAdd=SpriteWidth-TotalRowLength;
if (add>0)
ftcp += add+RowAdd;
}
}
break;
case 3:
{
for (ui32 i=0;i<SpriteHeight;i++)
{
BaseOffset = BaseOffsetor + read_le_u16(FDef + BaseOffsetor+i*2*(SpriteWidth/32));
if (LeftMargin>0)
ftcp += LeftMargin;
TotalRowLength=0;
do
{
SegmentType=FDef[BaseOffset++];
ui8 code = SegmentType / 32;
ui8 value = (SegmentType & 31) + 1;
int len = std::min<ui32>(value, SpriteWidth - TotalRowLength) - std::max(0, -LeftMargin);
vstd::amax(len, 0);
if(code==7)
{
memcpy((ui8*)ret->pixels + ftcp, FDef + BaseOffset, len);
ftcp += len;
BaseOffset += len;
}
else
{
memset((ui8*)ret->pixels + ftcp, code, len);
ftcp += len;
}
TotalRowLength+=( LeftMargin>=0 ? value : value+LeftMargin );
}while(TotalRowLength<SpriteWidth);
if (RightMargin>0)
ftcp += RightMargin;
RowAdd=SpriteWidth-TotalRowLength;
if (add>0)
ftcp += add+RowAdd;
}
}
break;
default:
throw std::runtime_error("Unknown sprite format.");
break;
}
SDL_Color ttcol = ret->format->palette->colors[0];
Uint32 keycol = SDL_MapRGBA(ret->format, ttcol.r, ttcol.b, ttcol.g, ttcol.a);
SDL_SetColorKey(ret, SDL_TRUE, keycol);
return ret;
}
CDefHandler * CDefHandler::giveDef(const std::string & defName)
{
ResourceID resID(std::string("SPRITES/") + defName, EResType::ANIMATION);
auto data = CResourceHandler::get()->load(resID)->readAll().first;
if(!data)
throw std::runtime_error("bad def name!");
auto nh = new CDefHandler();
nh->openFromMemory(data.get(), defName);
return nh;
}

View File

@ -1,94 +0,0 @@
/*
* CDefHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/vcmi_endian.h"
struct SDL_Surface;
struct SDL_Color;
struct Cimage
{
int groupNumber;
std::string imName; //name without extension
SDL_Surface * bitmap;
};
// Def entry in file. Integer fields are all little endian and will
// need to be converted.
struct SDefEntryBlock
{
ui32 unknown1;
ui32 totalInBlock;
ui32 unknown2;
ui32 unknown3;
ui8 data[0];
} PACKED_STRUCT;
// Def entry in file. Integer fields are all little endian and will
// need to be converted.
struct SDefEntry
{
ui32 DEFType;
ui32 width;
ui32 height;
ui32 totalBlocks;
struct {
ui8 R;
ui8 G;
ui8 B;
} palette[256];
// SDefEntry is followed by a series of SDefEntryBlock
// This is commented out because VC++ doesn't accept C99 syntax.
//struct SDefEntryBlock blocks[];
} PACKED_STRUCT;
// Def entry in file. Integer fields are all little endian and will
// need to be converted.
struct SSpriteDef
{
ui32 prSize;
ui32 defType2;
ui32 FullWidth;
ui32 FullHeight;
ui32 SpriteWidth;
ui32 SpriteHeight;
ui32 LeftMargin;
ui32 TopMargin;
} PACKED_STRUCT;
class CDefHandler
{
private:
ui32 DEFType;
struct SEntry
{
std::string name;
int offset;
int group;
} ;
std::vector<SEntry> SEntries ;
void openFromMemory(ui8 * table, const std::string & name);
SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const;
public:
int width, height; //width and height
std::string defName;
std::vector<Cimage> ourImages;
CDefHandler();
~CDefHandler();
static CDefHandler * giveDef(const std::string & defName);
};

View File

@ -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";

View File

@ -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

View File

@ -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)
{

View File

@ -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;

View File

@ -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();

View File

@ -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);
}

View File

@ -26,7 +26,7 @@ class CGameState;
class CGameInterface;
class CConnection;
class CCallback;
struct BattleAction;
class BattleAction;
struct SharedMemory;
class CClient;
class CScriptingModule;

View File

@ -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;

View File

@ -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" />

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
};

View File

@ -1641,11 +1641,11 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
new CLabel(78, 11, FONT_SMALL, CENTER, Colors::WHITE, getMyCreature()->namePl);
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;

View File

@ -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());

View File

@ -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)
{

View File

@ -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

View File

@ -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

View File

@ -16,6 +16,7 @@
#include "../CreatureCostBox.h"
#include "QuickRecruitmentWindow.h"
#include "../gui/CGuiHandler.h"
#include "../../lib/CCreatureHandler.h"
void CreaturePurchaseCard::initButtons()
{

View File

@ -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"

View File

@ -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" ]
}
}
},

View File

@ -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":{

View File

@ -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"
}
}
}
}

View File

@ -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
}

View File

@ -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" : {

View File

@ -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
View File

@ -0,0 +1,58 @@
/*
* RNG.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
namespace vstd
{
typedef std::function<int64_t()> TRandI64;
typedef std::function<double()> TRand;
class DLL_LINKAGE RNG
{
public:
virtual ~RNG() = default;
virtual TRandI64 getInt64Range(int64_t lower, int64_t upper) = 0;
virtual TRand getDoubleRange(double lower, double upper) = 0;
};
}
namespace RandomGeneratorUtil
{
template<typename Container>
auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
{
assert(!container.empty());
return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
}
template<typename Container>
auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
{
assert(!container.empty());
return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
}
template<typename T>
void randomShuffle(std::vector<T> & container, vstd::RNG & rand)
{
int64_t n = (container.end() - container.begin());
for(int64_t i = n-1; i>0; --i)
{
std::swap(container.begin()[i],container.begin()[rand.getInt64Range(0, i)()]);
}
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -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));
}

View File

@ -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)]);
}
}
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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
};

View File

@ -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()
{

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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;
};

View File

@ -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){};
};

View File

@ -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;

View File

@ -305,7 +305,7 @@ namespace JsonDetail
return node.Bool();
}
};
} // namespace JsonDetail
}
template<typename Type>
Type JsonNode::convertTo() const

View File

@ -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){};

View File

@ -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;
}
};

View File

@ -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:

View File

@ -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

View File

@ -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);

View File

@ -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 />

View File

@ -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" />

View File

@ -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;
}

View File

@ -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
};

View File

@ -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();

View File

@ -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

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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();

View File

@ -20,6 +20,13 @@ namespace BattleSide
};
}
namespace GameConstants
{
const int BFIELD_WIDTH = 17;
const int BFIELD_HEIGHT = 11;
const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT;
}
typedef boost::optional<ui8> BattleSideOpt;
// 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);

View File

@ -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