diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 96d07918d..f4ad85b46 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -44,8 +44,7 @@ DisposedHero::DisposedHero() : heroId(0), portrait(255) } CMapEvent::CMapEvent() - : players(0) - , humanAffected(false) + : humanAffected(false) , computerAffected(false) , firstOccurrence(0) , nextOccurrence(0) @@ -53,21 +52,51 @@ CMapEvent::CMapEvent() } -bool CMapEvent::earlierThan(const CMapEvent & other) const +bool CMapEvent::occursToday(int currentDay) const { - return firstOccurrence < other.firstOccurrence; + if (currentDay == firstOccurrence + 1) + return true; + + if (nextOccurrence == 0) + return false; + + if (currentDay < firstOccurrence) + return false; + + return (currentDay - firstOccurrence - 1) % nextOccurrence == 0; } -bool CMapEvent::earlierThanOrEqual(const CMapEvent & other) const +bool CMapEvent::affectsPlayer(PlayerColor color, bool isHuman) const { - return firstOccurrence <= other.firstOccurrence; + if (players.count(color) == 0) + return false; + + if (!isHuman && !computerAffected) + return false; + + if (isHuman && !humanAffected) + return false; + + return true; } void CMapEvent::serializeJson(JsonSerializeFormat & handler) { handler.serializeString("name", name); handler.serializeStruct("message", message); - handler.serializeInt("players", players); + if (!handler.saving && handler.getCurrent()["players"].isNumber()) + { + // compatibility for old maps + int playersMask = 0; + handler.serializeInt("players", playersMask); + for (int i = 0; i < 8; ++i) + if ((playersMask & (1 << i)) != 0) + players.insert(PlayerColor(i)); + } + else + { + handler.serializeIdArray("players", players); + } handler.serializeInt("humanAffected", humanAffected); handler.serializeInt("computerAffected", computerAffected); handler.serializeInt("firstOccurrence", firstOccurrence); diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 772520192..0fbee42c3 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -136,7 +136,7 @@ public: std::set allowedSpells; std::set allowedArtifact; std::set allowedAbilities; - std::list events; + std::vector events; int3 grailPos; int grailRadius; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index aa3e767a9..36034db33 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -30,13 +30,13 @@ public: CMapEvent(); virtual ~CMapEvent() = default; - bool earlierThan(const CMapEvent & other) const; - bool earlierThanOrEqual(const CMapEvent & other) const; + bool occursToday(int currentDay) const; + bool affectsPlayer(PlayerColor player, bool isHuman) const; std::string name; MetaString message; TResources resources; - ui8 players; // affected players, bit field? + std::set players; bool humanAffected; bool computerAffected; ui32 firstOccurrence; @@ -48,7 +48,18 @@ public: h & name; h & message; h & resources; - h & players; + if (h.version >= Handler::Version::EVENTS_PLAYER_SET) + { + h & players; + } + else + { + ui8 playersMask = 0; + h & playersMask; + for (int i = 0; i < 8; ++i) + if ((playersMask & (1 << i)) != 0) + players.insert(PlayerColor(i)); + } h & humanAffected; h & computerAffected; h & firstOccurrence; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 2565084f6..18b51e1b6 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2244,7 +2244,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt reader->readResources(event.resources); - event.players = reader->readUInt8(); + reader->readBitmaskPlayers(event.players, false); if(features.levelSOD) event.humanAffected = reader->readBool(); else @@ -2313,7 +2313,7 @@ void CMapLoaderH3M::readEvents() event.message.appendTextID(readLocalizedString(TextIdentifier("event", eventID, "description"))); reader->readResources(event.resources); - event.players = reader->readUInt8(); + reader->readBitmaskPlayers(event.players, false); if(features.levelSOD) { event.humanAffected = reader->readBool(); diff --git a/lib/networkPacks/NetPackVisitor.h b/lib/networkPacks/NetPackVisitor.h index faeaecf1b..488367624 100644 --- a/lib/networkPacks/NetPackVisitor.h +++ b/lib/networkPacks/NetPackVisitor.h @@ -55,8 +55,6 @@ public: virtual void visitSetCommanderProperty(SetCommanderProperty & pack) {} virtual void visitAddQuest(AddQuest & pack) {} virtual void visitUpdateArtHandlerLists(UpdateArtHandlerLists & pack) {} - virtual void visitUpdateMapEvents(UpdateMapEvents & pack) {} - virtual void visitUpdateCastleEvents(UpdateCastleEvents & pack) {} virtual void visitChangeFormation(ChangeFormation & pack) {} virtual void visitRemoveObject(RemoveObject & pack) {} virtual void visitTryMoveHero(TryMoveHero & pack) {} diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index b6cd10376..c242b23b9 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -225,16 +225,6 @@ void UpdateArtHandlerLists::visitTyped(ICPackVisitor & visitor) visitor.visitUpdateArtHandlerLists(*this); } -void UpdateMapEvents::visitTyped(ICPackVisitor & visitor) -{ - visitor.visitUpdateMapEvents(*this); -} - -void UpdateCastleEvents::visitTyped(ICPackVisitor & visitor) -{ - visitor.visitUpdateCastleEvents(*this); -} - void ChangeFormation::visitTyped(ICPackVisitor & visitor) { visitor.visitChangeFormation(*this); @@ -907,17 +897,6 @@ void UpdateArtHandlerLists::applyGs(CGameState * gs) const gs->allocatedArtifacts = allocatedArtifacts; } -void UpdateMapEvents::applyGs(CGameState * gs) const -{ - gs->map->events = events; -} - -void UpdateCastleEvents::applyGs(CGameState * gs) const -{ - auto * t = gs->getTown(town); - t->events = events; -} - void ChangeFormation::applyGs(CGameState * gs) const { gs->getHero(hid)->setFormation(formation); diff --git a/lib/networkPacks/PacksForClient.h b/lib/networkPacks/PacksForClient.h index 48f3c0b6f..e1e3e8b43 100644 --- a/lib/networkPacks/PacksForClient.h +++ b/lib/networkPacks/PacksForClient.h @@ -545,34 +545,6 @@ struct DLL_LINKAGE UpdateArtHandlerLists : public CPackForClient } }; -struct DLL_LINKAGE UpdateMapEvents : public CPackForClient -{ - std::list events; - - void applyGs(CGameState * gs) const; - void visitTyped(ICPackVisitor & visitor) override; - - template void serialize(Handler & h) - { - h & events; - } -}; - -struct DLL_LINKAGE UpdateCastleEvents : public CPackForClient -{ - ObjectInstanceID town; - std::vector events; - - void applyGs(CGameState * gs) const; - void visitTyped(ICPackVisitor & visitor) override; - - template void serialize(Handler & h) - { - h & town; - h & events; - } -}; - struct DLL_LINKAGE ChangeFormation : public CPackForClient { ObjectInstanceID hid; diff --git a/lib/registerTypes/RegisterTypesClientPacks.h b/lib/registerTypes/RegisterTypesClientPacks.h index af64194ab..d81593818 100644 --- a/lib/registerTypes/RegisterTypesClientPacks.h +++ b/lib/registerTypes/RegisterTypesClientPacks.h @@ -44,8 +44,6 @@ void registerTypesClientPacks(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/lib/serializer/ESerializationVersion.h b/lib/serializer/ESerializationVersion.h index 0395dfe96..30a45da0e 100644 --- a/lib/serializer/ESerializationVersion.h +++ b/lib/serializer/ESerializationVersion.h @@ -50,6 +50,7 @@ enum class ESerializationVersion : int32_t BANK_UNIT_PLACEMENT, // 843 Banks have unit placement flag RELEASE_152 = BANK_UNIT_PLACEMENT, + RELEASE_156 = BANK_UNIT_PLACEMENT, COMPACT_STRING_SERIALIZATION, // 844 - optimized serialization of previously encountered strings COMPACT_INTEGER_SERIALIZATION, // 845 - serialize integers in forms similar to protobuf @@ -61,6 +62,7 @@ enum class ESerializationVersion : int32_t PLAYER_HANDICAP, // 851 - player handicap selection at game start STATISTICS, // 852 - removed random number generators from library classes CAMPAIGN_REGIONS, // 853 - configurable campaign regions + EVENTS_PLAYER_SET, // 854 - map & town events use std::set instead of bitmask to store player list - CURRENT = CAMPAIGN_REGIONS + CURRENT = EVENTS_PLAYER_SET }; diff --git a/mapeditor/inspector/towneventswidget.cpp b/mapeditor/inspector/towneventswidget.cpp index f963a2ae3..963a893f1 100644 --- a/mapeditor/inspector/towneventswidget.cpp +++ b/mapeditor/inspector/towneventswidget.cpp @@ -69,7 +69,7 @@ QVariant toVariant(const CCastleEvent& event) QVariantMap result; result["name"] = QString::fromStdString(event.name); result["message"] = QString::fromStdString(event.message.toString()); - result["players"] = QVariant::fromValue(event.players); + result["players"] = toVariant(event.players); result["humanAffected"] = QVariant::fromValue(event.humanAffected); result["computerAffected"] = QVariant::fromValue(event.computerAffected); result["firstOccurrence"] = QVariant::fromValue(event.firstOccurrence); @@ -87,7 +87,7 @@ CCastleEvent eventFromVariant(CMapHeader& map, const CGTownInstance& town, const auto v = variant.toMap(); result.name = v.value("name").toString().toStdString(); result.message.appendTextID(mapRegisterLocalizedString("map", map, TextIdentifier("town", town.instanceName, "event", result.name, "message"), v.value("message").toString().toStdString())); - result.players = v.value("players").toInt(); + result.players = playersFromVariant(v.value("players")); result.humanAffected = v.value("humanAffected").toInt(); result.computerAffected = v.value("computerAffected").toInt(); result.firstOccurrence = v.value("firstOccurrence").toInt(); diff --git a/mapeditor/mapsettings/eventsettings.cpp b/mapeditor/mapsettings/eventsettings.cpp index 9a060e50a..7f127e45f 100644 --- a/mapeditor/mapsettings/eventsettings.cpp +++ b/mapeditor/mapsettings/eventsettings.cpp @@ -16,6 +16,24 @@ #include "../../lib/constants/NumericConstants.h" #include "../../lib/constants/StringConstants.h" +QVariant toVariant(const std::set & players) +{ + QVariantList result; + for(auto const id : players) + result.push_back(QString::fromStdString(id.toString())); + return result; +} + +std::set playersFromVariant(const QVariant & v) +{ + std::set result; + + for(auto const & id : v.toList()) + result.insert(PlayerColor(PlayerColor::decode(id.toString().toStdString()))); + + return result; +} + QVariant toVariant(const TResources & resources) { QVariantMap result; @@ -30,7 +48,6 @@ TResources resourcesFromVariant(const QVariant & v) for(auto r : v.toMap().keys()) vJson[r.toStdString()].Integer() = v.toMap().value(r).toInt(); return TResources(vJson); - } QVariant toVariant(const CMapEvent & event) @@ -38,7 +55,7 @@ QVariant toVariant(const CMapEvent & event) QVariantMap result; result["name"] = QString::fromStdString(event.name); result["message"] = QString::fromStdString(event.message.toString()); - result["players"] = QVariant::fromValue(event.players); + result["players"] = toVariant(event.players); result["humanAffected"] = QVariant::fromValue(event.humanAffected); result["computerAffected"] = QVariant::fromValue(event.computerAffected); result["firstOccurrence"] = QVariant::fromValue(event.firstOccurrence); @@ -53,7 +70,7 @@ CMapEvent eventFromVariant(CMapHeader & mapHeader, const QVariant & variant) auto v = variant.toMap(); result.name = v.value("name").toString().toStdString(); result.message.appendTextID(mapRegisterLocalizedString("map", mapHeader, TextIdentifier("header", "event", result.name, "message"), v.value("message").toString().toStdString())); - result.players = v.value("players").toInt(); + result.players = playersFromVariant(v.value("players")); result.humanAffected = v.value("humanAffected").toInt(); result.computerAffected = v.value("computerAffected").toInt(); result.firstOccurrence = v.value("firstOccurrence").toInt(); diff --git a/mapeditor/mapsettings/eventsettings.h b/mapeditor/mapsettings/eventsettings.h index 991e09037..2d1cee00c 100644 --- a/mapeditor/mapsettings/eventsettings.h +++ b/mapeditor/mapsettings/eventsettings.h @@ -16,7 +16,10 @@ class EventSettings; } QVariant toVariant(const TResources & resources); +QVariant toVariant(const std::set & players); + TResources resourcesFromVariant(const QVariant & v); +std::set playersFromVariant(const QVariant & v); class EventSettings : public AbstractSettings { diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 48edff715..0a8ad5fea 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -584,11 +584,6 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack reinitScripting(); } -static bool evntCmp(const CMapEvent &a, const CMapEvent &b) -{ - return a.earlierThan(b); -} - void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=false, bool clear = false) {// bool forced = true - if creature should be replaced, if false - only if no creature was set @@ -635,6 +630,11 @@ void CGameHandler::onPlayerTurnStarted(PlayerColor which) { events::PlayerGotTurn::defaultExecute(serverEventBus.get(), which); turnTimerHandler->onPlayerGetTurn(which); + + handleTimeEvents(which); + + for (auto t : getPlayerState(which)->towns) + handleTownEvents(t); } void CGameHandler::onPlayerTurnEnded(PlayerColor which) @@ -860,7 +860,6 @@ void CGameHandler::onNewTurn() for (CGTownInstance *t : gs->map->towns) { PlayerColor player = t->tempOwner; - handleTownEvents(t, n); if (newWeek) //first day of week { if (t->hasBuilt(BuildingSubID::PORTAL_OF_SUMMONING)) @@ -1017,7 +1016,6 @@ void CGameHandler::onNewTurn() checkVictoryLossConditionsForAll(); // check for map turn limit logGlobal->trace("Info about turn %d has been sent!", n.day); - handleTimeEvents(); //call objects for (auto & elem : gs->map->objects) { @@ -3406,148 +3404,87 @@ bool CGameHandler::queryReply(QueryID qid, std::optional answer, Player return true; } -void CGameHandler::handleTimeEvents() +void CGameHandler::handleTimeEvents(PlayerColor color) { - gs->map->events.sort(evntCmp); - while(gs->map->events.size() && gs->map->events.front().firstOccurrence+1 == gs->day) + for (auto const & event : gs->map->events) { - CMapEvent ev = gs->map->events.front(); + if (!event.occursToday(gs->day)) + continue; - for (int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++) + if (!event.affectsPlayer(color, getPlayerState(color)->isHuman())) + continue; + + InfoWindow iw; + iw.player = color; + iw.text = event.message; + + //give resources + if (!event.resources.empty()) { - auto color = PlayerColor(player); - - const PlayerState * pinfo = getPlayerState(color, false); //do not output error if player does not exist - - if (pinfo //player exists - && (ev.players & 1<human) - || (ev.humanAffected && pinfo->human) - ) - ) - { - //give resources - giveResources(color, ev.resources); - - //prepare dialog - InfoWindow iw; - iw.player = color; - iw.text = ev.message; - - for (GameResID i : GameResID::ALL_RESOURCES()) - { - if (ev.resources[i]) //if resource is changed, we add it to the dialog - iw.components.emplace_back(ComponentType::RESOURCE, i, ev.resources[i]); - } - - sendAndApply(&iw); //show dialog - } - } //PLAYERS LOOP - - if (ev.nextOccurrence) - { - gs->map->events.pop_front(); - - ev.firstOccurrence += ev.nextOccurrence; - auto it = gs->map->events.begin(); - while(it != gs->map->events.end() && it->earlierThanOrEqual(ev)) - it++; - gs->map->events.insert(it, ev); - } - else - { - gs->map->events.pop_front(); + giveResources(color, event.resources); + for (GameResID i : GameResID::ALL_RESOURCES()) + if (event.resources[i]) + iw.components.emplace_back(ComponentType::RESOURCE, i, event.resources[i]); } + sendAndApply(&iw); //show dialog } - - //TODO send only if changed - UpdateMapEvents ume; - ume.events = gs->map->events; - sendAndApply(&ume); } -void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n) +void CGameHandler::handleTownEvents(CGTownInstance * town) { - std::sort(town->events.begin(), town->events.end(), evntCmp); - while(town->events.size() && town->events.front().firstOccurrence == gs->day) + for (auto const & event : town->events) { - PlayerColor player = town->tempOwner; - CCastleEvent ev = town->events.front(); - const PlayerState * pinfo = getPlayerState(player, false); + if (!event.occursToday(gs->day)) + continue; - if (pinfo //player exists - && (ev.players & 1<human) - || (ev.humanAffected && pinfo->human))) + PlayerColor player = town->getOwner(); + if (!event.affectsPlayer(player, getPlayerState(player)->isHuman())) + continue; + + // dialog + InfoWindow iw; + iw.player = player; + iw.text = event.message; + + if (event.resources.nonZero()) { - // dialog - InfoWindow iw; - iw.player = player; - iw.text = ev.message; + giveResources(player, event.resources); - if (ev.resources.nonZero()) + for (GameResID i : GameResID::ALL_RESOURCES()) + if (event.resources[i]) + iw.components.emplace_back(ComponentType::RESOURCE, i, event.resources[i]); + } + + for (auto & i : event.buildings) + { + // Only perform action if: + // 1. Building exists in town (don't attempt to build Lvl 5 guild in Fortress + // 2. Building was not built yet + // othervice, silently ignore / skip it + if (town->town->buildings.count(i) && !town->hasBuilt(i)) { - TResources was = n.res[player]; - n.res[player] += ev.resources; - n.res[player].amax(0); - - for (GameResID i : GameResID::ALL_RESOURCES()) - if (ev.resources[i] && pinfo->resources[i] != n.res.at(player)[i]) //if resource had changed, we add it to the dialog - iw.components.emplace_back(ComponentType::RESOURCE, i, n.res.at(player)[i] - was[i]); + buildStructure(town->id, i, true); + iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), i)); } + } - for (auto & i : ev.buildings) + if (!event.creatures.empty()) + { + SetAvailableCreatures sac; + sac.tid = town->id; + sac.creatures = town->creatures; + + for (si32 i=0;itown->buildings.count(i) && !town->hasBuilt(i)) + if (!town->creatures.at(i).second.empty() && event.creatures.at(i) > 0)//there is dwelling { - buildStructure(town->id, i, true); - iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), i)); + sac.creatures[i].first += event.creatures.at(i); + iw.components.emplace_back(ComponentType::CREATURE, town->creatures.at(i).second.back(), event.creatures.at(i)); } } - - if (!ev.creatures.empty() && !vstd::contains(n.cres, town->id)) - { - n.cres[town->id].tid = town->id; - n.cres[town->id].creatures = town->creatures; - } - auto & sac = n.cres[town->id]; - - for (si32 i=0;icreatures.at(i).second.empty() && ev.creatures.at(i) > 0)//there is dwelling - { - sac.creatures[i].first += ev.creatures.at(i); - iw.components.emplace_back(ComponentType::CREATURE, town->creatures.at(i).second.back(), ev.creatures.at(i)); - } - } - sendAndApply(&iw); //show dialog - } - - if (ev.nextOccurrence) - { - town->events.erase(town->events.begin()); - - ev.firstOccurrence += ev.nextOccurrence; - auto it = town->events.begin(); - while(it != town->events.end() && it->earlierThanOrEqual(ev)) - it++; - town->events.insert(it, ev); - } - else - { - town->events.erase(town->events.begin()); } + sendAndApply(&iw); //show dialog } - - //TODO send only if changed - UpdateCastleEvents uce; - uce.town = town->id; - uce.events = town->events; - sendAndApply(&uce); } bool CGameHandler::complain(const std::string &problem) diff --git a/server/CGameHandler.h b/server/CGameHandler.h index d250f6b03..4ce8a0d85 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -229,8 +229,8 @@ public: void onNewTurn(); void addStatistics(); - void handleTimeEvents(); - void handleTownEvents(CGTownInstance *town, NewTurn &n); + void handleTimeEvents(PlayerColor player); + void handleTownEvents(CGTownInstance *town); bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true void objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h ); void objectVisitEnded(const CObjectVisitQuery &query);