/* * GameStatePackVisitor.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 "GameStatePackVisitor.h" #include "CGameState.h" #include "TavernHeroesPool.h" #include "../CPlayerState.h" #include "../CStack.h" #include "../IGameSettings.h" #include "../campaign/CampaignState.h" #include "../entities/artifact/ArtifactUtils.h" #include "../entities/artifact/CArtifact.h" #include "../entities/artifact/CArtifactFittingSet.h" #include "../mapObjects/CGHeroInstance.h" #include "../mapObjects/CGMarket.h" #include "../mapObjects/CGTownInstance.h" #include "../mapObjects/CQuest.h" #include "../mapObjects/FlaggableMapObject.h" #include "../mapObjects/MiscObjects.h" #include "../mapObjects/TownBuildingInstance.h" #include "../mapping/CMap.h" #include "../networkPacks/StackLocation.h" VCMI_LIB_NAMESPACE_BEGIN void GameStatePackVisitor::visitSetResources(SetResources & pack) { assert(pack.player.isValidPlayer()); if(pack.abs) gs.getPlayerState(pack.player)->resources = pack.res; else gs.getPlayerState(pack.player)->resources += pack.res; gs.getPlayerState(pack.player)->resources.amin(GameConstants::PLAYER_RESOURCES_CAP); //just ensure that player resources are not negative //server is responsible to check if player can afford deal //but events on server side are allowed to take more than player have gs.getPlayerState(pack.player)->resources.positive(); } void GameStatePackVisitor::visitSetPrimSkill(SetPrimSkill & pack) { CGHeroInstance * hero = gs.getHero(pack.id); assert(hero); hero->setPrimarySkill(pack.which, pack.val, pack.abs); } void GameStatePackVisitor::visitSetSecSkill(SetSecSkill & pack) { CGHeroInstance *hero = gs.getHero(pack.id); hero->setSecSkillLevel(pack.which, pack.val, pack.abs); } void GameStatePackVisitor::visitSetCommanderProperty(SetCommanderProperty & pack) { const auto & commander = gs.getHero(pack.heroid)->getCommander(); assert (commander); switch (pack.which) { case SetCommanderProperty::BONUS: commander->accumulateBonus (std::make_shared(pack.accumulatedBonus)); break; case SetCommanderProperty::SPECIAL_SKILL: commander->accumulateBonus (std::make_shared(pack.accumulatedBonus)); commander->specialSkills.insert (pack.additionalInfo); break; case SetCommanderProperty::SECONDARY_SKILL: commander->secondarySkills[pack.additionalInfo] = static_cast(pack.amount); break; case SetCommanderProperty::ALIVE: if (pack.amount) commander->setAlive(true); else commander->setAlive(false); break; case SetCommanderProperty::EXPERIENCE: commander->giveTotalStackExperience(pack.amount); commander->nodeHasChanged(); break; } } void GameStatePackVisitor::visitAddQuest(AddQuest & pack) { assert(vstd::contains(gs.players, pack.player)); auto * vec = &gs.players.at(pack.player).quests; if (!vstd::contains(*vec, pack.quest)) vec->push_back(pack.quest); else logNetwork->warn("Warning! Attempt to add duplicated quest"); } void GameStatePackVisitor::visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack) { gs.allocatedArtifacts = pack.allocatedArtifacts; } void GameStatePackVisitor::visitChangeFormation(ChangeFormation & pack) { gs.getHero(pack.hid)->setFormation(pack.formation); } void GameStatePackVisitor::visitHeroVisitCastle(HeroVisitCastle & pack) { CGHeroInstance *h = gs.getHero(pack.hid); CGTownInstance *t = gs.getTown(pack.tid); assert(h); assert(t); if(pack.start()) t->setVisitingHero(h); else t->setVisitingHero(nullptr); } void GameStatePackVisitor::visitChangeSpells(ChangeSpells & pack) { CGHeroInstance *hero = gs.getHero(pack.hid); if(pack.learn) for(const auto & sid : pack.spells) hero->addSpellToSpellbook(sid); else for(const auto & sid : pack.spells) hero->removeSpellFromSpellbook(sid); } void GameStatePackVisitor::visitSetResearchedSpells(SetResearchedSpells & pack) { CGTownInstance *town = gs.getTown(pack.tid); town->spells[pack.level] = pack.spells; town->spellResearchCounterDay++; if(pack.accepted) town->spellResearchAcceptedCounter++; } void GameStatePackVisitor::visitSetMana(SetMana & pack) { CGHeroInstance * hero = gs.getHero(pack.hid); assert(hero); if(pack.absolute) hero->mana = pack.val; else hero->mana += pack.val; vstd::amax(hero->mana, 0); //not less than 0 } void GameStatePackVisitor::visitSetMovePoints(SetMovePoints & pack) { CGHeroInstance *hero = gs.getHero(pack.hid); assert(hero); if(pack.absolute) hero->setMovementPoints(pack.val); else hero->setMovementPoints(hero->movementPointsRemaining() + pack.val); } void GameStatePackVisitor::visitFoWChange(FoWChange & pack) { TeamState * team = gs.getPlayerTeam(pack.player); auto & fogOfWarMap = team->fogOfWarMap; for(const int3 & t : pack.tiles) fogOfWarMap[t.z][t.x][t.y] = pack.mode != ETileVisibility::HIDDEN; if (pack.mode == ETileVisibility::HIDDEN) //do not hide too much { std::unordered_set tilesRevealed; for (auto & o : gs.getMap().getObjects()) { if (o->asOwnable()) { if(vstd::contains(team->players, o->getOwner())) //check owned observators gs.getTilesInRange(tilesRevealed, o->getSightCenter(), o->getSightRadius(), ETileVisibility::HIDDEN, o->tempOwner); } } for(const int3 & t : tilesRevealed) //probably not the most optimal solution ever fogOfWarMap[t.z][t.x][t.y] = 1; } } void GameStatePackVisitor::visitSetAvailableHero(SetAvailableHero & pack) { gs.heroesPool->setHeroForPlayer(pack.player, pack.slotID, pack.hid, pack.army, pack.roleID, pack.replenishPoints); } void GameStatePackVisitor::visitGiveBonus(GiveBonus & pack) { CBonusSystemNode *cbsn = nullptr; switch(pack.who) { case GiveBonus::ETarget::OBJECT: cbsn = dynamic_cast(gs.getObjInstance(pack.id.as())); break; case GiveBonus::ETarget::HERO_COMMANDER: cbsn = gs.getHero(pack.id.as())->getCommander(); break; case GiveBonus::ETarget::PLAYER: cbsn = gs.getPlayerState(pack.id.as()); break; case GiveBonus::ETarget::BATTLE: assert(Bonus::OneBattle(&pack.bonus)); cbsn = dynamic_cast(gs.getBattle(pack.id.as())); break; } assert(cbsn); if(Bonus::OneWeek(&pack.bonus)) pack.bonus.turnsRemain = 8 - gs.getDate(Date::DAY_OF_WEEK); // set correct number of days before adding bonus auto b = std::make_shared(pack.bonus); cbsn->addNewBonus(b); } void GameStatePackVisitor::visitChangeObjPos(ChangeObjPos & pack) { CGObjectInstance *obj = gs.getObjInstance(pack.objid); if(!obj) { logNetwork->error("Wrong ChangeObjPos: object %d doesn't exist!", pack.objid.getNum()); return; } gs.getMap().moveObject(pack.objid, pack.nPos + obj->getVisitableOffset()); } void GameStatePackVisitor::visitChangeObjectVisitors(ChangeObjectVisitors & pack) { auto objectPtr = gs.getObjInstance(pack.object); switch (pack.mode) { case ChangeObjectVisitors::VISITOR_ADD_HERO: gs.getHero(pack.hero)->visitedObjects.insert(pack.object); [[fallthrough]]; case ChangeObjectVisitors::VISITOR_ADD_PLAYER: gs.getPlayerTeam(gs.getHero(pack.hero)->tempOwner)->scoutedObjects.insert(pack.object); gs.getPlayerState(gs.getHero(pack.hero)->tempOwner)->visitedObjects.insert(pack.object); gs.getPlayerState(gs.getHero(pack.hero)->tempOwner)->visitedObjectsGlobal.insert({objectPtr->ID, objectPtr->subID}); break; case ChangeObjectVisitors::VISITOR_CLEAR: // remove visit info from all heroes, including those that are not present on map for (auto heroID : gs.getMap().getHeroesOnMap()) gs.getHero(heroID)->visitedObjects.erase(pack.object); for (auto heroID : gs.getMap().getHeroesInPool()) gs.getMap().tryGetFromHeroPool(heroID)->visitedObjects.erase(pack.object); for(auto &elem : gs.players) elem.second.visitedObjects.erase(pack.object); for(auto &elem : gs.teams) elem.second.scoutedObjects.erase(pack.object); break; case ChangeObjectVisitors::VISITOR_SCOUTED: gs.getPlayerTeam(gs.getHero(pack.hero)->tempOwner)->scoutedObjects.insert(pack.object); break; } } void GameStatePackVisitor::visitChangeArtifactsCostume(ChangeArtifactsCostume & pack) { auto & allCostumes = gs.getPlayerState(pack.player)->costumesArtifacts; if(const auto & costume = allCostumes.find(pack.costumeIdx); costume != allCostumes.end()) costume->second = pack.costumeSet; else allCostumes.try_emplace(pack.costumeIdx, pack.costumeSet); } void GameStatePackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack) { PlayerState *p = gs.getPlayerState(pack.player); if(pack.victoryLossCheckResult.victory()) { p->status = EPlayerStatus::WINNER; // TODO: Campaign-specific code might as well go somewhere else // keep all heroes from the winning player if(p->human && gs.getStartInfo()->campState) { std::vector crossoverHeroes; for (auto hero : p->getHeroes()) if (hero->tempOwner == pack.player) crossoverHeroes.push_back(hero); gs.getStartInfo()->campState->setCurrentMapAsConquered(crossoverHeroes); } } else { p->status = EPlayerStatus::LOSER; } // defeated player may be making turn right now gs.actingPlayers.erase(pack.player); } void GameStatePackVisitor::visitPlayerReinitInterface(PlayerReinitInterface & pack) { if(!gs.getStartInfo()) return; //TODO: what does mean if more that one player connected? if(pack.playerConnectionId == PlayerSettings::PLAYER_AI) { for(const auto & player : pack.players) gs.getStartInfo()->getIthPlayersSettings(player).connectedPlayerIDs.clear(); } } void GameStatePackVisitor::visitRemoveBonus(RemoveBonus & pack) { CBonusSystemNode *node = nullptr; switch(pack.who) { case GiveBonus::ETarget::OBJECT: node = dynamic_cast(gs.getObjInstance(pack.whoID.as())); break; case GiveBonus::ETarget::PLAYER: node = gs.getPlayerState(pack.whoID.as()); break; case GiveBonus::ETarget::BATTLE: assert(Bonus::OneBattle(&pack.bonus)); node = dynamic_cast(gs.getBattle(pack.whoID.as())); break; } BonusList &bonuses = node->getExportedBonusList(); for(const auto & b : bonuses) { if(b->source == pack.source && b->sid == pack.id) { pack.bonus = *b; //backup bonus (to show to interfaces later) node->removeBonus(b); break; } } } void GameStatePackVisitor::visitRemoveObject(RemoveObject & pack) { CGObjectInstance *obj = gs.getObjInstance(pack.objectID); logGlobal->debug("removing object id=%d; address=%x; name=%s", pack.objectID, (intptr_t)obj, obj->getObjectName()); if (pack.initiator.isValidPlayer()) gs.getPlayerState(pack.initiator)->destroyedObjects.insert(pack.objectID); if(obj->getOwner().isValidPlayer()) { gs.getPlayerState(obj->getOwner())->removeOwnedObject(obj); //object removed via map event or hero got beaten FlaggableMapObject* flaggableObject = dynamic_cast(obj); if(flaggableObject) { flaggableObject->markAsDeleted(); } } if(obj->ID == Obj::HERO) //remove beaten hero { auto beatenHero = dynamic_cast(obj); assert(beatenHero); auto * siegeNode = beatenHero->whereShouldBeAttachedOnSiege(gs); vstd::erase_if(beatenHero->artifactsInBackpack, [](const ArtSlotInfo& asi) { return asi.getArt()->getTypeId() == ArtifactID::GRAIL; }); if(beatenHero->getVisitedTown()) { if(beatenHero->getVisitedTown()->getGarrisonHero() == beatenHero) beatenHero->getVisitedTown()->setGarrisonedHero(nullptr); else beatenHero->getVisitedTown()->setVisitingHero(nullptr); beatenHero->setVisitedTown(nullptr, false); } beatenHero->detachFromBonusSystem(gs); beatenHero->tempOwner = PlayerColor::NEUTRAL; //no one owns beaten hero // FIXME: workaround: // hero should be attached to siegeNode after battle // however this code might also be called on dismissing hero while in town if (siegeNode && vstd::contains(beatenHero->getParentNodes(), siegeNode)) beatenHero->detachFrom(*siegeNode); //If hero on Boat is removed, the Boat disappears if(beatenHero->inBoat()) { auto boat = beatenHero->getBoat(); beatenHero->setBoat(nullptr); gs.getMap().eraseObject(boat->id); } auto beatenObject = gs.getMap().eraseObject(obj->id); //return hero to the pool, so he may reappear in tavern gs.heroesPool->addHeroToPool(beatenHero->getHeroTypeID()); gs.getMap().addToHeroPool(std::dynamic_pointer_cast(beatenObject)); return; } const auto * quest = dynamic_cast(obj); if (quest) { for (auto &player : gs.players) { vstd::erase_if(player.second.quests, [obj](const QuestInfo & q){ return q.obj == obj->id; }); } } gs.getMap().eraseObject(pack.objectID); gs.getMap().calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles } static int getDir(const int3 & src, const int3 & dst) { int ret = -1; if(dst.x+1 == src.x && dst.y+1 == src.y) //tl { ret = 1; } else if(dst.x == src.x && dst.y+1 == src.y) //t { ret = 2; } else if(dst.x-1 == src.x && dst.y+1 == src.y) //tr { ret = 3; } else if(dst.x-1 == src.x && dst.y == src.y) //r { ret = 4; } else if(dst.x-1 == src.x && dst.y-1 == src.y) //br { ret = 5; } else if(dst.x == src.x && dst.y-1 == src.y) //b { ret = 6; } else if(dst.x+1 == src.x && dst.y-1 == src.y) //bl { ret = 7; } else if(dst.x+1 == src.x && dst.y == src.y) //l { ret = 8; } return ret; } void GameStatePackVisitor::visitTryMoveHero(TryMoveHero & pack) { CGHeroInstance *h = gs.getHero(pack.id); if (!h) { logGlobal->error("Attempt ot move unavailable hero %d", pack.id.getNum()); return; } const TerrainTile & fromTile = gs.getMap().getTile(h->convertToVisitablePos(pack.start)); const TerrainTile & destTile = gs.getMap().getTile(h->convertToVisitablePos(pack.end)); h->setMovementPoints(pack.movePoints); if((pack.result == TryMoveHero::SUCCESS || pack.result == TryMoveHero::BLOCKING_VISIT || pack.result == TryMoveHero::EMBARK || pack.result == TryMoveHero::DISEMBARK) && pack.start != pack.end) { auto dir = getDir(pack.start, pack.end); if(dir > 0 && dir <= 8) h->moveDir = dir; //else don`t change move direction - hero might have traversed the subterranean gate, direction should be kept } if(pack.result == TryMoveHero::EMBARK) //hero enters boat at destination tile { const TerrainTile &tt = gs.getMap().getTile(h->convertToVisitablePos(pack.end)); ObjectInstanceID topObjectID = tt.visitableObjects.back(); CGObjectInstance * topObject = gs.getObjInstance(topObjectID); assert(tt.visitableObjects.size() >= 1 && topObject->ID == Obj::BOAT); //the only visitable object at destination is Boat auto * boat = dynamic_cast(topObject); assert(boat); gs.getMap().hideObject(boat); //hero blockvis mask will be used, we don't need to duplicate it with boat h->setBoat(boat); } else if(pack.result == TryMoveHero::DISEMBARK) //hero leaves boat to destination tile { auto * b = h->getBoat(); b->direction = h->moveDir; b->pos = pack.start; gs.getMap().showObject(b); h->setBoat(nullptr); } if(pack.start != pack.end && (pack.result == TryMoveHero::SUCCESS || pack.result == TryMoveHero::TELEPORTATION || pack.result == TryMoveHero::EMBARK || pack.result == TryMoveHero::DISEMBARK)) { gs.getMap().hideObject(h); h->setAnchorPos(pack.end); if(auto * b = h->getBoat()) b->setAnchorPos(pack.end); gs.getMap().showObject(h); } auto & fogOfWarMap = gs.getPlayerTeam(h->getOwner())->fogOfWarMap; for(const int3 & t : pack.fowRevealed) fogOfWarMap[t.z][t.x][t.y] = 1; if (fromTile.getTerrainID() != destTile.getTerrainID()) h->nodeHasChanged(); // update bonuses with terrain limiter } void GameStatePackVisitor::visitNewStructures(NewStructures & pack) { CGTownInstance *t = gs.getTown(pack.tid); for(const auto & id : pack.bid) { assert(t->getTown()->buildings.at(id) != nullptr); t->addBuilding(id); } t->updateAppearance(); t->built = pack.built; t->recreateBuildingsBonuses(); } void GameStatePackVisitor::visitRazeStructures(RazeStructures & pack) { CGTownInstance *t = gs.getTown(pack.tid); for(const auto & id : pack.bid) { t->removeBuilding(id); t->updateAppearance(); } t->destroyed = pack.destroyed; //yeaha t->recreateBuildingsBonuses(); } void GameStatePackVisitor::visitSetAvailableCreatures(SetAvailableCreatures & pack) { auto * dw = dynamic_cast(gs.getObjInstance(pack.tid)); assert(dw); dw->creatures = pack.creatures; } void GameStatePackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack) { CGTownInstance *t = gs.getTown(pack.tid); CGHeroInstance * v = gs.getHero(pack.visiting); CGHeroInstance * g = gs.getHero(pack.garrison); bool newVisitorComesFromGarrison = v && v == t->getGarrisonHero(); bool newGarrisonComesFromVisiting = g && g == t->getVisitingHero(); if(newVisitorComesFromGarrison) t->setGarrisonedHero(nullptr); if(newGarrisonComesFromVisiting) t->setVisitingHero(nullptr); if(!newGarrisonComesFromVisiting || v) t->setVisitingHero(v); if(!newVisitorComesFromGarrison || g) t->setGarrisonedHero(g); if(v) gs.getMap().showObject(v); if(g) gs.getMap().hideObject(g); } void GameStatePackVisitor::visitHeroRecruited(HeroRecruited & pack) { auto h = gs.heroesPool->takeHeroFromPool(pack.hid); CGTownInstance *t = gs.getTown(pack.tid); PlayerState *p = gs.getPlayerState(pack.player); if (pack.boatId.hasValue()) { CGObjectInstance *obj = gs.getObjInstance(pack.boatId); auto * boat = dynamic_cast(obj); if (boat) { gs.getMap().hideObject(boat); h->setBoat(boat); } } h->setOwner(pack.player); h->pos = pack.tile; h->updateAppearance(); assert(h->id.hasValue()); gs.getMap().addNewObject(h); p->addOwnedObject(h.get()); h->attachToBonusSystem(gs); if(t) t->setVisitingHero(h.get()); } void GameStatePackVisitor::visitGiveHero(GiveHero & pack) { CGHeroInstance *h = gs.getHero(pack.id); if (pack.boatId.hasValue()) { CGObjectInstance *obj = gs.getObjInstance(pack.boatId); auto * boat = dynamic_cast(obj); if (boat) { gs.getMap().hideObject(boat); h->setBoat(boat); } } //bonus system h->detachFrom(gs.globalEffects); h->attachTo(*gs.getPlayerState(pack.player)); auto oldVisitablePos = h->visitablePos(); gs.getMap().hideObject(h); h->updateAppearance(); h->setOwner(pack.player); h->setMovementPoints(h->movementPointsLimit(true)); h->setAnchorPos(h->convertFromVisitablePos(oldVisitablePos)); gs.getMap().heroAddedToMap(h); gs.getPlayerState(h->getOwner())->addOwnedObject(h); gs.getMap().showObject(h); h->setVisitedTown(nullptr, false); } void GameStatePackVisitor::visitNewObject(NewObject & pack) { gs.getMap().addNewObject(pack.newObject); gs.getMap().calculateGuardingGreaturePositions(); // attach newly spawned wandering monster to global bonus system node auto newArmy = std::dynamic_pointer_cast(pack.newObject); if (newArmy) newArmy->attachToBonusSystem(gs); logGlobal->debug("Added object id=%d; name=%s", pack.newObject->id, pack.newObject->getObjectName()); } void GameStatePackVisitor::visitNewArtifact(NewArtifact & pack) { auto art = gs.createArtifact(pack.artId, pack.spellId); PutArtifact pa(art->getId(), ArtifactLocation(pack.artHolder, pack.pos), false); pa.visit(*this); } void GameStatePackVisitor::visitChangeStackCount(ChangeStackCount & pack) { auto * srcObj = gs.getArmyInstance(pack.army); if(!srcObj) throw std::runtime_error("ChangeStackCount: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption."); if(pack.absoluteValue) srcObj->setStackCount(pack.slot, pack.count); else srcObj->changeStackCount(pack.slot, pack.count); } void GameStatePackVisitor::visitSetStackType(SetStackType & pack) { auto * srcObj = gs.getArmyInstance(pack.army); if(!srcObj) throw std::runtime_error("SetStackType: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption."); srcObj->setStackType(pack.slot, pack.type); } void GameStatePackVisitor::visitEraseStack(EraseStack & pack) { auto * srcObj = gs.getArmyInstance(pack.army); if(!srcObj) throw std::runtime_error("EraseStack: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption."); srcObj->eraseStack(pack.slot); } void GameStatePackVisitor::visitSwapStacks(SwapStacks & pack) { auto * srcObj = gs.getArmyInstance(pack.srcArmy); if(!srcObj) throw std::runtime_error("SwapStacks: invalid army object " + std::to_string(pack.srcArmy.getNum()) + ", possible game state corruption."); auto * dstObj = gs.getArmyInstance(pack.dstArmy); if(!dstObj) throw std::runtime_error("SwapStacks: invalid army object " + std::to_string(pack.dstArmy.getNum()) + ", possible game state corruption."); auto s1 = srcObj->detachStack(pack.srcSlot); auto s2 = dstObj->detachStack(pack.dstSlot); srcObj->putStack(pack.srcSlot, std::move(s2)); dstObj->putStack(pack.dstSlot, std::move(s1)); } void GameStatePackVisitor::visitInsertNewStack(InsertNewStack & pack) { if(auto * obj = gs.getArmyInstance(pack.army)) obj->putStack(pack.slot, std::make_unique(gs.cb, pack.type, pack.count)); else throw std::runtime_error("InsertNewStack: invalid army object " + std::to_string(pack.army.getNum()) + ", possible game state corruption."); } void GameStatePackVisitor::visitRebalanceStacks(RebalanceStacks & pack) { auto * srcObj = gs.getArmyInstance(pack.srcArmy); if(!srcObj) throw std::runtime_error("RebalanceStacks: invalid army object " + std::to_string(pack.srcArmy.getNum()) + ", possible game state corruption."); auto * dstObj = gs.getArmyInstance(pack.dstArmy); if(!dstObj) throw std::runtime_error("RebalanceStacks: invalid army object " + std::to_string(pack.dstArmy.getNum()) + ", possible game state corruption."); StackLocation src(srcObj->id, pack.srcSlot); StackLocation dst(dstObj->id, pack.dstSlot); [[maybe_unused]] const CCreature * srcType = srcObj->getCreature(src.slot); const CCreature * dstType = dstObj->getCreature(dst.slot); TQuantity srcCount = srcObj->getStackCount(src.slot); if(srcCount == pack.count) //moving whole stack { if(dstType) //stack at dest -> merge { assert(dstType == srcType); const auto srcHero = dynamic_cast(srcObj); const auto dstHero = dynamic_cast(dstObj); auto srcStack = const_cast(srcObj->getStackPtr(src.slot)); auto dstStack = const_cast(dstObj->getStackPtr(dst.slot)); if(srcStack->getArt(ArtifactPosition::CREATURE_SLOT)) { if(auto dstArt = dstStack->getArt(ArtifactPosition::CREATURE_SLOT)) { bool artifactIsLost = true; if(srcHero) { auto dstSlot = ArtifactUtils::getArtBackpackPosition(srcHero, dstArt->getTypeId()); if (dstSlot != ArtifactPosition::PRE_FIRST) { gs.getMap().moveArtifactInstance(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot); artifactIsLost = false; } } if (artifactIsLost) { BulkEraseArtifacts ea; ea.artHolder = dstHero->id; ea.posPack.emplace_back(ArtifactPosition::CREATURE_SLOT); ea.creature = dst.slot; ea.visit(*this); logNetwork->warn("Cannot move artifact! No free slots"); } gs.getMap().moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT); //TODO: choose from dialog } else //just move to the other slot before stack gets erased { gs.getMap().moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT); } } auto movedStack = srcObj->detachStack(src.slot); dstObj->joinStack(dst.slot, std::move(movedStack)); } else { auto movedStack = srcObj->detachStack(src.slot); dstObj->putStack(dst.slot, std::move(movedStack)); } } else { auto movedStack = srcObj->splitStack(src.slot, pack.count); if(dstType) //stack at dest -> rebalance { assert(dstType == srcType); dstObj->joinStack(dst.slot, std::move(movedStack)); } else //move new stack to an empty slot { dstObj->putStack(dst.slot, std::move(movedStack)); } } srcObj->nodeHasChanged(); if (srcObj != dstObj) dstObj->nodeHasChanged(); } void GameStatePackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack) { for(auto & move : pack.moves) move.visit(*this); } void GameStatePackVisitor::visitPutArtifact(PutArtifact & pack) { auto art = gs.getArtInstance(pack.id); assert(!art->getParentNodes().empty()); auto hero = gs.getHero(pack.al.artHolder); assert(hero); assert(art && art->canBePutAt(hero, pack.al.slot)); assert(ArtifactUtils::checkIfSlotValid(*hero, pack.al.slot)); gs.getMap().putArtifactInstance(*hero, art->getId(), pack.al.slot); } void GameStatePackVisitor::visitBulkEraseArtifacts(BulkEraseArtifacts & pack) { const auto artSet = gs.getArtSet(pack.artHolder); assert(artSet); std::sort(pack.posPack.begin(), pack.posPack.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool { return slot0.num > slot1.num; }); for(const auto & slot : pack.posPack) { const auto slotInfo = artSet->getSlot(slot); const ArtifactInstanceID artifactID = slotInfo->artifactID; const CArtifactInstance * artifact = gs.getArtInstance(artifactID); if(slotInfo->locked) { logGlobal->debug("Erasing locked artifact: %s", artifact->getType()->getNameTranslated()); DisassembledArtifact dis; dis.al.artHolder = pack.artHolder; for(auto & slotInfoWorn : artSet->artifactsWorn) { auto art = slotInfoWorn.second.getArt(); if(art->isCombined() && art->isPart(artifact)) { dis.al.slot = artSet->getArtPos(art); break; } } assert((dis.al.slot != ArtifactPosition::PRE_FIRST) && "Failed to determine the assembly this locked artifact belongs to"); logGlobal->debug("Found the corresponding assembly: %s", artSet->getArt(dis.al.slot)->getType()->getNameTranslated()); dis.visit(*this); } else { logGlobal->debug("Erasing artifact %s", artifact->getType()->getNameTranslated()); } gs.getMap().removeArtifactInstance(*artSet, slot); } } void GameStatePackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack) { const auto bulkArtsRemove = [this](std::vector & artsPack, CArtifactSet & artSet) { std::vector packToRemove; for(const auto & slotsPair : artsPack) packToRemove.push_back(slotsPair.srcPos); std::sort(packToRemove.begin(), packToRemove.end(), [](const ArtifactPosition & slot0, const ArtifactPosition & slot1) -> bool { return slot0.num > slot1.num; }); for(const auto & slot : packToRemove) gs.getMap().removeArtifactInstance(artSet, slot); }; const auto bulkArtsPut = [this](std::vector & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet) { for(const auto & slotsPair : artsPack) { auto * art = initArtSet.getArt(slotsPair.srcPos); assert(art); gs.getMap().putArtifactInstance(dstArtSet, art->getId(), slotsPair.dstPos); } }; auto * leftSet = gs.getArtSet(ArtifactLocation(pack.srcArtHolder, pack.srcCreature)); assert(leftSet); auto * rightSet = gs.getArtSet(ArtifactLocation(pack.dstArtHolder, pack.dstCreature)); assert(rightSet); CArtifactFittingSet artInitialSetLeft(*leftSet); bulkArtsRemove(pack.artsPack0, *leftSet); if(!pack.artsPack1.empty()) { CArtifactFittingSet artInitialSetRight(*rightSet); bulkArtsRemove(pack.artsPack1, *rightSet); bulkArtsPut(pack.artsPack1, artInitialSetRight, *leftSet); } bulkArtsPut(pack.artsPack0, artInitialSetLeft, *rightSet); } void GameStatePackVisitor::visitAssembledArtifact(AssembledArtifact & pack) { auto artSet = gs.getArtSet(pack.al.artHolder); assert(artSet); const auto transformedArt = artSet->getArt(pack.al.slot); assert(transformedArt); const auto builtArt = pack.artId.toArtifact(); assert(vstd::contains_if(ArtifactUtils::assemblyPossibilities(artSet, transformedArt->getTypeId()), [=](const CArtifact * art)->bool { return art->getId() == builtArt->getId(); })); auto * combinedArt = gs.getMap().createArtifactComponent(pack.artId); // Find slots for all involved artifacts std::set> slotsInvolved = { pack.al.slot }; CArtifactFittingSet fittingSet(*artSet); auto parts = builtArt->getConstituents(); parts.erase(std::find(parts.begin(), parts.end(), transformedArt->getType())); for(const auto constituent : parts) { const auto slot = fittingSet.getArtPos(constituent->getId(), false, false); fittingSet.lockSlot(slot); assert(slot != ArtifactPosition::PRE_FIRST); slotsInvolved.insert(slot); } // Find a slot for combined artifact if(ArtifactUtils::isSlotEquipment(pack.al.slot) && ArtifactUtils::isSlotBackpack(*slotsInvolved.begin())) { pack.al.slot = ArtifactPosition::BACKPACK_START; } else if(ArtifactUtils::isSlotBackpack(pack.al.slot)) { for(const auto & slot : slotsInvolved) if(ArtifactUtils::isSlotBackpack(slot)) pack.al.slot = slot; } else { for(const auto & slot : slotsInvolved) if(!vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), pack.al.slot) && vstd::contains(builtArt->getPossibleSlots().at(artSet->bearerType()), slot)) { pack.al.slot = slot; break; } } // Delete parts from hero for(const auto & slot : slotsInvolved) { const auto constituentInstance = artSet->getArt(slot); gs.getMap().removeArtifactInstance(*artSet, slot); if(!combinedArt->getType()->isFused()) { if(ArtifactUtils::isSlotEquipment(pack.al.slot) && slot != pack.al.slot) combinedArt->addPart(constituentInstance, slot); else combinedArt->addPart(constituentInstance, ArtifactPosition::PRE_FIRST); } } // Put new combined artifacts gs.getMap().putArtifactInstance(*artSet, combinedArt->getId(), pack.al.slot); } void GameStatePackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack) { auto hero = gs.getHero(pack.al.artHolder); assert(hero); auto disassembledArtID = hero->getArtID(pack.al.slot); auto disassembledArt = gs.getArtInstance(disassembledArtID); assert(disassembledArt); const auto parts = disassembledArt->getPartsInfo(); gs.getMap().removeArtifactInstance(*hero, pack.al.slot); for(auto & part : parts) { // ArtifactPosition::PRE_FIRST is value of main part slot -> it'll replace combined artifact in its pos auto slot = (ArtifactUtils::isSlotEquipment(part.slot) ? part.slot : pack.al.slot); disassembledArt->detachFromSource(*part.getArtifact()); gs.getMap().putArtifactInstance(*hero, part.getArtifact()->getId(), slot); } gs.getMap().eraseArtifactInstance(disassembledArt->getId()); } void GameStatePackVisitor::visitHeroVisit(HeroVisit & pack) { } void GameStatePackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts & pack) { if(pack.id != ObjectInstanceID::NONE) { if(auto * bm = dynamic_cast(gs.getObjInstance(pack.id))) { bm->artifacts = pack.arts; } else { logNetwork->error("Wrong black market id!"); } } else { gs.getMap().townMerchantArtifacts = pack.arts; } } void GameStatePackVisitor::visitNewTurn(NewTurn & pack) { gs.day = pack.day; // Update bonuses before doing anything else so hero don't get more MP than needed gs.globalEffects.removeBonusesRecursive(Bonus::OneDay); //works for children -> all game objs gs.globalEffects.reduceBonusDurations(Bonus::NDays); gs.globalEffects.reduceBonusDurations(Bonus::OneWeek); //TODO not really a single root hierarchy, what about bonuses placed elsewhere? [not an issue with H3 mechanics but in the future...] for(auto & manaPack : pack.heroesMana) manaPack.visit(*this); for(auto & movePack : pack.heroesMovement) movePack.visit(*this); gs.heroesPool->onNewDay(); for(auto & entry : pack.playerIncome) { gs.getPlayerState(entry.first)->resources += entry.second; gs.getPlayerState(entry.first)->resources.amin(GameConstants::PLAYER_RESOURCES_CAP); } for(auto & creatureSet : pack.availableCreatures) //set available creatures in towns creatureSet.visit(*this); for (const auto & townID : gs.getMap().getAllTowns()) { auto t = gs.getTown(townID); t->built = 0; t->spellResearchCounterDay = 0; } if(pack.newRumor) gs.currentRumor = *pack.newRumor; } void GameStatePackVisitor::visitSetObjectProperty(SetObjectProperty & pack) { CGObjectInstance *obj = gs.getObjInstance(pack.id); if(!obj) { logNetwork->error("Wrong object ID - property cannot be set!"); return; } auto * cai = dynamic_cast(obj); if(pack.what == ObjProperty::OWNER && obj->asOwnable()) { PlayerColor oldOwner = obj->getOwner(); PlayerColor newOwner = pack.identifier.as(); if(oldOwner.isValidPlayer()) gs.getPlayerState(oldOwner)->removeOwnedObject(obj); if(newOwner.isValidPlayer()) gs.getPlayerState(newOwner)->addOwnedObject(obj); } if(pack.what == ObjProperty::OWNER) { if(obj->ID == Obj::TOWN) { auto * t = dynamic_cast(obj); assert(t); PlayerColor oldOwner = t->tempOwner; if(oldOwner.isValidPlayer()) { auto * state = gs.getPlayerState(oldOwner); if(state->getTowns().empty()) state->daysWithoutCastle = 0; } if(pack.identifier.as().isValidPlayer()) { //reset counter before NewTurn to avoid no town message if game loaded at turn when one already captured PlayerState * p = gs.getPlayerState(pack.identifier.as()); if(p->daysWithoutCastle) p->daysWithoutCastle = std::nullopt; } } obj->detachFromBonusSystem(gs); obj->setProperty(pack.what, pack.identifier); obj->attachToBonusSystem(gs); } else //not an armed instance { obj->setProperty(pack.what, pack.identifier); } } void GameStatePackVisitor::visitHeroLevelUp(HeroLevelUp & pack) { auto * hero = gs.getHero(pack.heroId); assert(hero); hero->levelUp(pack.skills); } void GameStatePackVisitor::visitCommanderLevelUp(CommanderLevelUp & pack) { auto * hero = gs.getHero(pack.heroId); assert(hero); const auto & commander = hero->getCommander(); assert(commander); commander->levelUp(); } void GameStatePackVisitor::visitBattleStart(BattleStart & pack) { assert(pack.battleID == gs.nextBattleID); pack.info->battleID = gs.nextBattleID; pack.info->localInit(); gs.currentBattles.push_back(std::move(pack.info)); gs.nextBattleID = BattleID(gs.nextBattleID.getNum() + 1); } void GameStatePackVisitor::visitBattleNextRound(BattleNextRound & pack) { gs.getBattle(pack.battleID)->nextRound(); } void GameStatePackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & pack) { gs.getBattle(pack.battleID)->nextTurn(pack.stack, pack.reason); } void GameStatePackVisitor::visitBattleTriggerEffect(BattleTriggerEffect & pack) { CStack * st = gs.getBattle(pack.battleID)->getStack(pack.stackID); assert(st); switch(static_cast(pack.effect)) { case BonusType::HP_REGENERATION: { int64_t toHeal = pack.val; st->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT); break; } case BonusType::MANA_DRAIN: { CGHeroInstance * h = gs.getHero(ObjectInstanceID(pack.additionalInfo)); st->drainedMana = true; h->mana -= pack.val; vstd::amax(h->mana, 0); break; } case BonusType::POISON: { auto b = st->getLocalBonus(Selector::source(BonusSource::SPELL_EFFECT, SpellID(SpellID::POISON)) .And(Selector::type()(BonusType::STACK_HEALTH))); if (b) b->val = pack.val; break; } case BonusType::ENCHANTER: case BonusType::MORALE: break; case BonusType::FEAR: st->fear = true; break; default: logNetwork->error("Unrecognized trigger effect type %d", pack.effect); } } void GameStatePackVisitor::visitBattleUpdateGateState(BattleUpdateGateState & pack) { if(gs.getBattle(pack.battleID)) gs.getBattle(pack.battleID)->si.gateState = pack.state; } void GameStatePackVisitor::visitBattleCancelled(BattleCancelled & pack) { auto currentBattle = boost::range::find_if(gs.currentBattles, [&](const auto & battle) { return battle->battleID == pack.battleID; }); assert(currentBattle != gs.currentBattles.end()); gs.currentBattles.erase(currentBattle); } void GameStatePackVisitor::visitBattleResultAccepted(BattleResultAccepted & pack) { // Remove any "until next battle" bonuses if(const auto attackerHero = gs.getHero(pack.heroResult[BattleSide::ATTACKER].heroID)) attackerHero->removeBonusesRecursive(Bonus::OneBattle); if(const auto defenderHero = gs.getHero(pack.heroResult[BattleSide::DEFENDER].heroID)) defenderHero->removeBonusesRecursive(Bonus::OneBattle); if(pack.winnerSide != BattleSide::NONE) { // Grow up growing artifacts if(const auto winnerHero = gs.getHero(pack.heroResult[pack.winnerSide].heroID)) { if(winnerHero->getCommander() && winnerHero->getCommander()->alive) { for(auto & art : winnerHero->getCommander()->artifactsWorn) gs.getArtInstance(art.second.getID())->growingUp(); } for(auto & art : winnerHero->artifactsWorn) gs.getArtInstance(art.second.getID())->growingUp(); } } if(gs.getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE)) { if(const auto attackerArmy = gs.getArmyInstance(pack.heroResult[BattleSide::ATTACKER].armyID)) attackerArmy->giveAverageStackExperience(pack.heroResult[BattleSide::ATTACKER].exp); if(const auto defenderArmy = gs.getArmyInstance(pack.heroResult[BattleSide::DEFENDER].armyID)) defenderArmy->giveAverageStackExperience(pack.heroResult[BattleSide::DEFENDER].exp); } } void GameStatePackVisitor::visitBattleStackMoved(BattleStackMoved & pack) { BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID)); pack.visitTyped(battleVisitor); } void GameStatePackVisitor::visitBattleAttack(BattleAttack & pack) { CStack * attacker = gs.getBattle(pack.battleID)->getStack(pack.stackAttacking); assert(attacker); pack.attackerChanges.visit(*this); for(BattleStackAttacked & stack : pack.bsa) gs.getBattle(pack.battleID)->setUnitState(stack.newState.id, stack.newState.data, stack.newState.healthDelta); attacker->removeBonusesRecursive(Bonus::UntilAttack); if(!pack.counter()) attacker->removeBonusesRecursive(Bonus::UntilOwnAttack); } void GameStatePackVisitor::visitStartAction(StartAction & pack) { CStack *st = gs.getBattle(pack.battleID)->getStack(pack.ba.stackNumber); if(pack.ba.actionType == EActionType::END_TACTIC_PHASE) { gs.getBattle(pack.battleID)->tacticDistance = 0; return; } if(gs.getBattle(pack.battleID)->tacticDistance) { // moves in tactics phase do not affect creature status // (tactics stack queue is managed by client) return; } if (pack.ba.isUnitAction()) { assert(st); // stack must exists for all non-hero actions switch(pack.ba.actionType) { case EActionType::DEFEND: st->waiting = false; st->defending = true; st->defendingAnim = true; break; case EActionType::WAIT: st->defendingAnim = false; st->waiting = true; st->waitedThisTurn = true; break; case EActionType::HERO_SPELL: //no change in current stack state break; default: //any active stack action - attack, catapult, heal, spell... st->waiting = false; st->defendingAnim = false; st->movedThisRound = true; st->castSpellThisTurn = pack.ba.actionType == EActionType::MONSTER_SPELL; break; } } else { if(pack.ba.actionType == EActionType::HERO_SPELL) gs.getBattle(pack.battleID)->getSide(pack.ba.side).usedSpellsHistory.push_back(pack.ba.spell); } } void GameStatePackVisitor::visitBattleSpellCast(BattleSpellCast & pack) { if(pack.castByHero && pack.side != BattleSide::NONE) gs.getBattle(pack.battleID)->getSide(pack.side).castSpellsCount++; } void GameStatePackVisitor::visitSetStackEffect(SetStackEffect & pack) { BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID)); pack.visitTyped(battleVisitor); } void GameStatePackVisitor::visitStacksInjured(StacksInjured & pack) { BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID)); pack.visitTyped(battleVisitor); } void GameStatePackVisitor::visitBattleUnitsChanged(BattleUnitsChanged & pack) { BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID)); pack.visitTyped(battleVisitor); } void GameStatePackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack) { pack.learnedSpells.visit(*this); for(auto & artPack : pack.artifacts) artPack.visit(*this); const auto currentBattle = std::find_if(gs.currentBattles.begin(), gs.currentBattles.end(), [&](const auto & battle) { return battle->battleID == pack.battleID; }); assert(currentBattle != gs.currentBattles.end()); gs.currentBattles.erase(currentBattle); } void GameStatePackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack) { BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID)); pack.visitTyped(battleVisitor); } void GameStatePackVisitor::visitCatapultAttack(CatapultAttack & pack) { BattleStatePackVisitor battleVisitor(*gs.getBattle(pack.battleID)); pack.visitTyped(battleVisitor); } void GameStatePackVisitor::visitBattleSetStackProperty(BattleSetStackProperty & pack) { CStack * stack = gs.getBattle(pack.battleID)->getStack(pack.stackID, false); switch(pack.which) { case BattleSetStackProperty::CASTS: { if(pack.absolute) logNetwork->error("Can not change casts in absolute mode"); else stack->casts.use(-pack.val); break; } case BattleSetStackProperty::ENCHANTER_COUNTER: { auto & counter = gs.getBattle(pack.battleID)->getSide(gs.getBattle(pack.battleID)->whatSide(stack->unitOwner())).enchanterCounter; if(pack.absolute) counter = pack.val; else counter += pack.val; vstd::amax(counter, 0); break; } case BattleSetStackProperty::UNBIND: { stack->removeBonusesRecursive(Selector::type()(BonusType::BIND_EFFECT)); break; } case BattleSetStackProperty::CLONED: { stack->cloned = true; break; } case BattleSetStackProperty::HAS_CLONE: { stack->cloneID = pack.val; break; } } } void GameStatePackVisitor::visitPlayerCheated(PlayerCheated & pack) { assert(pack.player.isValidPlayer()); gs.getPlayerState(pack.player)->enteredLosingCheatCode = pack.losingCheatCode; gs.getPlayerState(pack.player)->enteredWinningCheatCode = pack.winningCheatCode; gs.getPlayerState(pack.player)->cheated = true; } void GameStatePackVisitor::visitPlayerStartsTurn(PlayerStartsTurn & pack) { //assert(gs.actingPlayers.count(player) == 0);//Legal - may happen after loading of deserialized map state gs.actingPlayers.insert(pack.player); } void GameStatePackVisitor::visitPlayerEndsTurn(PlayerEndsTurn & pack) { assert(gs.actingPlayers.count(pack.player) == 1); gs.actingPlayers.erase(pack.player); } void GameStatePackVisitor::visitDaysWithoutTown(DaysWithoutTown & pack) { auto & playerState = gs.players.at(pack.player); playerState.daysWithoutCastle = pack.daysWithoutCastle; } void GameStatePackVisitor::visitTurnTimeUpdate(TurnTimeUpdate & pack) { auto & playerState = gs.players.at(pack.player); playerState.turnTimer = pack.turnTimer; } void GameStatePackVisitor::visitEntitiesChanged(EntitiesChanged & pack) { for(const auto & change : pack.changes) gs.updateEntity(change.metatype, change.entityIndex, change.data); } void GameStatePackVisitor::visitSetRewardableConfiguration(SetRewardableConfiguration & pack) { auto * objectPtr = gs.getObjInstance(pack.objectID); if (!pack.buildingID.hasValue()) { auto * rewardablePtr = dynamic_cast(objectPtr); assert(rewardablePtr); rewardablePtr->configuration = pack.configuration; rewardablePtr->initializeGuards(); } else { auto * townPtr = dynamic_cast(objectPtr); TownBuildingInstance * buildingPtr = nullptr; for (auto & building : townPtr->rewardableBuildings) if (building.second->getBuildingType() == pack.buildingID) buildingPtr = building.second.get(); auto * rewardablePtr = dynamic_cast(buildingPtr); assert(rewardablePtr); rewardablePtr->configuration = pack.configuration; } } void BattleStatePackVisitor::visitBattleStackMoved(BattleStackMoved & pack) { battleState.moveUnit(pack.stack, pack.tilesToMove.back()); } void BattleStatePackVisitor::visitCatapultAttack(CatapultAttack & pack) { const auto * town = battleState.getDefendedTown(); if(!town) throw std::runtime_error("CatapultAttack without town!"); if(town->fortificationsLevel().wallsHealth == 0) throw std::runtime_error("CatapultAttack without walls!"); for(const auto & part : pack.attackedParts) { auto newWallState = SiegeInfo::applyDamage(battleState.getWallState(part.attackedPart), part.damageDealt); battleState.setWallState(part.attackedPart, newWallState); } } void BattleStatePackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack) { for(const auto & change : pack.changes) { switch(change.operation) { case BattleChanges::EOperation::REMOVE: battleState.removeObstacle(change.id); break; case BattleChanges::EOperation::ADD: battleState.addObstacle(change); break; case BattleChanges::EOperation::UPDATE: battleState.updateObstacle(change); break; default: throw std::runtime_error("Unknown obstacle operation"); break; } } } void BattleStatePackVisitor::visitSetStackEffect(SetStackEffect & pack) { for(const auto & stackData : pack.toRemove) battleState.removeUnitBonus(stackData.first, stackData.second); for(const auto & stackData : pack.toUpdate) battleState.updateUnitBonus(stackData.first, stackData.second); for(const auto & stackData : pack.toAdd) battleState.addUnitBonus(stackData.first, stackData.second); } void BattleStatePackVisitor::visitStacksInjured(StacksInjured & pack) { for(const BattleStackAttacked & stack : pack.stacks) { battleState.setUnitState(stack.newState.id, stack.newState.data, stack.newState.healthDelta); } } void BattleStatePackVisitor::visitBattleUnitsChanged(BattleUnitsChanged & pack) { for(auto & elem : pack.changedStacks) { switch(elem.operation) { 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; case BattleChanges::EOperation::UPDATE: battleState.updateUnit(elem.id, elem.data); break; default: throw std::runtime_error("Unknown unit operation"); break; } } } VCMI_LIB_NAMESPACE_END