feat(ui): share indicator detail panels
Port the authored Link Status, Vitae, and Mini Game detail roots and register every indicator page with retail's one-active gmPanelUI owner. Helpful/Harmful and the new pages now replace Inventory, Character, or Magic at one canonical window position while preserving the DAT restore-previous flag. Correct the retail ping wire to its payload-free request/response, publish measured RTT, and port Vitae recovery XP from the live modifier and player properties. Keep transport packet-loss averaging and mini-game gameplay explicitly tracked under AP-110. Release build and all 5,814 tests pass with five intentional skips. Connected visual gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
52c529be86
commit
a96767ba6d
28 changed files with 6539 additions and 32 deletions
|
|
@ -529,20 +529,49 @@ public sealed class WorldSession : IDisposable
|
|||
CurrentState,
|
||||
Volatile.Read(ref _lastInboundPacketTicks),
|
||||
Stopwatch.GetTimestamp(),
|
||||
Stopwatch.Frequency);
|
||||
Stopwatch.Frequency,
|
||||
PingRoundTripSeconds);
|
||||
|
||||
internal double? PingRoundTripSeconds
|
||||
{
|
||||
get
|
||||
{
|
||||
double value = BitConverter.Int64BitsToDouble(
|
||||
Volatile.Read(ref _lastPingRoundTripBits));
|
||||
return double.IsFinite(value) && value >= 0d ? value : null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static LinkStatusSnapshot BuildLinkStatus(
|
||||
State state,
|
||||
long lastInboundPacketTicks,
|
||||
long nowTicks,
|
||||
long frequency)
|
||||
long frequency,
|
||||
double? roundTripSeconds = null,
|
||||
double packetLossPercentage = 0d)
|
||||
{
|
||||
bool connected = state is not State.Disconnected and not State.Failed;
|
||||
if (!connected || frequency <= 0)
|
||||
return LinkStatusSnapshot.Disconnected;
|
||||
|
||||
long elapsed = Math.Max(0, nowTicks - lastInboundPacketTicks);
|
||||
return new LinkStatusSnapshot(true, elapsed / (double)frequency);
|
||||
return new LinkStatusSnapshot(
|
||||
true,
|
||||
elapsed / (double)frequency,
|
||||
packetLossPercentage,
|
||||
roundTripSeconds);
|
||||
}
|
||||
|
||||
private void RecordPingResponse(long nowTicks)
|
||||
{
|
||||
long requestTicks = Interlocked.Exchange(ref _lastPingRequestTicks, 0L);
|
||||
if (requestTicks <= 0L || nowTicks < requestTicks || Stopwatch.Frequency <= 0)
|
||||
return;
|
||||
|
||||
double elapsed = (nowTicks - requestTicks) / (double)Stopwatch.Frequency;
|
||||
Volatile.Write(
|
||||
ref _lastPingRoundTripBits,
|
||||
BitConverter.DoubleToInt64Bits(elapsed));
|
||||
}
|
||||
|
||||
/// <summary>Movement sequence counters for outbound MoveToState/AutonomousPosition.</summary>
|
||||
|
|
@ -574,6 +603,8 @@ public sealed class WorldSession : IDisposable
|
|||
|
||||
private readonly NetClient _net;
|
||||
private long _lastInboundPacketTicks = Stopwatch.GetTimestamp();
|
||||
private long _lastPingRequestTicks;
|
||||
private long _lastPingRoundTripBits = BitConverter.DoubleToInt64Bits(double.NaN);
|
||||
private readonly IPEndPoint _loginEndpoint;
|
||||
private readonly IPEndPoint _connectEndpoint;
|
||||
private readonly FragmentAssembler _assembler = new();
|
||||
|
|
@ -656,6 +687,11 @@ public sealed class WorldSession : IDisposable
|
|||
var parsed = SetTurbineChatChannels.TryParse(e.Payload.Span);
|
||||
if (parsed is not null) TurbineChannelsReceived?.Invoke(parsed.Value);
|
||||
});
|
||||
GameEvents.Register(GameEventType.PingResponse, e =>
|
||||
{
|
||||
if (Messages.GameEvents.ParsePingResponse(e.Payload.Span))
|
||||
RecordPingResponse(Stopwatch.GetTimestamp());
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1701,6 +1737,18 @@ public sealed class WorldSession : IDisposable
|
|||
SendGameAction(body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request the round-trip sample displayed by retail <c>gmLinkStatusUI</c>.
|
||||
/// The response is an empty 0x01EA GameEvent, so the session retains the
|
||||
/// monotonic send timestamp rather than putting an invented id on the wire.
|
||||
/// </summary>
|
||||
public void RequestLinkStatusPing()
|
||||
{
|
||||
Volatile.Write(ref _lastPingRequestTicks, Stopwatch.GetTimestamp());
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(SocialActions.BuildPingRequest(seq));
|
||||
}
|
||||
|
||||
/// <summary>Send retail TargetedMeleeAttack (0x0008).</summary>
|
||||
public void SendMeleeAttack(uint targetGuid, AttackHeight attackHeight, float powerLevel)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue