using AcDream.Plugin.Abstractions; namespace AcDream.Plugins.MossTank.Tests; public sealed class MossTankPanelTests { [Fact] public void RetainedLabelReads_UseUpdateSideSnapshotsWithoutAllocating() { var automation = new FakeAutomation { CurrentHealth = 90, MaxHealth = 100, CurrentStamina = 80, MaxStamina = 110, CurrentMana = 70, MaxMana = 120, Skills = [ new PluginSkillInfo(1, "Life Magic", PluginSkillTraining.Trained, 300), new PluginSkillInfo(2, "War Magic", PluginSkillTraining.Specialized, 350), new PluginSkillInfo(3, "Run", PluginSkillTraining.Untrained, 100), ], Attributes = [ new PluginAttributeInfo(0, "Strength", 100), new PluginAttributeInfo(1, "Endurance", 100), ], KnownSelfBuffs = [ Spell(1, 10, "Increases the caster's Life Magic skill by 10 points."), ], }; var panel = new MossTankPanel(new FakeHost(automation)); panel.OnTick(0.0); Assert.Equal("Health 90/100 Stam 80/110 Mana 70/120", panel.Vitals); Assert.Equal("2 attributes, 2 trained skills, 1 buff lines", panel.Coverage); string expectedVitals = panel.Vitals; string expectedCoverage = panel.Coverage; _ = panel.Vitals; _ = panel.Coverage; long before = GC.GetAllocatedBytesForCurrentThread(); bool sameReferences = true; for (int i = 0; i < 10_000; i++) { sameReferences &= ReferenceEquals(expectedVitals, panel.Vitals); sameReferences &= ReferenceEquals(expectedCoverage, panel.Coverage); } long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.True(sameReferences); Assert.Equal(0, allocated); Assert.Equal(1, automation.KnownSelfBuffReads); } [Fact] public void UpdateTick_RefreshesRareCoverageAndChangedVitals() { var automation = new FakeAutomation { CurrentHealth = 90, MaxHealth = 100, CurrentStamina = 80, MaxStamina = 110, CurrentMana = 70, MaxMana = 120, Skills = [ new PluginSkillInfo(1, "Life Magic", PluginSkillTraining.Trained, 300), ], Attributes = [new PluginAttributeInfo(0, "Strength", 100)], KnownSelfBuffs = [ Spell(1, 10, "Increases the caster's Life Magic skill by 10 points."), ], }; var panel = new MossTankPanel(new FakeHost(automation)); panel.OnTick(0.0); automation.CurrentHealth = 75; automation.Skills = [ new PluginSkillInfo(1, "Life Magic", PluginSkillTraining.Trained, 300), new PluginSkillInfo(2, "War Magic", PluginSkillTraining.Specialized, 350), ]; panel.OnTick(0.5); Assert.StartsWith("Health 75/100", panel.Vitals, StringComparison.Ordinal); Assert.Equal("1 attributes, 1 trained skills, 1 buff lines", panel.Coverage); panel.OnTick(0.5); Assert.Equal("1 attributes, 2 trained skills, 1 buff lines", panel.Coverage); automation.KnownSelfBuffs = [ Spell(1, 10, "Increases the caster's Life Magic skill by 10 points."), Spell(2, 11, "Increases the caster's War Magic skill by 10 points."), ]; panel.OnTick(0.0); Assert.Equal("1 attributes, 2 trained skills, 2 buff lines", panel.Coverage); } private static PluginSpellInfo Spell(uint id, uint family, string description) => new( id, $"Spell {id}", family, Tier: 1, Difficulty: 10, ManaCost: 5, DurationSeconds: 60f, School: 1, description, IsSelfTargeted: true, IsBeneficial: true); private sealed class FakeHost(IAutomationSurface automation) : 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 IAutomationSurface Automation { get; } = automation; } private sealed class FakeAutomation : IAutomationSurface, ICharacterInfo, ISpellCatalog, IMagicCommands, IPluginChat { private IReadOnlyList _knownSelfBuffs = []; public bool IsAvailable { get; set; } = true; public ICharacterInfo Character => this; public ISpellCatalog Spells => this; public IMagicCommands Magic => this; public IPluginChat Chat => this; public bool IsInWorld => IsAvailable; public uint ObjectId { get; set; } = 1; 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 IReadOnlyList Skills { get; set; } = []; public IReadOnlyList Attributes { get; set; } = []; public IReadOnlyList ActiveEnchantments { get; set; } = []; public int KnownSelfBuffReads { get; private set; } public IReadOnlyList KnownSelfBuffs { get { KnownSelfBuffReads++; return _knownSelfBuffs; } set => _knownSelfBuffs = value; } 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 TryGet(uint spellId, out PluginSpellInfo info) { foreach (PluginSpellInfo candidate in _knownSelfBuffs) { if (candidate.SpellId == spellId) { info = candidate; return true; } } info = default; return false; } public bool IsCasting => false; public PluginCastGate EvaluateGate(uint spellId) => PluginCastGate.Ready; public bool Cast(uint spellId) => true; public void PostSystemMessage(string text) { } } 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 Entities => []; } private sealed class FakeEvents : IEvents { public event Action EntitySpawned { add { } remove { } } public event Action Tick { add { } remove { } } } private sealed class FakeSelection : ISelectionService { public uint? SelectedObjectId { get; private set; } public uint? PreviousObjectId { get; private set; } public event Action Changed { add { } remove { } } public bool Select(uint objectId) { PreviousObjectId = SelectedObjectId; SelectedObjectId = objectId; return true; } public bool Clear() { PreviousObjectId = SelectedObjectId; SelectedObjectId = null; return true; } } }