1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Use spell mechanics clases in GameHandler

This commit is contained in:
AlexVinS 2014-11-25 18:16:14 +03:00
parent 6d4cd1c91b
commit c67887ae58
2 changed files with 49 additions and 505 deletions

View File

@ -59,6 +59,18 @@ extern bool end2;
bnr.round = gs->curB->round + 1;\
sendAndApply(&bnr);
class ServerSpellCastEnvironment: public SpellCastEnvironment
{
public:
ServerSpellCastEnvironment(CGameHandler * gh);
~ServerSpellCastEnvironment(){};
void sendAndApply(CPackForClient * info) const override;
CRandomGenerator & getRandomGenerator() const override;
void complain(const std::string & problem) const override;
private:
CGameHandler * gh;
};
CondSh<bool> battleMadeAction;
CondSh<BattleResult *> battleResult(nullptr);
template <typename T> class CApplyOnGH;
@ -3932,518 +3944,28 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
}
}
void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
void CGameHandler::handleSpellCasting(SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
{
const CSpell * spell = SpellID(spellID).toSpell();
ServerSpellCastEnvironment spellEnvironment(this);
//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
//It creates, sends and applies needed package.
auto placeObstacle = [&](BattleHex pos)
{
static int obstacleIdToGive = gs->curB->obstacles.size()
? (gs->curB->obstacles.back()->uniqueID+1)
: 0;
BattleSpellCastParameters parameters;
parameters.spellLvl = spellLvl;
parameters.destination = destination;
parameters.casterSide = casterSide;
parameters.casterColor = casterColor;
parameters.caster = caster;
parameters.secHero = secHero;
auto obstacle = make_shared<SpellCreatedObstacle>();
switch(spellID.toEnum()) // :/
{
case SpellID::QUICKSAND:
obstacle->obstacleType = CObstacleInstance::QUICKSAND;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
break;
case SpellID::LAND_MINE:
obstacle->obstacleType = CObstacleInstance::LAND_MINE;
obstacle->turnsRemaining = -1;
obstacle->visibleForAnotherSide = false;
break;
case SpellID::FIRE_WALL:
obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
break;
case SpellID::FORCE_FIELD:
obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
obstacle->turnsRemaining = 2;
obstacle->visibleForAnotherSide = true;
break;
default:
//this function cannot be used with spells that do not create obstacles
assert(0);
}
parameters.usedSpellPower = usedSpellPower;
parameters.mode = mode;
parameters.casterStack = stack;
parameters.selectedStack = gs->curB->battleGetStackByID(selectedStack, false);
obstacle->pos = pos;
obstacle->casterSide = casterSide;
obstacle->ID = spellID;
obstacle->spellLevel = spellLvl;
obstacle->casterSpellPower = usedSpellPower;
obstacle->uniqueID = obstacleIdToGive++;
BattleObstaclePlaced bop;
bop.obstacle = obstacle;
sendAndApply(&bop);
};
BattleSpellCast sc;
sc.side = casterSide;
sc.id = spellID;
sc.skill = spellLvl;
sc.tile = destination;
sc.dmgToDisplay = 0;
sc.castedByHero = (bool)caster;
sc.attackerType = (stack ? stack->type->idNumber : CreatureID(CreatureID::NONE));
sc.manaGained = 0;
sc.spellCost = 0;
if (caster) //calculate spell cost
{
sc.spellCost = gs->curB->battleGetSpellCost(spell, caster);
if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
{
int manaChannel = 0;
for(CStack * stack : gs->curB->stacks) //TODO: shouldn't bonus system handle it somehow?
{
if (stack->owner == secHero->tempOwner)
{
vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
}
}
sc.manaGained = (manaChannel * sc.spellCost) / 100;
}
}
//calculating affected creatures for all spells
//must be vector, as in Chain Lightning order matters
std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
auto creatures = spell->getAffectedStacks(gs->curB, mode, casterColor, spellLvl, destination, caster);
std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
for (auto cre : attackedCres)
{
sc.affectedCres.insert (cre->ID);
}
//checking if creatures resist
//resistance is applied only to negative spells
if(spell->isNegative())
{
for(auto s : attackedCres)
{
const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
if(gs->getRandomGenerator().nextInt(99) < prob)
{
sc.resisted.push_back(s->ID);
}
}
}
//calculating dmg to display
if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE)
{
sc.dmgToDisplay = usedSpellPower;
if (spellID == SpellID::DEATH_STARE)
vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
}
StacksInjured si;
//applying effects
if (spell->isOffensiveSpell())
{
int spellDamage = 0;
if (stack && mode != ECastingMode::MAGIC_MIRROR)
{
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
if (unitSpellPower)
sc.dmgToDisplay = spellDamage = stack->count * unitSpellPower; //TODO: handle immunities
else //Faerie Dragon
{
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
sc.dmgToDisplay = 0;
}
}
int chainLightningModifier = 0;
for(auto & attackedCre : attackedCres)
{
if(vstd::contains(sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
continue;
BattleStackAttacked bsa;
if ((destination > -1 && (attackedCre)->coversPos(destination)) || (spell->getLevelInfo(spellLvl).range == "X" || mode == ECastingMode::ENCHANTER_CASTING))
//display effect only upon primary target of area spell
//FIXME: if no stack is attacked, there is no animation and interface freezes
{
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = spell->mainEffectAnim;
}
if (spellDamage)
bsa.damageAmount = spellDamage >> chainLightningModifier;
else
bsa.damageAmount = spell->calculateDamage(caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier;
sc.dmgToDisplay += bsa.damageAmount;
bsa.stackAttacked = (attackedCre)->ID;
if (mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
bsa.attackerID = stack->ID;
else
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, gs->getRandomGenerator());
si.stacks.push_back(bsa);
if (spellID == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
}
}
if (spell->hasEffects())
{
int stackSpellPower = 0;
if (stack && mode != ECastingMode::MAGIC_MIRROR)
{
stackSpellPower = stack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
}
SetStackEffect sse;
Bonus pseudoBonus;
pseudoBonus.sid = spellID;
pseudoBonus.val = spellLvl;
pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
CStack::stackEffectToFeature(sse.effect, pseudoBonus);
if (spellID == SpellID::SHIELD || spellID == SpellID::AIR_SHIELD)
{
sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
}
if (spellID == SpellID::BIND && stack)//bind
{
sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
}
const Bonus * bonus = nullptr;
if (caster)
bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
//TODO does hero specialty should affects his stack casting spells?
si32 power = 0;
for(const CStack *affected : attackedCres)
{
if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell
continue;
sse.stacks.push_back(affected->ID);
//Apply hero specials - peculiar enchants
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
if (bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1: case 2:
power = 3;
break;
case 3: case 4:
power = 2;
break;
case 5: case 6:
power = 1;
break;
}
Bonus specialBonus(sse.effect.back());
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
specialBonus.sid = spellID;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
{
int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID.toEnum()) / tier;
Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
specialBonus.valType = Bonus::PERCENT_TO_ALL;
specialBonus.sid = spellID;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
}
}
if(!sse.stacks.empty())
sendAndApply(&sse);
}
if(spell->isHealingSpell())
{
int hpGained = 0;
if (stack)
{
int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
if (unitSpellPower)
hpGained = stack->count * unitSpellPower; //Archangel
else //Faerie Dragon-like effect - unused so far
usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
}
StacksHealedOrResurrected shr;
shr.lifeDrain = false;
shr.tentHealing = false;
for(auto & attackedCre : attackedCres)
{
StacksHealedOrResurrected::HealInfo hi;
hi.stackID = (attackedCre)->ID;
if (stack) //casted by creature
{
if (hpGained)
{
hi.healedHP = gs->curB->calculateHealedHP(hpGained, spell, attackedCre); //archangel
}
else
hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, attackedCre); //any typical spell (commander's cure or animate dead)
}
else
hi.healedHP = spell->calculateHealedHP(caster, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero
hi.lowLevelResurrection = spellLvl <= 1;
shr.healedStacks.push_back(hi);
}
if(!shr.healedStacks.empty())
sendAndApply(&shr);
if (spellID == SpellID::SACRIFICE) //remove victim
{
if (selectedStack == gs->curB->activeStack)
//set another active stack than the one removed, or bad things will happen
//TODO: make that part of BattleStacksRemoved? what about client update?
{
//makeStackDoNothing(gs->curB->getStack (selectedStack));
BattleSetActiveStack sas;
//std::vector<const CStack *> hlp;
//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
//if(hlp.size())
//{
// sas.stack = hlp[0]->ID;
//}
//else
// complain ("No new stack to activate!");
sas.stack = gs->curB->getNextStack()->ID; //why the hell next stack has same ID as current?
sendAndApply(&sas);
}
BattleStacksRemoved bsr;
bsr.stackIDs.insert (selectedStack); //somehow it works for teleport?
sendAndApply(&bsr);
}
}
switch (spellID)
{
case SpellID::QUICKSAND:
case SpellID::LAND_MINE:
{
std::vector<BattleHex> availableTiles;
for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
{
BattleHex hex = i;
if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) && !battleGetObstacleOnPos(hex, false))
availableTiles.push_back(hex);
}
boost::range::random_shuffle(availableTiles);
const int patchesForSkill[] = {4, 4, 6, 8};
const int patchesToPut = std::min<int>(patchesForSkill[spellLvl], availableTiles.size());
//land mines or quicksand patches are handled as spell created obstacles
for (int i = 0; i < patchesToPut; i++)
placeObstacle(availableTiles.at(i));
}
break;
case SpellID::FORCE_FIELD:
placeObstacle(destination);
break;
case SpellID::FIRE_WALL:
{
//fire wall is build from multiple obstacles - one fire piece for each affected hex
auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
for(BattleHex hex : affectedHexes)
placeObstacle(hex);
}
break;
case SpellID::TELEPORT:
{
BattleStackMoved bsm;
bsm.distance = -1;
bsm.stack = selectedStack;
std::vector<BattleHex> tiles;
tiles.push_back(destination);
bsm.tilesToMove = tiles;
bsm.teleporting = true;
sendAndApply(&bsm);
break;
}
case SpellID::SUMMON_FIRE_ELEMENTAL:
case SpellID::SUMMON_EARTH_ELEMENTAL:
case SpellID::SUMMON_WATER_ELEMENTAL:
case SpellID::SUMMON_AIR_ELEMENTAL:
{ //elemental summoning
CreatureID creID;
switch(spellID)
{
case SpellID::SUMMON_FIRE_ELEMENTAL:
creID = CreatureID::FIRE_ELEMENTAL;
break;
case SpellID::SUMMON_EARTH_ELEMENTAL:
creID = CreatureID::EARTH_ELEMENTAL;
break;
case SpellID::SUMMON_WATER_ELEMENTAL:
creID = CreatureID::WATER_ELEMENTAL;
break;
case SpellID::SUMMON_AIR_ELEMENTAL:
creID = CreatureID::AIR_ELEMENTAL;
break;
}
BattleStackAdded bsa;
bsa.creID = creID;
bsa.attacker = !(bool)casterSide;
bsa.summoned = true;
bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
int percentBonus = caster ? caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID.toEnum()) : 0;
bsa.amount = usedSpellPower
* SpellID(spellID).toSpell()->getPower(spellLvl)
* (100 + percentBonus) / 100.0; //new feature - percentage bonus
if(bsa.amount)
sendAndApply(&bsa);
else
complain("Summoning elementals didn't summon any!");
}
break;
case SpellID::CLONE:
{
const CStack * clonedStack = nullptr;
if (attackedCres.size())
clonedStack = *attackedCres.begin();
if (!clonedStack)
{
complain ("No target stack to clone!");
break;
}
BattleStackAdded bsa;
bsa.creID = clonedStack->type->idNumber;
bsa.attacker = !(bool)casterSide;
bsa.summoned = true;
bsa.pos = gs->curB->getAvaliableHex(bsa.creID, !(bool)casterSide); //TODO: unify it
bsa.amount = clonedStack->count;
sendAndApply (&bsa);
BattleSetStackProperty ssp;
ssp.stackID = bsa.newStackID;
ssp.which = BattleSetStackProperty::CLONED; //using enum values
ssp.val = 0;
ssp.absolute = 1;
sendAndApply(&ssp);
}
break;
case SpellID::REMOVE_OBSTACLE:
{
if(auto obstacleToRemove = battleGetObstacleOnPos(destination, false))
{
ObstaclesRemoved obr;
obr.obstacles.insert(obstacleToRemove->uniqueID);
sendAndApply(&obr);
}
else
complain("There's no obstacle to remove!");
}
break;
case SpellID::DEATH_STARE: //handled in a bit different way
{
for(auto & attackedCre : attackedCres)
{
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
bsa.damageAmount = usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
bsa.stackAttacked = (attackedCre)->ID;
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
si.stacks.push_back(bsa);
}
}
break;
case SpellID::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
{
for(auto & attackedCre : attackedCres) //no immunities
{
BattleStackAttacked bsa;
bsa.flags |= BattleStackAttacked::EFFECT;
bsa.effect = spell->mainEffectAnim;
bsa.damageAmount = usedSpellPower; //damage times the number of attackers
bsa.stackAttacked = (attackedCre)->ID;
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
si.stacks.push_back(bsa);
}
}
break;
}
sendAndApply(&sc);
if(!si.stacks.empty()) //after spellcast info shows
sendAndApply(&si);
if (mode == ECastingMode::CREATURE_ACTIVE_CASTING || mode == ECastingMode::ENCHANTER_CASTING) //reduce number of casts remaining
{
BattleSetStackProperty ssp;
ssp.stackID = stack->ID;
ssp.which = BattleSetStackProperty::CASTS;
ssp.val = -1;
ssp.absolute = false;
sendAndApply(&ssp);
}
//Magic Mirror effect
if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
{
for(auto & attackedCre : attackedCres)
{
int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
if(mirrorChance > gs->getRandomGenerator().nextInt(99))
{
std::vector<CStack *> mirrorTargets;
std::vector<CStack *> & battleStacks = gs->curB->stacks;
for (auto & battleStack : battleStacks)
{
if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
{
if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, battleStack))
mirrorTargets.push_back(battleStack);
}
}
if (!mirrorTargets.empty())
{
int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, gs->getRandomGenerator()))->position;
handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (attackedCre)->owner, nullptr, (caster ? caster : nullptr), usedSpellPower, ECastingMode::MAGIC_MIRROR, (attackedCre));
}
}
}
}
spell->battleCast(&spellEnvironment, parameters);
}
bool CGameHandler::makeCustomAction( BattleAction &ba )
@ -6401,3 +5923,24 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
{
winnerHero = loserHero = nullptr;
}
ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
{
}
void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
{
gh->sendAndApply(info);
}
CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
{
return gh->gameState()->getRandomGenerator();
}
void ServerSpellCastEnvironment::complain(const std::string& problem) const
{
gh->complain(problem);
}

View File

@ -288,6 +288,7 @@ public:
friend class CVCMIServer;
private:
std::list<PlayerColor> generatePlayerTurnOrder() const;
void makeStackDoNothing(const CStack * next);
void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;