refactor(runtime): expose canonical gameplay state
Move character options and movement skills into the Runtime-owned character graph, expose borrowed inventory, character, and social views, and route retained UI state commands through generation-gated typed Runtime contracts. Preserve the existing synchronous wire path while deleting the App-owned option and skill mirrors and extending normalized parity checkpoints. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
9d0d9b07e0
commit
dcb61efb5a
34 changed files with 2076 additions and 103 deletions
|
|
@ -135,6 +135,114 @@ public interface IRuntimePortalCommands
|
|||
RuntimePortalCommand command);
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeShortcutCommand(
|
||||
int Index,
|
||||
uint ObjectId,
|
||||
uint SpellId);
|
||||
|
||||
public interface IRuntimeInventoryStateCommands
|
||||
{
|
||||
RuntimeCommandResult AddShortcut(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
in RuntimeShortcutCommand command);
|
||||
|
||||
RuntimeCommandResult RemoveShortcut(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
int index);
|
||||
}
|
||||
|
||||
public interface IRuntimeSpellbookCommands
|
||||
{
|
||||
RuntimeCommandResult AddFavorite(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
int tabIndex,
|
||||
int position,
|
||||
uint spellId);
|
||||
|
||||
RuntimeCommandResult RemoveFavorite(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
int tabIndex,
|
||||
uint spellId);
|
||||
|
||||
RuntimeCommandResult SetFilter(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint filters);
|
||||
|
||||
RuntimeCommandResult ForgetSpell(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint spellId);
|
||||
|
||||
RuntimeCommandResult SetDesiredComponent(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint componentId,
|
||||
uint amount);
|
||||
|
||||
RuntimeCommandResult ClearDesiredComponents(
|
||||
RuntimeGenerationToken expectedGeneration);
|
||||
}
|
||||
|
||||
public enum RuntimeAdvancementKind
|
||||
{
|
||||
Attribute,
|
||||
Vital,
|
||||
Skill,
|
||||
TrainSkill,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeAdvancementCommand(
|
||||
RuntimeAdvancementKind Kind,
|
||||
uint StatId,
|
||||
ulong Cost);
|
||||
|
||||
public interface IRuntimeCharacterCommands
|
||||
{
|
||||
RuntimeCommandResult Advance(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
in RuntimeAdvancementCommand command);
|
||||
|
||||
RuntimeCommandResult SetOptions1(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
uint options);
|
||||
}
|
||||
|
||||
public enum RuntimeFriendCommandKind
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Clear,
|
||||
RequestLegacyList,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeFriendCommand(
|
||||
RuntimeFriendCommandKind Kind,
|
||||
uint CharacterId = 0u,
|
||||
string? Name = null);
|
||||
|
||||
public enum RuntimeSquelchScope
|
||||
{
|
||||
Character,
|
||||
Account,
|
||||
Global,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeSquelchCommand(
|
||||
RuntimeSquelchScope Scope,
|
||||
bool Add,
|
||||
uint CharacterId = 0u,
|
||||
string? Name = null,
|
||||
uint MessageType = 0u);
|
||||
|
||||
public interface IRuntimeSocialCommands
|
||||
{
|
||||
RuntimeCommandResult Execute(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
in RuntimeFriendCommand command);
|
||||
|
||||
RuntimeCommandResult Execute(
|
||||
RuntimeGenerationToken expectedGeneration,
|
||||
in RuntimeSquelchCommand command);
|
||||
}
|
||||
|
||||
public interface IGameRuntimeCommands
|
||||
{
|
||||
IRuntimeSessionCommands Session { get; }
|
||||
|
|
@ -148,4 +256,12 @@ public interface IGameRuntimeCommands
|
|||
IRuntimeChatCommands Chat { get; }
|
||||
|
||||
IRuntimePortalCommands Portal { get; }
|
||||
|
||||
IRuntimeInventoryStateCommands InventoryState { get; }
|
||||
|
||||
IRuntimeSpellbookCommands Spellbook { get; }
|
||||
|
||||
IRuntimeCharacterCommands Character { get; }
|
||||
|
||||
IRuntimeSocialCommands Social { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ public enum RuntimeCommandDomain
|
|||
Movement,
|
||||
Chat,
|
||||
Portal,
|
||||
InventoryState,
|
||||
Spellbook,
|
||||
Character,
|
||||
Social,
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeLifecycleDelta(
|
||||
|
|
@ -145,6 +149,19 @@ public sealed class RuntimeTraceRecorder : IRuntimeEventObserver
|
|||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
$"frame={checkpoint.FrameNumber};materialized={checkpoint.MaterializedEntityCount};" +
|
||||
$"containers={checkpoint.InventoryContainerCount};chat={checkpoint.ChatCount};" +
|
||||
$"inventory-state={checkpoint.InventoryState.ShortcutCount}:" +
|
||||
$"{checkpoint.InventoryState.ShortcutRevision}:" +
|
||||
$"{checkpoint.InventoryState.ItemManaCount}:" +
|
||||
$"{checkpoint.InventoryState.ItemManaRevision};" +
|
||||
$"character={checkpoint.Character.CharacterRevision}:" +
|
||||
$"{checkpoint.Character.SpellbookRevision}:" +
|
||||
$"{checkpoint.Character.LearnedSpellCount}:" +
|
||||
$"{checkpoint.Character.SkillCount}:" +
|
||||
$"{checkpoint.Character.Options.Revision}:" +
|
||||
$"{checkpoint.Character.MovementSkills.Revision};" +
|
||||
$"social={checkpoint.Social.FriendsRevision}:" +
|
||||
$"{checkpoint.Social.FriendCount}:" +
|
||||
$"{checkpoint.Social.SquelchRevision};" +
|
||||
$"portal={checkpoint.Portal.Generation}:{(int)checkpoint.Portal.Kind}:" +
|
||||
$"{checkpoint.Portal.DestinationCell:X8}:{checkpoint.Portal.IsReady}")));
|
||||
}
|
||||
|
|
|
|||
110
src/AcDream.Runtime/GameRuntimeGameplayViews.cs
Normal file
110
src/AcDream.Runtime/GameRuntimeGameplayViews.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.Runtime;
|
||||
|
||||
public readonly record struct RuntimePendingInventoryRequestSnapshot(
|
||||
ulong Token,
|
||||
int Kind,
|
||||
uint ItemId,
|
||||
bool Dispatched);
|
||||
|
||||
public readonly record struct RuntimeInventoryStateSnapshot(
|
||||
uint RequestedExternalContainerId,
|
||||
uint CurrentExternalContainerId,
|
||||
int BusyCount,
|
||||
bool CanBeginRequest,
|
||||
RuntimePendingInventoryRequestSnapshot? PendingRequest,
|
||||
int ShortcutCount,
|
||||
long ShortcutRevision,
|
||||
int ItemManaCount,
|
||||
long ItemManaRevision);
|
||||
|
||||
public readonly record struct RuntimeShortcutSnapshot(
|
||||
int Index,
|
||||
uint ObjectId,
|
||||
uint SpellId);
|
||||
|
||||
public interface IRuntimeInventoryStateView
|
||||
{
|
||||
RuntimeInventoryStateSnapshot Snapshot { get; }
|
||||
|
||||
bool TryGetShortcut(int index, out RuntimeShortcutSnapshot shortcut);
|
||||
|
||||
bool TryGetItemMana(uint objectId, out float fraction);
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeCharacterSnapshot(
|
||||
long CharacterRevision,
|
||||
long SpellbookRevision,
|
||||
RuntimeCharacterOptionsSnapshot Options,
|
||||
RuntimeMovementSkillSnapshot MovementSkills,
|
||||
int LearnedSpellCount,
|
||||
int ActiveEnchantmentCount,
|
||||
int DesiredComponentCount,
|
||||
int SkillCount,
|
||||
uint SpellbookFilters);
|
||||
|
||||
public readonly record struct RuntimeVitalSnapshot(
|
||||
int Kind,
|
||||
uint Ranks,
|
||||
uint Start,
|
||||
uint Experience,
|
||||
uint Current,
|
||||
uint Maximum);
|
||||
|
||||
public readonly record struct RuntimeAttributeSnapshot(
|
||||
int Kind,
|
||||
uint Ranks,
|
||||
uint Start,
|
||||
uint Experience,
|
||||
uint Current);
|
||||
|
||||
public readonly record struct RuntimeSkillSnapshot(
|
||||
uint SkillId,
|
||||
uint Ranks,
|
||||
uint Status,
|
||||
uint Experience,
|
||||
uint Initial,
|
||||
uint Resistance,
|
||||
double LastUsed,
|
||||
uint FormulaBonus,
|
||||
uint CurrentLevel);
|
||||
|
||||
public interface IRuntimeCharacterView
|
||||
{
|
||||
RuntimeCharacterSnapshot Snapshot { get; }
|
||||
|
||||
bool TryGetVital(int kind, out RuntimeVitalSnapshot vital);
|
||||
|
||||
bool TryGetAttribute(int kind, out RuntimeAttributeSnapshot attribute);
|
||||
|
||||
bool TryGetSkill(uint skillId, out RuntimeSkillSnapshot skill);
|
||||
|
||||
bool KnowsSpell(uint spellId);
|
||||
|
||||
bool TryGetFavorite(int tabIndex, int position, out uint spellId);
|
||||
|
||||
bool TryGetDesiredComponent(uint componentId, out uint amount);
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeSocialSnapshot(
|
||||
long FriendsRevision,
|
||||
int FriendCount,
|
||||
long SquelchRevision,
|
||||
int SquelchedAccountCount,
|
||||
int SquelchedCharacterCount,
|
||||
int GlobalSquelchTypeCount,
|
||||
int NegotiatedChatRoomCount);
|
||||
|
||||
public readonly record struct RuntimeFriendSnapshot(
|
||||
uint Id,
|
||||
string Name,
|
||||
bool Online,
|
||||
bool AppearOffline);
|
||||
|
||||
public interface IRuntimeSocialView
|
||||
{
|
||||
RuntimeSocialSnapshot Snapshot { get; }
|
||||
|
||||
bool TryGetFriend(uint characterId, out RuntimeFriendSnapshot friend);
|
||||
}
|
||||
|
|
@ -110,6 +110,9 @@ public readonly record struct RuntimeStateCheckpoint(
|
|||
int MaterializedEntityCount,
|
||||
int InventoryObjectCount,
|
||||
int InventoryContainerCount,
|
||||
RuntimeInventoryStateSnapshot InventoryState,
|
||||
RuntimeCharacterSnapshot Character,
|
||||
RuntimeSocialSnapshot Social,
|
||||
long ChatRevision,
|
||||
int ChatCount,
|
||||
RuntimeMovementSnapshot Movement,
|
||||
|
|
@ -127,6 +130,12 @@ public interface IGameRuntimeView
|
|||
|
||||
IRuntimeInventoryView Inventory { get; }
|
||||
|
||||
IRuntimeInventoryStateView InventoryState { get; }
|
||||
|
||||
IRuntimeCharacterView Character { get; }
|
||||
|
||||
IRuntimeSocialView Social { get; }
|
||||
|
||||
IRuntimeChatView Chat { get; }
|
||||
|
||||
IRuntimeMovementView Movement { get; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AcDream.Core.Player;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.Runtime.Gameplay;
|
||||
|
|
@ -11,15 +12,27 @@ namespace AcDream.Runtime.Gameplay;
|
|||
public sealed class RuntimeCharacterState : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private long _characterRevision;
|
||||
private long _spellbookRevision;
|
||||
|
||||
public RuntimeCharacterState(SpellTable? spellTable = null)
|
||||
{
|
||||
Spellbook = new Spellbook(spellTable);
|
||||
LocalPlayer = new LocalPlayerState(Spellbook);
|
||||
Options = new RuntimeCharacterOptionsState();
|
||||
MovementSkills = new RuntimeMovementSkillState();
|
||||
View = new CharacterView(this);
|
||||
Spellbook.StateChanged += OnSpellbookChanged;
|
||||
LocalPlayer.Changed += OnVitalChanged;
|
||||
LocalPlayer.AttributeChanged += OnAttributeChanged;
|
||||
LocalPlayer.CharacterChanged += OnCharacterChanged;
|
||||
}
|
||||
|
||||
public Spellbook Spellbook { get; }
|
||||
public LocalPlayerState LocalPlayer { get; }
|
||||
public RuntimeCharacterOptionsState Options { get; }
|
||||
public RuntimeMovementSkillState MovementSkills { get; }
|
||||
public IRuntimeCharacterView View { get; }
|
||||
public bool IsDisposed => _disposed;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,6 +68,8 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
List<Exception>? failures = null;
|
||||
Try(Spellbook.Clear, ref failures);
|
||||
Try(LocalPlayer.Clear, ref failures);
|
||||
Try(Options.ResetSession, ref failures);
|
||||
Try(MovementSkills.ResetSession, ref failures);
|
||||
if (failures is not null)
|
||||
{
|
||||
throw new AggregateException(
|
||||
|
|
@ -68,9 +83,25 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
if (_disposed)
|
||||
return;
|
||||
ResetSession();
|
||||
Spellbook.StateChanged -= OnSpellbookChanged;
|
||||
LocalPlayer.Changed -= OnVitalChanged;
|
||||
LocalPlayer.AttributeChanged -= OnAttributeChanged;
|
||||
LocalPlayer.CharacterChanged -= OnCharacterChanged;
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void OnSpellbookChanged() =>
|
||||
Interlocked.Increment(ref _spellbookRevision);
|
||||
|
||||
private void OnVitalChanged(LocalPlayerState.VitalKind _) =>
|
||||
Interlocked.Increment(ref _characterRevision);
|
||||
|
||||
private void OnAttributeChanged(LocalPlayerState.AttributeKind _) =>
|
||||
Interlocked.Increment(ref _characterRevision);
|
||||
|
||||
private void OnCharacterChanged() =>
|
||||
Interlocked.Increment(ref _characterRevision);
|
||||
|
||||
private static void Try(Action action, ref List<Exception>? failures)
|
||||
{
|
||||
try
|
||||
|
|
@ -82,4 +113,212 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
(failures ??= []).Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CharacterView(RuntimeCharacterState owner)
|
||||
: IRuntimeCharacterView
|
||||
{
|
||||
public RuntimeCharacterSnapshot Snapshot => new(
|
||||
Interlocked.Read(ref owner._characterRevision),
|
||||
Interlocked.Read(ref owner._spellbookRevision),
|
||||
owner.Options.Snapshot,
|
||||
owner.MovementSkills.Snapshot,
|
||||
owner.Spellbook.LearnedSpells.Count,
|
||||
owner.Spellbook.ActiveEnchantments.Count(),
|
||||
owner.Spellbook.DesiredComponents.Count,
|
||||
owner.LocalPlayer.Skills.Count,
|
||||
owner.Spellbook.SpellbookFilters);
|
||||
|
||||
public bool TryGetVital(int kind, out RuntimeVitalSnapshot vital)
|
||||
{
|
||||
if (!Enum.IsDefined((LocalPlayerState.VitalKind)kind)
|
||||
|| owner.LocalPlayer.Get((LocalPlayerState.VitalKind)kind)
|
||||
is not LocalPlayerState.VitalSnapshot current)
|
||||
{
|
||||
vital = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
vital = new RuntimeVitalSnapshot(
|
||||
kind,
|
||||
current.Ranks,
|
||||
current.Start,
|
||||
current.Xp,
|
||||
current.Current,
|
||||
owner.LocalPlayer.GetMaxApprox(
|
||||
(LocalPlayerState.VitalKind)kind) ?? 0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetAttribute(
|
||||
int kind,
|
||||
out RuntimeAttributeSnapshot attribute)
|
||||
{
|
||||
if (!Enum.IsDefined((LocalPlayerState.AttributeKind)kind)
|
||||
|| owner.LocalPlayer.GetAttribute(
|
||||
(LocalPlayerState.AttributeKind)kind)
|
||||
is not LocalPlayerState.AttributeSnapshot current)
|
||||
{
|
||||
attribute = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
attribute = new RuntimeAttributeSnapshot(
|
||||
kind,
|
||||
current.Ranks,
|
||||
current.Start,
|
||||
current.Xp,
|
||||
current.Current);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetSkill(uint skillId, out RuntimeSkillSnapshot skill)
|
||||
{
|
||||
if (owner.LocalPlayer.GetSkill(skillId)
|
||||
is not LocalPlayerState.SkillSnapshot current)
|
||||
{
|
||||
skill = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
skill = new RuntimeSkillSnapshot(
|
||||
current.SkillId,
|
||||
current.Ranks,
|
||||
current.Status,
|
||||
current.Xp,
|
||||
current.Init,
|
||||
current.Resistance,
|
||||
current.LastUsed,
|
||||
current.FormulaBonus,
|
||||
current.CurrentLevel);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool KnowsSpell(uint spellId) =>
|
||||
owner.Spellbook.Knows(spellId);
|
||||
|
||||
public bool TryGetFavorite(
|
||||
int tabIndex,
|
||||
int position,
|
||||
out uint spellId)
|
||||
{
|
||||
IReadOnlyList<uint> favorites =
|
||||
owner.Spellbook.GetFavorites(tabIndex);
|
||||
if ((uint)position >= (uint)favorites.Count)
|
||||
{
|
||||
spellId = 0u;
|
||||
return false;
|
||||
}
|
||||
spellId = favorites[position];
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetDesiredComponent(
|
||||
uint componentId,
|
||||
out uint amount) =>
|
||||
owner.Spellbook.DesiredComponents.TryGetValue(
|
||||
componentId,
|
||||
out amount);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeCharacterOptionsSnapshot(
|
||||
uint Options1,
|
||||
uint Options2,
|
||||
long Revision)
|
||||
{
|
||||
public bool DragItemOnPlayerOpensSecureTrade =>
|
||||
(Options1
|
||||
& (uint)PlayerDescriptionParser.CharacterOptions1
|
||||
.DragItemOnPlayerOpensSecureTrade) != 0u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Canonical session-owned copy of retail's two character-option bitfields.
|
||||
/// <c>PlayerModule::PlayerModule @ 0x005D51F0</c> installs the defaults.
|
||||
/// Runtime reset restores the equivalent fresh-player-module state because
|
||||
/// one Runtime owner survives across graphical and no-window sessions.
|
||||
/// </summary>
|
||||
public sealed class RuntimeCharacterOptionsState
|
||||
{
|
||||
public const uint DefaultOptions1 =
|
||||
(uint)PlayerDescriptionParser.CharacterOptions1.Default;
|
||||
public const uint DefaultOptions2 = 0x00948700u;
|
||||
|
||||
private uint _options1 = DefaultOptions1;
|
||||
private uint _options2 = DefaultOptions2;
|
||||
private long _revision;
|
||||
|
||||
public uint Options1 => Volatile.Read(ref _options1);
|
||||
public uint Options2 => Volatile.Read(ref _options2);
|
||||
public long Revision => Interlocked.Read(ref _revision);
|
||||
public RuntimeCharacterOptionsSnapshot Snapshot =>
|
||||
new(_options1, _options2, Revision);
|
||||
|
||||
public bool DragItemOnPlayerOpensSecureTrade =>
|
||||
Snapshot.DragItemOnPlayerOpensSecureTrade;
|
||||
|
||||
public void Replace(uint options1, uint options2)
|
||||
{
|
||||
Volatile.Write(ref _options1, options1);
|
||||
Volatile.Write(ref _options2, options2);
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
public void ResetSession()
|
||||
{
|
||||
Volatile.Write(ref _options1, DefaultOptions1);
|
||||
Volatile.Write(ref _options2, DefaultOptions2);
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeMovementSkillSnapshot(
|
||||
int RunSkill,
|
||||
int JumpSkill,
|
||||
long Revision)
|
||||
{
|
||||
public bool IsComplete => RunSkill >= 0 && JumpSkill >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-authoritative run/jump values retained independently of any
|
||||
/// graphical movement controller. App applies this borrowed state whenever
|
||||
/// its presentation/physics controller exists or is rebuilt.
|
||||
/// </summary>
|
||||
public sealed class RuntimeMovementSkillState
|
||||
{
|
||||
private int _runSkill = -1;
|
||||
private int _jumpSkill = -1;
|
||||
private long _revision;
|
||||
|
||||
public int RunSkill => Volatile.Read(ref _runSkill);
|
||||
public int JumpSkill => Volatile.Read(ref _jumpSkill);
|
||||
public bool IsComplete => _runSkill >= 0 && _jumpSkill >= 0;
|
||||
public long Revision => Interlocked.Read(ref _revision);
|
||||
public RuntimeMovementSkillSnapshot Snapshot =>
|
||||
new(_runSkill, _jumpSkill, Revision);
|
||||
|
||||
public void Update(int runSkill, int jumpSkill)
|
||||
{
|
||||
bool changed = false;
|
||||
if (runSkill >= 0 && RunSkill != runSkill)
|
||||
{
|
||||
Volatile.Write(ref _runSkill, runSkill);
|
||||
changed = true;
|
||||
}
|
||||
if (jumpSkill >= 0 && JumpSkill != jumpSkill)
|
||||
{
|
||||
Volatile.Write(ref _jumpSkill, jumpSkill);
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
|
||||
public void ResetSession()
|
||||
{
|
||||
Volatile.Write(ref _runSkill, -1);
|
||||
Volatile.Write(ref _jumpSkill, -1);
|
||||
Interlocked.Increment(ref _revision);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ public sealed class RuntimeCommunicationState : IDisposable
|
|||
Friends = new FriendsState();
|
||||
Squelch = new SquelchState();
|
||||
View = new CommunicationView(Chat);
|
||||
SocialView = new CommunicationSocialView(
|
||||
TurbineChat,
|
||||
Friends,
|
||||
Squelch);
|
||||
}
|
||||
|
||||
public ChatLog Chat { get; }
|
||||
|
|
@ -44,6 +48,7 @@ public sealed class RuntimeCommunicationState : IDisposable
|
|||
public FriendsState Friends { get; }
|
||||
public SquelchState Squelch { get; }
|
||||
public IRuntimeChatView View { get; }
|
||||
public IRuntimeSocialView SocialView { get; }
|
||||
public IRuntimeCommunicationEventSource Events => _events;
|
||||
|
||||
public bool IsDisposed => _disposed;
|
||||
|
|
@ -79,6 +84,61 @@ public sealed class RuntimeCommunicationState : IDisposable
|
|||
public long Revision => chat.Revision;
|
||||
public int Count => chat.Count;
|
||||
}
|
||||
|
||||
private sealed class CommunicationSocialView(
|
||||
TurbineChatState turbineChat,
|
||||
FriendsState friends,
|
||||
SquelchState squelch)
|
||||
: IRuntimeSocialView
|
||||
{
|
||||
public RuntimeSocialSnapshot Snapshot
|
||||
{
|
||||
get
|
||||
{
|
||||
SquelchDatabase database = squelch.Snapshot();
|
||||
return new RuntimeSocialSnapshot(
|
||||
friends.Revision,
|
||||
friends.Count,
|
||||
squelch.Revision,
|
||||
database.Accounts.Count,
|
||||
database.Characters.Count,
|
||||
database.Global.MessageTypes.Count,
|
||||
CountRooms(turbineChat));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetFriend(
|
||||
uint characterId,
|
||||
out RuntimeFriendSnapshot friend)
|
||||
{
|
||||
if (!friends.TryGet(characterId, out FriendEntry? current)
|
||||
|| current is null)
|
||||
{
|
||||
friend = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
friend = new RuntimeFriendSnapshot(
|
||||
current.Id,
|
||||
current.Name,
|
||||
current.Online,
|
||||
current.AppearOffline);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int CountRooms(TurbineChatState state)
|
||||
{
|
||||
int count = 0;
|
||||
if (state.AllegianceRoom != 0u) count++;
|
||||
if (state.GeneralRoom != 0u) count++;
|
||||
if (state.TradeRoom != 0u) count++;
|
||||
if (state.LfgRoom != 0u) count++;
|
||||
if (state.RoleplayRoom != 0u) count++;
|
||||
if (state.SocietyRoom != 0u) count++;
|
||||
if (state.OlthoiRoom != 0u) count++;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RuntimeCommunicationEventStream
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public sealed class RuntimeInventoryState : IDisposable
|
|||
ItemMana = new ItemManaState();
|
||||
Shortcuts = new ShortcutState();
|
||||
Transactions = new InventoryTransactionState(_entityObjects.Objects);
|
||||
View = new InventoryStateView(this);
|
||||
}
|
||||
|
||||
public ClientObjectTable Objects => _entityObjects.Objects;
|
||||
|
|
@ -28,6 +29,7 @@ public sealed class RuntimeInventoryState : IDisposable
|
|||
public ItemManaState ItemMana { get; }
|
||||
public ShortcutState Shortcuts { get; }
|
||||
public InventoryTransactionState Transactions { get; }
|
||||
public IRuntimeInventoryStateView View { get; }
|
||||
public bool IsDisposed => _disposed;
|
||||
|
||||
public void ResetExternalContainer() => ExternalContainers.Reset();
|
||||
|
|
@ -66,6 +68,61 @@ public sealed class RuntimeInventoryState : IDisposable
|
|||
(failures ??= []).Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InventoryStateView(RuntimeInventoryState owner)
|
||||
: IRuntimeInventoryStateView
|
||||
{
|
||||
public RuntimeInventoryStateSnapshot Snapshot
|
||||
{
|
||||
get
|
||||
{
|
||||
RuntimePendingInventoryRequestSnapshot? pending = null;
|
||||
if (owner.Transactions.TryGetPending(
|
||||
out PendingInventoryRequest request))
|
||||
{
|
||||
pending = new RuntimePendingInventoryRequestSnapshot(
|
||||
request.Token,
|
||||
(int)request.Kind,
|
||||
request.ItemId,
|
||||
request.Dispatched);
|
||||
}
|
||||
|
||||
return new RuntimeInventoryStateSnapshot(
|
||||
owner.ExternalContainers.RequestedContainerId,
|
||||
owner.ExternalContainers.CurrentContainerId,
|
||||
owner.Transactions.BusyCount,
|
||||
owner.Transactions.CanBeginRequest,
|
||||
pending,
|
||||
owner.Shortcuts.Items.Count,
|
||||
owner.Shortcuts.Revision,
|
||||
owner.ItemMana.Count,
|
||||
owner.ItemMana.Revision);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetShortcut(
|
||||
int index,
|
||||
out RuntimeShortcutSnapshot shortcut)
|
||||
{
|
||||
IReadOnlyList<ShortcutEntry> shortcuts = owner.Shortcuts.Items;
|
||||
for (int i = 0; i < shortcuts.Count; i++)
|
||||
{
|
||||
if (shortcuts[i].Index != index)
|
||||
continue;
|
||||
ShortcutEntry current = shortcuts[i];
|
||||
shortcut = new RuntimeShortcutSnapshot(
|
||||
current.Index,
|
||||
current.ObjectId,
|
||||
current.SpellId);
|
||||
return true;
|
||||
}
|
||||
shortcut = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetItemMana(uint objectId, out float fraction) =>
|
||||
owner.ItemMana.TryGetManaPercent(objectId, out fraction);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ShortcutState
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ public sealed record LiveCharacterSessionBindings(
|
|||
Action<int, int>? OnSkillsUpdated,
|
||||
Action<GameEvents.CharacterConfirmationRequest>? OnConfirmationRequest,
|
||||
Action<GameEvents.CharacterConfirmationDone>? OnConfirmationDone,
|
||||
Action<uint, uint>? OnCharacterOptions,
|
||||
Func<double>? ClientTime);
|
||||
|
||||
public sealed record LiveSocialSessionBindings(
|
||||
|
|
@ -152,7 +151,13 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
social.Chat,
|
||||
character.Character.LocalPlayer,
|
||||
social.TurbineChat,
|
||||
onSkillsUpdated: character.OnSkillsUpdated,
|
||||
onSkillsUpdated: (runSkill, jumpSkill) =>
|
||||
{
|
||||
character.Character.MovementSkills.Update(
|
||||
runSkill,
|
||||
jumpSkill);
|
||||
character.OnSkillsUpdated?.Invoke(runSkill, jumpSkill);
|
||||
},
|
||||
resolveSkillFormulaBonus: character.ResolveSkillFormulaBonus,
|
||||
onShortcuts: inventory.OnShortcuts,
|
||||
playerGuid: inventory.PlayerGuid,
|
||||
|
|
@ -164,7 +169,7 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
friends: social.Friends,
|
||||
squelch: social.Squelch,
|
||||
onDesiredComponents: null,
|
||||
onCharacterOptions: character.OnCharacterOptions,
|
||||
onCharacterOptions: character.Character.Options.Replace,
|
||||
clientTime: character.ClientTime,
|
||||
externalContainers: inventory.ExternalContainers,
|
||||
accepting: IsAccepting));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue