From a588e3bb67ee0950818dd129b4c326c79c4883d0 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 13 Mar 2026 16:05:55 -0700 Subject: [PATCH] Disable all NPCs when maxActors=0 Allow the game room server to accept maxActors=0 (previously floored at 5). When received, the client disables extra actor spawning, camera animations, and continuously purges all extras including the ambient NPCs (mama, papa, brickster) that PurgeExtra(TRUE) deliberately skips. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../extensions/multiplayer/networkmanager.h | 2 + extensions/src/multiplayer/networkmanager.cpp | 55 +++++++++++++++++-- extensions/src/multiplayer/server/gameroom.ts | 2 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/extensions/include/extensions/multiplayer/networkmanager.h b/extensions/include/extensions/multiplayer/networkmanager.h index c44a7382..de82f72e 100644 --- a/extensions/include/extensions/multiplayer/networkmanager.h +++ b/extensions/include/extensions/multiplayer/networkmanager.h @@ -115,6 +115,7 @@ class NetworkManager : public MxCore { void RemoveAllRemotePlayers(); void NotifyPlayerCountChanged(); + void EnforceDisableNPCs(); // Serialize and send a fixed-size message via the transport template @@ -143,6 +144,7 @@ class NetworkManager : public MxCore { std::atomic m_pendingEmote; std::atomic m_pendingToggleAllowCustomize; + bool m_disableAllNPCs; bool m_showNameBubbles; bool m_lastCameraEnabled; diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index 24b43b8a..d128ae25 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -6,9 +6,12 @@ #include "extensions/thirdpersoncamera.h" #include "extensions/thirdpersoncamera/controller.h" #include "legoanimationmanager.h" +#include "legocharactermanager.h" +#include "legoextraactor.h" #include "legogamestate.h" #include "legomain.h" #include "legopathactor.h" +#include "legopathcontroller.h" #include "legoworld.h" #include "misc.h" #include "mxmisc.h" @@ -41,9 +44,9 @@ void NetworkManager::SendMessage(const T& p_msg) NetworkManager::NetworkManager() : m_transport(nullptr), m_callbacks(nullptr), m_localNameBubble(nullptr), m_localPeerId(0), m_hostPeerId(0), m_sequence(0), m_lastBroadcastTime(0), m_lastValidActorId(0), m_localAllowRemoteCustomize(true), - m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), - m_pendingToggleNameBubbles(false), m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), - m_pendingToggleAllowCustomize(false), m_showNameBubbles(true), m_lastCameraEnabled(false) + m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false), + m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), m_pendingToggleAllowCustomize(false), + m_disableAllNPCs(false), m_showNameBubbles(true), m_lastCameraEnabled(false) { } @@ -61,6 +64,10 @@ MxResult NetworkManager::Tickle() { ProcessPendingRequests(); + if (m_disableAllNPCs) { + EnforceDisableNPCs(); + } + // Detect camera state changes for platform notification ThirdPersonCamera::Controller* cam = GetCamera(); if (cam) { @@ -202,6 +209,10 @@ void NetworkManager::OnWorldEnabled(LegoWorld* p_world) } NotifyPlayerCountChanged(); + + if (m_disableAllNPCs) { + EnforceDisableNPCs(); + } } } @@ -267,6 +278,36 @@ MxBool NetworkManager::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_ch return m_worldSync.HandleSkyLightMutation(p_entityType, p_changeType); } +void NetworkManager::EnforceDisableNPCs() +{ + LegoAnimationManager* am = AnimationManager(); + if (!am) { + return; + } + + am->m_numAllowedExtras = 0; + am->m_enableCamAnims = FALSE; + am->m_unk0x400 = FALSE; + + // Purge all extras including ambient NPCs (mama, papa, brickster) + // that are spawned by camera path triggers via FUN_10064380. + // PurgeExtra(TRUE) deliberately skips mama/papa, so we purge manually. + for (MxS32 i = 0; i < (MxS32) sizeOfArray(am->m_extras); i++) { + if (am->m_extras[i].m_roi != NULL) { + LegoPathActor* actor = CharacterManager()->GetExtraActor(am->m_extras[i].m_roi->GetName()); + if (actor != NULL && actor->GetController() != NULL) { + actor->GetController()->RemoveActor(actor); + actor->SetController(NULL); + } + + CharacterManager()->ReleaseActor(am->m_extras[i].m_roi); + am->m_extras[i].m_roi = NULL; + am->m_extras[i].m_characterId = -1; + am->m_unk0x414--; + } + } +} + void NetworkManager::ProcessPendingRequests() { ThirdPersonCamera::Controller* cam = GetCamera(); @@ -409,10 +450,16 @@ void NetworkManager::ProcessIncomingPackets() } if (length >= 6) { uint8_t maxActors = data[5]; - if (maxActors >= 5 && maxActors <= 40) { + if (maxActors <= 40) { LegoAnimationManager::configureLegoAnimationManager(maxActors); if (AnimationManager()) { AnimationManager()->m_maxAllowedExtras = maxActors; + AnimationManager()->m_numAllowedExtras = + SDL_min(AnimationManager()->m_numAllowedExtras, (MxU32) maxActors); + } + m_disableAllNPCs = (maxActors == 0); + if (m_disableAllNPCs) { + EnforceDisableNPCs(); } } } diff --git a/extensions/src/multiplayer/server/gameroom.ts b/extensions/src/multiplayer/server/gameroom.ts index 94829bf0..bc97ec22 100644 --- a/extensions/src/multiplayer/server/gameroom.ts +++ b/extensions/src/multiplayer/server/gameroom.ts @@ -90,7 +90,7 @@ export class GameRoom implements DurableObject { } if (body.maxActors !== undefined) { this.maxActors = Math.max( - 5, + 0, Math.min(body.maxActors, 40) ); }