isle-portable/extensions/include/extensions/multiplayer/animation/sessionhost.h
Christian Semmler 11bf290396
Fix animation system to work when host is outside ISLE world
- Move TickHostSessions outside m_inIsleWorld gate so the host can
  coordinate animations from any world
- Load animation catalog early in HandleCreate so the host can
  coordinate before entering the ISLE world
- Use network-reported positions for remote player location detection
  instead of requiring spawned ROIs
- Always erase sessions at launch — the host's job ends when the
  animation starts; clients play and complete independently
- Replace BroadcastAnimComplete with locally-driven completion
  callbacks: host generates eventId at launch, clients cache
  completion JSON at start time, fire it when ScenePlayer finishes
- Make StopAnimation only do local cleanup (stop playback, cancel
  own interest, reset coordinator) without destroying the session
  host, so other players' sessions survive world transitions
- Broadcast state=0 in ResetAnimationState for full teardown paths
  (shutdown, reconnect, host migration) so clients aren't left with
  stale session state
2026-04-03 20:21:02 -07:00

81 lines
2.4 KiB
C++

#pragma once
#include <cstdint>
#include <map>
#include <vector>
namespace Multiplayer::Animation
{
class Catalog;
struct CatalogEntry;
enum class CoordinationState : uint8_t;
struct SessionSlot {
uint32_t peerId; // 0 = unfilled
int8_t charIndex; // g_actorInfoInit index, or -1 for spectator
bool IsSpectator() const { return IsSpectatorCharIndex(charIndex); }
static bool IsSpectatorCharIndex(int8_t p_charIndex) { return p_charIndex < 0; }
};
struct AnimSession {
uint16_t animIndex;
CoordinationState state;
std::vector<SessionSlot> slots;
uint32_t countdownEndTime; // SDL_GetTicks timestamp when countdown expires
};
class SessionHost {
public:
void SetCatalog(const Catalog* p_catalog);
bool HandleInterest(
uint32_t p_peerId,
uint16_t p_animIndex,
uint8_t p_displayActorIndex,
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);
// Returns animIndices of all sessions ready to play
std::vector<uint16_t> Tick(uint32_t p_now);
void StartCountdown(uint16_t p_animIndex);
void RevertCountdown(uint16_t p_animIndex);
void Reset();
void EraseSession(uint16_t p_animIndex);
const AnimSession* FindSession(uint16_t p_animIndex) const;
const std::map<uint16_t, AnimSession>& GetSessions() const;
bool AreAllSlotsFilled(uint16_t p_animIndex) const;
static uint16_t ComputeCountdownMs(const AnimSession& p_session, uint32_t p_now);
// Reconstruct slot charIndex assignments from CatalogEntry::performerMask.
// Same iteration order as CreateSession — deterministic across all clients.
static std::vector<int8_t> ComputeSlotCharIndices(const CatalogEntry* p_entry);
bool HasCountdownSession() const;
private:
AnimSession CreateSession(const CatalogEntry* p_entry, uint16_t p_animIndex);
bool TryAssignSlot(AnimSession& p_session, uint32_t p_peerId, int8_t p_charIndex);
bool AllSlotsFilled(const AnimSession& p_session) const;
void RemovePlayerFromAllSessions(uint32_t p_peerId, std::vector<uint16_t>& p_changedAnims);
void RemovePlayerFromSessions(
uint32_t p_peerId,
bool p_includePlayingSessions,
std::vector<uint16_t>& p_changedAnims
);
const Catalog* m_catalog = nullptr;
std::map<uint16_t, AnimSession> m_sessions;
static const uint32_t COUNTDOWN_DURATION_MS = 4000;
};
} // namespace Multiplayer::Animation