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) <noreply@anthropic.com>
This commit is contained in:
Christian Semmler 2026-03-13 16:05:55 -07:00
parent 4269a1b0fc
commit a588e3bb67
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
3 changed files with 54 additions and 5 deletions

View File

@ -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 <typename T>
@ -143,6 +144,7 @@ class NetworkManager : public MxCore {
std::atomic<int> m_pendingEmote;
std::atomic<bool> m_pendingToggleAllowCustomize;
bool m_disableAllNPCs;
bool m_showNameBubbles;
bool m_lastCameraEnabled;

View File

@ -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();
}
}
}

View File

@ -90,7 +90,7 @@ export class GameRoom implements DurableObject {
}
if (body.maxActors !== undefined) {
this.maxActors = Math.max(
5,
0,
Math.min(body.maxActors, 40)
);
}