mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-13 01:20:34 +02:00
@ -405,7 +405,10 @@ std::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(const
|
|||||||
|
|
||||||
std::shared_ptr<CPlayerBattleCallback> CBattleCallback::getBattle(const BattleID & battleID)
|
std::shared_ptr<CPlayerBattleCallback> CBattleCallback::getBattle(const BattleID & battleID)
|
||||||
{
|
{
|
||||||
return activeBattles.at(battleID);
|
if (activeBattles.count(battleID))
|
||||||
|
return activeBattles.at(battleID);
|
||||||
|
|
||||||
|
throw std::runtime_error("Failed to find battle " + std::to_string(battleID.getNum()) + " of player " + player->toString() + ". Number of ongoing battles: " + std::to_string(activeBattles.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<PlayerColor> CBattleCallback::getPlayerID() const
|
std::optional<PlayerColor> CBattleCallback::getPlayerID() const
|
||||||
@ -415,10 +418,18 @@ std::optional<PlayerColor> CBattleCallback::getPlayerID() const
|
|||||||
|
|
||||||
void CBattleCallback::onBattleStarted(const IBattleInfo * info)
|
void CBattleCallback::onBattleStarted(const IBattleInfo * info)
|
||||||
{
|
{
|
||||||
|
if (activeBattles.count(info->getBattleID()) > 0)
|
||||||
|
throw std::runtime_error("Player " + player->toString() + " is already engaged in battle " + std::to_string(info->getBattleID().getNum()));
|
||||||
|
|
||||||
|
logGlobal->debug("Battle %d started for player %s", info->getBattleID(), player->toString());
|
||||||
activeBattles[info->getBattleID()] = std::make_shared<CPlayerBattleCallback>(info, *getPlayerID());
|
activeBattles[info->getBattleID()] = std::make_shared<CPlayerBattleCallback>(info, *getPlayerID());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CBattleCallback::onBattleEnded(const BattleID & battleID)
|
void CBattleCallback::onBattleEnded(const BattleID & battleID)
|
||||||
{
|
{
|
||||||
|
if (activeBattles.count(battleID) == 0)
|
||||||
|
throw std::runtime_error("Player " + player->toString() + " is not engaged in battle " + std::to_string(battleID.getNum()));
|
||||||
|
|
||||||
|
logGlobal->debug("Battle %d ended for player %s", battleID, player->toString());
|
||||||
activeBattles.erase(battleID);
|
activeBattles.erase(battleID);
|
||||||
}
|
}
|
||||||
|
@ -560,18 +560,16 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
|
|||||||
|
|
||||||
void CClient::battleStarted(const BattleInfo * info)
|
void CClient::battleStarted(const BattleInfo * info)
|
||||||
{
|
{
|
||||||
|
std::shared_ptr<CPlayerInterface> att, def;
|
||||||
|
auto & leftSide = info->sides[0];
|
||||||
|
auto & rightSide = info->sides[1];
|
||||||
|
|
||||||
for(auto & battleCb : battleCallbacks)
|
for(auto & battleCb : battleCallbacks)
|
||||||
{
|
{
|
||||||
if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })
|
if(!battleCb.first.isValidPlayer() || battleCb.first == leftSide.color || battleCb.first == rightSide.color)
|
||||||
|| !battleCb.first.isValidPlayer())
|
|
||||||
{
|
|
||||||
battleCb.second->onBattleStarted(info);
|
battleCb.second->onBattleStarted(info);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<CPlayerInterface> att, def;
|
|
||||||
auto & leftSide = info->sides[0], & rightSide = info->sides[1];
|
|
||||||
|
|
||||||
//If quick combat is not, do not prepare interfaces for battleint
|
//If quick combat is not, do not prepare interfaces for battleint
|
||||||
auto callBattleStart = [&](PlayerColor color, ui8 side)
|
auto callBattleStart = [&](PlayerColor color, ui8 side)
|
||||||
{
|
{
|
||||||
|
@ -202,6 +202,7 @@ public:
|
|||||||
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
|
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
|
||||||
void giveHeroBonus(GiveBonus * bonus) override {};
|
void giveHeroBonus(GiveBonus * bonus) override {};
|
||||||
void setMovePoints(SetMovePoints * smp) override {};
|
void setMovePoints(SetMovePoints * smp) override {};
|
||||||
|
void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override {};
|
||||||
void setManaPoints(ObjectInstanceID hid, int val) override {};
|
void setManaPoints(ObjectInstanceID hid, int val) override {};
|
||||||
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {};
|
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {};
|
||||||
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {};
|
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {};
|
||||||
|
@ -691,7 +691,7 @@ void AdventureMapInterface::onTileHovered(const int3 &mapPos)
|
|||||||
if(pathNode->layer == EPathfindingLayer::LAND)
|
if(pathNode->layer == EPathfindingLayer::LAND)
|
||||||
CCS->curh->set(cursorMove[turns]);
|
CCS->curh->set(cursorMove[turns]);
|
||||||
else
|
else
|
||||||
CCS->curh->set(cursorSailVisit[turns]);
|
CCS->curh->set(cursorSail[turns]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EPathNodeAction::VISIT:
|
case EPathNodeAction::VISIT:
|
||||||
@ -706,6 +706,15 @@ void AdventureMapInterface::onTileHovered(const int3 &mapPos)
|
|||||||
}
|
}
|
||||||
else if(pathNode->layer == EPathfindingLayer::LAND)
|
else if(pathNode->layer == EPathfindingLayer::LAND)
|
||||||
CCS->curh->set(cursorVisit[turns]);
|
CCS->curh->set(cursorVisit[turns]);
|
||||||
|
else if (pathNode->layer == EPathfindingLayer::SAIL &&
|
||||||
|
objAtTile &&
|
||||||
|
objAtTile->isCoastVisitable() &&
|
||||||
|
pathNode->theNodeBefore &&
|
||||||
|
pathNode->theNodeBefore->layer == EPathfindingLayer::LAND )
|
||||||
|
{
|
||||||
|
// exception - when visiting shipwreck located on coast from land - show 'horse' cursor, not 'ship' cursor
|
||||||
|
CCS->curh->set(cursorVisit[turns]);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
CCS->curh->set(cursorSailVisit[turns]);
|
CCS->curh->set(cursorSailVisit[turns]);
|
||||||
break;
|
break;
|
||||||
|
@ -411,12 +411,6 @@
|
|||||||
"type" : "LEVEL_SPELL_IMMUNITY",
|
"type" : "LEVEL_SPELL_IMMUNITY",
|
||||||
"val" : 5
|
"val" : 5
|
||||||
},
|
},
|
||||||
"hateGiants" :
|
|
||||||
{
|
|
||||||
"type" : "HATE",
|
|
||||||
"subtype" : "creature.giant",
|
|
||||||
"val" : 50
|
|
||||||
},
|
|
||||||
"hateTitans" :
|
"hateTitans" :
|
||||||
{
|
{
|
||||||
"type" : "HATE",
|
"type" : "HATE",
|
||||||
|
@ -61,6 +61,10 @@
|
|||||||
"positive": true,
|
"positive": true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// If true, then creature capable of casting this spell can cast this spell on itself
|
||||||
|
// If false, then creature can only cast this spell on other units
|
||||||
|
"canCastOnSelf" : false,
|
||||||
|
|
||||||
// If true, spell won't be available on a map without water
|
// If true, spell won't be available on a map without water
|
||||||
"onlyOnWaterMap" : true,
|
"onlyOnWaterMap" : true,
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ Following options can be used to configure simultaneous turns:
|
|||||||
|
|
||||||
While simultaneous turns are active, VCMI tracks contacts for each pair of player separately.
|
While simultaneous turns are active, VCMI tracks contacts for each pair of player separately.
|
||||||
|
|
||||||
Players are considered to be "in contact" if movement range of their heroes at the start of turn overlaps, or, in other words - if their heroes can meet on this turn if both walk towards each other. When calculating movement range, game uses same rules as standard movement range calculation in vcmi, meaning that game will track movement through monoliths and subterranean gates, but will not account for any removable obstacles, such as wandering monsters or treasures that block path between heroes. At the moment, game will not account for any ways to extend movement range - Dimension Door or Town Portal spells, visiting map objects such as Stables, releasing heroes from prisons, etc.
|
Players are considered to be "in contact" if movement range of their heroes at the start of turn overlaps, or, in other words - if their heroes can meet on this turn if both walk towards each other. When calculating movement range, game uses rules similar to standard movement range calculation in vcmi, meaning that game will track movement through monoliths and subterranean gates, but will not account for any removable obstacles, such as pickable treasures that block path between heroes. Any existing wandering monsters that block path between heroes are ignored for range calculation. At the moment, game will not account for any ways to extend movement range - Dimension Door or Town Portal spells, visiting map objects such as Stables, releasing heroes from prisons, etc.
|
||||||
|
|
||||||
Once detected, contact can never be "lost". If game detected contact between two players, this contact will remain active till the end of the game, even if their heroes move far enough from each other.
|
Once detected, contact can never be "lost". If game detected contact between two players, this contact will remain active till the end of the game, even if their heroes move far enough from each other.
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ public:
|
|||||||
virtual bool isMagical() const = 0; //Should this spell considered as magical effect or as ability (like dendroid's bind)
|
virtual bool isMagical() const = 0; //Should this spell considered as magical effect or as ability (like dendroid's bind)
|
||||||
|
|
||||||
virtual bool hasSchool(SpellSchool school) const = 0;
|
virtual bool hasSchool(SpellSchool school) const = 0;
|
||||||
|
virtual bool canCastOnSelf() const = 0;
|
||||||
virtual void forEachSchool(const SchoolCallback & cb) const = 0;
|
virtual void forEachSchool(const SchoolCallback & cb) const = 0;
|
||||||
virtual int32_t getCost(const int32_t skillLevel) const = 0;
|
virtual int32_t getCost(const int32_t skillLevel) const = 0;
|
||||||
|
|
||||||
|
@ -397,7 +397,10 @@ void CCreature::serializeJson(JsonSerializeFormat & handler)
|
|||||||
if(!handler.saving)
|
if(!handler.saving)
|
||||||
{
|
{
|
||||||
if(ammMin > ammMax)
|
if(ammMin > ammMax)
|
||||||
|
{
|
||||||
logMod->error("Invalid creature '%s' configuration, advMapAmount.min > advMapAmount.max", identifier);
|
logMod->error("Invalid creature '%s' configuration, advMapAmount.min > advMapAmount.max", identifier);
|
||||||
|
std::swap(ammMin, ammMax);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,7 +625,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logGlobal->error("Mod %s: creature %s has minimal damage (%d) greater than maximal damage (%d)!", scope, identifier, minDamage, maxDamage);
|
logMod->error("Mod %s: creature %s has minimal damage (%d) greater than maximal damage (%d)!", scope, identifier, minDamage, maxDamage);
|
||||||
cre->addBonus(maxDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);
|
cre->addBonus(maxDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMin);
|
||||||
cre->addBonus(minDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);
|
cre->addBonus(minDamage, BonusType::CREATURE_DAMAGE, BonusCustomSubtype::creatureDamageMax);
|
||||||
}
|
}
|
||||||
|
@ -473,7 +473,11 @@ void CHeroHandler::loadHeroArmy(CHero * hero, const JsonNode & node) const
|
|||||||
hero->initialArmy[i].minAmount = static_cast<ui32>(source["min"].Float());
|
hero->initialArmy[i].minAmount = static_cast<ui32>(source["min"].Float());
|
||||||
hero->initialArmy[i].maxAmount = static_cast<ui32>(source["max"].Float());
|
hero->initialArmy[i].maxAmount = static_cast<ui32>(source["max"].Float());
|
||||||
|
|
||||||
assert(hero->initialArmy[i].minAmount <= hero->initialArmy[i].maxAmount);
|
if (hero->initialArmy[i].minAmount > hero->initialArmy[i].maxAmount)
|
||||||
|
{
|
||||||
|
logMod->error("Hero %s has minimal army size (%d) greater than maximal size (%d)!", hero->getJsonKey(), hero->initialArmy[i].minAmount, hero->initialArmy[i].maxAmount);
|
||||||
|
std::swap(hero->initialArmy[i].minAmount, hero->initialArmy[i].maxAmount);
|
||||||
|
}
|
||||||
|
|
||||||
VLC->identifiers()->requestIdentifier("creature", source["creature"], [=](si32 creature)
|
VLC->identifiers()->requestIdentifier("creature", source["creature"], [=](si32 creature)
|
||||||
{
|
{
|
||||||
|
@ -37,21 +37,25 @@ void CRandomGenerator::resetSeed()
|
|||||||
|
|
||||||
TRandI CRandomGenerator::getIntRange(int lower, int upper)
|
TRandI CRandomGenerator::getIntRange(int lower, int upper)
|
||||||
{
|
{
|
||||||
|
assert(lower <= 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)
|
vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper)
|
||||||
{
|
{
|
||||||
|
assert(lower <= upper);
|
||||||
return std::bind(TInt64Dist(lower, upper), std::ref(rand));
|
return std::bind(TInt64Dist(lower, upper), std::ref(rand));
|
||||||
}
|
}
|
||||||
|
|
||||||
int CRandomGenerator::nextInt(int upper)
|
int CRandomGenerator::nextInt(int upper)
|
||||||
{
|
{
|
||||||
|
assert(0 <= upper);
|
||||||
return getIntRange(0, upper)();
|
return getIntRange(0, upper)();
|
||||||
}
|
}
|
||||||
|
|
||||||
int CRandomGenerator::nextInt(int lower, int upper)
|
int CRandomGenerator::nextInt(int lower, int upper)
|
||||||
{
|
{
|
||||||
|
assert(lower <= upper);
|
||||||
return getIntRange(lower, upper)();
|
return getIntRange(lower, upper)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,16 +66,19 @@ int CRandomGenerator::nextInt()
|
|||||||
|
|
||||||
vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper)
|
vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper)
|
||||||
{
|
{
|
||||||
return std::bind(TRealDist(lower, upper), std::ref(rand));
|
assert(lower <= upper);
|
||||||
|
return std::bind(TRealDist(lower, upper), std::ref(rand));
|
||||||
}
|
}
|
||||||
|
|
||||||
double CRandomGenerator::nextDouble(double upper)
|
double CRandomGenerator::nextDouble(double upper)
|
||||||
{
|
{
|
||||||
|
assert(0 <= upper);
|
||||||
return getDoubleRange(0, upper)();
|
return getDoubleRange(0, upper)();
|
||||||
}
|
}
|
||||||
|
|
||||||
double CRandomGenerator::nextDouble(double lower, double upper)
|
double CRandomGenerator::nextDouble(double lower, double upper)
|
||||||
{
|
{
|
||||||
|
assert(lower <= upper);
|
||||||
return getDoubleRange(lower, upper)();
|
return getDoubleRange(lower, upper)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ public:
|
|||||||
virtual bool swapGarrisonOnSiege(ObjectInstanceID tid)=0;
|
virtual bool swapGarrisonOnSiege(ObjectInstanceID tid)=0;
|
||||||
virtual void giveHeroBonus(GiveBonus * bonus)=0;
|
virtual void giveHeroBonus(GiveBonus * bonus)=0;
|
||||||
virtual void setMovePoints(SetMovePoints * smp)=0;
|
virtual void setMovePoints(SetMovePoints * smp)=0;
|
||||||
|
virtual void setMovePoints(ObjectInstanceID hid, int val, bool absolute)=0;
|
||||||
virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
|
virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
|
||||||
virtual void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) = 0;
|
virtual void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) = 0;
|
||||||
virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)=0;
|
virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator)=0;
|
||||||
|
@ -210,17 +210,21 @@ void CGameStateCampaign::placeCampaignHeroes()
|
|||||||
// with the same hero type id
|
// with the same hero type id
|
||||||
std::vector<CGHeroInstance *> removedHeroes;
|
std::vector<CGHeroInstance *> removedHeroes;
|
||||||
|
|
||||||
std::set<HeroTypeID> heroesToRemove = campaignState->getReservedHeroes();
|
std::set<HeroTypeID> reservedHeroes = campaignState->getReservedHeroes();
|
||||||
|
std::set<HeroTypeID> heroesToRemove;
|
||||||
|
|
||||||
|
for (auto const & heroID : reservedHeroes )
|
||||||
|
{
|
||||||
|
// Do not replace reserved heroes initially, e.g. in 1st campaign scenario in which they appear
|
||||||
|
if (!campaignState->getHeroByType(heroID).isNull())
|
||||||
|
heroesToRemove.insert(heroID);
|
||||||
|
}
|
||||||
|
|
||||||
for(auto & campaignHeroReplacement : campaignHeroReplacements)
|
for(auto & campaignHeroReplacement : campaignHeroReplacements)
|
||||||
heroesToRemove.insert(campaignHeroReplacement.hero->getHeroType());
|
heroesToRemove.insert(campaignHeroReplacement.hero->getHeroType());
|
||||||
|
|
||||||
for(auto & heroID : heroesToRemove)
|
for(auto & heroID : heroesToRemove)
|
||||||
{
|
{
|
||||||
// Do not replace reserved heroes initially, e.g. in 1st campaign scenario in which they appear
|
|
||||||
if (campaignState->getHeroByType(heroID).isNull())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto * hero = gameState->getUsedHero(heroID);
|
auto * hero = gameState->getUsedHero(heroID);
|
||||||
if(hero)
|
if(hero)
|
||||||
{
|
{
|
||||||
|
@ -40,7 +40,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
|
|||||||
return TavernSlotRole::NONE;
|
return TavernSlotRole::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role)
|
void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role, bool replenishPoints)
|
||||||
{
|
{
|
||||||
vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
|
vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
|
||||||
return entry.player == player && entry.slot == slot;
|
return entry.player == player && entry.slot == slot;
|
||||||
@ -54,6 +54,12 @@ void TavernHeroesPool::setHeroForPlayer(PlayerColor player, TavernHeroSlot slot,
|
|||||||
if (h && army)
|
if (h && army)
|
||||||
h->setToArmy(army);
|
h->setToArmy(army);
|
||||||
|
|
||||||
|
if (h && replenishPoints)
|
||||||
|
{
|
||||||
|
h->setMovementPoints(h->movementPointsLimit(true));
|
||||||
|
h->mana = h->manaLimit();
|
||||||
|
}
|
||||||
|
|
||||||
TavernSlot newSlot;
|
TavernSlot newSlot;
|
||||||
newSlot.hero = h;
|
newSlot.hero = h;
|
||||||
newSlot.player = player;
|
newSlot.player = player;
|
||||||
|
@ -74,7 +74,7 @@ public:
|
|||||||
void setAvailability(HeroTypeID hero, std::set<PlayerColor> mask);
|
void setAvailability(HeroTypeID hero, std::set<PlayerColor> mask);
|
||||||
|
|
||||||
/// Makes hero available in tavern of specified player
|
/// Makes hero available in tavern of specified player
|
||||||
void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role);
|
void setHeroForPlayer(PlayerColor player, TavernHeroSlot slot, HeroTypeID hero, CSimpleArmy & army, TavernSlotRole role, bool replenishPoints);
|
||||||
|
|
||||||
template <typename Handler> void serialize(Handler &h, const int version)
|
template <typename Handler> void serialize(Handler &h, const int version)
|
||||||
{
|
{
|
||||||
|
@ -436,14 +436,14 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst)
|
|||||||
|
|
||||||
int count = rand.nextInt(stack.minAmount, stack.maxAmount);
|
int count = rand.nextInt(stack.minAmount, stack.maxAmount);
|
||||||
|
|
||||||
const CCreature * creature = stack.creature.toCreature();
|
if(stack.creature == CreatureID::NONE)
|
||||||
|
|
||||||
if(creature == nullptr)
|
|
||||||
{
|
{
|
||||||
logGlobal->error("Hero %s has invalid creature with id %d in initial army", getNameTranslated(), stack.creature.toEnum());
|
logGlobal->error("Hero %s has invalid creature in initial army", getNameTranslated());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CCreature * creature = stack.creature.toCreature();
|
||||||
|
|
||||||
if(creature->warMachine != ArtifactID::NONE) //war machine
|
if(creature->warMachine != ArtifactID::NONE) //war machine
|
||||||
{
|
{
|
||||||
warMachinesGiven++;
|
warMachinesGiven++;
|
||||||
|
@ -151,11 +151,7 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
|
|||||||
gb.id = heroID;
|
gb.id = heroID;
|
||||||
cb->giveHeroBonus(&gb);
|
cb->giveHeroBonus(&gb);
|
||||||
|
|
||||||
SetMovePoints mp;
|
cb->setMovePoints(heroID, 600, false);
|
||||||
mp.val = 600;
|
|
||||||
mp.absolute = false;
|
|
||||||
mp.hid = heroID;
|
|
||||||
cb->setMovePoints(&mp);
|
|
||||||
|
|
||||||
iw.text.appendRawString(VLC->generaltexth->allTexts[580]);
|
iw.text.appendRawString(VLC->generaltexth->allTexts[580]);
|
||||||
cb->showInfoDialog(&iw);
|
cb->showInfoDialog(&iw);
|
||||||
|
@ -959,7 +959,7 @@ void FoWChange::applyGs(CGameState *gs)
|
|||||||
|
|
||||||
void SetAvailableHero::applyGs(CGameState *gs)
|
void SetAvailableHero::applyGs(CGameState *gs)
|
||||||
{
|
{
|
||||||
gs->heroesPool->setHeroForPlayer(player, slotID, hid, army, roleID);
|
gs->heroesPool->setHeroForPlayer(player, slotID, hid, army, roleID, replenishPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiveBonus::applyGs(CGameState *gs)
|
void GiveBonus::applyGs(CGameState *gs)
|
||||||
|
@ -352,6 +352,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
|
|||||||
PlayerColor player;
|
PlayerColor player;
|
||||||
HeroTypeID hid; //HeroTypeID::NONE if no hero
|
HeroTypeID hid; //HeroTypeID::NONE if no hero
|
||||||
CSimpleArmy army;
|
CSimpleArmy army;
|
||||||
|
bool replenishPoints;
|
||||||
|
|
||||||
void visitTyped(ICPackVisitor & visitor) override;
|
void visitTyped(ICPackVisitor & visitor) override;
|
||||||
|
|
||||||
@ -362,6 +363,7 @@ struct DLL_LINKAGE SetAvailableHero : public CPackForClient
|
|||||||
h & player;
|
h & player;
|
||||||
h & hid;
|
h & hid;
|
||||||
h & army;
|
h & army;
|
||||||
|
h & replenishPoints;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -213,7 +213,24 @@ bool BattleSpellMechanics::canBeCastAt(const Target & target, Problem & problem)
|
|||||||
|
|
||||||
Target spellTarget = transformSpellTarget(target);
|
Target spellTarget = transformSpellTarget(target);
|
||||||
|
|
||||||
return effects->applicable(problem, this, target, spellTarget);
|
const battle::Unit * mainTarget = nullptr;
|
||||||
|
|
||||||
|
if (!getSpell()->canCastOnSelf())
|
||||||
|
{
|
||||||
|
if(spellTarget.front().unitValue)
|
||||||
|
{
|
||||||
|
mainTarget = target.front().unitValue;
|
||||||
|
}
|
||||||
|
else if(spellTarget.front().hexValue.isValid())
|
||||||
|
{
|
||||||
|
mainTarget = battle()->battleGetUnitByPos(target.front().hexValue, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainTarget && mainTarget == caster)
|
||||||
|
return false; // can't cast on self
|
||||||
|
}
|
||||||
|
|
||||||
|
return effects->applicable(problem, this, target, spellTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const CStack *> BattleSpellMechanics::getAffectedStacks(const Target & target) const
|
std::vector<const CStack *> BattleSpellMechanics::getAffectedStacks(const Target & target) const
|
||||||
|
@ -76,6 +76,7 @@ CSpell::CSpell():
|
|||||||
power(0),
|
power(0),
|
||||||
combat(false),
|
combat(false),
|
||||||
creatureAbility(false),
|
creatureAbility(false),
|
||||||
|
castOnSelf(false),
|
||||||
positiveness(ESpellPositiveness::NEUTRAL),
|
positiveness(ESpellPositiveness::NEUTRAL),
|
||||||
defaultProbability(0),
|
defaultProbability(0),
|
||||||
rising(false),
|
rising(false),
|
||||||
@ -285,6 +286,11 @@ bool CSpell::hasBattleEffects() const
|
|||||||
return levels[0].battleEffects.getType() == JsonNode::JsonType::DATA_STRUCT && !levels[0].battleEffects.Struct().empty();
|
return levels[0].battleEffects.getType() == JsonNode::JsonType::DATA_STRUCT && !levels[0].battleEffects.Struct().empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CSpell::canCastOnSelf() const
|
||||||
|
{
|
||||||
|
return castOnSelf;
|
||||||
|
}
|
||||||
|
|
||||||
const std::string & CSpell::getIconImmune() const
|
const std::string & CSpell::getIconImmune() const
|
||||||
{
|
{
|
||||||
return iconImmune;
|
return iconImmune;
|
||||||
@ -702,6 +708,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
|
|||||||
spell->school[info.id] = schoolNames[info.jsonName].Bool();
|
spell->school[info.id] = schoolNames[info.jsonName].Bool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spell->castOnSelf = json["canCastOnSelf"].Bool();
|
||||||
spell->level = static_cast<si32>(json["level"].Integer());
|
spell->level = static_cast<si32>(json["level"].Integer());
|
||||||
spell->power = static_cast<si32>(json["power"].Integer());
|
spell->power = static_cast<si32>(json["power"].Integer());
|
||||||
|
|
||||||
|
@ -203,6 +203,7 @@ public:
|
|||||||
int64_t calculateDamage(const spells::Caster * caster) const override;
|
int64_t calculateDamage(const spells::Caster * caster) const override;
|
||||||
|
|
||||||
bool hasSchool(SpellSchool school) const override;
|
bool hasSchool(SpellSchool school) const override;
|
||||||
|
bool canCastOnSelf() const override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls cb for each school this spell belongs to
|
* Calls cb for each school this spell belongs to
|
||||||
@ -329,6 +330,7 @@ private:
|
|||||||
si32 power; //spell's power
|
si32 power; //spell's power
|
||||||
bool combat; //is this spell combat (true) or adventure (false)
|
bool combat; //is this spell combat (true) or adventure (false)
|
||||||
bool creatureAbility; //if true, only creatures can use this spell
|
bool creatureAbility; //if true, only creatures can use this spell
|
||||||
|
bool castOnSelf; // if set, creature caster can cast this spell on itself
|
||||||
si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
|
si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
|
||||||
|
|
||||||
std::unique_ptr<spells::ISpellMechanicsFactory> mechanics;//(!) do not serialize
|
std::unique_ptr<spells::ISpellMechanicsFactory> mechanics;//(!) do not serialize
|
||||||
|
@ -169,7 +169,6 @@ void Timed::apply(ServerCallback * server, const Mechanics * m, const EffectTarg
|
|||||||
case 1:
|
case 1:
|
||||||
//Coronius style specialty bonus.
|
//Coronius style specialty bonus.
|
||||||
//Please note that actual Coronius isnt here, because Slayer is a spell that doesnt affect monster stats and is used only in calculateDmgRange
|
//Please note that actual Coronius isnt here, because Slayer is a spell that doesnt affect monster stats and is used only in calculateDmgRange
|
||||||
power = std::max(5 - tier, 0);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(m->isNegativeSpell())
|
if(m->isNegativeSpell())
|
||||||
|
@ -1467,6 +1467,9 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
|
|||||||
sendAndApply(&vc);
|
sendAndApply(&vc);
|
||||||
visitCastleObjects(obj, hero);
|
visitCastleObjects(obj, hero);
|
||||||
giveSpells (obj, hero);
|
giveSpells (obj, hero);
|
||||||
|
|
||||||
|
if (obj->visitingHero && obj->garrisonHero)
|
||||||
|
useScholarSkill(obj->visitingHero->id, obj->garrisonHero->id);
|
||||||
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
|
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1510,6 +1513,15 @@ void CGameHandler::setMovePoints(SetMovePoints * smp)
|
|||||||
sendAndApply(smp);
|
sendAndApply(smp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CGameHandler::setMovePoints(ObjectInstanceID hid, int val, bool absolute)
|
||||||
|
{
|
||||||
|
SetMovePoints smp;
|
||||||
|
smp.hid = hid;
|
||||||
|
smp.val = val;
|
||||||
|
smp.absolute = absolute;
|
||||||
|
sendAndApply(&smp);
|
||||||
|
}
|
||||||
|
|
||||||
void CGameHandler::setManaPoints(ObjectInstanceID hid, int val)
|
void CGameHandler::setManaPoints(ObjectInstanceID hid, int val)
|
||||||
{
|
{
|
||||||
SetMana sm;
|
SetMana sm;
|
||||||
|
@ -142,6 +142,7 @@ public:
|
|||||||
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
|
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
|
||||||
void giveHeroBonus(GiveBonus * bonus) override;
|
void giveHeroBonus(GiveBonus * bonus) override;
|
||||||
void setMovePoints(SetMovePoints * smp) override;
|
void setMovePoints(SetMovePoints * smp) override;
|
||||||
|
void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override;
|
||||||
void setManaPoints(ObjectInstanceID hid, int val) override;
|
void setManaPoints(ObjectInstanceID hid, int val) override;
|
||||||
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override;
|
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override;
|
||||||
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override;
|
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override;
|
||||||
|
@ -74,6 +74,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer
|
|||||||
sah.slotID = selectSlotForRole(color, sah.roleID);
|
sah.slotID = selectSlotForRole(color, sah.roleID);
|
||||||
sah.player = color;
|
sah.player = color;
|
||||||
sah.hid = hero->getHeroType();
|
sah.hid = hero->getHeroType();
|
||||||
|
sah.replenishPoints = false;
|
||||||
gameHandler->sendAndApply(&sah);
|
gameHandler->sendAndApply(&sah);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +88,7 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns
|
|||||||
sah.hid = hero->getHeroType();
|
sah.hid = hero->getHeroType();
|
||||||
sah.army.clearSlots();
|
sah.army.clearSlots();
|
||||||
sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1);
|
sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1);
|
||||||
|
sah.replenishPoints = false;
|
||||||
|
|
||||||
gameHandler->sendAndApply(&sah);
|
gameHandler->sendAndApply(&sah);
|
||||||
}
|
}
|
||||||
@ -98,6 +100,7 @@ void HeroPoolProcessor::clearHeroFromSlot(const PlayerColor & color, TavernHeroS
|
|||||||
sah.roleID = TavernSlotRole::NONE;
|
sah.roleID = TavernSlotRole::NONE;
|
||||||
sah.slotID = slot;
|
sah.slotID = slot;
|
||||||
sah.hid = HeroTypeID::NONE;
|
sah.hid = HeroTypeID::NONE;
|
||||||
|
sah.replenishPoints = false;
|
||||||
gameHandler->sendAndApply(&sah);
|
gameHandler->sendAndApply(&sah);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +109,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
|
|||||||
SetAvailableHero sah;
|
SetAvailableHero sah;
|
||||||
sah.player = color;
|
sah.player = color;
|
||||||
sah.slotID = slot;
|
sah.slotID = slot;
|
||||||
|
sah.replenishPoints = true;
|
||||||
|
|
||||||
CGHeroInstance *newHero = pickHeroFor(needNativeHero, color);
|
CGHeroInstance *newHero = pickHeroFor(needNativeHero, color);
|
||||||
|
|
||||||
@ -129,6 +133,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
|
|||||||
{
|
{
|
||||||
sah.hid = HeroTypeID::NONE;
|
sah.hid = HeroTypeID::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameHandler->sendAndApply(&sah);
|
gameHandler->sendAndApply(&sah);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ public:
|
|||||||
bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;}
|
bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;}
|
||||||
void giveHeroBonus(GiveBonus * bonus) override {}
|
void giveHeroBonus(GiveBonus * bonus) override {}
|
||||||
void setMovePoints(SetMovePoints * smp) override {}
|
void setMovePoints(SetMovePoints * smp) override {}
|
||||||
|
void setMovePoints(ObjectInstanceID hid, int val, bool absolute) override {};
|
||||||
void setManaPoints(ObjectInstanceID hid, int val) override {}
|
void setManaPoints(ObjectInstanceID hid, int val) override {}
|
||||||
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {}
|
void giveHero(ObjectInstanceID id, PlayerColor player, ObjectInstanceID boatId = ObjectInstanceID()) override {}
|
||||||
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {}
|
void changeObjPos(ObjectInstanceID objid, int3 newPos, const PlayerColor & initiator) override {}
|
||||||
|
@ -45,6 +45,7 @@ public:
|
|||||||
MOCK_CONST_METHOD0(isOffensive, bool());
|
MOCK_CONST_METHOD0(isOffensive, bool());
|
||||||
MOCK_CONST_METHOD0(isSpecial, bool());
|
MOCK_CONST_METHOD0(isSpecial, bool());
|
||||||
MOCK_CONST_METHOD0(isMagical, bool());
|
MOCK_CONST_METHOD0(isMagical, bool());
|
||||||
|
MOCK_CONST_METHOD0(canCastOnSelf, bool());
|
||||||
MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool));
|
MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool));
|
||||||
MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &));
|
MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &));
|
||||||
MOCK_CONST_METHOD0(getCastSound, const std::string &());
|
MOCK_CONST_METHOD0(getCastSound, const std::string &());
|
||||||
|
Reference in New Issue
Block a user