acdream/tests/AcDream.Plugins.MossTank.Tests/ProfileGiveControllerTests.cs

230 lines
8.3 KiB
C#

using AcDream.Plugin.Abstractions;
namespace AcDream.Plugins.MossTank.Tests;
public sealed class ProfileGiveControllerTests
{
[Fact]
public void NamedProfileGivesOnlyKeepMatchesAndWaitsForCompletion()
{
var automation = new FakeAutomation
{
ObjectsValue =
[
new PluginWorldObject(
100u, 0u, "Mule", PluginObjectClass.Player,
0u, 0u, 0u),
],
ItemsValue =
[
Item(10u, "Trade Pyreal"),
Item(11u, "Personal Note"),
],
};
var storage = new MemoryStorage();
var host = new FakeHost(automation, storage);
var profiles = new MossTankLootProfileStore(host);
profiles.BindCharacter(automation.Name);
Assert.True(profiles.Create("Mule Items", false, [], out _));
profiles.SaveCurrent(
[
new LootRule
{
Expression = "name ~= trade",
Action = LootAction.Keep,
},
new LootRule
{
Expression = "*",
Action = LootAction.NoLoot,
},
]);
var controller = new ProfileGiveController(host, profiles);
Assert.True(controller.TryStart("Mule Items.utl", "Mule"));
Assert.True(controller.Tick(0.1d, canAct: true));
Assert.Equal([(10u, 100u, 0u)], automation.Gives);
// A second tick cannot submit another command until the server's
// inventory completion advances for the exact source object.
Assert.True(controller.Tick(0.1d, canAct: true));
Assert.Single(automation.Gives);
automation.Completion = new PluginInventoryCompletion(
1,
PluginInventoryCommandKind.Give,
10u,
0u);
Assert.True(controller.Tick(0.1d, canAct: true));
Assert.False(controller.Tick(0.1d, canAct: true));
Assert.False(controller.IsRunning);
Assert.Contains("1 item(s)", controller.Status, StringComparison.Ordinal);
}
[Fact]
public void StartRejectsBusyMissingProfileAndMissingTarget()
{
var automation = new FakeAutomation
{
ObjectsValue =
[
new PluginWorldObject(
100u, 0u, "Mule", PluginObjectClass.Npc,
0u, 0u, 0u),
],
};
var storage = new MemoryStorage();
var host = new FakeHost(automation, storage);
var profiles = new MossTankLootProfileStore(host);
profiles.BindCharacter(automation.Name);
var controller = new ProfileGiveController(host, profiles);
Assert.False(controller.TryStart("Missing", "Mule"));
Assert.True(profiles.Create("Empty", false, [], out _));
Assert.False(controller.TryStart("Empty", "Missing"));
Assert.True(controller.TryStart("Empty", "Mule"));
Assert.False(controller.TryStart("Empty", "Mule"));
}
private static PluginInventoryItem Item(uint id, string name) => new(
id, 0u, name, 0u, 1u, 0u, 0u, 0u, 0u, 0u, 0u,
1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0);
private sealed class FakeAutomation
: IAutomationSurface, ICharacterInfo, IItemAutomation,
IWorldObjectAutomation
{
public bool IsAvailable { get; set; } = true;
public ICharacterInfo Character => this;
public ISpellCatalog Spells => NoOpAutomationSurface.Instance;
public IMagicCommands Magic => NoOpAutomationSurface.Instance;
public IPluginChat Chat => NoOpAutomationSurface.Instance;
public IItemAutomation Items => this;
public IWorldObjectAutomation Objects => this;
public bool IsInWorld => IsAvailable;
public string Name => "Tester";
public uint ObjectId => 1u;
public uint CurrentHealth => 0u;
public uint MaxHealth => 0u;
public uint CurrentStamina => 0u;
public uint MaxStamina => 0u;
public uint CurrentMana => 0u;
public uint MaxMana => 0u;
public IReadOnlyList<PluginSkillInfo> Skills => [];
public IReadOnlyList<PluginAttributeInfo> Attributes => [];
public IReadOnlyList<PluginActiveEnchantment> ActiveEnchantments => [];
public IReadOnlyList<PluginInventoryItem> ItemsValue { get; set; } = [];
public IReadOnlyList<PluginWorldObject> ObjectsValue { get; set; } = [];
public PluginInventoryCompletion Completion { get; set; }
public List<(uint Item, uint Target, uint Amount)> Gives { get; } = [];
bool IItemAutomation.IsAvailable => true;
bool IItemAutomation.IsBusy => false;
bool IWorldObjectAutomation.IsAvailable => true;
public PluginInventoryCompletion LastInventoryCompletion => Completion;
public IReadOnlyList<PluginInventoryItem> CaptureOwnedItems() => ItemsValue;
public IReadOnlyList<PluginWorldObject> CaptureObjects() => ObjectsValue;
public bool TryGet(uint objectId, out PluginWorldObject value)
{
foreach (PluginWorldObject item in ObjectsValue)
{
if (item.ObjectId == objectId)
{
value = item;
return true;
}
}
value = default;
return false;
}
public bool TryCaptureProperties(
uint objectId,
out PluginItemProperties properties)
{
properties = new PluginItemProperties(
new Dictionary<uint, int>(),
new Dictionary<uint, long>(),
new Dictionary<uint, bool>(),
new Dictionary<uint, double>(),
new Dictionary<uint, string>(),
new Dictionary<uint, uint>(),
new Dictionary<uint, uint>());
return true;
}
public PluginItemCommandResult Give(
uint objectId,
uint targetObjectId,
uint amount = 0u)
{
Gives.Add((objectId, targetObjectId, amount));
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public bool TryGetSkill(uint skillId, out PluginSkillInfo skill)
{
skill = default;
return false;
}
}
private sealed class FakeHost(
FakeAutomation automation,
IPluginStorage storage) : IPluginHost
{
public bool HasUi => false;
public IPluginLogger Log { get; } = new FakeLogger();
public IGameState State { get; } = new FakeState();
public IEvents Events { get; } = new FakeEvents();
public ISelectionService Selection { get; } = new FakeSelection();
public IUiRegistry Ui => NoOpUiRegistry.Instance;
public IPluginStorage Storage => storage;
public IAutomationSurface Automation => automation;
}
private sealed class MemoryStorage : IPluginStorage
{
private readonly Dictionary<string, string> _text =
new(StringComparer.Ordinal);
public bool IsAvailable => true;
public string? ReadText(string key) =>
_text.TryGetValue(key, out string? value) ? value : null;
public void WriteText(string key, string content) => _text[key] = content;
public bool Delete(string key) => _text.Remove(key);
}
private sealed class FakeLogger : IPluginLogger
{
public void Info(string message) { }
public void Warn(string message) { }
public void Error(string message, Exception? exception = null) { }
}
private sealed class FakeState : IGameState
{
public IReadOnlyList<WorldEntitySnapshot> Entities => [];
}
private sealed class FakeEvents : IEvents
{
public event Action<WorldEntitySnapshot> EntitySpawned
{
add { }
remove { }
}
public event Action<double> Tick
{
add { }
remove { }
}
}
private sealed class FakeSelection : ISelectionService
{
public uint? SelectedObjectId => null;
public uint? PreviousObjectId => null;
public event Action<SelectionChangedEvent> Changed
{
add { }
remove { }
}
public bool Select(uint objectId) => false;
public bool Clear() => false;
}
}