mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-01 18:13:57 +00:00
Nick bricks memories (#21)
* WIP: Add animation completion protocol message for Nick Brick's Memories Add MSG_ANIM_COMPLETE (15) protocol message broadcast by the host on natural animation completion. Contains a 64-bit random event ID, the SI object ID, and per-participant data (charIndex, displayName). - BroadcastAnimComplete: gathers participants from session slots, resolves spectator characters from display actors, generates event ID - HandleAnimComplete: filters observers (only participants get callback), builds JSON with eventId/objectId/participants for frontend - OnAnimationCompleted callback in PlatformCallbacks, implemented for Emscripten (CustomEvent dispatch) and Native (SDL_Log) - GetDisplayName() getter on RemotePlayer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix participant ordering in animation completion message Emit the local player first in the JSON participants array so the frontend can rely on participants[0] being self when reporting to the server. Extract participant JSON-building into a lambda to avoid duplication. Retain null-termination safety for displayName. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Disable workers.dev and preview URLs for relay server Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix path actor assertion failure / freeze on repeated vehicle enter/exit When the third-person camera is active, LMB triggers both forward movement and vehicle interaction. This leaves the previous actor with m_worldSpeed > 0 when entering a vehicle, causing it to wander on the path system in non-user-nav mode. On exit, SetBoundary() overwrites m_boundary without updating m_destEdge, creating a boundary/edge mismatch. On the next vehicle enter, the stale spline finishes and SwitchBoundary asserts (debug) or loops infinitely (release). Stop the previous actor from wandering by zeroing its world speed on enter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add hold threshold to disambiguate LMB click from hold-to-walk A 300ms time threshold prevents brief clicks (for interacting with world objects) from also triggering forward movement in third-person camera mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add missing SDL_timer.h include for SDL_GetTicks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove maxActors setting from multiplayer — NPCs are always disabled The maxActors room setting added unnecessary complexity for a feature that should always be off in multiplayer. Remove it from the relay server protocol, room configuration API, C++ client, and simplify NPC enforcement to be unconditional. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9a20bd42d1
commit
99c871ab16
@ -1,6 +1,7 @@
|
||||
#ifndef ISLEPATHACTOR_H
|
||||
#define ISLEPATHACTOR_H
|
||||
|
||||
#include "extensions/fwd.h"
|
||||
#include "legogamestate.h"
|
||||
#include "legopathactor.h"
|
||||
#include "mxtypes.h"
|
||||
@ -139,6 +140,8 @@ class IslePathActor : public LegoPathActor {
|
||||
// IslePathActor::`scalar deleting destructor'
|
||||
|
||||
protected:
|
||||
friend class Extensions::ThirdPersonCamera::Controller;
|
||||
|
||||
LegoWorld* m_world; // 0x154
|
||||
LegoPathActor* m_previousActor; // 0x158
|
||||
MxFloat m_previousVel; // 0x15c
|
||||
|
||||
@ -140,6 +140,8 @@ class NetworkManager : public MxCore {
|
||||
void BroadcastAnimUpdate(uint16_t p_animIndex);
|
||||
void SendAnimUpdateToPlayer(uint16_t p_animIndex, uint32_t p_targetPeerId);
|
||||
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 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;
|
||||
@ -191,7 +193,6 @@ class NetworkManager : public MxCore {
|
||||
std::atomic<int32_t> m_pendingAnimInterest;
|
||||
std::atomic<bool> m_pendingAnimCancel;
|
||||
|
||||
bool m_disableAllNPCs;
|
||||
bool m_showNameBubbles;
|
||||
bool m_lastCameraEnabled;
|
||||
bool m_wasInRestrictedArea;
|
||||
|
||||
@ -33,6 +33,11 @@ class PlatformCallbacks {
|
||||
// Called when animation eligibility state changes (location change, player join/leave, etc.).
|
||||
// p_json = JSON payload with location, coordinator state, and per-animation slot fill status.
|
||||
virtual void OnAnimationsAvailable(const char* p_json) = 0;
|
||||
|
||||
// Called when an animation completes successfully (natural completion, not cancellation).
|
||||
// Only fired for actual participants, not observers.
|
||||
// p_json = JSON with eventId, animIndex, and participant details (charIndex, displayName).
|
||||
virtual void OnAnimationCompleted(const char* p_json) = 0;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
|
||||
@ -15,6 +15,7 @@ class EmscriptenCallbacks : public PlatformCallbacks {
|
||||
void OnAllowCustomizeChanged(bool p_enabled) override;
|
||||
void OnConnectionStatusChanged(int p_status) override;
|
||||
void OnAnimationsAvailable(const char* p_json) override;
|
||||
void OnAnimationCompleted(const char* p_json) override;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
|
||||
@ -15,6 +15,7 @@ class NativeCallbacks : public PlatformCallbacks {
|
||||
void OnAllowCustomizeChanged(bool p_enabled) override;
|
||||
void OnConnectionStatusChanged(int p_status) override;
|
||||
void OnAnimationsAvailable(const char* p_json) override;
|
||||
void OnAnimationCompleted(const char* p_json) override;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
|
||||
@ -29,6 +29,7 @@ enum MessageType : uint8_t {
|
||||
MSG_ANIM_CANCEL = 12,
|
||||
MSG_ANIM_UPDATE = 13,
|
||||
MSG_ANIM_START = 14,
|
||||
MSG_ANIM_COMPLETE = 15,
|
||||
MSG_ASSIGN_ID = 0xFF
|
||||
};
|
||||
|
||||
@ -187,6 +188,22 @@ struct AnimStartMsg {
|
||||
uint16_t animIndex;
|
||||
};
|
||||
|
||||
// Per-participant data in AnimCompleteMsg
|
||||
struct AnimCompletionParticipant {
|
||||
uint32_t peerId;
|
||||
int8_t charIndex; // Participant's character (g_characters index)
|
||||
char displayName[8]; // 7 chars + null
|
||||
};
|
||||
|
||||
// Host -> All: animation completed successfully (natural completion only, not cancellation)
|
||||
struct AnimCompleteMsg {
|
||||
MessageHeader header;
|
||||
uint64_t eventId; // Random 64-bit ID unique to this completion event
|
||||
uint32_t objectId; // SI file object ID (stable, used as frontend key)
|
||||
uint8_t participantCount;
|
||||
AnimCompletionParticipant participants[8];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
using Extensions::Common::IsValidActorId;
|
||||
|
||||
@ -54,6 +54,8 @@ class RemotePlayer {
|
||||
bool IsMoving() const { return m_animator.IsInVehicle() || m_targetSpeed > 0.01f; }
|
||||
bool IsInMultiPartEmote() const { return m_animator.IsInMultiPartEmote(); }
|
||||
|
||||
const char* GetDisplayName() const { return m_displayName; }
|
||||
|
||||
void SetAnimationLocked(bool p_locked) { m_animationLocked = p_locked; }
|
||||
bool IsAnimationLocked() const { return m_animationLocked; }
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ class InputHandler {
|
||||
SDL_FingerID GetFingerID(int p_idx) const { return m_touch.id[p_idx]; }
|
||||
|
||||
bool IsLeftButtonHeld() const { return m_leftButtonHeld; }
|
||||
bool IsLmbHeldForMovement() const;
|
||||
|
||||
bool ConsumeAutoDisable();
|
||||
bool ConsumeAutoEnable();
|
||||
@ -31,6 +32,7 @@ class InputHandler {
|
||||
|
||||
static constexpr float CAMERA_ZONE_X = 0.5f;
|
||||
static constexpr float PINCH_TRANSITION_THRESHOLD = 0.03f;
|
||||
static constexpr Uint64 LMB_HOLD_THRESHOLD_MS = 300;
|
||||
|
||||
private:
|
||||
struct TouchState {
|
||||
@ -46,6 +48,7 @@ class InputHandler {
|
||||
bool m_wantsAutoEnable;
|
||||
bool m_rightButtonHeld;
|
||||
bool m_leftButtonHeld;
|
||||
Uint64 m_leftButtonDownTime;
|
||||
float m_savedMouseX;
|
||||
float m_savedMouseY;
|
||||
};
|
||||
|
||||
@ -68,10 +68,10 @@ NetworkManager::NetworkManager()
|
||||
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_pendingAnimInterest(-1), m_pendingAnimCancel(false), m_localPendingAnimInterest(-1),
|
||||
m_playingAnimIndex(Animation::ANIM_INDEX_NONE), m_disableAllNPCs(false), m_showNameBubbles(true),
|
||||
m_lastCameraEnabled(false), m_wasInRestrictedArea(false), m_animStateDirty(false), m_animInterestDirty(false),
|
||||
m_lastAnimPushTime(0), m_connectionState(STATE_DISCONNECTED), m_wasRejected(false), m_reconnectAttempt(0),
|
||||
m_reconnectDelay(0), m_nextReconnectTime(0)
|
||||
m_playingAnimIndex(Animation::ANIM_INDEX_NONE), m_showNameBubbles(true), m_lastCameraEnabled(false),
|
||||
m_wasInRestrictedArea(false), m_animStateDirty(false), m_animInterestDirty(false), m_lastAnimPushTime(0),
|
||||
m_connectionState(STATE_DISCONNECTED), m_wasRejected(false), m_reconnectAttempt(0), m_reconnectDelay(0),
|
||||
m_nextReconnectTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -90,9 +90,7 @@ MxResult NetworkManager::Tickle()
|
||||
ProcessPendingRequests();
|
||||
CheckConnectionState();
|
||||
|
||||
if (m_disableAllNPCs) {
|
||||
EnforceDisableNPCs();
|
||||
}
|
||||
EnforceDisableNPCs();
|
||||
|
||||
// Detect camera state changes for platform notification
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
@ -354,10 +352,7 @@ void NetworkManager::OnWorldEnabled(LegoWorld* p_world)
|
||||
}
|
||||
|
||||
NotifyPlayerCountChanged();
|
||||
|
||||
if (m_disableAllNPCs) {
|
||||
EnforceDisableNPCs();
|
||||
}
|
||||
EnforceDisableNPCs();
|
||||
|
||||
// Refresh animation catalog from the animation manager
|
||||
if (AnimationManager()) {
|
||||
@ -752,21 +747,13 @@ void NetworkManager::ProcessIncomingPackets()
|
||||
m_localPeerId = assignedId;
|
||||
m_worldSync.SetLocalPeerId(assignedId);
|
||||
m_animCoordinator.SetLocalPeerId(assignedId);
|
||||
}
|
||||
if (length >= 6) {
|
||||
uint8_t maxActors = data[5];
|
||||
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();
|
||||
}
|
||||
|
||||
LegoAnimationManager::configureLegoAnimationManager(0);
|
||||
if (AnimationManager()) {
|
||||
AnimationManager()->m_maxAllowedExtras = 0;
|
||||
AnimationManager()->m_numAllowedExtras = 0;
|
||||
}
|
||||
EnforceDisableNPCs();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -858,6 +845,13 @@ void NetworkManager::ProcessIncomingPackets()
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MSG_ANIM_COMPLETE: {
|
||||
AnimCompleteMsg msg;
|
||||
if (DeserializeMsg(data, length, msg) && msg.header.type == MSG_ANIM_COMPLETE) {
|
||||
HandleAnimComplete(msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1224,6 +1218,7 @@ void NetworkManager::TickAnimation()
|
||||
}
|
||||
|
||||
if (IsHost() && m_playingAnimIndex != Animation::ANIM_INDEX_NONE) {
|
||||
BroadcastAnimComplete(m_playingAnimIndex); // Must fire before EraseSession destroys participant data
|
||||
m_animSessionHost.EraseSession(m_playingAnimIndex);
|
||||
BroadcastAnimUpdate(m_playingAnimIndex); // Broadcast cleared state
|
||||
}
|
||||
@ -1579,6 +1574,136 @@ void NetworkManager::BroadcastAnimStart(uint16_t p_animIndex)
|
||||
m_animCoordinator.ApplyAnimStart(p_animIndex);
|
||||
}
|
||||
|
||||
void NetworkManager::BroadcastAnimComplete(uint16_t p_animIndex)
|
||||
{
|
||||
const Animation::AnimSession* session = m_animSessionHost.FindSession(p_animIndex);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const AnimInfo* animInfo = m_animCatalog.GetAnimInfo(p_animIndex);
|
||||
if (!animInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimCompleteMsg msg{};
|
||||
msg.header = {MSG_ANIM_COMPLETE, m_localPeerId, m_sequence++, TARGET_BROADCAST};
|
||||
msg.eventId = (static_cast<uint64_t>(SDL_rand_bits()) << 32) | static_cast<uint64_t>(SDL_rand_bits());
|
||||
msg.objectId = animInfo->m_objectId;
|
||||
msg.participantCount = 0;
|
||||
|
||||
char localName[8];
|
||||
EncodeUsername(localName);
|
||||
|
||||
for (const auto& slot : session->slots) {
|
||||
if (slot.peerId == 0 || msg.participantCount >= 8) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AnimCompletionParticipant& p = msg.participants[msg.participantCount];
|
||||
p.peerId = slot.peerId;
|
||||
|
||||
if (slot.IsSpectator()) {
|
||||
// Resolve spectator's actual character from their display actor
|
||||
if (slot.peerId == m_localPeerId) {
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
p.charIndex = cam ? Animation::Catalog::DisplayActorToCharacterIndex(cam->GetDisplayActorIndex()) : -1;
|
||||
}
|
||||
else {
|
||||
auto it = m_remotePlayers.find(slot.peerId);
|
||||
p.charIndex = it != m_remotePlayers.end()
|
||||
? Animation::Catalog::DisplayActorToCharacterIndex(it->second->GetDisplayActorIndex())
|
||||
: -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
p.charIndex = slot.charIndex;
|
||||
}
|
||||
|
||||
if (slot.peerId == m_localPeerId) {
|
||||
SDL_memcpy(p.displayName, localName, sizeof(p.displayName));
|
||||
}
|
||||
else {
|
||||
auto it = m_remotePlayers.find(slot.peerId);
|
||||
if (it != m_remotePlayers.end()) {
|
||||
SDL_memcpy(p.displayName, it->second->GetDisplayName(), sizeof(p.displayName));
|
||||
}
|
||||
else {
|
||||
p.displayName[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
msg.participantCount++;
|
||||
}
|
||||
|
||||
SendMessage(msg);
|
||||
|
||||
// Also handle locally on the host (message sent to TARGET_BROADCAST excludes sender)
|
||||
HandleAnimComplete(msg);
|
||||
}
|
||||
|
||||
void NetworkManager::HandleAnimComplete(const AnimCompleteMsg& p_msg)
|
||||
{
|
||||
// Only fire callback for actual participants, not observers
|
||||
int localIdx = -1;
|
||||
for (uint8_t i = 0; i < p_msg.participantCount; i++) {
|
||||
if (p_msg.participants[i].peerId == m_localPeerId) {
|
||||
localIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (localIdx < 0 || !m_callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build JSON for frontend
|
||||
char eventIdHex[17];
|
||||
SDL_snprintf(
|
||||
eventIdHex,
|
||||
sizeof(eventIdHex),
|
||||
"%08x%08x",
|
||||
static_cast<uint32_t>(p_msg.eventId >> 32),
|
||||
static_cast<uint32_t>(p_msg.eventId & 0xFFFFFFFF)
|
||||
);
|
||||
|
||||
std::string json = "{\"eventId\":\"";
|
||||
json += eventIdHex;
|
||||
json += "\",\"objectId\":";
|
||||
json += std::to_string(p_msg.objectId);
|
||||
json += ",\"participants\":[";
|
||||
|
||||
// Emit local player first so frontend can rely on participants[0] being self
|
||||
bool first = true;
|
||||
auto appendParticipant = [&](uint8_t i) {
|
||||
if (!first) {
|
||||
json += ',';
|
||||
}
|
||||
first = false;
|
||||
const AnimCompletionParticipant& p = p_msg.participants[i];
|
||||
// Ensure null-termination safety for displayName (protocol uses fixed char[8])
|
||||
char name[8];
|
||||
SDL_memcpy(name, p.displayName, sizeof(name));
|
||||
name[7] = '\0';
|
||||
json += "{\"charIndex\":";
|
||||
json += std::to_string(static_cast<int>(p.charIndex));
|
||||
json += ",\"displayName\":\"";
|
||||
json += name;
|
||||
json += "\"}";
|
||||
};
|
||||
|
||||
appendParticipant(static_cast<uint8_t>(localIdx));
|
||||
for (uint8_t i = 0; i < p_msg.participantCount; i++) {
|
||||
if (i != static_cast<uint8_t>(localIdx)) {
|
||||
appendParticipant(i);
|
||||
}
|
||||
}
|
||||
|
||||
json += "]}";
|
||||
|
||||
m_callbacks->OnAnimationCompleted(json.c_str());
|
||||
}
|
||||
|
||||
int16_t NetworkManager::GetPeerLocation(uint32_t p_peerId) const
|
||||
{
|
||||
if (p_peerId == m_localPeerId) {
|
||||
|
||||
@ -78,6 +78,20 @@ void EmscriptenCallbacks::OnAnimationsAvailable(const char* p_json)
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void EmscriptenCallbacks::OnAnimationCompleted(const char* p_json)
|
||||
{
|
||||
// clang-format off
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var canvas = Module.canvas;
|
||||
if (canvas) {
|
||||
canvas.dispatchEvent(new CustomEvent('animationCompleted', {
|
||||
detail: { json: UTF8ToString($0) }
|
||||
}));
|
||||
}
|
||||
}, p_json);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
} // namespace Multiplayer
|
||||
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
@ -57,6 +57,11 @@ void NativeCallbacks::OnAnimationsAvailable(const char* p_json)
|
||||
(void) p_json;
|
||||
}
|
||||
|
||||
void NativeCallbacks::OnAnimationCompleted(const char* p_json)
|
||||
{
|
||||
SDL_Log("[Multiplayer] Animation completed: %s", p_json);
|
||||
}
|
||||
|
||||
} // namespace Multiplayer
|
||||
|
||||
#endif // !__EMSCRIPTEN__
|
||||
|
||||
@ -23,7 +23,6 @@ export class GameRoom implements DurableObject {
|
||||
private nextPeerId = 1;
|
||||
private hostPeerId = 0;
|
||||
private maxPlayers = 5;
|
||||
private maxActors = 0;
|
||||
|
||||
constructor(
|
||||
private state: DurableObjectState,
|
||||
@ -52,7 +51,7 @@ export class GameRoom implements DurableObject {
|
||||
server.accept();
|
||||
this.connections.set(peerId, server);
|
||||
|
||||
server.send(createAssignIdMsg(peerId, this.maxActors));
|
||||
server.send(createAssignIdMsg(peerId));
|
||||
this.assignHostIfNeeded(peerId, server);
|
||||
|
||||
server.addEventListener("message", (event) =>
|
||||
@ -77,7 +76,6 @@ export class GameRoom implements DurableObject {
|
||||
try {
|
||||
const body = (await request.json()) as {
|
||||
maxPlayers?: number;
|
||||
maxActors?: number;
|
||||
};
|
||||
const ceiling = this.env.MAX_PLAYERS_CEILING
|
||||
? Number(this.env.MAX_PLAYERS_CEILING)
|
||||
@ -88,17 +86,11 @@ export class GameRoom implements DurableObject {
|
||||
Math.min(body.maxPlayers, ceiling)
|
||||
);
|
||||
}
|
||||
if (body.maxActors !== undefined) {
|
||||
this.maxActors = Math.max(
|
||||
0,
|
||||
Math.min(body.maxActors, 40)
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors, keep defaults
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({ maxPlayers: this.maxPlayers, maxActors: this.maxActors }),
|
||||
JSON.stringify({ maxPlayers: this.maxPlayers }),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@ -113,7 +105,6 @@ export class GameRoom implements DurableObject {
|
||||
JSON.stringify({
|
||||
players: this.connections.size,
|
||||
maxPlayers: this.maxPlayers,
|
||||
maxActors: this.maxActors,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
|
||||
@ -16,18 +16,17 @@ export const MSG_LEAVE = 2;
|
||||
export const MSG_HOST_ASSIGN = 4;
|
||||
export const MSG_ASSIGN_ID = 0xff;
|
||||
|
||||
// AssignIdMsg: compact server-only message — type(1) + peerId(4) + maxActors(1)
|
||||
const ASSIGN_ID_SIZE = 1 + 4 + 1;
|
||||
// AssignIdMsg: compact server-only message — type(1) + peerId(4)
|
||||
const ASSIGN_ID_SIZE = 1 + 4;
|
||||
|
||||
// HostAssignMsg: header(13) + hostPeerId(4)
|
||||
const HOST_ASSIGN_SIZE = HEADER_SIZE + 4;
|
||||
|
||||
export function createAssignIdMsg(peerId: number, maxActors: number): ArrayBuffer {
|
||||
export function createAssignIdMsg(peerId: number): ArrayBuffer {
|
||||
const buf = new ArrayBuffer(ASSIGN_ID_SIZE);
|
||||
const view = new DataView(buf);
|
||||
view.setUint8(0, MSG_ASSIGN_ID);
|
||||
view.setUint32(1, peerId, true);
|
||||
view.setUint8(5, maxActors);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
name = "isle-relay"
|
||||
main = "relay.ts"
|
||||
compatibility_date = "2024-01-01"
|
||||
workers_dev = false
|
||||
preview_urls = false
|
||||
|
||||
[durable_objects]
|
||||
bindings = [
|
||||
|
||||
@ -87,6 +87,14 @@ void Controller::OnActorEnter(IslePathActor* p_actor)
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the previous actor from wandering on the path system with stale
|
||||
// spline state while the player is in a vehicle. Exit() will later call
|
||||
// SetBoundary() without updating m_destEdge, so any non-user-nav animation
|
||||
// with the old spline would use a mismatched boundary/edge pair.
|
||||
if (p_actor->m_previousActor) {
|
||||
p_actor->m_previousActor->SetWorldSpeed(0);
|
||||
}
|
||||
|
||||
m_animator.SetCurrentVehicleType(DetectVehicleType(userActor));
|
||||
|
||||
if (!m_enabled || IsRestrictedArea(GameState()->m_currentArea)) {
|
||||
@ -392,7 +400,7 @@ MxBool Controller::HandleCameraRelativeMovement(
|
||||
p_newDir,
|
||||
p_deltaTime,
|
||||
m_animator.IsInMultiPartEmote() || m_animPlaying,
|
||||
m_input.IsLeftButtonHeld()
|
||||
m_input.IsLmbHeldForMovement()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -3,13 +3,14 @@
|
||||
#include "extensions/thirdpersoncamera/orbitcamera.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <utility>
|
||||
|
||||
using namespace Extensions::ThirdPersonCamera;
|
||||
|
||||
InputHandler::InputHandler()
|
||||
: m_touch{}, m_wantsAutoDisable(false), m_wantsAutoEnable(false), m_rightButtonHeld(false),
|
||||
m_leftButtonHeld(false), m_savedMouseX(0.0f), m_savedMouseY(0.0f)
|
||||
m_leftButtonHeld(false), m_leftButtonDownTime(0), m_savedMouseX(0.0f), m_savedMouseY(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
@ -75,6 +76,12 @@ bool InputHandler::ConsumeAutoEnable()
|
||||
return std::exchange(m_wantsAutoEnable, false);
|
||||
}
|
||||
|
||||
bool InputHandler::IsLmbHeldForMovement() const
|
||||
{
|
||||
return m_leftButtonHeld && m_leftButtonDownTime > 0 &&
|
||||
(SDL_GetTicks() - m_leftButtonDownTime) >= LMB_HOLD_THRESHOLD_MS;
|
||||
}
|
||||
|
||||
void InputHandler::SuppressGestures()
|
||||
{
|
||||
m_touch.synced[0] = false;
|
||||
@ -132,6 +139,7 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool
|
||||
}
|
||||
else if (p_event->button.button == SDL_BUTTON_LEFT) {
|
||||
m_leftButtonHeld = p_event->button.down;
|
||||
m_leftButtonDownTime = p_event->button.down ? SDL_GetTicks() : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user