Disable third person camera and hide remote players in Isle overlay areas

Overlay areas (elevator rides, observatory, gas station/police doorways)
stay within the Isle world but use fixed cameras with no free-roaming
movement. The third person camera and player display were incorrectly
staying active in these areas, and remote players remained visible at
their last open-world position.

Query GameState::m_currentArea at runtime to detect restricted areas,
avoiding new state variables or hooks into decompiled code. Extract
shared IsRestrictedArea() into a common header used by both the camera
controller and multiplayer networking. On the multiplayer side, broadcast
WORLD_NOT_VISIBLE as the worldId so remote clients hide the player via
existing visibility logic, and exclude the local player from the player
count when in a restricted area.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Semmler 2026-03-14 16:19:16 -07:00
parent 13f6239808
commit aa48001eb3
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
6 changed files with 80 additions and 21 deletions

View File

@ -0,0 +1,34 @@
#pragma once
#include "legogamestate.h"
namespace Extensions
{
namespace Common
{
// Broadcast world ID indicating the player is not visible in any world.
static constexpr int8_t WORLD_NOT_VISIBLE = -1;
// Overlay areas within the Isle world (e_act1) that use fixed camera angles
// and have no free-roaming player movement. The player character should not
// be visible (locally or to remote peers) in these areas.
inline bool IsRestrictedArea(LegoGameState::Area p_area)
{
switch (p_area) {
case LegoGameState::e_elevride:
case LegoGameState::e_elevride2:
case LegoGameState::e_elevopen:
case LegoGameState::e_seaview:
case LegoGameState::e_observe:
case LegoGameState::e_elevdown:
case LegoGameState::e_garadoor:
case LegoGameState::e_polidoor:
return true;
default:
return false;
}
}
} // namespace Common
} // namespace Extensions

View File

@ -95,6 +95,7 @@ class Controller {
static constexpr float MIN_DISTANCE = OrbitCamera::MIN_DISTANCE;
private:
void Deactivate();
void ReinitForCharacter();
OrbitCamera m_orbit;

View File

@ -1,6 +1,7 @@
#include "extensions/multiplayer/networkmanager.h"
#include "extensions/common/animdata.h"
#include "extensions/common/arearestriction.h"
#include "extensions/common/charactercustomizer.h"
#include "extensions/multiplayer/namebubblerenderer.h"
#include "extensions/thirdpersoncamera.h"
@ -26,6 +27,8 @@ using namespace Extensions;
using namespace Multiplayer;
using Common::DetectVehicleType;
using Common::IsMultiPartEmote;
using Common::IsRestrictedArea;
using Common::WORLD_NOT_VISIBLE;
template <typename T>
void NetworkManager::SendMessage(const T& p_msg)
@ -407,7 +410,8 @@ void NetworkManager::BroadcastLocalState()
PlayerStateMsg msg{};
msg.header = {MSG_STATE, m_localPeerId, m_sequence++, TARGET_BROADCAST};
msg.actorId = actorId;
msg.worldId = (int8_t) currentWorld->GetWorldId();
msg.worldId =
IsRestrictedArea(GameState()->m_currentArea) ? WORLD_NOT_VISIBLE : (int8_t) currentWorld->GetWorldId();
msg.vehicleType = DetectVehicleType(userActor);
SDL_memcpy(msg.position, pos, sizeof(msg.position));
SDL_memcpy(msg.direction, dir, sizeof(msg.direction));
@ -714,11 +718,9 @@ void NetworkManager::NotifyPlayerCountChanged()
if (m_inIsleWorld) {
count = 0;
// Only count the local player if they have a valid actor.
// UserActor() can be temporarily NULL during world transitions
// (e.g. returning from a race, where LegoRace stashes the actor
// and only restores it in its destructor). Fall back to the
// GameState actorId which is restored earlier.
// Only count the local player if they have a valid actor and
// are not in a restricted overlay area (elevator, observatory, etc.).
if (!IsRestrictedArea(GameState()->m_currentArea)) {
LegoPathActor* userActor = UserActor();
if (userActor && IsValidActorId(static_cast<LegoActor*>(userActor)->GetActorId())) {
count = 1;
@ -726,6 +728,7 @@ void NetworkManager::NotifyPlayerCountChanged()
else if (IsValidActorId(GameState()->GetActorId())) {
count = 1;
}
}
for (auto& [peerId, player] : m_remotePlayers) {
if (player->GetWorldId() == (int8_t) LegoOmni::e_act1) {

View File

@ -1,6 +1,7 @@
#include "extensions/multiplayer/remoteplayer.h"
#include "3dmanager/lego3dmanager.h"
#include "extensions/common/arearestriction.h"
#include "extensions/common/charactercloner.h"
#include "extensions/common/charactercustomizer.h"
#include "extensions/multiplayer/namebubblerenderer.h"
@ -23,11 +24,12 @@ using Common::g_idleAnimCount;
using Common::g_vehicleROINames;
using Common::g_walkAnimCount;
using Common::IsLargeVehicle;
using Common::WORLD_NOT_VISIBLE;
RemotePlayer::RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex)
: m_peerId(p_peerId), m_actorId(p_actorId), m_displayActorIndex(p_displayActorIndex), m_roi(nullptr),
m_spawned(false), m_visible(false), m_targetSpeed(0.0f), m_targetVehicleType(VEHICLE_NONE), m_targetWorldId(-1),
m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
m_spawned(false), m_visible(false), m_targetSpeed(0.0f), m_targetVehicleType(VEHICLE_NONE),
m_targetWorldId(WORLD_NOT_VISIBLE), m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false, /*.propSuffix=*/p_peerId}),
m_vehicleROI(nullptr), m_nameBubble(nullptr), m_allowRemoteCustomize(true)
{

View File

@ -1,5 +1,6 @@
#include "extensions/thirdpersoncamera.h"
#include "extensions/common/arearestriction.h"
#include "extensions/common/charactercustomizer.h"
#include "extensions/thirdpersoncamera/controller.h"
#include "islepathactor.h"
@ -112,7 +113,7 @@ void ThirdPersonCameraExt::HandleCamAnimEnd(LegoPathActor* p_actor)
void ThirdPersonCameraExt::OnSDLEvent(SDL_Event* p_event)
{
if (!s_camera || !s_inIsleWorld) {
if (!s_camera || !s_inIsleWorld || IsRestrictedArea(GameState()->m_currentArea)) {
return;
}

View File

@ -3,6 +3,7 @@
#include "3dmanager/lego3dmanager.h"
#include "anim/legoanim.h"
#include "extensions/common/animutils.h"
#include "extensions/common/arearestriction.h"
#include "extensions/common/charactercustomizer.h"
#include "extensions/common/constants.h"
#include "islepathactor.h"
@ -42,11 +43,17 @@ void Controller::Enable()
void Controller::Disable(bool p_preserveTouch)
{
m_enabled = false;
Deactivate();
if (!p_preserveTouch) {
m_input.ResetTouchState();
}
}
void Controller::Deactivate()
{
if (m_active && m_playerROI) {
m_playerROI->SetVisibility(FALSE);
VideoManager()->Get3DManager()->Remove(*m_playerROI);
m_orbit.RestoreFirstPersonCamera();
}
@ -58,11 +65,7 @@ void Controller::Disable(bool p_preserveTouch)
m_playerROI = nullptr;
m_animator.ClearRideAnimation();
m_animator.ClearAll();
m_orbit.ResetOrbitState();
if (!p_preserveTouch) {
m_input.ResetTouchState();
}
}
void Controller::OnActorEnter(IslePathActor* p_actor)
@ -74,7 +77,7 @@ void Controller::OnActorEnter(IslePathActor* p_actor)
m_animator.SetCurrentVehicleType(DetectVehicleType(userActor));
if (!m_enabled) {
if (!m_enabled || IsRestrictedArea(GameState()->m_currentArea)) {
return;
}
@ -164,6 +167,10 @@ void Controller::OnCamAnimEnd(LegoPathActor* p_actor)
void Controller::Tick(float p_deltaTime)
{
if (IsRestrictedArea(GameState()->m_currentArea)) {
return;
}
if (!m_display.IsDisplayActorFrozen()) {
LegoPathActor* userActor = UserActor();
if (userActor) {
@ -312,6 +319,12 @@ void Controller::OnWorldEnabled(LegoWorld* p_world)
return;
}
if (IsRestrictedArea(GameState()->m_currentArea)) {
Deactivate();
m_input.ResetTouchState();
return;
}
m_animator.ClearAll();
m_orbit.ResetOrbitState();
@ -363,6 +376,11 @@ void Controller::HandleSDLEventImpl(SDL_Event* p_event)
void Controller::ReinitForCharacter()
{
if (IsRestrictedArea(GameState()->m_currentArea)) {
m_active = false;
return;
}
LegoPathActor* userActor = UserActor();
if (!userActor) {
m_active = false;