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

Demon summon is now a spell. DEMON_SUMMONING bonus has been removed

This commit is contained in:
Ivan Savenko 2022-12-22 23:11:55 +02:00
parent 5b453bf530
commit 9248e06ae0
22 changed files with 268 additions and 117 deletions

View File

@ -171,8 +171,6 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
break;
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
return 2; break;
case PossiblePlayerBattleAction::RISE_DEMONS:
return 3; break;
case PossiblePlayerBattleAction::SHOOT:
return 4; break;
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
@ -374,19 +372,6 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
if (shere && ourStack && shere->canBeHealed())
legalAction = true;
break;
case PossiblePlayerBattleAction::RISE_DEMONS:
if (shere && ourStack && !shere->alive())
{
if (!(shere->hasBonusOfType(Bonus::UNDEAD)
|| shere->hasBonusOfType(Bonus::NON_LIVING)
|| shere->hasBonusOfType(Bonus::GARGOYLE)
|| shere->summoned
|| shere->isClone()
|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
))
legalAction = true;
}
break;
}
if (legalAction)
localActions.push_back (action);
@ -542,13 +527,6 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
break;
case PossiblePlayerBattleAction::RISE_DEMONS:
spellcastingCursor = true;
realizeAction = [=]()
{
owner.giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
};
break;
case PossiblePlayerBattleAction::CATAPULT:
cursorFrame = Cursor::Combat::SHOOT_CATAPULT;
realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };

View File

@ -847,7 +847,7 @@ void BattleStacksController::updateHoveredStacks()
continue;
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive())
stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON);
}

View File

@ -139,6 +139,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/spells/effects/Catapult.cpp
${MAIN_LIB_DIR}/spells/effects/Clone.cpp
${MAIN_LIB_DIR}/spells/effects/Damage.cpp
${MAIN_LIB_DIR}/spells/effects/DemonSummon.cpp
${MAIN_LIB_DIR}/spells/effects/Dispel.cpp
${MAIN_LIB_DIR}/spells/effects/Effect.cpp
${MAIN_LIB_DIR}/spells/effects/Effects.cpp
@ -374,6 +375,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/spells/effects/Catapult.h
${MAIN_LIB_DIR}/spells/effects/Clone.h
${MAIN_LIB_DIR}/spells/effects/Damage.h
${MAIN_LIB_DIR}/spells/effects/DemonSummon.h
${MAIN_LIB_DIR}/spells/effects/Dispel.h
${MAIN_LIB_DIR}/spells/effects/Effect.h
${MAIN_LIB_DIR}/spells/effects/Effects.h

View File

@ -2240,12 +2240,6 @@
"val" : 0,
"valueType" : "BASE_NUMBER"
},
{
"subtype" : "creature.vampireLord",
"type" : "DAEMON_SUMMONING",
"val" : 10,
"valueType" : "BASE_NUMBER"
},
{
"addInfo" : 2,
"subtype" : "spell.lightningBolt",

View File

@ -84,14 +84,6 @@
}
},
"DAEMON_SUMMONING":
{
"graphics":
{
"icon": "zvs/Lib1.res/RiseDemons"
}
},
"DARKNESS":
{
},

View File

@ -72,12 +72,6 @@
"description": "Immune to Champion charge"
},
"DAEMON_SUMMONING":
{
"name": "Summoner (${subtype.creature})",
"description": "Can rise creatures from corpses"
},
"DARKNESS":
{
"name": "Darkness cover",

View File

@ -216,12 +216,18 @@
"faction": "inferno",
"abilities":
{
"demonSummon" :
"summons50HP" :
{
"type" : "DAEMON_SUMMONING",
"subtype" : "creature.demon",
"type" : "SPECIFIC_SPELL_POWER",
"subtype" : "spell.summonDemons",
"val" : 50
},
"resurrects" :
{
"type" : "SPELLCASTER",
"subtype" : "spell.summonDemons",
"val" : 0
},
"castsAmount" :
{
"type" : "CASTS",

View File

@ -430,5 +430,52 @@
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
}
}
},
"summonDemons" : {
"type": "ability",
"targetType" : "CREATURE",
"name": "Summon Demons",
"school" : {},
"level": 2,
"power": 50,
"defaultGainChance": 0,
"gainChance": {},
"animation":{
},
"sounds": {
"cast": "RESURECT"
},
"levels" : {
"base": {
"description" : "",
"aiValue" : 0,
"power" : 40,
"cost" : 1,
"range" : "0",
"battleEffects":{
"demonSummon":{
"id":"demon",
"permanent":true,
"type":"core:demonSummon"
}
}
},
"none" :{},
"basic" :{},
"advanced" :{},
"expert" :{}
},
"flags" : {
"rising": true,
"positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "absolute",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute",
"bonus.GARGOYLE" : "absolute"
}
}
},
}

View File

@ -232,7 +232,6 @@ std::ostream & operator<<(std::ostream & os, const EActionType actionType)
{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

@ -922,7 +922,6 @@ enum class EActionType : int32_t
MONSTER_SPELL,
BAD_MORALE,
STACK_HEAL,
DAEMON_SUMMONING
};
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EActionType actionType);

View File

@ -237,7 +237,6 @@ public:
BONUS_NAME(MANA_CHANNELING) /*value in %, eg. familiar*/ \
BONUS_NAME(SPELL_LIKE_ATTACK) /*subtype - spell, value - spell level; range is taken from spell, but damage from creature; eg. magog*/ \
BONUS_NAME(THREE_HEADED_ATTACK) /*eg. cerberus*/ \
BONUS_NAME(DAEMON_SUMMONING) /*pit lord, subtype - type of creatures, val - hp per unit*/ \
BONUS_NAME(FIRE_IMMUNITY) /*subtype 0 - all, 1 - all except positive, 2 - only damage spells*/ \
BONUS_NAME(WATER_IMMUNITY) \
BONUS_NAME(EARTH_IMMUNITY) \

View File

@ -230,8 +230,6 @@ std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsFor
}
if(stack->hasBonusOfType(Bonus::RANDOM_SPELLCASTER))
allowedActionList.push_back(PossiblePlayerBattleAction::RANDOM_GENIE_SPELL);
if(stack->hasBonusOfType(Bonus::DAEMON_SUMMONING))
allowedActionList.push_back(PossiblePlayerBattleAction::RISE_DEMONS);
}
if(stack->canShoot())
allowedActionList.push_back(PossiblePlayerBattleAction::SHOOT);

View File

@ -49,7 +49,7 @@ enum class PossiblePlayerBattleAction // actions performed at l-click
MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
CATAPULT, HEAL, RISE_DEMONS,
CATAPULT, HEAL,
AIMED_SPELL_CREATURE
};

View File

@ -608,9 +608,9 @@ std::vector<Destination> BattleSpellMechanics::getPossibleDestinations(size_t in
Target tmp = current;
tmp.emplace_back(dest);
detail::ProblemImpl ingored;
detail::ProblemImpl ignored;
if(canBeCastAt(tmp, ingored))
if(canBeCastAt(tmp, ignored))
ret.emplace_back(dest);
}
}
@ -630,7 +630,7 @@ bool BattleSpellMechanics::isReceptive(const battle::Unit * target) const
return targetCondition->isReceptive(this, target);
}
std::vector<BattleHex> BattleSpellMechanics::rangeInHexes(BattleHex centralHex, bool * outDroppedHexes) const
std::vector<BattleHex> BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const
{
if(isMassive() || !centralHex.isValid())
return std::vector<BattleHex>(1, BattleHex::INVALID);

View File

@ -27,22 +27,36 @@ public:
BattleSpellMechanics(const IBattleCast * event, std::shared_ptr<effects::Effects> effects_, std::shared_ptr<IReceptiveCheck> targetCondition_);
virtual ~BattleSpellMechanics();
// TODO: ??? (what's the difference compared to cast?)
void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override;
/// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield
bool canBeCast(Problem & problem) const override;
/// Returns false if spell can not be cast at specifid target
bool canBeCastAt(const Target & target, Problem & problem) const override;
// TODO: ??? (what's the difference compared to applyEffects?)
void cast(ServerCallback * server, const Target & target) override final;
// TODO: ??? (what's the difference compared to cast?)
void castEval(ServerCallback * server, const Target & target) override final;
/// Returns list of affected stack using currently configured target
std::vector<const CStack *> getAffectedStacks(const Target & target) const override final;
/// Returns list of target types that can be targeted by spell
std::vector<AimType> getTargetTypes() const override final;
/// Returns vector of all possible destinations for specified aim type
/// index - ???
/// current - ???
std::vector<Destination> getPossibleDestinations(size_t index, AimType aimType, const Target & current) const override final;
/// Returns true if spell can be cast on unit
bool isReceptive(const battle::Unit * target) const override;
std::vector<BattleHex> rangeInHexes(BattleHex centralHex, bool * outDroppedHexes = nullptr) const override;
/// Returns list of hexes that are affected by spell assuming cast at centralHex
std::vector<BattleHex> rangeInHexes(BattleHex centralHex) const override;
const Spell * getSpell() const override;

View File

@ -701,9 +701,9 @@ PlayerColor BaseMechanics::getCasterColor() const
std::vector<AimType> BaseMechanics::getTargetTypes() const
{
std::vector<AimType> ret;
detail::ProblemImpl ingored;
detail::ProblemImpl ignored;
if(canBeCast(ingored))
if(canBeCast(ignored))
{
auto spellTargetType = owner->getTargetType();

View File

@ -183,7 +183,7 @@ public:
virtual bool adaptProblem(ESpellCastProblem::ESpellCastProblem source, Problem & target) const = 0;
virtual bool adaptGenericProblem(Problem & target) const = 0;
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, bool * outDroppedHexes = nullptr) const = 0;
virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex) const = 0;
virtual std::vector<const CStack *> getAffectedStacks(const Target & target) const = 0;
virtual bool canBeCast(Problem & problem) const = 0;

View File

@ -0,0 +1,130 @@
/*
* DemonSummon.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 "DemonSummon.h"
#include "Registry.h"
#include "../ISpellMechanics.h"
#include "../../NetPacks.h"
#include "../../battle/CBattleInfoCallback.h"
#include "../../battle/CUnitState.h"
#include "../../serializer/JsonSerializeFormat.h"
VCMI_LIB_NAMESPACE_BEGIN
static const std::string EFFECT_NAME = "core:demonSummon";
namespace spells
{
namespace effects
{
VCMI_REGISTER_SPELL_EFFECT(DemonSummon, EFFECT_NAME);
DemonSummon::DemonSummon()
: UnitEffect()
, creature(0)
, permanent(false)
{
}
DemonSummon::~DemonSummon() = default;
void DemonSummon::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
{
BattleUnitsChanged pack;
for(const Destination & dest : target)
{
const battle::Unit * targetStack = dest.unitValue;
//we shall have all targets to be stacks
if(!targetStack || targetStack->alive() || targetStack->isGhost())
{
server->complain("No corpse to demonize! Invalid effect target transformation.");
continue;
}
auto hex = m->battle()->getAvaliableHex(targetStack->creatureId(), m->casterSide, targetStack->getPosition());
if(!hex.isValid())
{
server->complain("No place to put new summon!");
break;
}
auto creatureType = creature.toCreature(m->creatures());
int32_t deadCount = targetStack->unitBaseAmount();
int32_t deadTotalHealth = targetStack->getTotalHealth();
int32_t raisedMaxHealth = creatureType->getMaxHealth();
int32_t raisedTotalHealth = m->applySpellBonus(m->getEffectValue(), targetStack);
// Can't raise stack with more HP than original stack
int32_t maxAmountFromHealth = deadTotalHealth / raisedMaxHealth;
// Can't raise stack with more creatures than original stack
int32_t maxAmountFromAmount = deadCount;
// Can't raise stack with more HP than our spellpower
int32_t maxAmountFromSpellpower = raisedTotalHealth / raisedMaxHealth;
int32_t finalAmount = std::min( { maxAmountFromHealth, maxAmountFromAmount, maxAmountFromSpellpower } );
if(finalAmount < 1)
{
server->complain("Summoning didn't summon any!");
continue;
}
battle::UnitInfo info;
info.id = m->battle()->battleNextUnitId();
info.count = finalAmount;
info.type = creature;
info.side = m->casterSide;
info.position = dest.hexValue;
info.summoned = !permanent;
// add newly created creature
pack.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD);
info.save(pack.changedStacks.back().data);
// and remove corpse to prevent second raising or resurrection
pack.changedStacks.emplace_back(targetStack->unitId(), UnitChanges::EOperation::REMOVE);
}
if(!pack.changedStacks.empty())
server->apply(&pack);
}
bool DemonSummon::isValidTarget(const Mechanics * m, const battle::Unit * s) const
{
if(!s->isDead())
return false;
if (s->isGhost())
return false;
auto creatureType = creature.toCreature(m->creatures());
if (s->getTotalHealth() < creatureType->getMaxHealth())
return false;
return m->isReceptive(s);
}
void DemonSummon::serializeJsonUnitEffect(JsonSerializeFormat & handler)
{
handler.serializeId("id", creature, CreatureID());
handler.serializeBool("permanent", permanent, false);
}
}
}
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,44 @@
/*
* DemonSummon.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 "UnitEffect.h"
#include "../../GameConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
namespace spells
{
namespace effects
{
class DemonSummon : public UnitEffect
{
public:
DemonSummon();
virtual ~DemonSummon();
void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override;
protected:
bool isValidTarget(const Mechanics * m, const battle::Unit * s) const override;
void serializeJsonUnitEffect(JsonSerializeFormat & handler) override final;
private:
CreatureID creature;
bool permanent;
};
}
}
VCMI_LIB_NAMESPACE_END

View File

@ -51,19 +51,27 @@ public:
Effect();
virtual ~Effect();
// TODO: document me
virtual void adjustTargetTypes(std::vector<TargetType> & types) const = 0;
/// Generates list of hexes affected by spell, if spell were to cast at specified target
virtual void adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics * m, const Target & spellTarget) const = 0;
/// Returns whether effect has any valid targets on the battlefield
virtual bool applicable(Problem & problem, const Mechanics * m) const;
/// Returns whether effect is valid and can be applied onto selected target
virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const;
virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0;
/// Processes input target and generates subset-result that contains only valid targets
virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0;
// TODO: document me
virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0;
/// Serializes (or deserializes) parameters of Effect
void serializeJson(JsonSerializeFormat & handler);
static std::shared_ptr<Effect> create(const Registry * registry, const std::string & type);

View File

@ -63,14 +63,14 @@ bool Heal::isValidTarget(const Mechanics * m, const battle::Unit * unit) const
if(!validInGenaral)
return false;
auto insuries = unit->getTotalHealth() - unit->getAvailableHealth();
auto injuries = unit->getTotalHealth() - unit->getAvailableHealth();
if(insuries == 0)
if(injuries == 0)
return false;
if(minFullUnits > 0)
{
auto hpGained = std::min(m->getEffectValue(), insuries);
auto hpGained = std::min(m->getEffectValue(), injuries);
if(hpGained < minFullUnits * unit->MaxHealth())
return false;
}

View File

@ -4472,7 +4472,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
case EActionType::SHOOT: //shoot
case EActionType::CATAPULT: //catapult
case EActionType::STACK_HEAL: //healing with First Aid Tent
case EActionType::DAEMON_SUMMONING:
case EActionType::MONSTER_SPELL:
if (!stack)
@ -5006,58 +5005,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
}
break;
}
case EActionType::DAEMON_SUMMONING:
//TODO: From Strategija:
//Summon Demon is a level 2 spell.
{
if(target.size() < 1)
{
complain("Destination required for summon action.");
ok = false;
break;
}
const CStack * summoner = gs->curB->battleGetStackByID(ba.stackNumber);
const CStack * destStack = gs->curB->battleGetStackByPos(target.at(0).hexValue, false);
CreatureID summonedType(summoner->getBonusLocalFirst(Selector::type()(Bonus::DAEMON_SUMMONING))->subtype);//in case summoner can summon more than one type of monsters... scream!
ui64 risedHp = summoner->getCount() * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, summonedType.toEnum());
ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
ui64 canRiseHp = std::min(targetHealth, risedHp);
ui32 canRiseAmount = static_cast<ui32>(canRiseHp / summonedType.toCreature()->MaxHealth());
battle::UnitInfo info;
info.id = gs->curB->battleNextUnitId();
info.count = std::min(canRiseAmount, destStack->baseAmount);
info.type = summonedType;
info.side = summoner->side;
info.position = gs->curB->getAvaliableHex(summonedType, summoner->side, destStack->getPosition());
info.summoned = false;
BattleUnitsChanged addUnits;
addUnits.changedStacks.emplace_back(info.id, UnitChanges::EOperation::ADD);
info.save(addUnits.changedStacks.back().data);
if(info.count > 0) //there's rare possibility single creature cannot rise desired type
{
auto wrapper = wrapAction(ba);
BattleUnitsChanged removeUnits;
removeUnits.changedStacks.emplace_back(destStack->unitId(), UnitChanges::EOperation::REMOVE);
sendAndApply(&removeUnits);
sendAndApply(&addUnits);
BattleSetStackProperty ssp;
ssp.stackID = ba.stackNumber;
ssp.which = BattleSetStackProperty::CASTS; //reduce number of casts
ssp.val = -1;
ssp.absolute = false;
sendAndApply(&ssp);
}
break;
}
case EActionType::MONSTER_SPELL:
{
auto wrapper = wrapAction(ba);
@ -5089,7 +5036,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
break;
}
}
if(ba.actionType == EActionType::DAEMON_SUMMONING || ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND
if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND
|| ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL)
handleDamageFromObstacle(stack);
if(ba.stackNumber == gs->curB->activeStack || battleResult.get()) //active stack has moved or battle has finished