mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
Support concurrent animation playback by independent player groups
Replace the single ScenePlayer/m_playingAnimIndex with a map of ScenePlayers keyed by animation index, allowing non-overlapping groups of players to play different animations simultaneously. Each player can still only participate in one animation at a time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
343610ece5
commit
f92967735f
@ -69,6 +69,8 @@ class Coordinator {
|
|||||||
void OnLocationChanged(const std::vector<int16_t>& p_locations, const Catalog* p_catalog);
|
void OnLocationChanged(const std::vector<int16_t>& p_locations, const Catalog* p_catalog);
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
|
void ResetLocalState();
|
||||||
|
void RemoveSession(uint16_t p_animIndex);
|
||||||
|
|
||||||
// Apply authoritative session state from host
|
// Apply authoritative session state from host
|
||||||
void ApplySessionUpdate(
|
void ApplySessionUpdate(
|
||||||
|
|||||||
@ -45,8 +45,9 @@ class ScenePlayer {
|
|||||||
void Tick();
|
void Tick();
|
||||||
void Stop();
|
void Stop();
|
||||||
bool IsPlaying() const { return m_playing; }
|
bool IsPlaying() const { return m_playing; }
|
||||||
|
bool IsObserverMode() const { return m_observerMode; }
|
||||||
|
|
||||||
void PreloadAsync(uint32_t p_objectId) { m_loader.PreloadAsync(p_objectId); }
|
void SetLoader(Loader* p_loader) { m_loader = p_loader; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ComputeRebaseMatrix();
|
void ComputeRebaseMatrix();
|
||||||
@ -56,7 +57,7 @@ class ScenePlayer {
|
|||||||
void CleanupProps();
|
void CleanupProps();
|
||||||
|
|
||||||
// Sub-components
|
// Sub-components
|
||||||
Loader m_loader;
|
Loader* m_loader;
|
||||||
AudioPlayer m_audioPlayer;
|
AudioPlayer m_audioPlayer;
|
||||||
PhonemePlayer m_phonemePlayer;
|
PhonemePlayer m_phonemePlayer;
|
||||||
|
|
||||||
|
|||||||
@ -33,12 +33,13 @@ class SessionHost {
|
|||||||
uint32_t p_peerId,
|
uint32_t p_peerId,
|
||||||
uint16_t p_animIndex,
|
uint16_t p_animIndex,
|
||||||
uint8_t p_displayActorIndex,
|
uint8_t p_displayActorIndex,
|
||||||
std::vector<uint16_t>& p_changedAnims);
|
std::vector<uint16_t>& p_changedAnims
|
||||||
|
);
|
||||||
bool HandleCancel(uint32_t p_peerId, std::vector<uint16_t>& p_changedAnims);
|
bool HandleCancel(uint32_t p_peerId, std::vector<uint16_t>& p_changedAnims);
|
||||||
bool HandlePlayerRemoved(uint32_t p_peerId, std::vector<uint16_t>& p_changedAnims);
|
bool HandlePlayerRemoved(uint32_t p_peerId, std::vector<uint16_t>& p_changedAnims);
|
||||||
|
|
||||||
// Returns animIndex of session ready to play, or ANIM_INDEX_NONE
|
// Returns animIndices of all sessions ready to play
|
||||||
uint16_t Tick(uint32_t p_now);
|
std::vector<uint16_t> Tick(uint32_t p_now);
|
||||||
|
|
||||||
void StartCountdown(uint16_t p_animIndex);
|
void StartCountdown(uint16_t p_animIndex);
|
||||||
void RevertCountdown(uint16_t p_animIndex);
|
void RevertCountdown(uint16_t p_animIndex);
|
||||||
@ -66,7 +67,8 @@ class SessionHost {
|
|||||||
void RemovePlayerFromSessions(
|
void RemovePlayerFromSessions(
|
||||||
uint32_t p_peerId,
|
uint32_t p_peerId,
|
||||||
bool p_includePlayingSessions,
|
bool p_includePlayingSessions,
|
||||||
std::vector<uint16_t>& p_changedAnims);
|
std::vector<uint16_t>& p_changedAnims
|
||||||
|
);
|
||||||
|
|
||||||
const Catalog* m_catalog = nullptr;
|
const Catalog* m_catalog = nullptr;
|
||||||
std::map<uint16_t, AnimSession> m_sessions;
|
std::map<uint16_t, AnimSession> m_sessions;
|
||||||
|
|||||||
@ -199,15 +199,19 @@ class NetworkManager : public MxCore {
|
|||||||
|
|
||||||
// NPC animation playback
|
// NPC animation playback
|
||||||
Multiplayer::Animation::Catalog m_animCatalog;
|
Multiplayer::Animation::Catalog m_animCatalog;
|
||||||
Multiplayer::Animation::ScenePlayer m_scenePlayer;
|
Multiplayer::Animation::Loader m_animLoader;
|
||||||
Multiplayer::Animation::LocationProximity m_locationProximity;
|
Multiplayer::Animation::LocationProximity m_locationProximity;
|
||||||
Multiplayer::Animation::Coordinator m_animCoordinator;
|
Multiplayer::Animation::Coordinator m_animCoordinator;
|
||||||
Multiplayer::Animation::SessionHost m_animSessionHost;
|
Multiplayer::Animation::SessionHost m_animSessionHost;
|
||||||
int32_t m_localPendingAnimInterest;
|
int32_t m_localPendingAnimInterest;
|
||||||
uint16_t m_playingAnimIndex;
|
|
||||||
|
// Concurrent animation playback: one ScenePlayer per playing animation
|
||||||
|
std::map<uint16_t, std::unique_ptr<Multiplayer::Animation::ScenePlayer>> m_playingAnims;
|
||||||
|
|
||||||
void TickAnimation();
|
void TickAnimation();
|
||||||
void StopScenePlayback(bool p_unlockRemotes);
|
void StopScenePlayback(uint16_t p_animIndex, bool p_unlockRemotes);
|
||||||
|
void StopAllPlayback();
|
||||||
|
void UnlockRemotesForAnim(uint16_t p_animIndex);
|
||||||
|
|
||||||
// Animation state push
|
// Animation state push
|
||||||
bool m_animStateDirty;
|
bool m_animStateDirty;
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "extensions/common/characteranimator.h"
|
#include "extensions/common/characteranimator.h"
|
||||||
#include "extensions/common/customizestate.h"
|
#include "extensions/common/customizestate.h"
|
||||||
|
#include "extensions/multiplayer/animation/catalog.h"
|
||||||
#include "extensions/multiplayer/protocol.h"
|
#include "extensions/multiplayer/protocol.h"
|
||||||
#include "mxtypes.h"
|
#include "mxtypes.h"
|
||||||
|
|
||||||
@ -58,8 +59,15 @@ class RemotePlayer {
|
|||||||
|
|
||||||
const char* GetDisplayName() const { return m_displayName; }
|
const char* GetDisplayName() const { return m_displayName; }
|
||||||
|
|
||||||
void SetAnimationLocked(bool p_locked) { m_animationLocked = p_locked; }
|
void LockForAnimation(uint16_t p_animIndex) { m_lockedForAnimIndex = p_animIndex; }
|
||||||
bool IsAnimationLocked() const { return m_animationLocked; }
|
void UnlockFromAnimation(uint16_t p_animIndex)
|
||||||
|
{
|
||||||
|
if (m_lockedForAnimIndex == p_animIndex) {
|
||||||
|
m_lockedForAnimIndex = Animation::ANIM_INDEX_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ForceUnlockAnimation() { m_lockedForAnimIndex = Animation::ANIM_INDEX_NONE; }
|
||||||
|
bool IsAnimationLocked() const { return m_lockedForAnimIndex != Animation::ANIM_INDEX_NONE; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char* GetDisplayActorName() const;
|
const char* GetDisplayActorName() const;
|
||||||
@ -100,7 +108,7 @@ class RemotePlayer {
|
|||||||
|
|
||||||
Extensions::Common::CustomizeState m_customizeState;
|
Extensions::Common::CustomizeState m_customizeState;
|
||||||
bool m_allowRemoteCustomize;
|
bool m_allowRemoteCustomize;
|
||||||
bool m_animationLocked;
|
uint16_t m_lockedForAnimIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Multiplayer
|
} // namespace Multiplayer
|
||||||
|
|||||||
@ -185,6 +185,18 @@ void Coordinator::Reset()
|
|||||||
m_cancelPending = false;
|
m_cancelPending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Coordinator::ResetLocalState()
|
||||||
|
{
|
||||||
|
m_state = CoordinationState::e_idle;
|
||||||
|
m_currentAnimIndex = ANIM_INDEX_NONE;
|
||||||
|
m_cancelPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Coordinator::RemoveSession(uint16_t p_animIndex)
|
||||||
|
{
|
||||||
|
m_sessions.erase(p_animIndex);
|
||||||
|
}
|
||||||
|
|
||||||
void Coordinator::ApplySessionUpdate(
|
void Coordinator::ApplySessionUpdate(
|
||||||
uint16_t p_animIndex,
|
uint16_t p_animIndex,
|
||||||
uint8_t p_state,
|
uint8_t p_state,
|
||||||
|
|||||||
@ -61,9 +61,9 @@ static bool MatchesCharacter(const std::string& p_actorName, int8_t p_charIndex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ScenePlayer::ScenePlayer()
|
ScenePlayer::ScenePlayer()
|
||||||
: m_playing(false), m_rebaseComputed(false), m_startTime(0), m_currentData(nullptr), m_category(e_npcAnim),
|
: m_loader(nullptr), m_playing(false), m_rebaseComputed(false), m_startTime(0), m_currentData(nullptr),
|
||||||
m_animRootROI(nullptr), m_vehicleROI(nullptr), m_hiddenVehicleROI(nullptr), m_roiMap(nullptr), m_roiMapSize(0),
|
m_category(e_npcAnim), m_animRootROI(nullptr), m_vehicleROI(nullptr), m_hiddenVehicleROI(nullptr),
|
||||||
m_hasCamAnim(false), m_observerMode(false), m_hideOnStop(false)
|
m_roiMap(nullptr), m_roiMapSize(0), m_hasCamAnim(false), m_observerMode(false), m_hideOnStop(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ void ScenePlayer::Play(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneAnimData* data = m_loader.EnsureCached(p_animInfo->m_objectId);
|
SceneAnimData* data = m_loader->EnsureCached(p_animInfo->m_objectId);
|
||||||
if (!data || !data->anim) {
|
if (!data || !data->anim) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -221,16 +221,16 @@ void SessionHost::RevertCountdown(uint16_t p_animIndex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t SessionHost::Tick(uint32_t p_now)
|
std::vector<uint16_t> SessionHost::Tick(uint32_t p_now)
|
||||||
{
|
{
|
||||||
|
std::vector<uint16_t> ready;
|
||||||
for (auto& [animIndex, session] : m_sessions) {
|
for (auto& [animIndex, session] : m_sessions) {
|
||||||
if (session.state == CoordinationState::e_countdown && p_now >= session.countdownEndTime) {
|
if (session.state == CoordinationState::e_countdown && p_now >= session.countdownEndTime) {
|
||||||
session.state = CoordinationState::e_playing;
|
session.state = CoordinationState::e_playing;
|
||||||
return animIndex;
|
ready.push_back(animIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ready;
|
||||||
return ANIM_INDEX_NONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionHost::Reset()
|
void SessionHost::Reset()
|
||||||
|
|||||||
@ -69,11 +69,10 @@ NetworkManager::NetworkManager()
|
|||||||
m_sequence(0), m_lastBroadcastTime(0), m_lastValidActorId(0), m_localAllowRemoteCustomize(true),
|
m_sequence(0), m_lastBroadcastTime(0), m_lastValidActorId(0), m_localAllowRemoteCustomize(true),
|
||||||
m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false),
|
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_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), m_pendingToggleAllowCustomize(false),
|
||||||
m_pendingAnimInterest(-1), m_pendingAnimCancel(false), m_localPendingAnimInterest(-1),
|
m_pendingAnimInterest(-1), m_pendingAnimCancel(false), m_localPendingAnimInterest(-1), m_showNameBubbles(true),
|
||||||
m_playingAnimIndex(Animation::ANIM_INDEX_NONE), m_showNameBubbles(true), m_lastCameraEnabled(false),
|
m_lastCameraEnabled(false), m_wasInRestrictedArea(false), m_animStateDirty(false), m_animInterestDirty(false),
|
||||||
m_wasInRestrictedArea(false), m_animStateDirty(false), m_animInterestDirty(false), m_lastAnimPushTime(0),
|
m_lastAnimPushTime(0), m_connectionState(STATE_DISCONNECTED), m_wasRejected(false), m_reconnectAttempt(0),
|
||||||
m_connectionState(STATE_DISCONNECTED), m_wasRejected(false), m_reconnectAttempt(0), m_reconnectDelay(0),
|
m_reconnectDelay(0), m_nextReconnectTime(0)
|
||||||
m_nextReconnectTime(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +104,11 @@ MxResult NetworkManager::Tickle()
|
|||||||
|
|
||||||
// Cancel animation when camera is disabled (vehicle entry, restricted area, etc.)
|
// Cancel animation when camera is disabled (vehicle entry, restricted area, etc.)
|
||||||
if (!cameraEnabled && m_animCoordinator.GetState() != Animation::CoordinationState::e_idle) {
|
if (!cameraEnabled && m_animCoordinator.GetState() != Animation::CoordinationState::e_idle) {
|
||||||
|
uint16_t localAnim = m_animCoordinator.GetCurrentAnimIndex();
|
||||||
CancelLocalAnimInterest();
|
CancelLocalAnimInterest();
|
||||||
StopScenePlayback(false);
|
if (localAnim != Animation::ANIM_INDEX_NONE) {
|
||||||
|
StopScenePlayback(localAnim, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_localNameBubble) {
|
if (m_localNameBubble) {
|
||||||
@ -314,14 +316,7 @@ void NetworkManager::CancelLocalAnimInterest()
|
|||||||
void NetworkManager::StopAnimation()
|
void NetworkManager::StopAnimation()
|
||||||
{
|
{
|
||||||
ResetAnimationState();
|
ResetAnimationState();
|
||||||
|
StopAllPlayback();
|
||||||
if (m_scenePlayer.IsPlaying()) {
|
|
||||||
m_scenePlayer.Stop();
|
|
||||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
|
||||||
if (cam) {
|
|
||||||
cam->SetAnimPlaying(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkManager::OnWorldEnabled(LegoWorld* p_world)
|
void NetworkManager::OnWorldEnabled(LegoWorld* p_world)
|
||||||
@ -562,8 +557,11 @@ void NetworkManager::ProcessPendingRequests()
|
|||||||
if (m_pendingToggleThirdPerson.exchange(false, std::memory_order_relaxed)) {
|
if (m_pendingToggleThirdPerson.exchange(false, std::memory_order_relaxed)) {
|
||||||
if (cam->IsEnabled()) {
|
if (cam->IsEnabled()) {
|
||||||
if (m_animCoordinator.GetState() != Animation::CoordinationState::e_idle) {
|
if (m_animCoordinator.GetState() != Animation::CoordinationState::e_idle) {
|
||||||
|
uint16_t localAnim = m_animCoordinator.GetCurrentAnimIndex();
|
||||||
CancelLocalAnimInterest();
|
CancelLocalAnimInterest();
|
||||||
StopScenePlayback(false);
|
if (localAnim != Animation::ANIM_INDEX_NONE) {
|
||||||
|
StopScenePlayback(localAnim, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cam->Disable();
|
cam->Disable();
|
||||||
NotifyThirdPersonChanged(false);
|
NotifyThirdPersonChanged(false);
|
||||||
@ -1189,19 +1187,46 @@ void NetworkManager::SendCustomize(uint32_t p_targetPeerId, uint8_t p_changeType
|
|||||||
SendMessage(msg);
|
SendMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkManager::StopScenePlayback(bool p_unlockRemotes)
|
void NetworkManager::StopScenePlayback(uint16_t p_animIndex, bool p_unlockRemotes)
|
||||||
{
|
{
|
||||||
if (!m_scenePlayer.IsPlaying()) {
|
auto it = m_playingAnims.find(p_animIndex);
|
||||||
|
if (it == m_playingAnims.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_scenePlayer.Stop();
|
// Save before Stop() which resets the flag
|
||||||
m_playingAnimIndex = Animation::ANIM_INDEX_NONE;
|
bool wasObserver = it->second->IsObserverMode();
|
||||||
|
|
||||||
|
if (it->second->IsPlaying()) {
|
||||||
|
it->second->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (p_unlockRemotes) {
|
if (p_unlockRemotes) {
|
||||||
for (auto& [peerId, player] : m_remotePlayers) {
|
UnlockRemotesForAnim(p_animIndex);
|
||||||
player->SetAnimationLocked(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Release camera if local player was a participant (not observer) in this animation
|
||||||
|
if (!wasObserver) {
|
||||||
|
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||||
|
if (cam) {
|
||||||
|
cam->SetAnimPlaying(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_playingAnims.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkManager::StopAllPlayback()
|
||||||
|
{
|
||||||
|
for (auto& [animIndex, scenePlayer] : m_playingAnims) {
|
||||||
|
if (scenePlayer->IsPlaying()) {
|
||||||
|
scenePlayer->Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_playingAnims.clear();
|
||||||
|
|
||||||
|
for (auto& [peerId, player] : m_remotePlayers) {
|
||||||
|
player->ForceUnlockAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||||
@ -1210,32 +1235,56 @@ void NetworkManager::StopScenePlayback(bool p_unlockRemotes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkManager::UnlockRemotesForAnim(uint16_t p_animIndex)
|
||||||
|
{
|
||||||
|
for (auto& [peerId, player] : m_remotePlayers) {
|
||||||
|
player->UnlockFromAnimation(p_animIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkManager::TickAnimation()
|
void NetworkManager::TickAnimation()
|
||||||
{
|
{
|
||||||
if (!m_scenePlayer.IsPlaying()) {
|
if (m_playingAnims.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_scenePlayer.Tick();
|
// Collect completed animations with their observer mode (Tick/Stop resets the flag)
|
||||||
|
std::vector<std::pair<uint16_t, bool>> completed;
|
||||||
|
|
||||||
if (!m_scenePlayer.IsPlaying()) {
|
for (auto& [animIndex, scenePlayer] : m_playingAnims) {
|
||||||
for (auto& [peerId, player] : m_remotePlayers) {
|
if (!scenePlayer->IsPlaying()) {
|
||||||
player->SetAnimationLocked(false);
|
completed.push_back({animIndex, scenePlayer->IsObserverMode()});
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool wasObserver = scenePlayer->IsObserverMode();
|
||||||
|
scenePlayer->Tick();
|
||||||
|
|
||||||
|
if (!scenePlayer->IsPlaying()) {
|
||||||
|
completed.push_back({animIndex, wasObserver});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [animIndex, wasObserver] : completed) {
|
||||||
|
UnlockRemotesForAnim(animIndex);
|
||||||
|
|
||||||
|
// Release camera if local player was a participant (not observer)
|
||||||
|
if (!wasObserver) {
|
||||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||||
if (cam) {
|
if (cam) {
|
||||||
cam->SetAnimPlaying(false);
|
cam->SetAnimPlaying(false);
|
||||||
}
|
}
|
||||||
|
m_animCoordinator.ResetLocalState();
|
||||||
if (IsHost() && m_playingAnimIndex != Animation::ANIM_INDEX_NONE) {
|
m_animCoordinator.RemoveSession(animIndex);
|
||||||
BroadcastAnimComplete(m_playingAnimIndex); // Must fire before EraseSession destroys participant data
|
|
||||||
m_animSessionHost.EraseSession(m_playingAnimIndex);
|
|
||||||
BroadcastAnimUpdate(m_playingAnimIndex); // Broadcast cleared state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_playingAnimIndex = Animation::ANIM_INDEX_NONE;
|
if (IsHost()) {
|
||||||
m_animCoordinator.Reset();
|
BroadcastAnimComplete(animIndex); // Must fire before EraseSession destroys participant data
|
||||||
|
m_animSessionHost.EraseSession(animIndex);
|
||||||
|
BroadcastAnimUpdate(animIndex); // Broadcast cleared state
|
||||||
|
}
|
||||||
|
|
||||||
|
m_playingAnims.erase(animIndex);
|
||||||
m_animStateDirty = true;
|
m_animStateDirty = true;
|
||||||
m_animInterestDirty = true;
|
m_animInterestDirty = true;
|
||||||
}
|
}
|
||||||
@ -1286,7 +1335,7 @@ void NetworkManager::TickHostSessions()
|
|||||||
if (m_animCoordinator.IsLocalPlayerInSession(animIndex)) {
|
if (m_animCoordinator.IsLocalPlayerInSession(animIndex)) {
|
||||||
const AnimInfo* ai = m_animCatalog.GetAnimInfo(animIndex);
|
const AnimInfo* ai = m_animCatalog.GetAnimInfo(animIndex);
|
||||||
if (ai) {
|
if (ai) {
|
||||||
m_scenePlayer.PreloadAsync(ai->m_objectId);
|
m_animLoader.PreloadAsync(ai->m_objectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1300,9 +1349,9 @@ void NetworkManager::TickHostSessions()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check countdown expiry
|
// Check countdown expiry — multiple animations may be ready simultaneously
|
||||||
uint16_t readyAnim = m_animSessionHost.Tick(SDL_GetTicks());
|
std::vector<uint16_t> readyAnims = m_animSessionHost.Tick(SDL_GetTicks());
|
||||||
if (readyAnim != Animation::ANIM_INDEX_NONE) {
|
for (uint16_t readyAnim : readyAnims) {
|
||||||
BroadcastAnimStart(readyAnim);
|
BroadcastAnimStart(readyAnim);
|
||||||
HandleAnimStartLocally(readyAnim, m_animCoordinator.IsLocalPlayerInSession(readyAnim));
|
HandleAnimStartLocally(readyAnim, m_animCoordinator.IsLocalPlayerInSession(readyAnim));
|
||||||
}
|
}
|
||||||
@ -1363,6 +1412,7 @@ void NetworkManager::HandleAnimCancel(uint32_t p_peerId)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t localAnimBefore = m_animCoordinator.GetCurrentAnimIndex();
|
||||||
Animation::CoordinationState oldState = m_animCoordinator.GetState();
|
Animation::CoordinationState oldState = m_animCoordinator.GetState();
|
||||||
|
|
||||||
std::vector<uint16_t> changedAnims;
|
std::vector<uint16_t> changedAnims;
|
||||||
@ -1371,9 +1421,18 @@ void NetworkManager::HandleAnimCancel(uint32_t p_peerId)
|
|||||||
m_animInterestDirty = true;
|
m_animInterestDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop local player's animation if their session was erased
|
||||||
if (oldState == Animation::CoordinationState::e_playing &&
|
if (oldState == Animation::CoordinationState::e_playing &&
|
||||||
m_animCoordinator.GetState() == Animation::CoordinationState::e_idle) {
|
m_animCoordinator.GetState() == Animation::CoordinationState::e_idle &&
|
||||||
StopScenePlayback(true);
|
localAnimBefore != Animation::ANIM_INDEX_NONE) {
|
||||||
|
StopScenePlayback(localAnimBefore, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop observer-mode playback for any erased playing sessions
|
||||||
|
for (uint16_t animIndex : changedAnims) {
|
||||||
|
if (animIndex != localAnimBefore && m_playingAnims.count(animIndex)) {
|
||||||
|
StopScenePlayback(animIndex, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1383,6 +1442,7 @@ void NetworkManager::HandleAnimUpdate(const AnimUpdateMsg& p_msg)
|
|||||||
return; // Host already updated its own state
|
return; // Host already updated its own state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t localAnimBefore = m_animCoordinator.GetCurrentAnimIndex();
|
||||||
Animation::CoordinationState oldState = m_animCoordinator.GetState();
|
Animation::CoordinationState oldState = m_animCoordinator.GetState();
|
||||||
|
|
||||||
uint32_t slots[8];
|
uint32_t slots[8];
|
||||||
@ -1393,7 +1453,7 @@ void NetworkManager::HandleAnimUpdate(const AnimUpdateMsg& p_msg)
|
|||||||
if (p_msg.state == static_cast<uint8_t>(Animation::CoordinationState::e_countdown)) {
|
if (p_msg.state == static_cast<uint8_t>(Animation::CoordinationState::e_countdown)) {
|
||||||
const AnimInfo* ai = m_animCatalog.GetAnimInfo(p_msg.animIndex);
|
const AnimInfo* ai = m_animCatalog.GetAnimInfo(p_msg.animIndex);
|
||||||
if (ai) {
|
if (ai) {
|
||||||
m_scenePlayer.PreloadAsync(ai->m_objectId);
|
m_animLoader.PreloadAsync(ai->m_objectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1402,14 +1462,16 @@ void NetworkManager::HandleAnimUpdate(const AnimUpdateMsg& p_msg)
|
|||||||
m_localPendingAnimInterest = -1;
|
m_localPendingAnimInterest = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop local player's animation if their session was cleared
|
||||||
if (oldState == Animation::CoordinationState::e_playing &&
|
if (oldState == Animation::CoordinationState::e_playing &&
|
||||||
m_animCoordinator.GetState() == Animation::CoordinationState::e_idle) {
|
m_animCoordinator.GetState() == Animation::CoordinationState::e_idle &&
|
||||||
StopScenePlayback(true);
|
localAnimBefore != Animation::ANIM_INDEX_NONE) {
|
||||||
|
StopScenePlayback(localAnimBefore, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop observer playback when the observed session is cleared
|
// Stop observer playback when the observed session is cleared
|
||||||
if (m_scenePlayer.IsPlaying() && m_playingAnimIndex == p_msg.animIndex && p_msg.state == 0) {
|
if (m_playingAnims.count(p_msg.animIndex) && p_msg.state == 0) {
|
||||||
StopScenePlayback(true);
|
StopScenePlayback(p_msg.animIndex, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_animStateDirty = true;
|
m_animStateDirty = true;
|
||||||
@ -1438,7 +1500,7 @@ void NetworkManager::HandleAnimStartLocally(uint16_t p_animIndex, bool p_localIn
|
|||||||
m_animSessionHost.EraseSession(p_animIndex);
|
m_animSessionHost.EraseSession(p_animIndex);
|
||||||
BroadcastAnimUpdate(p_animIndex);
|
BroadcastAnimUpdate(p_animIndex);
|
||||||
}
|
}
|
||||||
m_animCoordinator.Reset();
|
m_animCoordinator.ResetLocalState();
|
||||||
}
|
}
|
||||||
m_animStateDirty = true;
|
m_animStateDirty = true;
|
||||||
};
|
};
|
||||||
@ -1496,7 +1558,7 @@ void NetworkManager::HandleAnimStartLocally(uint16_t p_animIndex, bool p_localIn
|
|||||||
|
|
||||||
// Lock performers to prevent network updates from fighting animation
|
// Lock performers to prevent network updates from fighting animation
|
||||||
if (!rp.IsSpectator()) {
|
if (!rp.IsSpectator()) {
|
||||||
it->second->SetAnimationLocked(true);
|
it->second->LockForAnimation(p_animIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1515,26 +1577,31 @@ void NetworkManager::HandleAnimStartLocally(uint16_t p_animIndex, bool p_localIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto scenePlayer = std::make_unique<Animation::ScenePlayer>();
|
||||||
|
scenePlayer->SetLoader(&m_animLoader);
|
||||||
|
|
||||||
if (!observerMode) {
|
if (!observerMode) {
|
||||||
bool localIsPerformer = (localCharIndex >= 0);
|
bool localIsPerformer = (localCharIndex >= 0);
|
||||||
cam->SetAnimPlaying(true, localIsPerformer, [this]() { m_scenePlayer.Stop(); });
|
cam->SetAnimPlaying(true, localIsPerformer, [this, p_animIndex]() {
|
||||||
|
auto it = m_playingAnims.find(p_animIndex);
|
||||||
|
if (it != m_playingAnims.end()) {
|
||||||
|
it->second->Stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
m_scenePlayer.Play(animInfo, entry->category, participants.data(), (uint8_t) participants.size(), observerMode);
|
scenePlayer->Play(animInfo, entry->category, participants.data(), (uint8_t) participants.size(), observerMode);
|
||||||
|
|
||||||
if (!m_scenePlayer.IsPlaying()) {
|
if (!scenePlayer->IsPlaying()) {
|
||||||
if (!observerMode) {
|
if (!observerMode) {
|
||||||
cam->SetAnimPlaying(false);
|
cam->SetAnimPlaying(false);
|
||||||
}
|
}
|
||||||
// Unlock remote players on failure
|
UnlockRemotesForAnim(p_animIndex);
|
||||||
for (auto& [peerId, player] : m_remotePlayers) {
|
|
||||||
player->SetAnimationLocked(false);
|
|
||||||
}
|
|
||||||
abortSession();
|
abortSession();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_playingAnimIndex = p_animIndex;
|
m_playingAnims[p_animIndex] = std::move(scenePlayer);
|
||||||
m_localPendingAnimInterest = -1;
|
m_localPendingAnimInterest = -1;
|
||||||
m_animStateDirty = true;
|
m_animStateDirty = true;
|
||||||
}
|
}
|
||||||
@ -1842,7 +1909,7 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg)
|
|||||||
it->second->GetCustomizeState(),
|
it->second->GetCustomizeState(),
|
||||||
p_msg.changeType == CHANGE_MOOD
|
p_msg.changeType == CHANGE_MOOD
|
||||||
);
|
);
|
||||||
if (!it->second->IsMoving() && !it->second->IsInMultiPartEmote() && !m_scenePlayer.IsPlaying()) {
|
if (!it->second->IsMoving() && !it->second->IsInMultiPartEmote() && !it->second->IsAnimationLocked()) {
|
||||||
it->second->StopClickAnimation();
|
it->second->StopClickAnimation();
|
||||||
MxU32 clickAnimId = Common::CharacterCustomizer::PlayClickAnimation(
|
MxU32 clickAnimId = Common::CharacterCustomizer::PlayClickAnimation(
|
||||||
it->second->GetROI(),
|
it->second->GetROI(),
|
||||||
|
|||||||
@ -32,7 +32,8 @@ RemotePlayer::RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displ
|
|||||||
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_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_lockedForAnimIndex(Animation::ANIM_INDEX_NONE)
|
||||||
{
|
{
|
||||||
m_displayName[0] = '\0';
|
m_displayName[0] = '\0';
|
||||||
const char* displayName = GetDisplayActorName();
|
const char* displayName = GetDisplayActorName();
|
||||||
@ -205,7 +206,7 @@ void RemotePlayer::Tick(float p_deltaTime)
|
|||||||
|
|
||||||
// During animation playback, skip transform/animation updates (ScenePlayer drives
|
// During animation playback, skip transform/animation updates (ScenePlayer drives
|
||||||
// our ROI), but still update the name bubble so it follows the animated position.
|
// our ROI), but still update the name bubble so it follows the animated position.
|
||||||
if (m_animationLocked) {
|
if (IsAnimationLocked()) {
|
||||||
if (m_nameBubble) {
|
if (m_nameBubble) {
|
||||||
m_nameBubble->Update(m_roi);
|
m_nameBubble->Update(m_roi);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user