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

669 lines
27 KiB
C#

using AcDream.Plugin.Abstractions;
using AcDream.Plugins.MossTank.Expressions;
namespace AcDream.Plugins.MossTank.Tests;
public sealed class HostExpressionFunctionsTests
{
[Fact]
public void CharacterAndNavigationFunctionsReadCanonicalAutomationFacts()
{
var automation = CreateAutomation();
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.Equal("Coldeve", runtime.Evaluate("getworldname[]").AsString());
Assert.Equal(2d, runtime.Evaluate("getcharacterindex[]").AsNumber());
Assert.Equal(90d, runtime.Evaluate("getcharvital_current[1]").AsNumber());
Assert.Equal(100d, runtime.Evaluate("getcharvital_buffedmax[1]").AsNumber());
Assert.Equal(100d, runtime.Evaluate("getcharattribute_base[1]").AsNumber());
Assert.Equal(110d, runtime.Evaluate("getcharattribute_buffed[1]").AsNumber());
Assert.Equal(275d, runtime.Evaluate("getcharskill_base[34]").AsNumber());
Assert.Equal(300d, runtime.Evaluate("getcharskill_buffed[34]").AsNumber());
Assert.Equal(2d, runtime.Evaluate("getcharskill_traininglevel[34]").AsNumber());
Assert.Equal(0x7F7Fu, runtime.Evaluate("getplayerlandblock[]").AsNumber());
Assert.Equal(90d, runtime.Evaluate("getheading[wobjectgetplayer[]]").AsNumber());
}
[Fact]
public void ObjectDiscoveryCountsPropertiesAndNearestMatchUtilityBeltShape()
{
var automation = CreateAutomation();
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.Equal(2d, runtime.Evaluate(
"listcount[wobjectfindallinventory[]]").AsNumber());
Assert.Equal(10d, runtime.Evaluate(
"getitemcountininventorybyname['Health Elixir']").AsNumber());
Assert.Equal(9d, runtime.Evaluate("getfreeitemslots[]").AsNumber());
Assert.Equal(1d, runtime.Evaluate("getfreecontainerslots[]").AsNumber());
Assert.Equal("Drudge", runtime.Evaluate(
"wobjectgetname[wobjectfindnearestmonster[]]").AsString());
Assert.Equal(77d, runtime.Evaluate(
"wobjectgetintprop[wobjectfindbyid[20],25]").AsNumber());
Assert.Equal(2d, runtime.Evaluate(
"listcount[wobjectfindallbynamerx['(?i)elixir|drudge']]").AsNumber());
Assert.Equal((double)PluginObjectClass.Monster, runtime.Evaluate(
"wobjectgetobjectclass[wobjectfindbyid[20]]").AsNumber());
Assert.Equal(54321d, runtime.Evaluate(
"wobjectlastidtime[wobjectfindbyid[20]]").AsNumber());
}
[Fact]
public void ActionFunctionsUseSharedSelectionInventoryMagicAndMovementCommands()
{
var automation = CreateAutomation();
var host = new Host(automation);
using var runtime = new MossTankExpressionRuntime(host);
Assert.True(runtime.Evaluate("actiontryselect[20]").IsTruthy);
Assert.Equal(20u, host.Selection.SelectedObjectId);
Assert.True(runtime.Evaluate("actiontryuseitem[10]").IsTruthy);
Assert.Equal(10u, automation.UsedObject);
Assert.Equal(1d, runtime.Evaluate("actiontrycastbyidontarget[1001,20]").AsNumber());
Assert.Equal((1001u, 20u), automation.LastCast);
Assert.True(runtime.Evaluate("setmotion['Forward',1]").IsTruthy);
Assert.True(automation.LastIntent.Forward);
Assert.True(runtime.Evaluate("clearmotion[]").IsTruthy);
Assert.Equal(1, automation.ClearMovementCount);
}
[Fact]
public void LoginFunctionsUseTheAuthoritativeSortedRosterAndOneShotOwner()
{
var automation = CreateAutomation();
automation.LoginRoster =
[
new PluginLoginCharacter(10u, "Beta", 2, false),
new PluginLoginCharacter(1u, "Expression Tester", 0, false),
new PluginLoginCharacter(30u, "Mule", 1, false),
];
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.Equal(1d, runtime.Evaluate("getcharacterindex[]").AsNumber());
Assert.Equal(0d, runtime.Evaluate(
"getcharacterindex['bet']").AsNumber());
Assert.True(runtime.Evaluate("setnextlogin[1]").IsTruthy);
Assert.Equal(30u, automation.NextLoginObjectId);
Assert.True(runtime.Evaluate("setnextlogin['bet']").IsTruthy);
Assert.Equal(10u, automation.NextLoginObjectId);
Assert.True(runtime.Evaluate("clearnextlogin[]").IsTruthy);
Assert.Equal(0u, automation.NextLoginObjectId);
}
[Fact]
public void NetworkClientsReturnUtilityBeltDictionariesAndTagFiltering()
{
var automation = CreateAutomation();
automation.NetworkClients =
[
new PluginNetworkClient(
7u,
70u,
"Remote Mule",
"Coldeve",
automation.Position,
["mules", "trade"],
90u,
70u,
80u,
100u,
100u,
100u,
90f),
];
using var runtime = new MossTankExpressionRuntime(new Host(automation));
ExpressionList clients = runtime.Evaluate("netclients['mules']").AsList();
ExpressionDictionary client = Assert.Single(clients.Items).AsDictionary();
Assert.Equal("Remote Mule", client.Items["Name"].AsString());
Assert.Equal(70d, client.Items["PlayerId"].AsNumber());
Assert.Equal(2, client.Items["Tags"].AsList().Items.Count);
Assert.Empty(runtime.Evaluate("netclients['combat']").AsList().Items);
}
[Fact]
public void DelayedExecutionUsesTickAndCanBeCancelled()
{
using var runtime = new MossTankExpressionRuntime(
new Host(CreateAutomation()));
double id = runtime.Evaluate(
"delayexec[100,\"setvar['done',1]\"]").AsNumber();
runtime.OnTick(0.099);
Assert.Equal(0d, runtime.Evaluate("getvar['done']").AsNumber());
runtime.OnTick(0.001);
Assert.Equal(1d, runtime.Evaluate("getvar['done']").AsNumber());
double cancelled = runtime.Evaluate(
"delayexec[1,\"setvar['bad',1]\"]").AsNumber();
Assert.True(runtime.Evaluate($"clearexec[{cancelled}]").IsTruthy);
runtime.OnTick(1d);
Assert.Equal(0d, runtime.Evaluate("getvar['bad']").AsNumber());
Assert.NotEqual(id, cancelled);
}
[Fact]
public void PersistentAndGlobalVariablesRoundTripThroughPluginStorage()
{
var storage = new MemoryStorage();
var automation = CreateAutomation();
using (var first = new MossTankExpressionRuntime(new Host(automation, storage)))
{
first.Evaluate("setpvar['count',42];setgvar['names',listcreate['a','b']]");
}
using var second = new MossTankExpressionRuntime(
new Host(CreateAutomation(), storage));
Assert.Equal(42d, second.Evaluate("getpvar['count']").AsNumber());
Assert.Equal(2d, second.Evaluate("listcount[getgvar['names']]").AsNumber());
Assert.Contains(
storage.Text.Keys,
static key => key.StartsWith("expressions/persistent/", StringComparison.Ordinal));
Assert.Contains(
storage.Text.Keys,
static key => key.StartsWith("expressions/global/", StringComparison.Ordinal));
}
[Fact]
public void FellowshipFunctionsExposeTheAuthoritativeCompleteRoster()
{
var automation = CreateAutomation();
automation.FellowRoster =
[
new PluginFellowMember(1, "Expression Tester", 90, 100, 80, 100, 70, 100, 0),
new PluginFellowMember(22, "Fellow Two", 50, 60, 40, 60, 30, 60, 1),
];
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.True(runtime.Evaluate("getfellowshipstatus[]").IsTruthy);
Assert.Equal("Test Fellowship", runtime.Evaluate("getfellowshipname[]").AsString());
Assert.Equal(2d, runtime.Evaluate("getfellowshipcount[]").AsNumber());
Assert.True(runtime.Evaluate("getfellowshipisleader[]").IsTruthy);
Assert.True(runtime.Evaluate("getfellowshipcanrecruit[]").IsTruthy);
Assert.Equal("Fellow Two", runtime.Evaluate("getfellowname[1]").AsString());
Assert.Equal(22d, runtime.Evaluate("getfellowid[1]").AsNumber());
Assert.Equal(2d, runtime.Evaluate("listcount[getfellowids[]]").AsNumber());
}
[Fact]
public void DerethTimeFunctionsUseTheRuntimeWorldClockProjection()
{
var automation = CreateAutomation();
automation.Time = new PluginWorldTimeSnapshot(
true,
123456d,
142,
6,
17,
10,
"HarvestGain",
"Warmtide",
true,
0d,
12.5d);
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.Equal(142d, runtime.Evaluate("getgameyear[]").AsNumber());
Assert.Equal(6d, runtime.Evaluate("getgamemonth[]").AsNumber());
Assert.Equal("HarvestGain", runtime.Evaluate("getgamemonthname[6]").AsString());
Assert.Equal(17d, runtime.Evaluate("getgameday[]").AsNumber());
Assert.Equal("Warmtide", runtime.Evaluate("getgamehourname[10]").AsString());
Assert.Equal(123456d, runtime.Evaluate("getgameticks[]").AsNumber());
Assert.True(runtime.Evaluate("getisday[]").IsTruthy);
Assert.Equal(12.5d, runtime.Evaluate("getminutesuntilnight[]").AsNumber());
}
[Fact]
public void ExperienceMeterAccumulatesCanonicalXpAndLuminanceDeltas()
{
var automation = CreateAutomation();
automation.Properties[1] = Properties(
ints: new Dictionary<uint, int> { [5] = 50, [96] = 100 },
int64s: new Dictionary<uint, long> { [1] = 1000, [6] = 10 });
using var runtime = new MossTankExpressionRuntime(new Host(automation));
runtime.OnTick(1d);
automation.Properties[1] = Properties(
ints: new Dictionary<uint, int> { [5] = 50, [96] = 100 },
int64s: new Dictionary<uint, long> { [1] = 1100, [6] = 15 });
runtime.OnTick(1d);
Assert.Equal(100d, runtime.Evaluate("xptotal[]").AsNumber());
Assert.Equal(5d, runtime.Evaluate("lumtotal[]").AsNumber());
Assert.Equal(2d, runtime.Evaluate("xpduration[]").AsNumber());
Assert.Equal(180000d, runtime.Evaluate("xpavg[]").AsNumber());
Assert.Contains("100 XP", runtime.Evaluate("xpmeter[]").AsString());
Assert.True(runtime.Evaluate("xpreset[]").IsTruthy);
Assert.Equal(0d, runtime.Evaluate("xptotal[]").AsNumber());
}
[Fact]
public void QuestFunctionsParseTheAuthoritativeMyquestsTranscript()
{
var automation = CreateAutomation();
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.Equal(["/myquests"], automation.SubmittedChat);
Assert.True(runtime.Evaluate("isrefreshingquests[]").IsTruthy);
automation.ChatMessages.Add(new PluginChatMessage(
1, 0, 0, string.Empty,
"killtaskdrudges - 3 solves (0)\"Drudges killed\" 10 0",
string.Empty));
automation.ChatMessages.Add(new PluginChatMessage(
2, 0, 0, string.Empty,
"onevisit - 1 solves (1700000000)\"Visited once\" 1 0",
string.Empty));
runtime.OnTick(0d);
Assert.True(runtime.Evaluate("testquestflag['killtaskdrudges']").IsTruthy);
Assert.Equal(3d, runtime.Evaluate(
"getquestktprogress['killtaskdrudges']").AsNumber());
Assert.Equal(10d, runtime.Evaluate(
"getquestktrequired['killtaskdrudges']").AsNumber());
Assert.False(runtime.Evaluate("getqueststatus['onevisit']").IsTruthy);
Assert.True(runtime.Evaluate("getqueststatus['unknown']").IsTruthy);
runtime.OnTick(1.001d);
Assert.False(runtime.Evaluate("isrefreshingquests[]").IsTruthy);
}
[Fact]
public void CorpseFunctionsUseTheCanonicalExternalContainerHistory()
{
var automation = CreateAutomation();
automation.Corpses =
[
new PluginLootContainer(30, 300, "Corpse", 2f, false, false, false),
new PluginLootContainer(31, 301, "Corpse", 3f, true, false, false),
];
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.False(runtime.Evaluate("hascorpsebeenopenedbyme[30]").IsTruthy);
Assert.True(runtime.Evaluate("hascorpsebeenopenedbyme[31]").IsTruthy);
Assert.Equal(30d, runtime.Evaluate(
"listgetitem[getcorpsesunopenedbyme[],0]").AsNumber());
}
[Fact]
public void ComponentFunctionsUseTheRetailDatCatalogProjection()
{
var automation = CreateAutomation();
automation.Components[7] = new PluginSpellComponentInfo(
7, 101, "Malar Herb", 0.25, 0x13000001, 0.75,
0x06000010, 4, "Herb", "Malar");
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.Equal("Malar Herb", runtime.Evaluate("componentname[7]").AsString());
Assert.Equal(0.25d, runtime.Evaluate(
"dictgetitem[componentdata[7],'BurnRate']").AsNumber());
Assert.Equal("Malar", runtime.Evaluate(
"dictgetitem[componentdata[7],'Word']").AsString());
}
[Fact]
public void UstFunctionsStageAndSubmitThroughTheCanonicalSalvageCommand()
{
var automation = CreateAutomation();
automation.InventoryItems =
[
InventoryItem(40, "Ust"),
InventoryItem(41, "Salvage One"),
InventoryItem(42, "Salvage Two"),
];
using var runtime = new MossTankExpressionRuntime(new Host(automation));
Assert.True(runtime.Evaluate("ustopen[]").IsTruthy);
Assert.Equal(40u, automation.UsedObject);
Assert.True(runtime.Evaluate("ustadd[41]").IsTruthy);
Assert.True(runtime.Evaluate("ustadd[42]").IsTruthy);
Assert.True(runtime.Evaluate("ustsalvage[]").IsTruthy);
Assert.Equal(40u, automation.LastSalvage.Tool);
Assert.Equal([41u, 42u], automation.LastSalvage.Items);
Assert.False(runtime.Evaluate("ustsalvage[]").IsTruthy);
}
private static Automation CreateAutomation()
{
var automation = new Automation
{
Position = new PluginNavigationPosition(
0x7F7F0001u, 10d, 20d, 3d, 90f, true),
Attributes =
[
new PluginAttributeInfo(0, "Strength", 110) { Base = 100 },
],
Skills =
[
new PluginSkillInfo(34, "War Magic", PluginSkillTraining.Trained, 300)
{
Base = 275,
},
],
};
automation.WorldObjects.Add(new PluginWorldObject(
1, 1, "Expression Tester", PluginObjectClass.Player, 0x10, 0, 0)
{
HasPosition = true,
Position = automation.Position,
ItemsCapacity = 10,
ContainersCapacity = 2,
});
automation.WorldObjects.Add(new PluginWorldObject(
10, 100, "Health Elixir", PluginObjectClass.Food, 0x20, 1, 0)
{
IsOwned = true,
StackSize = 10,
});
automation.WorldObjects.Add(new PluginWorldObject(
11, 101, "Small Pack", PluginObjectClass.Container, 0x200, 1, 0)
{
IsOwned = true,
ItemsCapacity = 8,
});
automation.WorldObjects.Add(new PluginWorldObject(
20, 200, "Drudge", PluginObjectClass.Monster, 0x10, 0, 0)
{
IsLandscape = true,
HasPosition = true,
Position = automation.Position with { EastWest = 10.1d },
HasAppraisalData = true,
LastIdTime = 54321,
});
automation.Properties[1] = Properties(
ints: new Dictionary<uint, int> { [5] = 50, [96] = 100 },
strings: new Dictionary<uint, string> { [1] = "Expression Tester" });
automation.Properties[20] = Properties(
ints: new Dictionary<uint, int> { [25] = 77 });
return automation;
}
private static PluginItemProperties Properties(
IReadOnlyDictionary<uint, int>? ints = null,
IReadOnlyDictionary<uint, long>? int64s = null,
IReadOnlyDictionary<uint, string>? strings = null) => new(
ints ?? new Dictionary<uint, int>(),
int64s ?? new Dictionary<uint, long>(),
new Dictionary<uint, bool>(),
new Dictionary<uint, double>(),
strings ?? new Dictionary<uint, string>(),
new Dictionary<uint, uint>(),
new Dictionary<uint, uint>());
private static PluginInventoryItem InventoryItem(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 Host(
Automation automation,
IPluginStorage? storage = null) : IPluginHost
{
public bool HasUi => false;
public IPluginLogger Log { get; } = new Logger();
public IGameState State { get; } = new State();
public IEvents Events { get; } = new Events();
public Selection Selection { get; } = new();
ISelectionService IPluginHost.Selection => Selection;
public IUiRegistry Ui => NoOpUiRegistry.Instance;
public IPluginStorage Storage { get; } = storage ?? NoOpPluginStorage.Instance;
public IAutomationSurface Automation { get; } = automation;
}
private sealed class Automation :
IAutomationSurface,
ICharacterInfo,
ISpellCatalog,
IMagicCommands,
IPluginChat,
IWorldObjectAutomation,
IItemAutomation,
INavigationAutomation,
IFellowshipAutomation,
IWorldTimeAutomation,
ILootAutomation,
ILoginAutomation,
INetworkAutomation
{
public bool IsAvailable => true;
public ICharacterInfo Character => this;
public ISpellCatalog Spells => this;
public IMagicCommands Magic => this;
public IPluginChat Chat => this;
public IWorldObjectAutomation Objects => this;
public IItemAutomation Items => this;
public INavigationAutomation Navigation => this;
public IFellowshipAutomation Fellowship => this;
public ILootAutomation Loot => this;
public IWorldTimeAutomation WorldTime => this;
public ILoginAutomation Login => this;
public INetworkAutomation Network => this;
public bool IsInWorld => true;
public string Name => "Expression Tester";
public string WorldName => "Coldeve";
public string AccountName => "testaccount";
public int CharacterIndex => 2;
public uint ObjectId => 1;
public uint CurrentHealth => 90;
public uint MaxHealth => 100;
public uint CurrentStamina => 80;
public uint MaxStamina => 100;
public uint CurrentMana => 70;
public uint MaxMana => 100;
public IReadOnlyList<PluginSkillInfo> Skills { get; set; } = [];
public IReadOnlyList<PluginAttributeInfo> Attributes { get; set; } = [];
public IReadOnlyList<PluginActiveEnchantment> ActiveEnchantments => [];
public IReadOnlyList<PluginSpellInfo> KnownSelfBuffs => [];
public IReadOnlyList<PluginSpellInfo> KnownAttackSpells => [];
public IReadOnlyList<PluginSpellInfo> KnownCombatSpells => [];
public List<PluginWorldObject> WorldObjects { get; } = [];
public Dictionary<uint, PluginItemProperties> Properties { get; } = [];
public Dictionary<uint, PluginSpellComponentInfo> Components { get; } = [];
public PluginNavigationPosition Position { get; set; }
public uint UsedObject { get; private set; }
public (uint SpellId, uint TargetId) LastCast { get; private set; }
public PluginMovementIntent LastIntent { get; private set; }
public int ClearMovementCount { get; private set; }
public List<PluginChatMessage> ChatMessages { get; } = [];
public List<string> SubmittedChat { get; } = [];
public IReadOnlyList<PluginInventoryItem> InventoryItems { get; set; } = [];
public (uint Tool, IReadOnlyList<uint> Items) LastSalvage { get; private set; }
public IReadOnlyList<PluginFellowMember> FellowRoster { get; set; } = [];
public IReadOnlyList<PluginLoginCharacter> LoginRoster { get; set; } = [];
public uint NextLoginObjectId { get; private set; }
public IReadOnlyList<PluginNetworkClient> NetworkClients { get; set; } = [];
public IReadOnlyList<PluginLootContainer> Corpses { get; set; } = [];
public IReadOnlyList<PluginLootContainer> CaptureCorpses(float maximumDistance) =>
Corpses.Where(corpse => corpse.Distance <= maximumDistance).ToArray();
public bool IsInFellowship => FellowRoster.Count != 0;
string IFellowshipAutomation.Name => "Test Fellowship";
public uint LeaderObjectId => 1;
public bool IsOpen => true;
public bool IsLocked => false;
public int MemberCount => FellowRoster.Count;
IReadOnlyList<PluginFellowMember> IFellowshipAutomation.CaptureRoster() =>
FellowRoster;
bool ILoginAutomation.IsAvailable => true;
IReadOnlyList<PluginLoginCharacter> ILoginAutomation.CaptureRoster() =>
LoginRoster;
public bool SetNextLogin(uint characterObjectId)
{
if (!LoginRoster.Any(character =>
character.ObjectId == characterObjectId
&& !character.IsPendingDelete))
{
return false;
}
NextLoginObjectId = characterObjectId;
return true;
}
public bool ClearNextLogin()
{
NextLoginObjectId = 0u;
return true;
}
bool INetworkAutomation.IsAvailable => true;
IReadOnlyList<PluginNetworkClient> INetworkAutomation.CaptureClients() =>
NetworkClients;
public PluginWorldTimeSnapshot Time { get; set; }
PluginWorldTimeSnapshot IWorldTimeAutomation.Snapshot => Time;
public bool TryGetSkill(uint skillId, out PluginSkillInfo skill)
{
foreach (PluginSkillInfo candidate in Skills)
{
if (candidate.SkillId == skillId)
{
skill = candidate;
return true;
}
}
skill = default;
return false;
}
public bool IsKnown(uint spellId) => spellId == 1001;
public bool TryGet(uint spellId, out PluginSpellInfo info)
{
info = default;
return false;
}
public bool TryGetComponent(uint componentId, out PluginSpellComponentInfo info) =>
Components.TryGetValue(componentId, out info);
public bool IsCasting => false;
public PluginCastGate EvaluateGate(uint spellId) =>
spellId == 1001 ? PluginCastGate.Ready : PluginCastGate.NotKnown;
public PluginCastGate EvaluateGate(uint spellId, uint targetObjectId) =>
EvaluateGate(spellId);
public bool Cast(uint spellId) => Cast(spellId, 0);
public bool Cast(uint spellId, uint targetObjectId)
{
LastCast = (spellId, targetObjectId);
return true;
}
public IReadOnlyList<PluginWorldObject> CaptureObjects() => WorldObjects;
public bool TryGet(uint objectId, out PluginWorldObject value)
{
foreach (PluginWorldObject candidate in WorldObjects)
{
if (candidate.ObjectId == objectId)
{
value = candidate;
return true;
}
}
value = default;
return false;
}
public bool TryCaptureProperties(uint objectId, out PluginItemProperties value) =>
Properties.TryGetValue(objectId, out value);
public PluginItemCommandResult Use(uint objectId)
{
UsedObject = objectId;
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public IReadOnlyList<PluginInventoryItem> CaptureOwnedItems() => InventoryItems;
public PluginItemCommandResult Salvage(
uint toolObjectId,
IReadOnlyList<uint> itemObjectIds)
{
LastSalvage = (toolObjectId, itemObjectIds.ToArray());
return new PluginItemCommandResult(PluginItemCommandStatus.Started);
}
public PluginNavigationSnapshot Snapshot => new(
true,
false,
ObjectId,
Position,
false,
false);
public bool TryGetObject(uint objectId, out PluginNavigationObject value)
{
if (TryGet(objectId, out PluginWorldObject obj) && obj.HasPosition)
{
value = new PluginNavigationObject(obj.ObjectId, obj.Name, obj.Position);
return true;
}
value = default;
return false;
}
public PluginNavigationCommandStatus SetMovementIntent(
in PluginMovementIntent intent)
{
LastIntent = intent;
return PluginNavigationCommandStatus.Accepted;
}
public PluginNavigationCommandStatus ClearMovementIntent()
{
ClearMovementCount++;
return PluginNavigationCommandStatus.Accepted;
}
public void PostSystemMessage(string text) { }
public IReadOnlyList<PluginChatMessage> CaptureMessages(ulong afterSequence) =>
ChatMessages.Where(message => message.Sequence > afterSequence).ToArray();
public bool Submit(string text)
{
SubmittedChat.Add(text);
return true;
}
}
private sealed class Selection : ISelectionService
{
public uint? SelectedObjectId { get; private set; }
public uint? PreviousObjectId { get; private set; }
public event Action<SelectionChangedEvent> Changed
{
add { }
remove { }
}
public bool Select(uint objectId)
{
PreviousObjectId = SelectedObjectId;
SelectedObjectId = objectId;
return true;
}
public bool Clear()
{
PreviousObjectId = SelectedObjectId;
SelectedObjectId = null;
return true;
}
}
private sealed class MemoryStorage : IPluginStorage
{
public Dictionary<string, string> Text { get; } = [];
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 Logger : IPluginLogger
{
public void Info(string message) { }
public void Warn(string message) { }
public void Error(string message, Exception? exception = null) { }
}
private sealed class State : IGameState
{
public IReadOnlyList<WorldEntitySnapshot> Entities => [];
}
private sealed class Events : IEvents
{
public event Action<WorldEntitySnapshot> EntitySpawned
{
add { }
remove { }
}
public event Action<double> Tick
{
add { }
remove { }
}
}
}