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();
}