From ba017c443d6fb3847696a892203f4a68388cffd7 Mon Sep 17 00:00:00 2001 From: AlexVinS <alexvins@users.noreply.github.com> Date: Sat, 7 Nov 2015 11:42:06 +0300 Subject: [PATCH 1/2] Start from diff of pull request #124 from vcmi/issue/1372 --- client/battle/CBattleInterface.cpp | 15 +++- lib/GameConstants.cpp | 1 + lib/GameConstants.h | 1 + lib/NetPacksLib.cpp | 2 +- server/CGameHandler.cpp | 112 ++++++++++++++++++++++------- server/CGameHandler.h | 9 ++- 6 files changed, 107 insertions(+), 33 deletions(-) diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 83bf8fde7..d43467c3f 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -2163,7 +2163,15 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) break; case RISE_DEMONS: if (shere && ourStack && !shere->alive()) - legalAction = true; + { + if(!(shere->hasBonusOfType(Bonus::UNDEAD) + || shere->hasBonusOfType(Bonus::NON_LIVING) + || vstd::contains(shere->state, EBattleStackState::SUMMONED) + || vstd::contains(shere->state, EBattleStackState::CLONED) + || shere->hasBonusOfType(Bonus::SIEGE_WEAPON) + )) + legalAction = true; + } break; } if (legalAction) @@ -2320,7 +2328,10 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) break; case RISE_DEMONS: cursorType = ECursor::SPELLBOOK; - realizeAction = [=]{ giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); }; + realizeAction = [=] + { + giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); + }; break; case CATAPULT: cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT; diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 864b1aba2..712cdd722 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -19,6 +19,7 @@ #include "spells/CSpellHandler.h" const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); +const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(255); const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253); const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254); const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 623eb29f5..1ece2170a 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -236,6 +236,7 @@ class SlotID : public BaseForID<SlotID, si32> friend class CNonConstInfoCallback; DLL_LINKAGE static const SlotID COMMANDER_SLOT_PLACEHOLDER; + DLL_LINKAGE static const SlotID SUMMONED_SLOT_PLACEHOLDER; ///<for all summoned creatures, only during battle bool validSlot() const { diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 46d515a78..a5b5cd314 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1616,7 +1616,7 @@ DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs) } CStackBasicDescriptor csbd(creID, amount); - CStack * addedStack = gs->curB->generateNewStack(csbd, attacker, SlotID(255), pos); //TODO: netpacks? + CStack * addedStack = gs->curB->generateNewStack(csbd, attacker, SlotID::SUMMONED_SLOT_PLACEHOLDER, pos); //TODO: netpacks? if (summoned) addedStack->state.insert(EBattleStackState::SUMMONED); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 368201a81..30cdeab52 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -662,8 +662,8 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer sendAndApply(&cs); } - cab1.takeFromArmy(this); - cab2.takeFromArmy(this); //take casualties after battle is deleted + cab1.updateArmy(this); + cab2.updateArmy(this); //take casualties after battle is deleted //if one hero has lost we will erase him if(battleResult.data->winner!=0 && hero1) @@ -3907,18 +3907,16 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) //TODO: From Strategija: //Summon Demon is a level 2 spell. { - StartAction start_action(ba); - sendAndApply(&start_action); - const CStack *summoner = gs->curB->battleGetStackByID(ba.stackNumber), *destStack = gs->curB->battleGetStackByPos(ba.destinationTile, false); + CreatureID summonedType(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype);//in case summoner can summon more than one type of monsters... scream! BattleStackAdded bsa; bsa.attacker = summoner->attackerOwned; - bsa.creID = CreatureID(summoner->getBonusLocalFirst(Selector::type(Bonus::DAEMON_SUMMONING))->subtype); //in case summoner can summon more than one type of monsters... scream! + bsa.creID = summonedType; ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum()); - ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount; + ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;//todo: ignore AGE effect ui64 canRiseHp = std::min(targetHealth, risedHp); ui32 canRiseAmount = canRiseHp / VLC->creh->creatures.at(bsa.creID)->MaxHealth(); @@ -3930,6 +3928,9 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) if (bsa.amount) //there's rare possibility single creature cannot rise desired type { + StartAction start_action(ba); + sendAndApply(&start_action); + BattleStacksRemoved bsr; //remove body bsr.stackIDs.insert(destStack->ID); sendAndApply(&bsr); @@ -3941,9 +3942,9 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) ssp.val = -1; ssp.absolute = false; sendAndApply(&ssp); - } - sendAndApply(&end_action); + sendAndApply(&end_action); + } break; } case Battle::MONSTER_SPELL: @@ -5878,13 +5879,52 @@ void CGameHandler::duelFinished() return; } -CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat) +CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat): + army(_army) { heroWithDeadCommander = ObjectInstanceID(); PlayerColor color = army->tempOwner; if(color == PlayerColor::UNFLAGGABLE) color = PlayerColor::NEUTRAL; + + auto killStack = [&, this](const SlotID slot, const CStackInstance * instance) + { + StackLocation sl(army, slot); + newStackCounts.push_back(TStackAndItsNewCount(sl, 0)); + if(nullptr == instance) + return; + auto c = dynamic_cast <const CCommanderInstance *>(instance); + if (c) //switch commander status to dead + { + auto h = dynamic_cast <const CGHeroInstance *>(army); + if (h && h->commander == c) + heroWithDeadCommander = army->id; //TODO: unify commander handling + } + }; + + //1. Find removed stacks. + for(const auto & slotInfo : army->stacks) + { + const SlotID slot = slotInfo.first; + const CStackInstance * instance = slotInfo.second; + + if(nullptr != instance)//just in case + { + bool found = false; + for(const CStack * sta : bat->stacks) + { + if(sta->base == instance) + { + found = true; + break; + } + } + //stack in this slot was removed == it is dead + if(!found) + killStack(slot, instance); + } + } for(CStack *st : bat->stacks) { @@ -5896,7 +5936,7 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleI //FIXME: this info is also used in BattleInfo::calculateCasualties, refactor st->count = std::max (0, st->count - st->resurrected); - if (!st->count && !st->base) //we can imagine stacks of war mahcines that are not spawned by artifacts? + if (!st->count && !st->base) //we can imagine stacks of war machines that are not spawned by artifacts? { auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber); //catapult artifact remain even if "creature" killed in siege @@ -5907,29 +5947,32 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleI removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true))); } } - - if(!army->slotEmpty(st->slot) && st->count < army->getStackCount(st->slot)) + + if(army->slotEmpty(st->slot)) { - StackLocation sl(army, st->slot); - if(st->alive()) - newStackCounts.push_back(std::pair<StackLocation, int>(sl, st->count)); - else - newStackCounts.push_back(std::pair<StackLocation, int>(sl, 0)); - } - if (st->base && !st->count) - { - auto c = dynamic_cast <const CCommanderInstance *>(st->base); - if (c) //switch commander status to dead + if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER && !vstd::contains(st->state, EBattleStackState::SUMMONED) && st->alive() && st->count > 0) { - auto h = dynamic_cast <const CGHeroInstance *>(army); - if (h && h->commander == c) - heroWithDeadCommander = army->id; //TODO: unify commander handling + //this stack was permanently summoned + const CreatureID summonedType = st->type->idNumber; + summoned[summonedType] += st->count; + } + } + else + { + if(st->count == 0 || !st->alive()) + { + killStack(st->slot, st->base); + } + else if(st->count < army->getStackCount(st->slot)) + { + StackLocation sl(army, st->slot); + newStackCounts.push_back(TStackAndItsNewCount(sl, st->count)); } } } } -void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh) +void CasualtiesAfterBattle::updateArmy(CGameHandler *gh) { for(TStackAndItsNewCount &ncount : newStackCounts) { @@ -5938,6 +5981,21 @@ void CasualtiesAfterBattle::takeFromArmy(CGameHandler *gh) else gh->eraseStack(ncount.first, true); } + for(auto summoned_iter : summoned) + { + SlotID slot = army->getSlotFor(summoned_iter.first); + if(slot.validSlot()) + { + StackLocation location(army, slot); + gh->addToSlot(location, summoned_iter.first.toCreature(), summoned_iter.second); + } + else + { + //even if it will be possible to summon anything permanently it should be checked for free slot + //necromancy is handled separately + gh->complain("No free slot to put summoned creature"); + } + } for (auto al : removedWarMachines) { gh->removeArtifact(al); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 104979140..e8ae1153b 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -69,13 +69,16 @@ public: struct CasualtiesAfterBattle { typedef std::pair<StackLocation, int> TStackAndItsNewCount; + typedef std::map<CreatureID, TQuantity> TSummoned; enum {ERASE = -1}; + const CArmedInstance * army; std::vector<TStackAndItsNewCount> newStackCounts; std::vector<ArtifactLocation> removedWarMachines; - ObjectInstanceID heroWithDeadCommander; //TODO: unify stack loactions + TSummoned summoned; + ObjectInstanceID heroWithDeadCommander; //TODO: unify stack locations - CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat); - void takeFromArmy(CGameHandler *gh); + CasualtiesAfterBattle(const CArmedInstance * _army, BattleInfo *bat); + void updateArmy(CGameHandler *gh); }; class CGameHandler : public IGameCallback, CBattleInfoCallback From 16e0d188807926c53dc5c84e41ac2f762d5ad39d Mon Sep 17 00:00:00 2001 From: AlexVinS <alexvins@users.noreply.github.com> Date: Sat, 30 Jan 2016 00:53:53 +0300 Subject: [PATCH 2/2] Added special slots for war machines and arrow towers --- lib/BattleState.cpp | 16 +++++++-------- lib/GameConstants.cpp | 5 ++++- lib/GameConstants.h | 6 ++++-- server/CGameHandler.cpp | 44 ++++++++++++++++++++++++++++++----------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 178f9b961..5c6fdefb4 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -469,7 +469,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp auto handleWarMachine= [&](int side, ArtifactPosition artslot, CreatureID cretype, BattleHex hex) { if(heroes[side] && heroes[side]->getArt(artslot)) - stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID(255), hex)); + stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID::WAR_MACHINES_SLOT, hex)); }; handleWarMachine(0, ArtifactPosition::MACH1, CreatureID::BALLISTA, 52); @@ -526,15 +526,15 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL) { // keep tower - CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID(255), -2); + CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID::ARROW_TOWERS_SLOT, -2); stacks.push_back(stack); if (curB->town->fortLevel() >= CGTownInstance::CASTLE) { // lower tower + upper tower - CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID(255), -4); + CStack * stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID::ARROW_TOWERS_SLOT, -4); stacks.push_back(stack); - stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID(255), -3); + stack = curB->generateNewStack(CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), false, SlotID::ARROW_TOWERS_SLOT, -3); stacks.push_back(stack); } @@ -712,7 +712,7 @@ std::shared_ptr<CObstacleInstance> BattleInfo::getObstacleOnTile(BattleHex tile) BattlefieldBI::BattlefieldBI BattleInfo::battlefieldTypeToBI(BFieldType bfieldType) { - static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap = + static const std::map<BFieldType, BattlefieldBI::BattlefieldBI> theMap = { {BFieldType::CLOVER_FIELD, BattlefieldBI::CLOVER_FIELD}, {BFieldType::CURSED_GROUND, BattlefieldBI::CURSED_GROUND}, @@ -1141,7 +1141,7 @@ bool CStack::ableToRetaliate() const //FIXME: crash after clone is killed ui8 CStack::counterAttacksTotal() const { - //after dispell bonus should remain during current round + //after dispell bonus should remain during current round ui8 val = 1 + valOfBonuses(Bonus::ADDITIONAL_RETALIATION); vstd::amax(counterAttacksTotalCache, val); return counterAttacksTotalCache; @@ -1183,9 +1183,9 @@ ui32 CStack::calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) cons 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; } diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 712cdd722..a5a4ca7f1 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -19,7 +19,10 @@ #include "spells/CSpellHandler.h" const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2); -const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(255); +const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3); +const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4); +const SlotID SlotID::ARROW_TOWERS_SLOT = SlotID(-5); + const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253); const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254); const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 1ece2170a..d75ffade5 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -237,6 +237,8 @@ class SlotID : public BaseForID<SlotID, si32> DLL_LINKAGE static const SlotID COMMANDER_SLOT_PLACEHOLDER; DLL_LINKAGE static const SlotID SUMMONED_SLOT_PLACEHOLDER; ///<for all summoned creatures, only during battle + DLL_LINKAGE static const SlotID WAR_MACHINES_SLOT; ///<for all war machines during battle + DLL_LINKAGE static const SlotID ARROW_TOWERS_SLOT; ///<for all arrow towers during battle bool validSlot() const { @@ -444,11 +446,11 @@ namespace ESpellCastProblem namespace ECastingMode { - enum ECastingMode + enum ECastingMode { HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING, - SPELL_LIKE_ATTACK, + SPELL_LIKE_ATTACK, PASSIVE_CASTING//f.e. opening battle spells }; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 30cdeab52..3740fcb7f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3914,7 +3914,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) BattleStackAdded bsa; bsa.attacker = summoner->attackerOwned; - bsa.creID = summonedType; + bsa.creID = summonedType; ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum()); ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;//todo: ignore AGE effect @@ -5887,7 +5887,7 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl PlayerColor color = army->tempOwner; if(color == PlayerColor::UNFLAGGABLE) color = PlayerColor::NEUTRAL; - + auto killStack = [&, this](const SlotID slot, const CStackInstance * instance) { StackLocation sl(army, slot); @@ -5928,47 +5928,67 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl for(CStack *st : bat->stacks) { - if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account summoned stacks + if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account temporary summoned stacks continue; if (st->owner != color) //remove only our stacks continue; + logGlobal->debugStream() << "Calculating casualties for " << st->nodeName(); + //FIXME: this info is also used in BattleInfo::calculateCasualties, refactor st->count = std::max (0, st->count - st->resurrected); - if (!st->count && !st->base) //we can imagine stacks of war machines that are not spawned by artifacts? + if(st->slot == SlotID::ARROW_TOWERS_SLOT) + { + //do nothing + logGlobal->debugStream() << "Ignored arrow towers stack"; + } + else if(st->slot == SlotID::WAR_MACHINES_SLOT) { auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber); - //catapult artifact remain even if "creature" killed in siege - if(warMachine != ArtifactID::NONE && warMachine != ArtifactID::CATAPULT) + + if(warMachine == ArtifactID::NONE) { + logGlobal->errorStream() << "Invalid creature in war machine virtual slot: " << st->nodeName(); + } + //catapult artifact remain even if "creature" killed in siege + else if(warMachine != ArtifactID::CATAPULT && !st->count) + { + logGlobal->debugStream() << "War machine has been destroyed"; auto hero = dynamic_ptr_cast<CGHeroInstance> (army); if (hero) removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true))); + else + logGlobal->errorStream() << "War machine in army without hero"; } } - - if(army->slotEmpty(st->slot)) + else if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER) { - if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER && !vstd::contains(st->state, EBattleStackState::SUMMONED) && st->alive() && st->count > 0) + if(st->alive() && st->count > 0) { + logGlobal->debugStream() << "Stack has been permanently summoned"; //this stack was permanently summoned const CreatureID summonedType = st->type->idNumber; summoned[summonedType] += st->count; - } + } } - else + else if(st->base && !army->slotEmpty(st->slot)) { if(st->count == 0 || !st->alive()) { killStack(st->slot, st->base); + logGlobal->debugStream() << "Stack has been destroyed"; } else if(st->count < army->getStackCount(st->slot)) - { + { StackLocation sl(army, st->slot); newStackCounts.push_back(TStackAndItsNewCount(sl, st->count)); } } + else + { + logGlobal->warnStream() << "Unhandled stack " << st->nodeName(); + } } }