feat(ui): port retail selected-item mana
Parse and route the complete 0x0264 guid/fraction/valid response, send exact 0x0263 item-mana queries, and reproduce retail meter visibility plus guid-zero cancellation for mana and health selection changes. Keep the behavior in Core state and the retained selected-object controller with wire/state/UI conformance coverage. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
df460f94c3
commit
fa1da02783
21 changed files with 402 additions and 38 deletions
|
|
@ -69,7 +69,8 @@ public static class GameEventWiring
|
|||
// B-Wire: the local player's server guid. When provided, the PD handler upserts
|
||||
// the player's own PropertyBundle (EncumbranceVal etc.) into the player ClientObject.
|
||||
Func<uint>? playerGuid = null,
|
||||
Action<uint /*weenieError*/>? onUseDone = null)
|
||||
Action<uint /*weenieError*/>? onUseDone = null,
|
||||
ItemManaState? itemMana = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dispatcher);
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
|
@ -161,6 +162,16 @@ public static class GameEventWiring
|
|||
var p = GameEvents.ParseUpdateHealth(e.Payload.Span);
|
||||
if (p is not null) combat.OnUpdateHealth(p.Value.TargetGuid, p.Value.HealthPercent);
|
||||
});
|
||||
if (itemMana is not null)
|
||||
{
|
||||
dispatcher.Register(GameEventType.QueryItemManaResponse, e =>
|
||||
{
|
||||
var p = GameEvents.ParseQueryItemManaResponse(e.Payload.Span);
|
||||
if (p is not null)
|
||||
itemMana.OnQueryItemManaResponse(
|
||||
p.Value.ItemGuid, p.Value.ManaPercent, p.Value.Valid);
|
||||
});
|
||||
}
|
||||
dispatcher.Register(GameEventType.VictimNotification, e =>
|
||||
{
|
||||
var p = GameEvents.ParseVictimNotification(e.Payload.Span);
|
||||
|
|
|
|||
|
|
@ -450,15 +450,20 @@ public static class GameEvents
|
|||
return BinaryPrimitives.ReadUInt32LittleEndian(payload);
|
||||
}
|
||||
|
||||
/// <summary>0x0264 QueryItemManaResponse: (itemGuid, manaPercent).</summary>
|
||||
public readonly record struct QueryItemManaResponse(uint ItemGuid, float ManaPercent);
|
||||
/// <summary>
|
||||
/// 0x0264 QueryItemManaResponse: (itemGuid, manaPercent, valid).
|
||||
/// Retail anchor: <c>CM_Item::DispatchUI_QueryItemManaResponse @ 0x006A84D0</c>
|
||||
/// reads the trailing 32-bit validity flag at message offset 0x0C.
|
||||
/// </summary>
|
||||
public readonly record struct QueryItemManaResponse(uint ItemGuid, float ManaPercent, bool Valid);
|
||||
|
||||
public static QueryItemManaResponse? ParseQueryItemManaResponse(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (payload.Length < 8) return null;
|
||||
if (payload.Length < 12) return null;
|
||||
return new QueryItemManaResponse(
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload),
|
||||
BinaryPrimitives.ReadSingleLittleEndian(payload.Slice(4)));
|
||||
BinaryPrimitives.ReadSingleLittleEndian(payload.Slice(4)),
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(8)) != 0);
|
||||
}
|
||||
|
||||
/// <summary>0x0274 CharacterConfirmationRequest — server-driven modal confirm.</summary>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public static class SocialActions
|
|||
|
||||
// Queries
|
||||
public const uint QueryHealthOpcode = 0x01BFu; // u32 targetGuid
|
||||
public const uint QueryItemManaOpcode = 0x0263u; // u32 itemGuid; zero cancels
|
||||
public const uint PingRequestOpcode = 0x01E9u; // u32 clientId
|
||||
|
||||
// Fellowship
|
||||
|
|
@ -56,6 +57,20 @@ public static class SocialActions
|
|||
return body;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query an owned item's mana fraction, or cancel the active query with item guid zero.
|
||||
/// Retail anchor: <c>CM_Item::Event_QueryItemMana @ 0x006A8610</c>.
|
||||
/// </summary>
|
||||
public static byte[] BuildQueryItemMana(uint seq, uint itemGuid)
|
||||
{
|
||||
byte[] body = new byte[16];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), QueryItemManaOpcode);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), itemGuid);
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>Keepalive ping — server echoes with PingResponse (0x01EA).</summary>
|
||||
public static byte[] BuildPingRequest(uint seq, uint clientId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1379,6 +1379,17 @@ public sealed class WorldSession : IDisposable
|
|||
SendGameAction(body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send retail QueryItemMana (0x0263), using item guid zero to cancel.
|
||||
/// Retail anchor: <c>CM_Item::Event_QueryItemMana @ 0x006A8610</c>.
|
||||
/// </summary>
|
||||
public void SendQueryItemMana(uint itemGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
byte[] body = SocialActions.BuildQueryItemMana(seq, itemGuid);
|
||||
SendGameAction(body);
|
||||
}
|
||||
|
||||
/// <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