using AcDream.Plugin.Abstractions;
namespace OpenAC.MosswartMassacre.Tests.Fakes;
///
/// A settable automation surface. Every child defaults to an empty fake, so a
/// test fills in only the state it exercises.
///
public sealed class FakeAutomation : IAutomationSurface
{
public bool IsAvailable { get; set; }
public FakeCharacter Character { get; } = new();
public RecordingChat Chat { get; } = new();
public FakeSpellCatalog Spells { get; } = new();
public FakeMagic Magic { get; } = new();
public FakeItems Items { get; } = new();
public FakeLoot Loot { get; } = new();
public FakeObjects Objects { get; } = new();
public FakeNavigation Navigation { get; } = new();
public FakeFellowship Fellowship { get; } = new();
public FakeCombat Combat { get; } = new();
public FakeEquipment Equipment { get; } = new();
public FakeDialogAutomation Dialogs { get; } = new();
public FakeTrade Trade { get; } = new();
public FakeVendor Vendor { get; } = new();
public FakeLogin Login { get; } = new();
public FakeNetwork Network { get; } = new();
public FakeDungeonMap DungeonMap { get; } = new();
ICharacterInfo IAutomationSurface.Character => Character;
ISpellCatalog IAutomationSurface.Spells => Spells;
IMagicCommands IAutomationSurface.Magic => Magic;
IPluginChat IAutomationSurface.Chat => Chat;
IItemAutomation IAutomationSurface.Items => Items;
ILootAutomation IAutomationSurface.Loot => Loot;
IWorldObjectAutomation IAutomationSurface.Objects => Objects;
INavigationAutomation IAutomationSurface.Navigation => Navigation;
IFellowshipAutomation IAutomationSurface.Fellowship => Fellowship;
ICombatAutomation IAutomationSurface.Combat => Combat;
IEquipmentAutomation IAutomationSurface.Equipment => Equipment;
IDialogAutomation IAutomationSurface.Dialogs => Dialogs;
ITradeAutomation IAutomationSurface.Trade => Trade;
IVendorAutomation IAutomationSurface.Vendor => Vendor;
ILoginAutomation IAutomationSurface.Login => Login;
INetworkAutomation IAutomationSurface.Network => Network;
IDungeonMapAutomation IAutomationSurface.DungeonMap => DungeonMap;
}
/// A settable : a test drives and inspects .
public sealed class FakeTrade : ITradeAutomation
{
public bool IsAvailable { get; set; } = true;
public bool IsOpen { get; set; }
public uint PartnerObjectId { get; set; }
public string PartnerName { get; set; } = string.Empty;
public List MyItems { get; } = [];
public List PartnerItems { get; } = [];
public bool MyAccepted { get; set; }
public bool PartnerAccepted { get; set; }
public List Calls { get; } = [];
/// The status //// answer with.
public PluginTradeCommandStatus NextStatus { get; set; } = PluginTradeCommandStatus.Sent;
IReadOnlyList ITradeAutomation.MyItems => MyItems;
IReadOnlyList ITradeAutomation.PartnerItems => PartnerItems;
public event Action? Opened;
public event Action? Closed;
public event Action? PartnerTradeAccepted;
public event Action? ItemAdded;
public void RaiseOpened(uint initiatorObjectId, uint partnerObjectId)
{
IsOpen = true;
PartnerObjectId = partnerObjectId;
Opened?.Invoke(new PluginTradeOpened(initiatorObjectId, partnerObjectId));
}
public void RaiseClosed()
{
IsOpen = false;
Closed?.Invoke();
}
public void RaisePartnerAccepted(uint partnerObjectId)
=> PartnerTradeAccepted?.Invoke(partnerObjectId);
public void RaiseItemAdded(uint itemObjectId, bool mine)
=> ItemAdded?.Invoke(new PluginTradeItemAdded(itemObjectId, mine));
public PluginTradeCommandResult Add(uint itemObjectId)
{
Calls.Add("add:" + itemObjectId);
return new PluginTradeCommandResult(NextStatus);
}
public PluginTradeCommandResult Accept()
{
Calls.Add("accept");
return new PluginTradeCommandResult(NextStatus);
}
public PluginTradeCommandResult Decline()
{
Calls.Add("decline");
return new PluginTradeCommandResult(NextStatus);
}
public PluginTradeCommandResult Reset()
{
Calls.Add("reset");
return new PluginTradeCommandResult(NextStatus);
}
public PluginTradeCommandResult End()
{
Calls.Add("end");
return new PluginTradeCommandResult(NextStatus);
}
event Action ITradeAutomation.Opened
{
add => Opened += value;
remove => Opened -= value;
}
event Action ITradeAutomation.Closed
{
add => Closed += value;
remove => Closed -= value;
}
event Action ITradeAutomation.PartnerTradeAccepted
{
add => PartnerTradeAccepted += value;
remove => PartnerTradeAccepted -= value;
}
event Action ITradeAutomation.ItemAdded
{
add => ItemAdded += value;
remove => ItemAdded -= value;
}
}
/// A settable .
public sealed class FakeVendor : IVendorAutomation
{
public bool IsAvailable { get; set; } = true;
public bool IsOpen { get; set; }
public uint VendorObjectId { get; set; }
public string VendorName { get; set; } = string.Empty;
public List Items { get; } = [];
public bool IsBusy { get; set; }
public Dictionary Properties { get; } = [];
public List<(uint TemplateObjectId, int Count)> BuyList { get; } = [];
public List SellList { get; } = [];
public List Calls { get; } = [];
public PluginVendorCommandStatus NextStatus { get; set; } = PluginVendorCommandStatus.Sent;
IReadOnlyList IVendorAutomation.Items => Items;
IReadOnlyList<(uint TemplateObjectId, int Count)> IVendorAutomation.BuyList => BuyList;
IReadOnlyList IVendorAutomation.SellList => SellList;
public event Action? Opened;
public event Action? Closed;
public event Action? TransactionCompleted;
public void RaiseOpened(uint vendorObjectId)
{
IsOpen = true;
VendorObjectId = vendorObjectId;
Opened?.Invoke(vendorObjectId);
}
public void RaiseClosed()
{
IsOpen = false;
Closed?.Invoke();
}
public void RaiseTransactionCompleted(PluginVendorTransactionKind kind, bool success = true, string? notice = null)
=> TransactionCompleted?.Invoke(new PluginVendorTransaction(kind, success, notice));
public bool TryCaptureProperties(uint templateObjectId, out PluginItemProperties properties)
=> Properties.TryGetValue(templateObjectId, out properties);
public PluginVendorCommandResult AddToBuyList(uint templateObjectId, int count)
{
Calls.Add("addbuy:" + templateObjectId + ":" + count);
BuyList.Add((templateObjectId, count));
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult AddToSellList(uint itemObjectId)
{
Calls.Add("addsell:" + itemObjectId);
SellList.Add(itemObjectId);
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult RemoveFromBuyList(uint templateObjectId)
{
Calls.Add("removebuy:" + templateObjectId);
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult RemoveFromSellList(uint itemObjectId)
{
Calls.Add("removesell:" + itemObjectId);
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult ClearBuyList()
{
Calls.Add("clearbuy");
BuyList.Clear();
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult ClearSellList()
{
Calls.Add("clearsell");
SellList.Clear();
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult BuyAll()
{
Calls.Add("buyall");
return new PluginVendorCommandResult(NextStatus);
}
public PluginVendorCommandResult SellAll()
{
Calls.Add("sellall");
return new PluginVendorCommandResult(NextStatus);
}
event Action IVendorAutomation.Opened
{
add => Opened += value;
remove => Opened -= value;
}
event Action IVendorAutomation.Closed
{
add => Closed += value;
remove => Closed -= value;
}
event Action IVendorAutomation.TransactionCompleted
{
add => TransactionCompleted += value;
remove => TransactionCompleted -= value;
}
}
/// A settable .
public sealed class FakeLogin : ILoginAutomation
{
public bool IsAvailable { get; set; } = true;
public bool LogoutResult { get; set; } = true;
public int LogoutCalls { get; private set; }
public bool Logout()
{
LogoutCalls++;
return LogoutResult;
}
}
/// Records every call; a test can pre-arm which context ids it accepts.
public sealed class FakeDialogAutomation : IDialogAutomation
{
public List<(uint ContextId, bool Accept)> Answers { get; } = [];
/// Context ids reports as outstanding (returns true for).
public HashSet OutstandingContextIds { get; } = [];
public bool Answer(uint contextId, bool accept)
{
Answers.Add((contextId, accept));
return OutstandingContextIds.Contains(contextId);
}
}
public sealed class FakeCharacter : ICharacterInfo
{
public bool IsInWorld { get; set; }
public string Name { get; set; } = string.Empty;
public string WorldName { get; set; } = string.Empty;
public int ServerPopulation { get; set; } = -1;
public string AccountName { get; set; } = string.Empty;
public int MainPackFreeSlots { get; set; }
public uint ObjectId { get; set; }
public uint CurrentHealth { get; set; }
public uint MaxHealth { get; set; }
public uint CurrentStamina { get; set; }
public uint MaxStamina { get; set; }
public uint CurrentMana { get; set; }
public uint MaxMana { get; set; }
public List Skills { get; } = [];
public IReadOnlyList Attributes { get; set; } = [];
public IReadOnlyList ActiveEnchantments { get; set; } = [];
IReadOnlyList ICharacterInfo.Skills => Skills;
public bool TryGetSkill(uint skillId, out PluginSkillInfo skill)
{
foreach (PluginSkillInfo candidate in Skills)
{
if (candidate.SkillId != skillId)
continue;
skill = candidate;
return true;
}
skill = default;
return false;
}
}
///
/// Records every posted line and every submitted command, and can raise
/// and drive registered filters the way the real host
/// does — before a line is added to or delivered to
/// , every registered filter gets a chance to eat it.
///
public sealed class RecordingChat : IPluginChat
{
private readonly List> _filters = [];
private ulong _nextSequence = 1;
public List Posted { get; } = [];
public List Submitted { get; } = [];
public List<(string Text, int LogTextType)> PostedTyped { get; } = [];
/// Every message offered to , filtered or not.
public List Offered { get; } = [];
/// Messages that survived every filter and reached .
public List Delivered { get; } = [];
public IReadOnlyList Messages { get; set; } = [];
public IReadOnlyList CaptureMessages(ulong afterSequence)
=> [.. Messages.Where(message => message.Sequence > afterSequence)];
public event Action? Received;
///
/// How many distinct handlers are currently subscribed to
/// — used to prove a shared fan-out
/// (ChatClassificationDispatcher) adds exactly one subscription
/// to the raw feed no matter how many of ITS OWN consumers subscribe to
/// it in turn.
///
public int ReceivedSubscriberCount => Received?.GetInvocationList().Length ?? 0;
public IDisposable RegisterFilter(Func suppress)
{
_filters.Add(suppress);
return new FilterRegistration(this, suppress);
}
/// Builds and offers a message the way the real client would, honoring filters.
public PluginChatMessage Deliver(
string text,
int kind = 0,
string sender = "",
string channelName = "",
int logTextType = 0,
int combatKind = 0,
uint senderObjectId = 0u)
{
var message = new PluginChatMessage(
_nextSequence++, senderObjectId, kind, sender, text, channelName)
{
LogTextType = logTextType,
CombatKind = combatKind,
Received = DateTimeOffset.UtcNow,
};
Offered.Add(message);
foreach (Func filter in _filters)
{
if (filter(message))
return message;
}
Delivered.Add(message);
Received?.Invoke(message);
return message;
}
public void PostSystemMessage(string text) => Posted.Add(text);
public void PostMessage(string text, int logTextType)
{
Posted.Add(text);
PostedTyped.Add((text, logTextType));
}
public bool Submit(string text)
{
Submitted.Add(text);
return true;
}
private sealed class FilterRegistration(
RecordingChat owner,
Func filter) : IDisposable
{
public void Dispose() => owner._filters.Remove(filter);
}
}
public sealed class FakeSpellCatalog : ISpellCatalog
{
public IReadOnlyList KnownSelfBuffs { get; set; } = [];
public IReadOnlyList KnownAttackSpells { get; set; } = [];
public IReadOnlyList KnownCombatSpells { get; set; } = [];
/// The full spell table, independent of what the character knows.
public IReadOnlyList All { get; set; } = [];
public bool TryGet(uint spellId, out PluginSpellInfo info)
{
foreach (PluginSpellInfo spell in KnownSelfBuffs
.Concat(KnownAttackSpells).Concat(KnownCombatSpells).Concat(All))
{
if (spell.SpellId != spellId)
continue;
info = spell;
return true;
}
info = default;
return false;
}
public bool TryFindByName(string name, bool partialMatch, out PluginSpellInfo spell)
{
spell = default;
if (string.IsNullOrWhiteSpace(name))
return false;
foreach (PluginSpellInfo candidate in All)
{
if (string.Equals(candidate.Name, name, StringComparison.OrdinalIgnoreCase))
{
spell = candidate;
return true;
}
}
if (!partialMatch)
return false;
foreach (PluginSpellInfo candidate in All)
{
if (candidate.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
{
spell = candidate;
return true;
}
}
return false;
}
/// Builds a minimal spell record for a test.
public static PluginSpellInfo Spell(uint spellId, string name)
=> new(spellId, name, 0u, 1, 0, 0, 0f, 0u, string.Empty, true, true);
}
public sealed class FakeMagic : IMagicCommands
{
public bool IsCasting { get; set; }
public PluginCastCompletion LastCompletion { get; set; }
public List<(uint SpellId, uint TargetObjectId)> Casts { get; } = [];
/// What / answer -- a test sets this to simulate a refusal that isn't a plain Cast()==false.
public PluginCastRequestResult RequestResult { get; set; } = PluginCastRequestResult.Sent;
public PluginCastGate EvaluateGate(uint spellId) => PluginCastGate.Ready;
public PluginCastGate EvaluateGate(uint spellId, uint targetObjectId)
=> PluginCastGate.Ready;
public bool Cast(uint spellId)
{
Casts.Add((spellId, 0u));
return RequestResult == PluginCastRequestResult.Sent;
}
public bool Cast(uint spellId, uint targetObjectId)
{
Casts.Add((spellId, targetObjectId));
return RequestResult == PluginCastRequestResult.Sent;
}
public PluginCastRequestResult RequestCast(uint spellId)
{
Casts.Add((spellId, 0u));
return RequestResult;
}
public PluginCastRequestResult RequestCast(uint spellId, uint targetObjectId)
{
Casts.Add((spellId, targetObjectId));
return RequestResult;
}
}
public sealed class FakeItems : IItemAutomation
{
public bool IsAvailable { get; set; } = true;
public bool IsBusy { get; set; }
/// What returns -- a test sets this to simulate the host rejecting the command.
public PluginItemCommandResult UseResult { get; set; } =
new(PluginItemCommandStatus.Started);
public List Owned { get; } = [];
public List<(string Command, uint ObjectId, uint TargetObjectId)> Calls { get; } = [];
/// Keyed by object id; set by a test to make answer.
public Dictionary Properties { get; } = [];
///
/// How many times has been called — used
/// to prove a coalescing host (H1/H3) captures at most once per tick no
/// matter how many ObjectChanged events arrived inside it.
///
public int CaptureCount { get; private set; }
/// Same counter as under the name the HUD-gate tests use.
public int CaptureOwnedItemsCalls => CaptureCount;
public IReadOnlyList CaptureOwnedItems()
{
CaptureCount++;
// LOW-C (P10 re-review): the real host's CaptureOwnedItems
// (AppAutomationSurface.cs:2573's `built`) allocates a fresh list
// every call rather than exposing a live view -- return a snapshot
// here too, so a caller that stores the result (InventoryLogger's
// _lastOwnedSnapshot) is not silently aliased to this mutable list
// and later corrupted by an unrelated Owned.Add/Clear.
return Owned.ToList();
}
public bool TryCaptureProperties(uint objectId, out PluginItemProperties properties)
=> Properties.TryGetValue(objectId, out properties);
public PluginItemCommandResult Use(uint objectId)
{
Calls.Add(("use", objectId, 0u));
return UseResult;
}
/// What returns -- a test sets this to simulate the host rejecting the command.
public PluginItemCommandResult ApplyResult { get; set; } =
new(PluginItemCommandStatus.Started);
public PluginItemCommandResult Apply(uint objectId, uint targetObjectId)
{
Calls.Add(("apply", objectId, targetObjectId));
return ApplyResult;
}
public PluginItemCommandResult Drop(uint objectId, uint amount = 0u)
{
Calls.Add(("drop", objectId, 0u));
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public PluginItemCommandResult Give(
uint objectId,
uint targetObjectId,
uint amount = 0u)
{
Calls.Add(("give", objectId, targetObjectId));
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
/// Every call, in order.
public List<(uint ObjectId, uint ContainerObjectId, uint Amount, int Placement)> Moves { get; } = [];
/// Every call, in order.
public List<(uint SourceObjectId, uint TargetObjectId, uint Amount)> Merges { get; } = [];
public PluginItemCommandResult MoveToContainer(
uint objectId,
uint containerObjectId,
uint amount = 0u,
int placement = 0)
{
Calls.Add(("move", objectId, containerObjectId));
Moves.Add((objectId, containerObjectId, amount, placement));
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public PluginItemCommandResult Merge(
uint sourceObjectId,
uint targetObjectId,
uint amount = 0u)
{
Calls.Add(("merge", sourceObjectId, targetObjectId));
Merges.Add((sourceObjectId, targetObjectId, amount));
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
/// Builds a minimal owned item for a test.
public static PluginInventoryItem Item(
uint objectId,
string name,
uint equippedLocation = 0u)
=> new(
objectId, 0u, name, 0u, 0u, 0u, 0u, equippedLocation, 0u, 0u, 0u,
1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0);
}
public sealed class FakeLoot : ILootAutomation
{
public bool IsAvailable { get; set; } = true;
public bool IsBusy { get; set; }
///
/// The shared appraisal slot's state (MEDIUM-2, P14 review) -- a test
/// sets AwaitingObjectId to a DIFFERENT nonzero id than what it
/// asked
/// for to simulate that request having been displaced by a later one.
///
public PluginAppraisalState Appraisal { get; set; }
public List Contents { get; } = [];
public List PickedUp { get; } = [];
public List Opened { get; } = [];
public List IdentifyRequests { get; } = [];
public Dictionary Properties { get; } = [];
public IReadOnlyList CaptureCurrentContents() => Contents;
public bool TryCaptureProperties(uint objectId, out PluginItemProperties properties)
=> Properties.TryGetValue(objectId, out properties);
public PluginItemCommandResult Open(uint containerObjectId)
{
Opened.Add(containerObjectId);
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public PluginItemCommandResult Identify(uint objectId)
{
IdentifyRequests.Add(objectId);
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public PluginItemCommandResult Pickup(uint objectId, bool mainPack = false)
{
PickedUp.Add(objectId);
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
}
public sealed class FakeObjects : IWorldObjectAutomation
{
public bool IsAvailable { get; set; } = true;
public uint OpenContainerObjectId { get; set; }
public List Objects { get; } = [];
public Dictionary Properties { get; } = [];
public List IdentifyRequests { get; } = [];
/// What returns by default for any object id not covered by .
public PluginItemCommandStatus IdentifyResult { get; set; } = PluginItemCommandStatus.Started;
/// Per-object-id override for 's returned status, checked before .
public Dictionary IdentifyResultOverrides { get; } = [];
///
/// Per-object-id remaining count of consecutive Busy responses
/// should return before falling through to
/// / --
/// simulates the host's single-in-flight-request gate
/// (InventoryTransactionState.CanBeginRequest) staying busy for a
/// bounded number of retries. Checked before, and decremented ahead of,
/// .
///
public Dictionary IdentifyBusyForCalls { get; } = [];
///
/// How many times has been called — used to
/// prove a headless host's HUD updater (a UI-only presenter) never runs
/// its 1 Hz full-landscape scan when is
/// false.
///
public int CaptureObjectsCalls { get; private set; }
public IReadOnlyList CaptureObjects()
{
CaptureObjectsCalls++;
return Objects;
}
public bool TryGet(uint objectId, out PluginWorldObject value)
{
foreach (PluginWorldObject candidate in Objects)
{
if (candidate.ObjectId != objectId)
continue;
value = candidate;
return true;
}
value = default;
return false;
}
public bool TryCaptureProperties(uint objectId, out PluginItemProperties properties)
=> Properties.TryGetValue(objectId, out properties);
public PluginItemCommandResult Identify(uint objectId)
{
IdentifyRequests.Add(objectId);
if (IdentifyBusyForCalls.TryGetValue(objectId, out int remaining) && remaining > 0)
{
IdentifyBusyForCalls[objectId] = remaining - 1;
return new PluginItemCommandResult(PluginItemCommandStatus.Busy);
}
PluginItemCommandStatus status = IdentifyResultOverrides.TryGetValue(objectId, out PluginItemCommandStatus overridden)
? overridden
: IdentifyResult;
return new PluginItemCommandResult(status);
}
/// Replaces a tracked object's snapshot (e.g. to flip HasAppraisalData).
public void Replace(PluginWorldObject value)
{
for (int index = 0; index < Objects.Count; index++)
{
if (Objects[index].ObjectId != value.ObjectId)
continue;
Objects[index] = value;
return;
}
Objects.Add(value);
}
///
/// Builds a landscape object at east of the
/// origin the fake navigation reports.
///
public static PluginWorldObject Landscape(
uint objectId,
string name,
PluginObjectClass objectClass,
double metres)
=> new(objectId, 0u, name, objectClass, 0u, 0u, 0u)
{
IsLandscape = true,
HasPosition = true,
Position = new PluginNavigationPosition(
1u, metres / 240d, 0d, 0d, 0f, true),
};
}
public sealed class FakeNavigation : INavigationAutomation
{
public PluginNavigationSnapshot Snapshot { get; set; } =
new(true, false, 1u, default, false, false);
public List Headings { get; } = [];
/// Backs for tests that need a nearby landscape object (e.g. a chest).
public List Objects { get; } = [];
public IReadOnlyList CaptureObjects() => Objects;
public PluginNavigationCommandStatus FaceHeading(float headingDegrees)
{
Headings.Add(headingDegrees);
return PluginNavigationCommandStatus.Accepted;
}
public bool TryGetObject(uint objectId, out PluginNavigationObject value)
{
value = default;
return false;
}
public PluginNavigationCommandStatus SetMovementIntent(
in PluginMovementIntent intent)
=> PluginNavigationCommandStatus.Accepted;
public PluginNavigationCommandStatus ClearMovementIntent()
=> PluginNavigationCommandStatus.Accepted;
}
public sealed class FakeFellowship : IFellowshipAutomation
{
public List Calls { get; } = [];
public bool IsInFellowship { get; set; }
/// What every command below answers -- a test sets this to simulate a server refusal.
public PluginFellowshipCommandResult Result { get; set; } =
new(PluginFellowshipCommandStatus.Accepted);
public PluginFellowshipCommandResult Create(string name, bool shareExperience)
{
Calls.Add("create:" + name + ":" + shareExperience);
return Result;
}
public PluginFellowshipCommandResult SetOpen(bool isOpen)
{
Calls.Add("setopen:" + isOpen);
return Result;
}
public PluginFellowshipCommandResult Quit(bool disband)
{
Calls.Add("quit:" + disband);
return Result;
}
public PluginFellowshipCommandResult Recruit(uint targetObjectId)
{
Calls.Add("recruit:" + targetObjectId);
return Result;
}
}
public sealed class FakeCombat : ICombatAutomation
{
public PluginCombatSnapshot Snapshot { get; set; } =
new(0u, PluginCombatMode.Peace, PluginAttackHeight.Medium, 1f, 0f,
false, false, false, false);
public List Calls { get; } = [];
/// Every BeginPhysicalAttack call, with the height/power it used.
public List<(uint TargetObjectId, PluginAttackHeight Height, float Power)>
BeginAttacks { get; } = [];
public PluginCombatCommandResult EnterDefaultMode()
{
Calls.Add("mode:default");
return new PluginCombatCommandResult(
PluginCombatCommandStatus.ModeChangeSent);
}
public PluginCombatCommandResult EnterMode(PluginCombatMode mode)
{
Calls.Add("mode:" + mode);
return new PluginCombatCommandResult(
PluginCombatCommandStatus.ModeChangeSent);
}
public PluginCombatCommandResult BeginPhysicalAttack(
uint targetObjectId,
PluginAttackHeight height,
float power)
{
Calls.Add("attack:" + targetObjectId);
BeginAttacks.Add((targetObjectId, height, power));
return new PluginCombatCommandResult(PluginCombatCommandStatus.Started);
}
public IReadOnlyList CaptureHostileTargets(
float maximumDistance) => [];
public PluginCombatCommandResult ReleasePhysicalAttack()
{
Calls.Add("release");
return new PluginCombatCommandResult(PluginCombatCommandStatus.Released);
}
public PluginCombatCommandResult AbortPhysicalAttack()
{
Calls.Add("abort");
return new PluginCombatCommandResult(PluginCombatCommandStatus.Stopped);
}
}
public sealed class FakeEquipment : IEquipmentAutomation
{
public bool IsAvailable { get; set; } = true;
public List Equipped { get; } = [];
public PluginEquipmentCommandResult Equip(
uint objectId,
uint requestedLocation = 0u)
{
Equipped.Add(objectId);
return new PluginEquipmentCommandResult(
PluginEquipmentCommandStatus.Started);
}
}