From 617eb4432b325dd175a19fbb8d9605a3b9e44e6a Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:41:45 -0300 Subject: [PATCH 01/11] Resolve empty trader lists in the bazaar window. This resolves a typo in the GetDistinctTraders routine. (#4862) --- common/repositories/trader_repository.h | 1 - 1 file changed, 1 deletion(-) diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index b9b3929eb..0354d6437 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -75,7 +75,6 @@ public: "JOIN character_data AS c ON t.char_id = c.id " "ORDER BY t.char_zone_instance_id ASC " "LIMIT {}", - char_zone_instance_id, max_results) ); } From 9869da2a0a3387633a3a4a12e66469e241f5a2d3 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:43:04 -0300 Subject: [PATCH 02/11] Resolve guild messaging for incorrect guild tags when creating/deleting guilds. (#4863) --- world/wguild_mgr.cpp | 2 +- zone/guild_mgr.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/world/wguild_mgr.cpp b/world/wguild_mgr.cpp index 4bacceb32..3c7050ba8 100644 --- a/world/wguild_mgr.cpp +++ b/world/wguild_mgr.cpp @@ -91,7 +91,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) { } //broadcast this packet to all zones. - zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack); + zoneserver_list.SendPacketToBootedZones(pack); break; } diff --git a/zone/guild_mgr.cpp b/zone/guild_mgr.cpp index ab96fb1fd..aa2d9fd70 100644 --- a/zone/guild_mgr.cpp +++ b/zone/guild_mgr.cpp @@ -406,6 +406,7 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack) c.second->SendGuildDeletePacket(s->guild_id); c.second->RefreshGuildInfo(); c.second->MessageString(Chat::Guild, GUILD_DISBANDED); + c.second->SendGuildList(); } } From 0ec07daebbf54aa3a12fd60d3957bd98d99b3d61 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:43:31 -0300 Subject: [PATCH 03/11] Fix an edge case for sending a no drop item within a parcel (#4865) --- zone/parcels.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/zone/parcels.cpp b/zone/parcels.cpp index 7d2c27429..54d6ad2c4 100644 --- a/zone/parcels.cpp +++ b/zone/parcels.cpp @@ -409,6 +409,13 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) parcel_out.aug_slot_6 = augs.at(5); } + if (!inst->IsDroppable(true)) { + Message(Chat::Yellow, "Unable to send a parcel that is NO-DROP or contains a NO-DROP item."); + SendParcelAck(); + DoParcelCancel(); + return; + } + auto result = CharacterParcelsRepository::InsertOne(database, parcel_out); if (!result.id) { LogError( From 5ae87b40e235f1d8d97c2d3f0e35f3ed5438b15a Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:45:15 -0300 Subject: [PATCH 04/11] Add some evolvingitem crash checks seen over the past few months. (#4870) --- common/evolving_items.cpp | 23 +++++++++++++++++++++++ common/evolving_items.h | 4 ++-- zone/client_evolving_items.cpp | 29 +++++++++++++++++++++++------ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/common/evolving_items.cpp b/common/evolving_items.cpp index 2b7eb38c6..b74efc17b 100644 --- a/common/evolving_items.cpp +++ b/common/evolving_items.cpp @@ -54,6 +54,10 @@ double EvolvingItemsManager::CalculateProgression(const uint64 current_amount, c void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const { + if (!inst) { + return; + } + inst.SetEvolveEquipped(false); if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) { inst.SetEvolveEquipped(true); @@ -87,6 +91,10 @@ void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_ uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const { + if (!inst) { + return 0; + } + const auto start_iterator = std::ranges::find_if( evolving_items_manager.GetEvolvingItemsCache().cbegin(), evolving_items_manager.GetEvolvingItemsCache().cend(), @@ -116,6 +124,10 @@ uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const { + if (!inst) { + return 0; + } + int8 const current_level = inst.GetEvolveLvl(); const auto iterator = std::ranges::find_if( @@ -191,6 +203,10 @@ uint64 EvolvingItemsManager::GetTotalEarnedXP(const EQ::ItemInstance &inst) EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp) { EvolveGetNextItem ets{}; + if (!inst_in) { + return ets; + } + const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID()); uint32 max_transfer_level = 0; int64 xp = in_xp; @@ -235,6 +251,9 @@ EvolveTransfer EvolvingItemsManager::DetermineTransferResults( ) { EvolveTransfer ets{}; + if (!inst_from || !inst_to) { + return ets; + } auto evolving_details_inst_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID()); auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID()); @@ -295,6 +314,10 @@ uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_i void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e) { + if (!inst) { + return; + } + e.item_id = inst.GetID(); e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string(); e.level = inst.GetEvolveLvl(); diff --git a/common/evolving_items.h b/common/evolving_items.h index 463b58493..56f7e4be8 100644 --- a/common/evolving_items.h +++ b/common/evolving_items.h @@ -53,11 +53,11 @@ public: ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id); EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to); EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp); - std::map& GetEvolvingItemsCache() { return evolving_items_cache; } + std::map& GetEvolvingItemsCache() { return m_evolving_items_cache; } std::vector GetEvolveIDItems(uint32 evolve_id); private: - std::map evolving_items_cache; + std::map m_evolving_items_cache; Database * m_db; Database * m_content_db; }; diff --git a/zone/client_evolving_items.cpp b/zone/client_evolving_items.cpp index c77b2fd12..e1259c77f 100644 --- a/zone/client_evolving_items.cpp +++ b/zone/client_evolving_items.cpp @@ -92,6 +92,16 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob) continue; } + if (!evolving_items_manager.GetEvolvingItemsCache().contains(inst->GetID())) { + LogEvolveItem( + "Character ID {} has an evolving item that is not found in the db. Please check your " + "items_evolving_details table for item id {}", + CharacterID(), + inst->GetID() + ); + continue; + } + auto const type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type; auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type; @@ -283,24 +293,31 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app) } std::unique_ptr const inst(database.CreateItem(item_id)); + if (!inst) { + return; + } LogEvolveItemDetail( "Character ID [{}] requested to view final evolve item id [{}] for evolve item id [{}]", CharacterID(), item_id, - evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id)); + evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id) + ); inst->SetEvolveProgression(100); - if (inst) { - LogEvolveItemDetail( - "Sending final result for item id [{}] to Character ID [{}]", item_id, CharacterID()); - SendItemPacket(0, inst.get(), ItemPacketViewLink); - } + LogEvolveItemDetail( + "Sending final result for item id [{}] to Character ID [{}]", item_id, CharacterID() + ); + SendItemPacket(0, inst.get(), ItemPacketViewLink); } bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) { + if (!inst) { + return false; + } + if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) { return false; } From 7e7fb7b758ac26b9e87aa33b79bdd43f315b3dfc Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:46:12 -0500 Subject: [PATCH 05/11] [Bots] Prevent non-taunters from potentially fleeing mob on TargetReflection (#4859) - Casters could endlessly flee a mob if they had target reflection and were too close. - Prevents all bots that aren't taunting from adjusting if they're too close and have target reflection. This is safer than limiting it to just casters and ranged bots to prevent situations where melee bots might not be able to outrun the mob to get to their melee range and continually flee. - WE'LL GET THIS RIGHT EVENTUALLY /sigh --- zone/bot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index af6723181..294e49963 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -12009,7 +12009,7 @@ bool Bot::DoCombatPositioning(const CombatPositioningInput& input) } else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments adjustment_needed = - is_too_close || + (IsTaunting() && is_too_close) || los_adjust || (is_melee && !input.front_mob); From 1a539f665638721eed0df7e90dc10411f0865625 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:48:30 -0500 Subject: [PATCH 06/11] [Hotfix] Fix #copycharacter command (#4860) This was failing due to a column change in `inventory` from `charid` to `character_id` and would result in no inventory for the new character. --- common/database_schema.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/database_schema.h b/common/database_schema.h index e92b7619a..de6f8472f 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -80,7 +80,7 @@ namespace DatabaseSchema { {"guild_members", "char_id"}, {"guilds", "id"}, {"instance_list_player", "id"}, - {"inventory", "charid"}, + {"inventory", "character_id"}, {"inventory_snapshots", "charid"}, {"keyring", "char_id"}, {"mail", "charid"}, From cd003ff0b78e1c0a4edca9f7c2c65b104188fd79 Mon Sep 17 00:00:00 2001 From: nytmyr <53322305+nytmyr@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:49:03 -0500 Subject: [PATCH 07/11] [Cleanup] Fix typo in QueryNameAvailablity (#4869) --- zone/bot_command.cpp | 2 +- zone/bot_commands/bot.cpp | 2 +- zone/bot_database.cpp | 2 +- zone/bot_database.h | 2 +- zone/questmgr.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 0fcaef114..42121b8ca 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -468,7 +468,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas bool available_flag = false; - !database.botdb.QueryNameAvailablity(bot_name, available_flag); + !database.botdb.QueryNameAvailability(bot_name, available_flag); if (!available_flag) { bot_owner->Message( diff --git a/zone/bot_commands/bot.cpp b/zone/bot_commands/bot.cpp index 9fd87a830..8a8c4a277 100644 --- a/zone/bot_commands/bot.cpp +++ b/zone/bot_commands/bot.cpp @@ -130,7 +130,7 @@ void bot_command_clone(Client *c, const Seperator *sep) bool available_flag = false; - !database.botdb.QueryNameAvailablity(bot_name, available_flag); + !database.botdb.QueryNameAvailability(bot_name, available_flag); if (!available_flag) { c->Message( diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 088b8bf35..70e535711 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -182,7 +182,7 @@ bool BotDatabase::LoadBotSpellCastingChances() return true; } -bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag) +bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag) { if ( bot_name.empty() || diff --git a/zone/bot_database.h b/zone/bot_database.h index 407050079..7dcdebc28 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -48,7 +48,7 @@ public: /* Bot functions */ - bool QueryNameAvailablity(const std::string& bot_name, bool& available_flag); + bool QueryNameAvailability(const std::string& bot_name, bool& available_flag); bool QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count); bool LoadBotsList(const uint32 owner_id, std::list& bots_list, bool by_account = false); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 86b4a9fee..b9807703e 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2786,7 +2786,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level std::string test_name = name; bool available_flag = false; - if (!database.botdb.QueryNameAvailablity(test_name, available_flag)) { + if (!database.botdb.QueryNameAvailability(test_name, available_flag)) { initiator->Message( Chat::White, fmt::format( From c08f28681786afc5f16919d74dc2b1696cefbf16 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:50:57 -0300 Subject: [PATCH 08/11] AddUntargetableToSpawn (#4872) Adds the db entry for untargetable to the spawn struct --- common/eq_packet_structs.h | 1 + common/patches/rof2.cpp | 2 +- zone/mob.cpp | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index bf20f6502..ef4d376e6 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -324,6 +324,7 @@ union bool guild_show; bool trader; bool buyer; + bool untargetable; }; struct PlayerState_Struct { diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 1cbbb5c5d..1fcfcf67a 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -4688,7 +4688,7 @@ namespace RoF2 Bitfields->linkdead = 0; Bitfields->showhelm = emu->showhelm; Bitfields->trader = emu->trader ? 1 : 0; - Bitfields->targetable = 1; + Bitfields->targetable = emu->NPC ? emu->untargetable : 1; Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0; Bitfields->showname = ShowName; diff --git a/zone/mob.cpp b/zone/mob.cpp index b411197e6..3b27482e2 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1312,7 +1312,8 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.NPC = IsClient() ? 0 : 1; ns->spawn.IsMercenary = IsMerc() ? 1 : 0; ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic! - + ns->spawn.untargetable = IsTargetable(); + ns->spawn.petOwnerId = ownerid; ns->spawn.haircolor = haircolor; From 99d249fefde93ab706ad67529d2454a90940d717 Mon Sep 17 00:00:00 2001 From: Alex King <89047260+Kinglykrab@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:15:53 -0400 Subject: [PATCH 09/11] [Bug Fix] Fix Crash with #task (#4874) --- zone/gm_commands/task.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zone/gm_commands/task.cpp b/zone/gm_commands/task.cpp index 37aafc84b..bd28c2518 100755 --- a/zone/gm_commands/task.cpp +++ b/zone/gm_commands/task.cpp @@ -8,6 +8,11 @@ extern WorldServer worldserver; void command_task(Client *c, const Seperator *sep) { + if (!RuleB(TaskSystem, EnableTaskSystem)) { + c->Message(Chat::White, "This command cannot be used while the Task system is disabled."); + return; + } + const int arguments = sep->argnum; if (!arguments) { c->Message(Chat::White, "Syntax: #task [subcommand]"); From 5919bb4dea90a337f22346c5bd358ee9cb68b554 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 16:24:44 -0400 Subject: [PATCH 10/11] Bump golang.org/x/net in /utils/scripts/build/should-release (#4864) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0. - [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- utils/scripts/build/should-release/go.mod | 4 ++-- utils/scripts/build/should-release/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/utils/scripts/build/should-release/go.mod b/utils/scripts/build/should-release/go.mod index 63b519f8d..b74fc2889 100644 --- a/utils/scripts/build/should-release/go.mod +++ b/utils/scripts/build/should-release/go.mod @@ -10,7 +10,7 @@ require ( require ( github.com/golang/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.1.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.36.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect google.golang.org/appengine v1.6.7 // indirect ) diff --git a/utils/scripts/build/should-release/go.sum b/utils/scripts/build/should-release/go.sum index d42bb936b..88fd4be02 100644 --- a/utils/scripts/build/should-release/go.sum +++ b/utils/scripts/build/should-release/go.sum @@ -10,12 +10,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 138612bc881383032809cb9972650c8249e15542 Mon Sep 17 00:00:00 2001 From: JJ <3617814+joligario@users.noreply.github.com> Date: Fri, 2 May 2025 16:25:18 -0400 Subject: [PATCH 11/11] Only resume NPC if npc_id is ready (#4875) --- zone/zone_save_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/zone_save_state.cpp b/zone/zone_save_state.cpp index 3401bc5e0..187d94d00 100644 --- a/zone/zone_save_state.cpp +++ b/zone/zone_save_state.cpp @@ -517,7 +517,7 @@ bool Zone::LoadZoneState( new_spawn->SetStoredLocation(glm::vec4(s.x, s.y, s.z, s.heading)); - if (spawn_time_left == 0) { + if (spawn_time_left == 0 && s.npc_id > 0) { new_spawn->SetResumedNPCID(s.npc_id); new_spawn->SetResumedFromZoneSuspend(true); new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));