mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
* Implement display actor override for multiplayer extension Add displayActorIndex to the multiplayer protocol, allowing players to choose any of the 66 character models from g_actorInfoInit via the multiplayer:actor INI setting. The visual display is decoupled from the gameplay actor ID while maintaining backward compatibility. - Protocol: Add displayActorIndex field to PlayerStateMsg and validation helpers - RemotePlayer: Use display actor name for cloning instead of actorId - NetworkManager: Broadcast/handle displayActorIndex, respawn on display change - ThirdPersonCamera: Create/manage display clone ROI for local player override - INI: Read multiplayer:actor setting and resolve to g_actorInfoInit index * Use array syntax for INI option access in display actor setup Consistent with how relayUrl and room are read from options. * Fix display actor ROI handling in 3rd person camera - Fix direction flip targeting display clone instead of native ROI in Disable(), ReinitForCharacter(), and OnCamAnimEnd(). The native ROI is the source of truth for TransformPointOfView and Tick() sync. - Fix use-after-free: DestroyDisplayClone() now nulls m_playerROI when it points to the destroyed clone, preventing dangling pointer access in BuildRideAnimation after a 3rd→1st→3rd person toggle on a vehicle. - Recreate display clone in ReinitForCharacter() vehicle branch. - Extract EnsureDisplayROI() helper to deduplicate clone setup pattern. - Move IsValidDisplayActorIndex() to charactercloner.h, replacing magic number 66 with sizeOfArray(g_actorInfoInit). * Remove display actors plan document
121 lines
3.8 KiB
C++
121 lines
3.8 KiB
C++
#pragma once
|
|
|
|
#include "extensions/multiplayer/networktransport.h"
|
|
#include "extensions/multiplayer/platformcallbacks.h"
|
|
#include "extensions/multiplayer/protocol.h"
|
|
#include "extensions/multiplayer/remoteplayer.h"
|
|
#include "extensions/multiplayer/thirdpersoncamera.h"
|
|
#include "extensions/multiplayer/worldstatesync.h"
|
|
#include "mxcore.h"
|
|
#include "mxtypes.h"
|
|
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
class LegoEntity;
|
|
class LegoWorld;
|
|
|
|
namespace Multiplayer
|
|
{
|
|
|
|
class NetworkManager : public MxCore {
|
|
public:
|
|
NetworkManager();
|
|
~NetworkManager() override;
|
|
|
|
MxResult Tickle() override;
|
|
|
|
const char* ClassName() const override { return "NetworkManager"; }
|
|
|
|
MxBool IsA(const char* p_name) const override
|
|
{
|
|
return !strcmp(p_name, NetworkManager::ClassName()) || MxCore::IsA(p_name);
|
|
}
|
|
|
|
void Initialize(NetworkTransport* p_transport, PlatformCallbacks* p_callbacks);
|
|
void Shutdown();
|
|
|
|
void Connect(const char* p_roomId);
|
|
void Disconnect();
|
|
bool IsConnected() const;
|
|
bool WasRejected() const;
|
|
|
|
void SetWalkAnimation(uint8_t p_index);
|
|
void SetIdleAnimation(uint8_t p_index);
|
|
void SendEmote(uint8_t p_emoteId);
|
|
void SetDisplayActorIndex(uint8_t p_index);
|
|
|
|
// Thread-safe request methods for cross-thread callers (e.g. WASM exports
|
|
// running on the browser main thread). Deferred to the game thread in Tickle().
|
|
void RequestToggleThirdPerson() { m_pendingToggleThirdPerson.store(true, std::memory_order_relaxed); }
|
|
void RequestSetWalkAnimation(uint8_t p_index) { m_pendingWalkAnim.store(p_index, std::memory_order_relaxed); }
|
|
void RequestSetIdleAnimation(uint8_t p_index) { m_pendingIdleAnim.store(p_index, std::memory_order_relaxed); }
|
|
void RequestSendEmote(uint8_t p_emoteId) { m_pendingEmote.store(p_emoteId, std::memory_order_relaxed); }
|
|
|
|
void OnWorldEnabled(LegoWorld* p_world);
|
|
void OnWorldDisabled(LegoWorld* p_world);
|
|
|
|
ThirdPersonCamera& GetThirdPersonCamera() { return m_thirdPersonCamera; }
|
|
|
|
// Called from multiplayer extension when a plant/building entity is clicked.
|
|
// Returns TRUE if the mutation should be suppressed locally (non-host).
|
|
MxBool HandleEntityMutation(LegoEntity* p_entity, MxU8 p_changeType);
|
|
|
|
bool IsHost() const { return m_localPeerId != 0 && m_localPeerId == m_hostPeerId; }
|
|
|
|
private:
|
|
void BroadcastLocalState();
|
|
void ProcessIncomingPackets();
|
|
void UpdateRemotePlayers(float p_deltaTime);
|
|
|
|
RemotePlayer* CreateAndSpawnPlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex);
|
|
|
|
void HandleJoin(const PlayerJoinMsg& p_msg);
|
|
void HandleLeave(const PlayerLeaveMsg& p_msg);
|
|
void HandleState(const PlayerStateMsg& p_msg);
|
|
void HandleHostAssign(const HostAssignMsg& p_msg);
|
|
void HandleEmote(const EmoteMsg& p_msg);
|
|
|
|
void ProcessPendingRequests();
|
|
void RemoveRemotePlayer(uint32_t p_peerId);
|
|
void RemoveAllRemotePlayers();
|
|
|
|
void NotifyPlayerCountChanged();
|
|
|
|
// Serialize and send a fixed-size message via the transport
|
|
template <typename T>
|
|
void SendMessage(const T& p_msg);
|
|
|
|
NetworkTransport* m_transport;
|
|
PlatformCallbacks* m_callbacks;
|
|
WorldStateSync m_worldSync;
|
|
ThirdPersonCamera m_thirdPersonCamera;
|
|
std::map<uint32_t, std::unique_ptr<RemotePlayer>> m_remotePlayers;
|
|
|
|
uint32_t m_localPeerId;
|
|
uint32_t m_hostPeerId;
|
|
uint32_t m_sequence;
|
|
uint32_t m_lastBroadcastTime;
|
|
uint8_t m_lastValidActorId;
|
|
uint8_t m_localWalkAnimId;
|
|
uint8_t m_localIdleAnimId;
|
|
uint8_t m_localDisplayActorIndex;
|
|
bool m_inIsleWorld;
|
|
bool m_registered;
|
|
|
|
std::atomic<bool> m_pendingToggleThirdPerson;
|
|
std::atomic<int> m_pendingWalkAnim;
|
|
std::atomic<int> m_pendingIdleAnim;
|
|
std::atomic<int> m_pendingEmote;
|
|
|
|
static const uint32_t BROADCAST_INTERVAL_MS = 66; // ~15Hz
|
|
static const uint32_t TIMEOUT_MS = 5000; // 5 second timeout
|
|
static const int EXIT_ROOM_FULL = 10;
|
|
};
|
|
|
|
} // namespace Multiplayer
|