The backend's inventory views and mana panel are fed by three streams the original sent: the whole inventory after login, at logoff and on /mm sendinventory; an add, update or remove per item as it enters, changes in or leaves the packs; and the per-item cantrip state of worn and wielded gear. This ports all three onto the host's object events, with the frames built exactly as the original built them. Behaviour kept: adds and removes go out at once, changes are coalesced into one update per item on a two-to-five-minute timer re-rolled per flush; the first login appraises everything worth appraising and waits before sending; later logins merge in the saved appraisal data (kept in the character's storage, as the original kept a file per character); the mana panel debounces a quarter second and projects mana down between reports. Adapted to the host: appraisals go through a tick-driven queue, one at a time, retrying while the host's single slot is busy; the login upload waits for the inventory to stop arriving and for the socket, so it is not written into a closed socket; the logoff upload subscribes ahead of the root so the socket is still open; enchantment changes are polled once a second (the host has no event); an item-cast enchantment is the one that never runs out. A request the server never answers no longer holds the upload back for good. The Prismatic Taper count telemetry reads is InventoryFeature.PrismaticTaperCount. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
95 lines
3.7 KiB
C#
95 lines
3.7 KiB
C#
using AcDream.Plugin.Abstractions;
|
|
using Newtonsoft.Json.Linq;
|
|
using OpenAC.MosswartMassacre.Inventory;
|
|
using static OpenAC.MosswartMassacre.Tests.Inventory.InventoryWorld;
|
|
|
|
namespace OpenAC.MosswartMassacre.Tests.Inventory;
|
|
|
|
public sealed class EquipmentCantripStateTests
|
|
{
|
|
private static PluginHarness Arrange(params PluginActiveEnchantment[] enchantments) =>
|
|
new("Mossy", arrange: host =>
|
|
{
|
|
Own(host, Tachi(appraised: true), TachiProps(), hostIdTime: 5000);
|
|
host.Automation.Spells.All =
|
|
[
|
|
Spell(2598, family: 100, difficulty: 150),
|
|
Spell(2603, family: 101, difficulty: 150),
|
|
Spell(5183, family: 200, difficulty: 400),
|
|
Spell(6089, family: 100, difficulty: 400),
|
|
Spell(6094, family: 101, difficulty: 400),
|
|
];
|
|
host.Automation.Character.ActiveEnchantments = enchantments;
|
|
});
|
|
|
|
private static JToken OnlyItem(PluginHarness harness) =>
|
|
JObject.Parse(harness.FramesOfTypeRaw("equipment_cantrip_state").Last())["items"]!.Single();
|
|
|
|
[Fact]
|
|
public void A_timed_buff_does_not_keep_an_item_spell_running_only_an_item_cast_one_does()
|
|
{
|
|
using var harness = Arrange(
|
|
new PluginActiveEnchantment(6089, 100, 8, double.PositiveInfinity),
|
|
new PluginActiveEnchantment(6094, 101, 8, 1800d));
|
|
harness.Login();
|
|
harness.Feature<EquipmentCantripStateFeature>().SendSnapshot();
|
|
|
|
JToken item = OnlyItem(harness);
|
|
Assert.Equal("not_active", (string)item["state"]!);
|
|
Assert.Equal(1500, (int)item["current_mana"]!);
|
|
Assert.Equal(JTokenType.Null, item["mana_time_remaining_seconds"]!.Type);
|
|
}
|
|
|
|
[Fact]
|
|
public void An_item_without_mana_is_not_active_and_one_without_spells_is_left_out()
|
|
{
|
|
using var harness = Arrange();
|
|
PluginItemProperties empty = TachiProps();
|
|
var ints = new Dictionary<uint, int>(empty.Ints) { [107] = 0 };
|
|
Own(harness.Host, Tachi(appraised: true), empty with { Ints = ints }, hostIdTime: 5000);
|
|
harness.Login();
|
|
harness.Feature<EquipmentCantripStateFeature>().SendSnapshot();
|
|
Assert.Equal("not_active", (string)OnlyItem(harness)["state"]!);
|
|
|
|
Own(harness.Host, Tachi(appraised: false) with { AppraisedSpellIds = [] }, TachiProps(), hostIdTime: 5000);
|
|
harness.Feature<EquipmentCantripStateFeature>().SendSnapshot();
|
|
Assert.Empty(JObject.Parse(harness.FramesOfTypeRaw("equipment_cantrip_state").Last())["items"]!);
|
|
}
|
|
|
|
[Fact]
|
|
public void Changes_within_a_quarter_second_send_one_frame()
|
|
{
|
|
using var harness = Arrange();
|
|
harness.Login();
|
|
harness.Tick(0.3);
|
|
harness.Frames.Clear();
|
|
|
|
Raise(harness.Host, 0x80000202u, PluginObjectChangeKind.Updated);
|
|
harness.Tick(0.1);
|
|
Raise(harness.Host, 0x80000202u, PluginObjectChangeKind.Updated);
|
|
harness.Tick(0.2);
|
|
Assert.Empty(harness.FramesOfTypeRaw("equipment_cantrip_state"));
|
|
harness.Tick(0.1);
|
|
|
|
Assert.Single(harness.FramesOfTypeRaw("equipment_cantrip_state"));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_change_in_the_characters_enchantments_sends_a_frame()
|
|
{
|
|
using var harness = Arrange();
|
|
harness.Login();
|
|
harness.Tick(0.3);
|
|
harness.Frames.Clear();
|
|
|
|
harness.Tick(1.0);
|
|
Assert.Empty(harness.FramesOfTypeRaw("equipment_cantrip_state"));
|
|
|
|
harness.Host.Automation.Character.ActiveEnchantments =
|
|
[new PluginActiveEnchantment(6089, 100, 8, double.PositiveInfinity)];
|
|
harness.Tick(1.0);
|
|
harness.Tick(0.3);
|
|
|
|
Assert.Single(harness.FramesOfTypeRaw("equipment_cantrip_state"));
|
|
}
|
|
}
|