refactor(net): converge live session state reset
This commit is contained in:
parent
78a9223b65
commit
4f31a5085f
35 changed files with 1460 additions and 83 deletions
|
|
@ -62,14 +62,27 @@ public sealed class ChatLog
|
|||
|
||||
/// <summary>
|
||||
/// Push the authoritative local-player GUID from <c>WorldSession</c>.
|
||||
/// One-way setter — only <c>GameWindow</c> should call it, exactly
|
||||
/// once per live session. Used by <see cref="OnLocalSpeech"/> to
|
||||
/// The host sets it after character selection and resets it at session
|
||||
/// teardown. Used by <see cref="OnLocalSpeech"/> to
|
||||
/// recognize ACE's HearSpeech echo of our own /say (server's
|
||||
/// HandleActionTalk broadcasts to all in range INCLUDING the
|
||||
/// sender) and re-render it as <c>"You say, ..."</c>.
|
||||
/// </summary>
|
||||
public void SetLocalPlayerGuid(uint guid) => _localPlayerGuid = guid;
|
||||
|
||||
/// <summary>
|
||||
/// Reset per-session speaker classification and duplicate suppression
|
||||
/// while preserving the visible transcript. Retail has an explicit
|
||||
/// <c>ChatInterface::RecvNotice_ClearChatBuffer @ 0x004F2F60</c> path;
|
||||
/// character-session teardown does not use that clear-buffer notice.
|
||||
/// </summary>
|
||||
public void ResetSessionIdentity()
|
||||
{
|
||||
_localPlayerGuid = 0u;
|
||||
_lastSystemText = string.Empty;
|
||||
_lastSystemAt = DateTime.MinValue;
|
||||
}
|
||||
|
||||
// ── Inbound adapters ─────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Local or ranged HearSpeech (0x02BB / 0x02BC).</summary>
|
||||
|
|
|
|||
|
|
@ -93,6 +93,33 @@ public sealed class TurbineChatState
|
|||
SocietyRadiantBloodRoom = societyRadiantBloodRoom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the server-assigned room table and outbound context sequence to
|
||||
/// their pre-login values. Room ids and cookies belong to one negotiated
|
||||
/// character session and must never cross a reconnect boundary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail <c>CCommunicationSystem</c> construction at <c>0x005566C0</c>
|
||||
/// starts Turbine chat disabled; SetTurbineChatChannels (0x0295) supplies
|
||||
/// rooms per character. Cookie 1 is the Holtburger protocol-reference
|
||||
/// initial value, with zero reserved for no context.
|
||||
/// </remarks>
|
||||
public void Reset()
|
||||
{
|
||||
Enabled = false;
|
||||
AllegianceRoom = 0u;
|
||||
GeneralRoom = 0u;
|
||||
TradeRoom = 0u;
|
||||
LfgRoom = 0u;
|
||||
RoleplayRoom = 0u;
|
||||
OlthoiRoom = 0u;
|
||||
SocietyRoom = 0u;
|
||||
SocietyCelestialHandRoom = 0u;
|
||||
SocietyEldrytchWebRoom = 0u;
|
||||
SocietyRadiantBloodRoom = 0u;
|
||||
_nextContextId = 1u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up the runtime room id for a Turbine
|
||||
/// <see cref="ChatChannelKindLite"/>. Returns 0 if the channel is not
|
||||
|
|
|
|||
|
|
@ -178,6 +178,21 @@ public sealed class CombatState
|
|||
public void Clear()
|
||||
{
|
||||
_healthByGuid.Clear();
|
||||
SetCombatMode(CombatMode.NonCombat);
|
||||
CurrentMode = CombatMode.NonCombat;
|
||||
|
||||
Action<CombatMode>? listeners = CombatModeChanged;
|
||||
if (listeners is null)
|
||||
return;
|
||||
|
||||
List<Exception>? failures = null;
|
||||
foreach (Action<CombatMode> listener in listeners.GetInvocationList())
|
||||
{
|
||||
try { listener(CombatMode.NonCombat); }
|
||||
catch (Exception error) { (failures ??= []).Add(error); }
|
||||
}
|
||||
if (failures is not null)
|
||||
throw new AggregateException(
|
||||
"One or more combat reset observers failed.",
|
||||
failures);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,12 +101,24 @@ public sealed class ExternalContainerState
|
|||
bool changed = previous != 0u || RequestedContainerId != 0u;
|
||||
CurrentContainerId = 0u;
|
||||
RequestedContainerId = 0u;
|
||||
if (changed)
|
||||
|
||||
var transition = new ExternalContainerTransition(
|
||||
ExternalContainerTransitionKind.Reset,
|
||||
previous,
|
||||
0u);
|
||||
Action<ExternalContainerTransition>? listeners = Changed;
|
||||
if (listeners is not null)
|
||||
{
|
||||
Changed?.Invoke(new ExternalContainerTransition(
|
||||
ExternalContainerTransitionKind.Reset,
|
||||
previous,
|
||||
0u));
|
||||
List<Exception>? failures = null;
|
||||
foreach (Action<ExternalContainerTransition> listener in listeners.GetInvocationList())
|
||||
{
|
||||
try { listener(transition); }
|
||||
catch (Exception error) { (failures ??= []).Add(error); }
|
||||
}
|
||||
if (failures is not null)
|
||||
throw new AggregateException(
|
||||
"One or more external-container reset observers failed.",
|
||||
failures);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,6 +454,39 @@ public sealed class LocalPlayerState
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the character snapshot to its pre-login state. The object itself
|
||||
/// is process-lived because UI view models subscribe to it once; session
|
||||
/// replacement clears its contents instead of replacing the owner.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail: <c>CPlayerSystem::OnEndCharacterSession @ 0x00562870</c>
|
||||
/// calls <c>End @ 0x005606C0</c>, which invokes
|
||||
/// <c>PlayerModule::Clear @ 0x005D48A0</c>, then immediately calls
|
||||
/// <c>CPlayerSystem::Begin @ 0x0055D410</c>. Re-publishing invalidation
|
||||
/// events is acdream's process-lived-view adaptation.
|
||||
/// </remarks>
|
||||
public void Clear()
|
||||
{
|
||||
_health = null;
|
||||
_stamina = null;
|
||||
_mana = null;
|
||||
_attrs.Clear();
|
||||
_skills.Clear();
|
||||
_positions.Clear();
|
||||
_properties = new PropertyBundle();
|
||||
|
||||
// Process-lived views pull through this object and use these events as
|
||||
// their invalidation edge. Publish every category even when Clear is
|
||||
// repeated so a failed reset attempt can safely converge on retry.
|
||||
Changed?.Invoke(VitalKind.Health);
|
||||
Changed?.Invoke(VitalKind.Stamina);
|
||||
Changed?.Invoke(VitalKind.Mana);
|
||||
foreach (AttributeKind kind in Enum.GetValues<AttributeKind>())
|
||||
AttributeChanged?.Invoke(kind);
|
||||
CharacterChanged?.Invoke();
|
||||
}
|
||||
|
||||
private static uint SaturatingAdd(uint value, uint delta)
|
||||
=> uint.MaxValue - value < delta ? uint.MaxValue : value + delta;
|
||||
|
||||
|
|
|
|||
|
|
@ -78,20 +78,25 @@ public sealed class SelectionState : ISelectionService
|
|||
public bool Reset(SelectionChangeSource source = SelectionChangeSource.System)
|
||||
{
|
||||
uint? old = SelectedObjectId;
|
||||
bool changed = old is not null
|
||||
|| PreviousObjectId is not null
|
||||
|| PreviousValidObjectId is not null;
|
||||
SelectedObjectId = null;
|
||||
PreviousObjectId = null;
|
||||
PreviousValidObjectId = null;
|
||||
if (old is null)
|
||||
return false;
|
||||
|
||||
var transition = new SelectionTransition(
|
||||
old,
|
||||
null,
|
||||
source,
|
||||
SelectionChangeReason.SessionReset);
|
||||
Changed?.Invoke(transition);
|
||||
List<Exception>? failures = DispatchChanged(transition);
|
||||
DispatchPluginChanged(new SelectionChangedEvent(old, null));
|
||||
return true;
|
||||
if (failures is not null)
|
||||
throw new AggregateException(
|
||||
"One or more selection reset observers failed.",
|
||||
failures);
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool ISelectionService.Select(uint objectId)
|
||||
|
|
@ -120,6 +125,21 @@ public sealed class SelectionState : ISelectionService
|
|||
return true;
|
||||
}
|
||||
|
||||
private List<Exception>? DispatchChanged(SelectionTransition transition)
|
||||
{
|
||||
Action<SelectionTransition>? listeners = Changed;
|
||||
if (listeners is null)
|
||||
return null;
|
||||
|
||||
List<Exception>? failures = null;
|
||||
foreach (Action<SelectionTransition> listener in listeners.GetInvocationList())
|
||||
{
|
||||
try { listener(transition); }
|
||||
catch (Exception error) { (failures ??= []).Add(error); }
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
private void DispatchPluginChanged(SelectionChangedEvent change)
|
||||
{
|
||||
Action<SelectionChangedEvent>? listeners = PluginChanged;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@ public sealed class SquelchState
|
|||
ArgumentNullException.ThrowIfNull(database);
|
||||
lock (_gate) _database = database;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop the server-owned squelch database for the old session. Retail
|
||||
/// <c>CPlayerSystem::LogOnCharacter @ 0x0055F890</c> calls
|
||||
/// <c>gmCCommunicationSystem::ClearSquelchDB @ 0x00589240</c> before
|
||||
/// entering the next character.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_gate) _database = SquelchDatabase.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record SquelchInfo(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue