feat(mosstank): add VTank-style automation PoC

This commit is contained in:
Erik 2026-08-27 18:57:21 +02:00
parent f6fe0f2a4f
commit 4e6e9bc9d9
212 changed files with 49462 additions and 416 deletions

View file

@ -1004,9 +1004,14 @@ public static class GameEventWiring
{
var p = AppraiseInfoParser.TryParse(e.Payload.Span);
if (p is null) return;
// Merge parsed properties into the item if we know about it.
// Retain the property tables and the item's own spell manifest as
// one projection. VTank consults the latter for proc weapons.
if (p.Value.Success && items.Get(p.Value.Guid) is not null)
items.UpdateProperties(p.Value.Guid, p.Value.Properties);
items.UpdateAppraisal(
p.Value.Guid,
p.Value.Properties,
p.Value.SpellBook,
clientTime());
if (p.Value.CreatureProfile is { HealthMax: > 0u } creature)
combat.OnUpdateHealth(
p.Value.Guid,
@ -1020,8 +1025,8 @@ public static class GameEventWiring
// spellbook arrives via PlayerDescription (0x0013), which uses
// a different wire format (see WorldSession + LocalPlayerState
// — feeds vitals from PrivateUpdateVital instead).
// The appraised spellbook belongs to that item. The local player's
// learned spell manifest arrives only in PlayerDescription.
// The appraised spellbook now belongs to that item. The local
// player's learned manifest arrives only in PlayerDescription.
});
// ── Player ────────────────────────────────────────────────

View file

@ -27,6 +27,7 @@ public static class InventoryActions
public const uint DropItemOpcode = 0x001Bu;
public const uint NoLongerViewingContentsOpcode = 0x0195u;
public const uint SetInscriptionOpcode = 0x00BFu;
public const uint CreateTinkeringToolOpcode = 0x027Du;
/// <summary>
/// Merge stack A into stack B of the same item type. Server validates
@ -205,4 +206,48 @@ public static class InventoryActions
text.CopyTo(body, 18);
return body;
}
/// <summary>
/// Salvage one or more carried items with an owned salvage tool. Retail
/// <c>CM_Inventory::Event_CreateTinkeringTool @ 0x006AB830</c> writes the
/// tool id followed by <c>PackableList&lt;unsigned long&gt;</c>: u32 count,
/// then the object ids in list order. The server replies with GameEvent
/// <c>0x02B4 SalvageOperationsResult</c> and removes accepted source items.
/// </summary>
public static byte[] BuildCreateTinkeringTool(
uint seq,
uint toolGuid,
IReadOnlyList<uint> itemGuids)
{
ArgumentNullException.ThrowIfNull(itemGuids);
if (toolGuid == 0u)
throw new ArgumentOutOfRangeException(nameof(toolGuid));
if (itemGuids.Count == 0)
throw new ArgumentException(
"At least one item is required for salvage.",
nameof(itemGuids));
byte[] body = new byte[20 + (itemGuids.Count * sizeof(uint))];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameActionEnvelope);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), seq);
BinaryPrimitives.WriteUInt32LittleEndian(
body.AsSpan(8),
CreateTinkeringToolOpcode);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), toolGuid);
BinaryPrimitives.WriteUInt32LittleEndian(
body.AsSpan(16),
checked((uint)itemGuids.Count));
for (int index = 0; index < itemGuids.Count; index++)
{
uint itemGuid = itemGuids[index];
if (itemGuid == 0u)
throw new ArgumentException(
"Salvage item ids must be non-zero.",
nameof(itemGuids));
BinaryPrimitives.WriteUInt32LittleEndian(
body.AsSpan(20 + (index * sizeof(uint))),
itemGuid);
}
return body;
}
}

View file

@ -3268,6 +3268,19 @@ public sealed class WorldSession : IDisposable
SendGameAction(InventoryActions.BuildStackableSplitTo3D(seq, stackGuid, amount));
}
/// <summary>
/// Send retail CreateTinkeringTool (0x027D), the salvage operation used by
/// gmSalvageUI and VTank.
/// </summary>
public void SendSalvage(uint toolGuid, IReadOnlyList<uint> itemGuids)
{
uint seq = NextGameActionSequence();
SendGameAction(InventoryActions.BuildCreateTinkeringTool(
seq,
toolGuid,
itemGuids));
}
/// <summary>Send retail QueryHealth (0x01BF). Server replies UpdateHealth (0x01C0).</summary>
/// <remarks>
/// Retail anchor: <c>CM_Combat::Event_QueryHealth</c> / <c>gmToolbarUI::HandleSelectionChanged:198635</c>

View file

@ -2,55 +2,6 @@
"version": 2,
"dependencies": {
"net10.0": {
"BCnEncoder.Net": {
"type": "Direct",
"requested": "[2.2.1, )",
"resolved": "2.2.1",
"contentHash": "tI5+/OQo0kciLqWrViRjpOH+IL3FjexYnoWZajiGV41g/EM9CGbWsxsPzBDmpoxNkrV9uox/EtIhCIi9chBSFw==",
"dependencies": {
"CommunityToolkit.HighPerformance": "8.4.0"
}
},
"Chorizite.Core": {
"type": "Direct",
"requested": "[0.0.18, )",
"resolved": "0.0.18",
"contentHash": "Pvf5idSsN0NfhZspJhrpo7QiNTmHx3mnKFkmAvHFpEFx+RnqfXh96Q+Pxam+hrqXbimmKGfk1ooIl5kpQcMo6w==",
"dependencies": {
"Autofac": "8.4.0",
"Chorizite.ACProtocol": "1.0.1",
"Chorizite.Common": "1.0.3",
"Chorizite.DatReaderWriter": "1.0.0",
"FontStashSharp": "1.3.10",
"Microsoft.Diagnostics.Runtime": "3.1.512801",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"NJsonSchema": "11.5.1",
"SixLabors.ImageSharp": "3.1.11",
"SixLabors.ImageSharp.Drawing": "2.1.7"
}
},
"Chorizite.DatReaderWriter": {
"type": "Direct",
"requested": "[2.1.7, )",
"resolved": "2.1.7",
"contentHash": "6CpUhfHDV/O+lNx0xUZUcXK7wGkEkHYCnGHFtD8BBeAv4i1BuaNfTID+VQoSzx2EtmOVM0A2O/9wRFibApz6rQ==",
"dependencies": {
"DotNet.Standard.Common": "2.0.1",
"ZLibDotNet": "0.1.1"
}
},
"Serilog": {
"type": "Direct",
"requested": "[4.0.2, )",
"resolved": "4.0.2",
"contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA=="
},
"StbImageSharp": {
"type": "Direct",
"requested": "[2.30.16, )",
"resolved": "2.30.16",
"contentHash": "qg1i+NHihXVKLKYacGKrauhSQIGL31eBWcTC4Vc7jnGmBFj87LkRuCdXB5aDZisWMRnB6x2mcfwYXQxMWEG/lw=="
},
"Autofac": {
"type": "Transitive",
"resolved": "8.4.0",
@ -245,9 +196,57 @@
"resolved": "0.1.1",
"contentHash": "QEti4O7dwRcOb9zbnLuudSrt2IT61OYjq0R7lcJb+EzUm5N6djOVGU/cp+FZIZzJUnaFIntwrpwiuRvhpS7ZHg=="
},
"acdream.core": {
"type": "Project",
"dependencies": {
"AcDream.Plugin.Abstractions": "[1.0.0, )",
"BCnEncoder.Net": "[2.2.1, )",
"Chorizite.Core": "[0.0.18, )",
"Chorizite.DatReaderWriter": "[2.1.7, )",
"Serilog": "[4.0.2, )",
"StbImageSharp": "[2.30.16, )"
}
},
"acdream.plugin.abstractions": {
"type": "Project"
},
"BCnEncoder.Net": {
"type": "CentralTransitive",
"requested": "[2.2.1, )",
"resolved": "2.2.1",
"contentHash": "tI5+/OQo0kciLqWrViRjpOH+IL3FjexYnoWZajiGV41g/EM9CGbWsxsPzBDmpoxNkrV9uox/EtIhCIi9chBSFw==",
"dependencies": {
"CommunityToolkit.HighPerformance": "8.4.0"
}
},
"Chorizite.Core": {
"type": "CentralTransitive",
"requested": "[0.0.18, )",
"resolved": "0.0.18",
"contentHash": "Pvf5idSsN0NfhZspJhrpo7QiNTmHx3mnKFkmAvHFpEFx+RnqfXh96Q+Pxam+hrqXbimmKGfk1ooIl5kpQcMo6w==",
"dependencies": {
"Autofac": "8.4.0",
"Chorizite.ACProtocol": "1.0.1",
"Chorizite.Common": "1.0.3",
"Chorizite.DatReaderWriter": "1.0.0",
"FontStashSharp": "1.3.10",
"Microsoft.Diagnostics.Runtime": "3.1.512801",
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"NJsonSchema": "11.5.1",
"SixLabors.ImageSharp": "3.1.11",
"SixLabors.ImageSharp.Drawing": "2.1.7"
}
},
"Chorizite.DatReaderWriter": {
"type": "CentralTransitive",
"requested": "[2.1.7, )",
"resolved": "2.1.7",
"contentHash": "6CpUhfHDV/O+lNx0xUZUcXK7wGkEkHYCnGHFtD8BBeAv4i1BuaNfTID+VQoSzx2EtmOVM0A2O/9wRFibApz6rQ==",
"dependencies": {
"DotNet.Standard.Common": "2.0.1",
"ZLibDotNet": "0.1.1"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[9.0.9, )",
@ -257,12 +256,24 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9"
}
},
"Serilog": {
"type": "CentralTransitive",
"requested": "[4.0.2, )",
"resolved": "4.0.2",
"contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA=="
},
"SixLabors.ImageSharp": {
"type": "CentralTransitive",
"requested": "[3.1.12, )",
"resolved": "3.1.11",
"contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw=="
},
"StbImageSharp": {
"type": "CentralTransitive",
"requested": "[2.30.16, )",
"resolved": "2.30.16",
"contentHash": "qg1i+NHihXVKLKYacGKrauhSQIGL31eBWcTC4Vc7jnGmBFj87LkRuCdXB5aDZisWMRnB6x2mcfwYXQxMWEG/lw=="
},
"StbTrueTypeSharp": {
"type": "CentralTransitive",
"requested": "[1.26.12, )",