using AcDream.Plugin.Abstractions; using Newtonsoft.Json.Linq; using OpenAC.MosswartMassacre.Inventory; using OpenAC.MosswartMassacre.Tests.Fakes; namespace OpenAC.MosswartMassacre.Tests.Inventory; /// /// Puts items into the fake host the way the real one holds them: the item /// in the owned list, its world object in the object table, its property /// tables when it has any. /// internal static class InventoryWorld { /// The harness character's id. public const uint Me = 0x50000001; public static PluginItemProperties Props( Dictionary? ints = null, Dictionary? floats = null, Dictionary? bools = null, Dictionary? strings = null, PluginWeaponProfile? weapon = null, PluginArmorProfile? armor = null) => new( ints ?? [], new Dictionary(), bools ?? [], floats ?? [], strings ?? [], new Dictionary(), new Dictionary()) { WeaponProfile = weapon, ArmorProfile = armor, }; /// /// Adds or replaces an owned item. non-zero /// with properties means appraised. /// public static void Own( FakeHost host, PluginInventoryItem item, PluginItemProperties? properties = null, int hostIdTime = 0) { List owned = host.Automation.Items.Owned; int index = owned.FindIndex(existing => existing.ObjectId == item.ObjectId); if (index >= 0) owned[index] = item; else owned.Add(item); if (properties is { } props) host.Automation.Items.Properties[item.ObjectId] = props; else host.Automation.Items.Properties.Remove(item.ObjectId); host.Automation.Objects.Replace(World(item, properties is not null, hostIdTime)); } /// Takes an item out of the owned list and the object table. public static void Forget(FakeHost host, uint objectId) { host.Automation.Items.Owned.RemoveAll(item => item.ObjectId == objectId); host.Automation.Items.Properties.Remove(objectId); host.Automation.Objects.Objects.RemoveAll(o => o.ObjectId == objectId); } public static PluginWorldObject World(PluginInventoryItem item, bool hasProperties, int hostIdTime) => new( item.ObjectId, item.WeenieClassId, item.Name, item.ObjectClass, item.ItemType, item.ContainerObjectId, item.WielderObjectId) { IsOwned = true, HasAppraisalData = hasProperties, LastIdTime = hostIdTime, SpellIds = item.AppraisedSpellIds, IconId = item.IconId, }; /// A change as the host raises it, with the object's current snapshot. public static PluginObjectChange Change(FakeHost host, uint objectId, PluginObjectChangeKind kind) => new(objectId, kind) { Current = host.Automation.Objects.TryGet(objectId, out PluginWorldObject current) ? current : null, }; public static void Raise(FakeHost host, uint objectId, PluginObjectChangeKind kind) => host.Events.RaiseObjectChanged(Change(host, objectId, kind)); /// Logs in and runs the ticks it takes the inventory to settle, without moving the clock. public static InventoryFeature LoginAndSettle(PluginHarness harness) { harness.Login(); InventoryFeature feature = harness.Feature(); feature.IsBackendReady = () => true; for (int i = 0; i < 40 && !feature.IsSettled; i++) harness.Tick(0.1); Assert.True(feature.IsSettled); return feature; } /// A stack of 57 Prismatic Tapers in the main pack, never appraised. public static PluginInventoryItem Tapers(int stack = 57) => new( 0x80000101u, 20631u, "Prismatic Taper", 0x1000u, Me, 0u, 0u, 0u, 1u, 0u, 18u, stack, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0) { ObjectClass = PluginObjectClass.SpellComponent, IconId = 0x06002D8Au, MaximumStackSize = 100, ContainerSlot = 0, Burden = stack, Value = stack * 10, PluralName = "Prismatic Tapers", }; /// A breastplate in the main pack, appraised, with two carried and two running spells. public static PluginInventoryItem Breastplate() => new( 0x80000303u, 6044u, "Celdon Breastplate", 0x2u, Me, 0u, 512u, 0u, 1u, 0u, 18u, 1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0) { ObjectClass = PluginObjectClass.Armor, IconId = 0x06001234u, ContainerSlot = 3, Burden = 800, Value = 5000, CoverageMask = 1024u, Effects = 1u, AppraisedSpellIds = [1486u, 2604u, 0x80000000u | 4407u, 0x80000000u | 1483u], }; public static PluginItemProperties BreastplateProps() => Props( ints: new() { [28] = 600, [265] = 5 }, bools: new() { [69] = true }, armor: new PluginArmorProfile(600, 1.2f, 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0f, 1.5f)); /// A wielded sword, before (no spells) or after appraisal. public static PluginInventoryItem Tachi(bool appraised) => new( 0x80000202u, 30310u, "Fire Tachi", 0x1u, 0u, Me, 1048576u, 1048576u, 6291460u, 16u, 0x04000012u, 1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0) { ObjectClass = PluginObjectClass.MeleeWeapon, IconId = 0x0600336Au, IconOverlayId = 0x06001B2Eu, IconUnderlayId = 0u, ContainerSlot = -1, CombatUse = 1, Burden = 270, Value = 12000, MaterialType = 64u, Workmanship = 8f, Effects = 1u, AppraisedSpellIds = appraised ? [2598u, 2603u, 0x80000000u | 5183u] : [], }; public static PluginItemProperties TachiProps() => Props( ints: new() { [105] = 8, [107] = 1500, [108] = 2000, [109] = 300, [158] = 2, [159] = 45, [160] = 370, [171] = 3, [179] = 1, [353] = 2, [370] = 2, }, floats: new() { [5] = -0.05, [29] = 1.15 }, strings: new() { [16] = "Fire Tachi of Blood Drinker" }, weapon: new PluginWeaponProfile(16, 30, 45u, 50, 0.5, 1.0, 1.5, 1.0, 1.2000000476837158, 0)); /// A ring the character wears, never appraised. public static PluginInventoryItem Ring() => new( 0x80000404u, 2575u, "Gold Ring", 0x8u, 0u, Me, 0xC0000u, 0x40000u, 1u, 0u, 18u, 1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0) { ObjectClass = PluginObjectClass.Jewelry, IconId = 0x06000F6Eu, ContainerSlot = -1, Burden = 30, Value = 400, }; public static PluginSpellInfo Spell(uint id, uint family, int difficulty, bool debuff = false) => new(id, $"Spell {id}", family, 1, difficulty, 10, -1f, 3u, string.Empty, false, true) { IsDebuff = debuff, }; } internal static class InventoryHarnessExtensions { /// The raw frames of one type, in the order they were sent. public static List FramesOfTypeRaw(this PluginHarness harness, string type) => harness.Frames.Where(frame => (string?)JObject.Parse(frame)["type"] == type).ToList(); }