mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
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:
parent
892fb808b6
commit
32fd90d804
@ -65,8 +65,8 @@ class Coordinator {
|
||||
uint8_t p_proximityCount
|
||||
) const;
|
||||
|
||||
// Auto-clear interest if current animation is not available at the new location.
|
||||
void OnLocationChanged(int16_t p_location, const Catalog* p_catalog);
|
||||
// Auto-clear interest if current animation is not available at any of the new locations.
|
||||
void OnLocationChanged(const std::vector<int16_t>& p_locations, const Catalog* p_catalog);
|
||||
|
||||
void Reset();
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Multiplayer::Animation
|
||||
{
|
||||
@ -11,23 +12,22 @@ class LocationProximity {
|
||||
public:
|
||||
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);
|
||||
|
||||
int16_t GetNearestLocation() const { return m_nearestLocation; }
|
||||
float GetNearestDistance() const { return m_nearestDistance; }
|
||||
// All locations within radius (sorted by index for stable comparison)
|
||||
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; }
|
||||
void Reset();
|
||||
|
||||
// Static version for computing any position's nearest location
|
||||
static int16_t ComputeNearest(float p_x, float p_z, float p_radius);
|
||||
// Static version returning all locations within radius (sorted by index)
|
||||
static std::vector<int16_t> ComputeAll(float p_x, float p_z, float p_radius);
|
||||
|
||||
private:
|
||||
int16_t m_nearestLocation;
|
||||
float m_nearestDistance;
|
||||
float m_radius;
|
||||
std::vector<int16_t> m_locations;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer::Animation
|
||||
|
||||
@ -142,7 +142,7 @@ class NetworkManager : public MxCore {
|
||||
void BroadcastAnimStart(uint16_t p_animIndex);
|
||||
void BroadcastAnimComplete(uint16_t p_animIndex);
|
||||
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 IsPeerNearby(uint32_t p_peerId, float p_refX, float p_refZ) const;
|
||||
bool ValidateSessionLocations(uint16_t p_animIndex);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
@ -36,8 +37,9 @@ class RemotePlayer {
|
||||
bool IsSpawned() const { return m_spawned; }
|
||||
bool IsVisible() const { return m_visible; }
|
||||
int8_t GetWorldId() const { return m_targetWorldId; }
|
||||
int16_t GetNearestLocation() const { return m_nearestLocation; }
|
||||
void SetNearestLocation(int16_t p_location) { m_nearestLocation = p_location; }
|
||||
const std::vector<int16_t>& GetLocations() const { return m_locations; }
|
||||
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; }
|
||||
void SetVisible(bool p_visible);
|
||||
void TriggerEmote(uint8_t p_emoteId);
|
||||
@ -84,7 +86,7 @@ class RemotePlayer {
|
||||
int8_t m_targetWorldId;
|
||||
uint32_t m_lastUpdateTime;
|
||||
bool m_hasReceivedUpdate;
|
||||
int16_t m_nearestLocation;
|
||||
std::vector<int16_t> m_locations;
|
||||
|
||||
float m_currentPosition[3];
|
||||
float m_currentDirection[3];
|
||||
|
||||
@ -145,20 +145,33 @@ std::vector<EligibilityInfo> Coordinator::ComputeEligibility(
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto anims = p_catalog->GetAnimationsAtLocation(p_location);
|
||||
for (const auto* e : anims) {
|
||||
if (e->animIndex == m_currentAnimIndex) {
|
||||
return; // still available
|
||||
// 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) {
|
||||
if (e->animIndex == m_currentAnimIndex) {
|
||||
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_currentAnimIndex = ANIM_INDEX_NONE;
|
||||
m_cancelPending = true;
|
||||
|
||||
@ -3,58 +3,52 @@
|
||||
#include "decomp.h"
|
||||
#include "legolocations.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
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
|
||||
static const int FIRST_VALID_LOCATION = 1;
|
||||
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)
|
||||
{
|
||||
int16_t prev = m_nearestLocation;
|
||||
m_nearestLocation = ComputeNearest(p_x, p_z, m_radius);
|
||||
std::vector<int16_t> prev = m_locations;
|
||||
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()
|
||||
{
|
||||
m_nearestLocation = -1;
|
||||
m_nearestDistance = 0.0f;
|
||||
m_locations.clear();
|
||||
}
|
||||
|
||||
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;
|
||||
int16_t bestLocation = -1;
|
||||
std::vector<int16_t> result;
|
||||
|
||||
for (int i = FIRST_VALID_LOCATION; i <= LAST_VALID_LOCATION; i++) {
|
||||
float dx = p_x - g_locations[i].m_position[0];
|
||||
float dz = p_z - g_locations[i].m_position[2];
|
||||
float dist = std::sqrt(dx * dx + dz * dz);
|
||||
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestLocation = static_cast<int16_t>(i);
|
||||
if (dist < p_radius) {
|
||||
result.push_back(static_cast<int16_t>(i));
|
||||
}
|
||||
}
|
||||
|
||||
return bestLocation;
|
||||
// Sorted by index (iteration order is already ascending), which gives stable comparison
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
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);
|
||||
|
||||
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])
|
||||
{
|
||||
@ -137,11 +139,10 @@ MxResult NetworkManager::Tickle()
|
||||
if (userActor && userActor->GetROI()) {
|
||||
const float* pos = userActor->GetROI()->GetWorldPosition();
|
||||
if (m_locationProximity.Update(pos[0], pos[2])) {
|
||||
int16_t loc = m_locationProximity.GetNearestLocation();
|
||||
m_animStateDirty = true;
|
||||
|
||||
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
|
||||
if (oldState != Animation::CoordinationState::e_idle &&
|
||||
@ -861,23 +862,29 @@ void NetworkManager::ProcessIncomingPackets()
|
||||
void NetworkManager::UpdateRemotePlayers(float p_deltaTime)
|
||||
{
|
||||
float radius = m_locationProximity.GetRadius();
|
||||
int16_t localLoc = m_locationProximity.GetNearestLocation();
|
||||
const auto& localLocs = m_locationProximity.GetLocations();
|
||||
bool anyInIsle = false;
|
||||
|
||||
for (auto& [peerId, player] : m_remotePlayers) {
|
||||
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
|
||||
if (player->IsSpawned() && player->GetROI() && player->GetWorldId() == (int8_t) LegoOmni::e_act1) {
|
||||
anyInIsle = true;
|
||||
|
||||
int16_t oldLoc = player->GetNearestLocation();
|
||||
auto oldLocs = player->GetLocations();
|
||||
const float* pos = player->GetROI()->GetWorldPosition();
|
||||
int16_t newLoc = Animation::LocationProximity::ComputeNearest(pos[0], pos[2], radius);
|
||||
player->SetNearestLocation(newLoc);
|
||||
if (oldLoc != newLoc && (oldLoc == localLoc || newLoc == localLoc)) {
|
||||
m_animStateDirty = true;
|
||||
auto newLocs = Animation::LocationProximity::ComputeAll(pos[0], pos[2], radius);
|
||||
player->SetLocations(std::move(newLocs));
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1058,8 +1065,12 @@ void NetworkManager::RemoveRemotePlayer(uint32_t p_peerId)
|
||||
{
|
||||
auto it = m_remotePlayers.find(p_peerId);
|
||||
if (it != m_remotePlayers.end()) {
|
||||
if (it->second->GetNearestLocation() == m_locationProximity.GetNearestLocation()) {
|
||||
m_animStateDirty = true;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (it->second->GetROI()) {
|
||||
m_roiToPlayer.erase(it->second->GetROI());
|
||||
@ -1251,7 +1262,7 @@ void NetworkManager::TickHostSessions()
|
||||
if (entry && entry->location >= 0) {
|
||||
std::vector<uint32_t> toRemove;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
const Animation::CatalogEntry* entry = m_animCatalog.FindEntry(p_animIndex);
|
||||
if (entry && entry->location >= 0) {
|
||||
if (GetPeerLocation(p_peerId) != entry->location) {
|
||||
if (!IsPeerAtLocation(p_peerId, entry->location)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1704,16 +1715,16 @@ void NetworkManager::HandleAnimComplete(const AnimCompleteMsg& p_msg)
|
||||
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) {
|
||||
return m_locationProximity.GetNearestLocation();
|
||||
return m_locationProximity.IsAtLocation(p_location);
|
||||
}
|
||||
auto it = m_remotePlayers.find(p_peerId);
|
||||
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
|
||||
@ -1775,8 +1786,7 @@ bool NetworkManager::ValidateSessionLocations(uint16_t p_animIndex)
|
||||
if (slot.peerId == 0) {
|
||||
continue;
|
||||
}
|
||||
int16_t loc = GetPeerLocation(slot.peerId);
|
||||
if (loc >= 0 && loc != entry->location) {
|
||||
if (!IsPeerAtLocation(slot.peerId, entry->location)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1978,7 +1988,7 @@ void NetworkManager::PushAnimationState()
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t location = m_locationProximity.GetNearestLocation();
|
||||
const auto& locations = m_locationProximity.GetLocations();
|
||||
uint8_t displayActorIndex = cam->GetDisplayActorIndex();
|
||||
int8_t localCharIndex = Animation::Catalog::DisplayActorToCharacterIndex(displayActorIndex);
|
||||
|
||||
@ -1992,46 +2002,75 @@ void NetworkManager::PushAnimationState()
|
||||
const float* localPos = userActor->GetROI()->GetWorldPosition();
|
||||
float localX = localPos[0], localZ = localPos[2];
|
||||
|
||||
// Build two sets of character indices:
|
||||
// - locationCharIndices: players at the same location (for cam anims)
|
||||
// - proximityCharIndices: players within NPC_ANIM_PROXIMITY (for NPC anims)
|
||||
std::vector<int8_t> locationCharIndices;
|
||||
// Build proximity character indices (for NPC anims — position-based, not location-based)
|
||||
std::vector<int8_t> proximityCharIndices;
|
||||
locationCharIndices.push_back(localCharIndex);
|
||||
proximityCharIndices.push_back(localCharIndex);
|
||||
|
||||
for (const auto& [peerId, player] : m_remotePlayers) {
|
||||
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
|
||||
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
|
||||
// (tighter than IsPeerNearby's NPC_ANIM_NEARBY_RADIUS_SQ used for session visibility)
|
||||
const float* rpos = player->GetROI()->GetWorldPosition();
|
||||
float dx = rpos[0] - localX;
|
||||
float dz = rpos[2] - localZ;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
auto eligibility = m_animCoordinator.ComputeEligibility(
|
||||
location,
|
||||
locationCharIndices.data(),
|
||||
static_cast<uint8_t>(locationCharIndices.size()),
|
||||
proximityCharIndices.data(),
|
||||
static_cast<uint8_t>(proximityCharIndices.size())
|
||||
);
|
||||
// Compute eligibility across all overlapping locations.
|
||||
// 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(),
|
||||
static_cast<uint8_t>(locationCharIndices.size()),
|
||||
proximityCharIndices.data(),
|
||||
static_cast<uint8_t>(proximityCharIndices.size())
|
||||
);
|
||||
|
||||
for (auto& info : locEligibility) {
|
||||
if (seenAnimIndices.insert(info.animIndex).second) {
|
||||
eligibility.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build JSON
|
||||
std::string json;
|
||||
json.reserve(2048);
|
||||
json += "{\"location\":";
|
||||
json += std::to_string(location);
|
||||
json += ",\"state\":";
|
||||
json += "{\"locations\":[";
|
||||
for (size_t i = 0; i < locations.size(); i++) {
|
||||
if (i > 0) {
|
||||
json += ',';
|
||||
}
|
||||
json += std::to_string(locations[i]);
|
||||
}
|
||||
json += "],\"state\":";
|
||||
json += std::to_string(static_cast<uint8_t>(m_animCoordinator.GetState()));
|
||||
json += ",\"currentAnimIndex\":";
|
||||
json += std::to_string(m_animCoordinator.GetCurrentAnimIndex());
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
#include <vec.h>
|
||||
|
||||
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)
|
||||
: 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(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_vehicleROI(nullptr), m_nameBubble(nullptr), m_allowRemoteCustomize(true), m_animationLocked(false)
|
||||
{
|
||||
@ -55,6 +56,11 @@ RemotePlayer::~RemotePlayer()
|
||||
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)
|
||||
{
|
||||
if (m_spawned) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user