Support multiple overlapping locations for scene animation eligibility

Players near multiple location points now see cam animations from all
overlapping locations instead of only the nearest one. Location proximity
radius reduced from 15 to 5 units. NPC animations unchanged (still
proximity-based). JSON output updated from "location" to "locations" array.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Semmler 2026-03-27 10:42:14 -07:00
parent 892fb808b6
commit 32fd90d804
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
8 changed files with 138 additions and 84 deletions

View File

@ -65,8 +65,8 @@ class Coordinator {
uint8_t p_proximityCount uint8_t p_proximityCount
) const; ) const;
// Auto-clear interest if current animation is not available at the new location. // Auto-clear interest if current animation is not available at any of the new locations.
void OnLocationChanged(int16_t p_location, const Catalog* p_catalog); void OnLocationChanged(const std::vector<int16_t>& p_locations, const Catalog* p_catalog);
void Reset(); void Reset();

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <vector>
namespace Multiplayer::Animation namespace Multiplayer::Animation
{ {
@ -11,23 +12,22 @@ class LocationProximity {
public: public:
LocationProximity(); LocationProximity();
// Returns true if nearest location changed since last call // Returns true if location set changed since last call
bool Update(float p_x, float p_z); bool Update(float p_x, float p_z);
int16_t GetNearestLocation() const { return m_nearestLocation; } // All locations within radius (sorted by index for stable comparison)
float GetNearestDistance() const { return m_nearestDistance; } const std::vector<int16_t>& GetLocations() const { return m_locations; }
bool IsAtLocation(int16_t p_location) const;
void SetRadius(float p_radius) { m_radius = p_radius; }
float GetRadius() const { return m_radius; } float GetRadius() const { return m_radius; }
void Reset(); void Reset();
// Static version for computing any position's nearest location // Static version returning all locations within radius (sorted by index)
static int16_t ComputeNearest(float p_x, float p_z, float p_radius); static std::vector<int16_t> ComputeAll(float p_x, float p_z, float p_radius);
private: private:
int16_t m_nearestLocation;
float m_nearestDistance;
float m_radius; float m_radius;
std::vector<int16_t> m_locations;
}; };
} // namespace Multiplayer::Animation } // namespace Multiplayer::Animation

View File

@ -142,7 +142,7 @@ class NetworkManager : public MxCore {
void BroadcastAnimStart(uint16_t p_animIndex); void BroadcastAnimStart(uint16_t p_animIndex);
void BroadcastAnimComplete(uint16_t p_animIndex); void BroadcastAnimComplete(uint16_t p_animIndex);
void HandleAnimComplete(const AnimCompleteMsg& p_msg); void HandleAnimComplete(const AnimCompleteMsg& p_msg);
int16_t GetPeerLocation(uint32_t p_peerId) const; bool IsPeerAtLocation(uint32_t p_peerId, int16_t p_location) const;
bool GetPeerPosition(uint32_t p_peerId, float& p_x, float& p_z) const; bool GetPeerPosition(uint32_t p_peerId, float& p_x, float& p_z) const;
bool IsPeerNearby(uint32_t p_peerId, float p_refX, float p_refZ) const; bool IsPeerNearby(uint32_t p_peerId, float p_refX, float p_refZ) const;
bool ValidateSessionLocations(uint16_t p_animIndex); bool ValidateSessionLocations(uint16_t p_animIndex);

View File

@ -7,6 +7,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <vector>
class LegoROI; class LegoROI;
class LegoWorld; class LegoWorld;
@ -36,8 +37,9 @@ class RemotePlayer {
bool IsSpawned() const { return m_spawned; } bool IsSpawned() const { return m_spawned; }
bool IsVisible() const { return m_visible; } bool IsVisible() const { return m_visible; }
int8_t GetWorldId() const { return m_targetWorldId; } int8_t GetWorldId() const { return m_targetWorldId; }
int16_t GetNearestLocation() const { return m_nearestLocation; } const std::vector<int16_t>& GetLocations() const { return m_locations; }
void SetNearestLocation(int16_t p_location) { m_nearestLocation = p_location; } void SetLocations(std::vector<int16_t> p_locations) { m_locations = std::move(p_locations); }
bool IsAtLocation(int16_t p_location) const;
uint32_t GetLastUpdateTime() const { return m_lastUpdateTime; } uint32_t GetLastUpdateTime() const { return m_lastUpdateTime; }
void SetVisible(bool p_visible); void SetVisible(bool p_visible);
void TriggerEmote(uint8_t p_emoteId); void TriggerEmote(uint8_t p_emoteId);
@ -84,7 +86,7 @@ class RemotePlayer {
int8_t m_targetWorldId; int8_t m_targetWorldId;
uint32_t m_lastUpdateTime; uint32_t m_lastUpdateTime;
bool m_hasReceivedUpdate; bool m_hasReceivedUpdate;
int16_t m_nearestLocation; std::vector<int16_t> m_locations;
float m_currentPosition[3]; float m_currentPosition[3];
float m_currentDirection[3]; float m_currentDirection[3];

View File

@ -145,20 +145,33 @@ std::vector<EligibilityInfo> Coordinator::ComputeEligibility(
return result; return result;
} }
void Coordinator::OnLocationChanged(int16_t p_location, const Catalog* p_catalog) void Coordinator::OnLocationChanged(const std::vector<int16_t>& p_locations, const Catalog* p_catalog)
{ {
if (m_state != CoordinationState::e_interested || !p_catalog) { if (m_state != CoordinationState::e_interested || !p_catalog) {
return; return;
} }
auto anims = p_catalog->GetAnimationsAtLocation(p_location); // Check if the currently interested animation is still available at any of the locations
for (int16_t loc : p_locations) {
auto anims = p_catalog->GetAnimationsAtLocation(loc);
for (const auto* e : anims) { for (const auto* e : anims) {
if (e->animIndex == m_currentAnimIndex) { if (e->animIndex == m_currentAnimIndex) {
return; // still available return; // still available at this location
}
} }
} }
// Animation not at new location — clear interest // Also check NPC anims when at no location
if (p_locations.empty()) {
auto anims = p_catalog->GetAnimationsAtLocation(-1);
for (const auto* e : anims) {
if (e->animIndex == m_currentAnimIndex) {
return;
}
}
}
// Animation not at any current location — clear interest
m_state = CoordinationState::e_idle; m_state = CoordinationState::e_idle;
m_currentAnimIndex = ANIM_INDEX_NONE; m_currentAnimIndex = ANIM_INDEX_NONE;
m_cancelPending = true; m_cancelPending = true;

View File

@ -3,58 +3,52 @@
#include "decomp.h" #include "decomp.h"
#include "legolocations.h" #include "legolocations.h"
#include <algorithm>
#include <cmath> #include <cmath>
using namespace Multiplayer::Animation; using namespace Multiplayer::Animation;
static const float DEFAULT_RADIUS = NPC_ANIM_PROXIMITY; static const float DEFAULT_RADIUS = 5.0f;
// Location 0 is the camera origin, and the last location is overhead — skip both // Location 0 is the camera origin, and the last location is overhead — skip both
static const int FIRST_VALID_LOCATION = 1; static const int FIRST_VALID_LOCATION = 1;
static const int LAST_VALID_LOCATION = sizeOfArray(g_locations) - 2; static const int LAST_VALID_LOCATION = sizeOfArray(g_locations) - 2;
LocationProximity::LocationProximity() : m_nearestLocation(-1), m_nearestDistance(0.0f), m_radius(DEFAULT_RADIUS) LocationProximity::LocationProximity() : m_radius(DEFAULT_RADIUS)
{ {
} }
bool LocationProximity::Update(float p_x, float p_z) bool LocationProximity::Update(float p_x, float p_z)
{ {
int16_t prev = m_nearestLocation; std::vector<int16_t> prev = m_locations;
m_nearestLocation = ComputeNearest(p_x, p_z, m_radius); m_locations = ComputeAll(p_x, p_z, m_radius);
return m_locations != prev;
if (m_nearestLocation >= 0) {
float dx = p_x - g_locations[m_nearestLocation].m_position[0];
float dz = p_z - g_locations[m_nearestLocation].m_position[2];
m_nearestDistance = std::sqrt(dx * dx + dz * dz);
}
else {
m_nearestDistance = 0.0f;
} }
return m_nearestLocation != prev; bool LocationProximity::IsAtLocation(int16_t p_location) const
{
return std::find(m_locations.begin(), m_locations.end(), p_location) != m_locations.end();
} }
void LocationProximity::Reset() void LocationProximity::Reset()
{ {
m_nearestLocation = -1; m_locations.clear();
m_nearestDistance = 0.0f;
} }
int16_t LocationProximity::ComputeNearest(float p_x, float p_z, float p_radius) std::vector<int16_t> LocationProximity::ComputeAll(float p_x, float p_z, float p_radius)
{ {
float bestDist = p_radius; std::vector<int16_t> result;
int16_t bestLocation = -1;
for (int i = FIRST_VALID_LOCATION; i <= LAST_VALID_LOCATION; i++) { for (int i = FIRST_VALID_LOCATION; i <= LAST_VALID_LOCATION; i++) {
float dx = p_x - g_locations[i].m_position[0]; float dx = p_x - g_locations[i].m_position[0];
float dz = p_z - g_locations[i].m_position[2]; float dz = p_z - g_locations[i].m_position[2];
float dist = std::sqrt(dx * dx + dz * dz); float dist = std::sqrt(dx * dx + dz * dz);
if (dist < bestDist) { if (dist < p_radius) {
bestDist = dist; result.push_back(static_cast<int16_t>(i));
bestLocation = static_cast<int16_t>(i);
} }
} }
return bestLocation; // Sorted by index (iteration order is already ascending), which gives stable comparison
return result;
} }

View File

@ -22,6 +22,8 @@
#include <SDL3/SDL_stdinc.h> #include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include <algorithm>
#include <set>
#include <vector> #include <vector>
using namespace Extensions; using namespace Extensions;
@ -39,7 +41,7 @@ static constexpr float NPC_ANIM_NEARBY_RADIUS_SQ =
(Animation::NPC_ANIM_PROXIMITY + 5.0f) * (Animation::NPC_ANIM_PROXIMITY + 5.0f); (Animation::NPC_ANIM_PROXIMITY + 5.0f) * (Animation::NPC_ANIM_PROXIMITY + 5.0f);
static const char* IDLE_ANIM_STATE_JSON = static const char* IDLE_ANIM_STATE_JSON =
"{\"location\":-1,\"state\":0,\"currentAnimIndex\":65535,\"pendingInterest\":-1,\"animations\":[]}"; "{\"locations\":[],\"state\":0,\"currentAnimIndex\":65535,\"pendingInterest\":-1,\"animations\":[]}";
static void ExtractSlotPeerIds(const AnimUpdateMsg& p_msg, uint32_t p_out[8]) static void ExtractSlotPeerIds(const AnimUpdateMsg& p_msg, uint32_t p_out[8])
{ {
@ -137,11 +139,10 @@ MxResult NetworkManager::Tickle()
if (userActor && userActor->GetROI()) { if (userActor && userActor->GetROI()) {
const float* pos = userActor->GetROI()->GetWorldPosition(); const float* pos = userActor->GetROI()->GetWorldPosition();
if (m_locationProximity.Update(pos[0], pos[2])) { if (m_locationProximity.Update(pos[0], pos[2])) {
int16_t loc = m_locationProximity.GetNearestLocation();
m_animStateDirty = true; m_animStateDirty = true;
Animation::CoordinationState oldState = m_animCoordinator.GetState(); Animation::CoordinationState oldState = m_animCoordinator.GetState();
m_animCoordinator.OnLocationChanged(loc, &m_animCatalog); m_animCoordinator.OnLocationChanged(m_locationProximity.GetLocations(), &m_animCatalog);
// Location change cleared interest — send cancel to host // Location change cleared interest — send cancel to host
if (oldState != Animation::CoordinationState::e_idle && if (oldState != Animation::CoordinationState::e_idle &&
@ -861,23 +862,29 @@ void NetworkManager::ProcessIncomingPackets()
void NetworkManager::UpdateRemotePlayers(float p_deltaTime) void NetworkManager::UpdateRemotePlayers(float p_deltaTime)
{ {
float radius = m_locationProximity.GetRadius(); float radius = m_locationProximity.GetRadius();
int16_t localLoc = m_locationProximity.GetNearestLocation(); const auto& localLocs = m_locationProximity.GetLocations();
bool anyInIsle = false; bool anyInIsle = false;
for (auto& [peerId, player] : m_remotePlayers) { for (auto& [peerId, player] : m_remotePlayers) {
player->Tick(p_deltaTime); player->Tick(p_deltaTime);
// Derive nearest location from remote player's current position // Derive locations from remote player's current position
// Skip players not in the isle world — their position is stale // Skip players not in the isle world — their position is stale
if (player->IsSpawned() && player->GetROI() && player->GetWorldId() == (int8_t) LegoOmni::e_act1) { if (player->IsSpawned() && player->GetROI() && player->GetWorldId() == (int8_t) LegoOmni::e_act1) {
anyInIsle = true; anyInIsle = true;
int16_t oldLoc = player->GetNearestLocation(); auto oldLocs = player->GetLocations();
const float* pos = player->GetROI()->GetWorldPosition(); const float* pos = player->GetROI()->GetWorldPosition();
int16_t newLoc = Animation::LocationProximity::ComputeNearest(pos[0], pos[2], radius); auto newLocs = Animation::LocationProximity::ComputeAll(pos[0], pos[2], radius);
player->SetNearestLocation(newLoc); player->SetLocations(std::move(newLocs));
if (oldLoc != newLoc && (oldLoc == localLoc || newLoc == localLoc)) { if (oldLocs != player->GetLocations()) {
// Dirty if remote's locations changed and any overlap with local player's locations
for (int16_t loc : localLocs) {
if (player->IsAtLocation(loc) || std::find(oldLocs.begin(), oldLocs.end(), loc) != oldLocs.end()) {
m_animStateDirty = true; m_animStateDirty = true;
break;
}
}
} }
} }
} }
@ -1058,8 +1065,12 @@ void NetworkManager::RemoveRemotePlayer(uint32_t p_peerId)
{ {
auto it = m_remotePlayers.find(p_peerId); auto it = m_remotePlayers.find(p_peerId);
if (it != m_remotePlayers.end()) { if (it != m_remotePlayers.end()) {
if (it->second->GetNearestLocation() == m_locationProximity.GetNearestLocation()) { const auto& localLocs = m_locationProximity.GetLocations();
for (int16_t loc : it->second->GetLocations()) {
if (std::find(localLocs.begin(), localLocs.end(), loc) != localLocs.end()) {
m_animStateDirty = true; m_animStateDirty = true;
break;
}
} }
if (it->second->GetROI()) { if (it->second->GetROI()) {
m_roiToPlayer.erase(it->second->GetROI()); m_roiToPlayer.erase(it->second->GetROI());
@ -1251,7 +1262,7 @@ void NetworkManager::TickHostSessions()
if (entry && entry->location >= 0) { if (entry && entry->location >= 0) {
std::vector<uint32_t> toRemove; std::vector<uint32_t> toRemove;
for (const auto& slot : session->slots) { for (const auto& slot : session->slots) {
if (slot.peerId != 0 && GetPeerLocation(slot.peerId) != entry->location) { if (slot.peerId != 0 && !IsPeerAtLocation(slot.peerId, entry->location)) {
toRemove.push_back(slot.peerId); toRemove.push_back(slot.peerId);
} }
} }
@ -1311,7 +1322,7 @@ void NetworkManager::HandleAnimInterest(uint32_t p_peerId, uint16_t p_animIndex,
// For location-bound animations, player must be at that location // For location-bound animations, player must be at that location
const Animation::CatalogEntry* entry = m_animCatalog.FindEntry(p_animIndex); const Animation::CatalogEntry* entry = m_animCatalog.FindEntry(p_animIndex);
if (entry && entry->location >= 0) { if (entry && entry->location >= 0) {
if (GetPeerLocation(p_peerId) != entry->location) { if (!IsPeerAtLocation(p_peerId, entry->location)) {
return; return;
} }
} }
@ -1704,16 +1715,16 @@ void NetworkManager::HandleAnimComplete(const AnimCompleteMsg& p_msg)
m_callbacks->OnAnimationCompleted(json.c_str()); m_callbacks->OnAnimationCompleted(json.c_str());
} }
int16_t NetworkManager::GetPeerLocation(uint32_t p_peerId) const bool NetworkManager::IsPeerAtLocation(uint32_t p_peerId, int16_t p_location) const
{ {
if (p_peerId == m_localPeerId) { if (p_peerId == m_localPeerId) {
return m_locationProximity.GetNearestLocation(); return m_locationProximity.IsAtLocation(p_location);
} }
auto it = m_remotePlayers.find(p_peerId); auto it = m_remotePlayers.find(p_peerId);
if (it != m_remotePlayers.end()) { if (it != m_remotePlayers.end()) {
return it->second->GetNearestLocation(); return it->second->IsAtLocation(p_location);
} }
return -1; return false;
} }
bool NetworkManager::GetPeerPosition(uint32_t p_peerId, float& p_x, float& p_z) const bool NetworkManager::GetPeerPosition(uint32_t p_peerId, float& p_x, float& p_z) const
@ -1775,8 +1786,7 @@ bool NetworkManager::ValidateSessionLocations(uint16_t p_animIndex)
if (slot.peerId == 0) { if (slot.peerId == 0) {
continue; continue;
} }
int16_t loc = GetPeerLocation(slot.peerId); if (!IsPeerAtLocation(slot.peerId, entry->location)) {
if (loc >= 0 && loc != entry->location) {
return false; return false;
} }
} }
@ -1978,7 +1988,7 @@ void NetworkManager::PushAnimationState()
return; return;
} }
int16_t location = m_locationProximity.GetNearestLocation(); const auto& locations = m_locationProximity.GetLocations();
uint8_t displayActorIndex = cam->GetDisplayActorIndex(); uint8_t displayActorIndex = cam->GetDisplayActorIndex();
int8_t localCharIndex = Animation::Catalog::DisplayActorToCharacterIndex(displayActorIndex); int8_t localCharIndex = Animation::Catalog::DisplayActorToCharacterIndex(displayActorIndex);
@ -1992,46 +2002,75 @@ void NetworkManager::PushAnimationState()
const float* localPos = userActor->GetROI()->GetWorldPosition(); const float* localPos = userActor->GetROI()->GetWorldPosition();
float localX = localPos[0], localZ = localPos[2]; float localX = localPos[0], localZ = localPos[2];
// Build two sets of character indices: // Build proximity character indices (for NPC anims — position-based, not location-based)
// - locationCharIndices: players at the same location (for cam anims)
// - proximityCharIndices: players within NPC_ANIM_PROXIMITY (for NPC anims)
std::vector<int8_t> locationCharIndices;
std::vector<int8_t> proximityCharIndices; std::vector<int8_t> proximityCharIndices;
locationCharIndices.push_back(localCharIndex);
proximityCharIndices.push_back(localCharIndex); proximityCharIndices.push_back(localCharIndex);
for (const auto& [peerId, player] : m_remotePlayers) { for (const auto& [peerId, player] : m_remotePlayers) {
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) { if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
continue; continue;
} }
int8_t charIdx = Animation::Catalog::DisplayActorToCharacterIndex(player->GetDisplayActorIndex());
if (player->GetNearestLocation() == location) {
locationCharIndices.push_back(charIdx);
}
// Exact NPC_ANIM_PROXIMITY radius for triggering eligibility // Exact NPC_ANIM_PROXIMITY radius for triggering eligibility
// (tighter than IsPeerNearby's NPC_ANIM_NEARBY_RADIUS_SQ used for session visibility) // (tighter than IsPeerNearby's NPC_ANIM_NEARBY_RADIUS_SQ used for session visibility)
const float* rpos = player->GetROI()->GetWorldPosition(); const float* rpos = player->GetROI()->GetWorldPosition();
float dx = rpos[0] - localX; float dx = rpos[0] - localX;
float dz = rpos[2] - localZ; float dz = rpos[2] - localZ;
if ((dx * dx + dz * dz) <= (Animation::NPC_ANIM_PROXIMITY * Animation::NPC_ANIM_PROXIMITY)) { if ((dx * dx + dz * dz) <= (Animation::NPC_ANIM_PROXIMITY * Animation::NPC_ANIM_PROXIMITY)) {
int8_t charIdx = Animation::Catalog::DisplayActorToCharacterIndex(player->GetDisplayActorIndex());
proximityCharIndices.push_back(charIdx); proximityCharIndices.push_back(charIdx);
} }
} }
auto eligibility = m_animCoordinator.ComputeEligibility( // Compute eligibility across all overlapping locations.
location, // Each call returns NPC anims + cam anims for that specific location.
// NPC anims are identical across calls (same proximityChars), so we deduplicate by animIndex.
std::vector<Animation::EligibilityInfo> eligibility;
std::set<uint16_t> seenAnimIndices;
// If at no location, still process once with -1 to get NPC anims
std::vector<int16_t> locationsToProcess = locations.empty() ? std::vector<int16_t>{int16_t(-1)} : locations;
for (int16_t loc : locationsToProcess) {
// Build per-location character indices (for cam anims at this location)
std::vector<int8_t> locationCharIndices;
locationCharIndices.push_back(localCharIndex);
for (const auto& [peerId, player] : m_remotePlayers) {
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
continue;
}
if (player->IsAtLocation(loc)) {
int8_t charIdx = Animation::Catalog::DisplayActorToCharacterIndex(player->GetDisplayActorIndex());
locationCharIndices.push_back(charIdx);
}
}
auto locEligibility = m_animCoordinator.ComputeEligibility(
loc,
locationCharIndices.data(), locationCharIndices.data(),
static_cast<uint8_t>(locationCharIndices.size()), static_cast<uint8_t>(locationCharIndices.size()),
proximityCharIndices.data(), proximityCharIndices.data(),
static_cast<uint8_t>(proximityCharIndices.size()) static_cast<uint8_t>(proximityCharIndices.size())
); );
for (auto& info : locEligibility) {
if (seenAnimIndices.insert(info.animIndex).second) {
eligibility.push_back(std::move(info));
}
}
}
// Build JSON // Build JSON
std::string json; std::string json;
json.reserve(2048); json.reserve(2048);
json += "{\"location\":"; json += "{\"locations\":[";
json += std::to_string(location); for (size_t i = 0; i < locations.size(); i++) {
json += ",\"state\":"; if (i > 0) {
json += ',';
}
json += std::to_string(locations[i]);
}
json += "],\"state\":";
json += std::to_string(static_cast<uint8_t>(m_animCoordinator.GetState())); json += std::to_string(static_cast<uint8_t>(m_animCoordinator.GetState()));
json += ",\"currentAnimIndex\":"; json += ",\"currentAnimIndex\":";
json += std::to_string(m_animCoordinator.GetCurrentAnimIndex()); json += std::to_string(m_animCoordinator.GetCurrentAnimIndex());

View File

@ -15,6 +15,7 @@
#include <SDL3/SDL_stdinc.h> #include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include <algorithm>
#include <vec.h> #include <vec.h>
using namespace Extensions; using namespace Extensions;
@ -29,7 +30,7 @@ using Common::WORLD_NOT_VISIBLE;
RemotePlayer::RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex) 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_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_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_nearestLocation(-1), m_targetWorldId(WORLD_NOT_VISIBLE), m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false, /*.propSuffix=*/p_peerId}), m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false, /*.propSuffix=*/p_peerId}),
m_vehicleROI(nullptr), m_nameBubble(nullptr), m_allowRemoteCustomize(true), m_animationLocked(false) m_vehicleROI(nullptr), m_nameBubble(nullptr), m_allowRemoteCustomize(true), m_animationLocked(false)
{ {
@ -55,6 +56,11 @@ RemotePlayer::~RemotePlayer()
Despawn(); Despawn();
} }
bool RemotePlayer::IsAtLocation(int16_t p_location) const
{
return std::find(m_locations.begin(), m_locations.end(), p_location) != m_locations.end();
}
void RemotePlayer::Spawn(LegoWorld* p_isleWorld) void RemotePlayer::Spawn(LegoWorld* p_isleWorld)
{ {
if (m_spawned) { if (m_spawned) {