using AcDream.Plugin.Abstractions; namespace AcDream.Plugins.MossTank.Tests; public sealed class FellowshipManagerTests { [Fact] public void XpTellQueuesAndRecruitsANearbyPlayerThroughTheHostCommand() { var automation = new FakeAutomation(); automation.AddTell(2u, "Alice", "xp"); var manager = new FellowshipManager(new FakeHost(automation)); manager.Tick(0.1d, enabled: true); Assert.Equal(2u, automation.RecruitedObjectId); Assert.Contains(automation.Submitted, value => value.Contains( "I will recruit you in a moment", StringComparison.Ordinal)); Assert.Contains("Alice", manager.WaitingNames); automation.Roster.Add(Member(2u, "Alice")); manager.Tick(0.1d, enabled: true); Assert.DoesNotContain("Alice", manager.WaitingNames); } [Fact] public void MemberVoteExecutesGiveLeaderAfterTheOfficialTwoMinuteWindow() { var automation = new FakeAutomation(); automation.Roster.Add(Member(2u, "Alice")); automation.Roster.Add(Member(3u, "Bob")); var manager = new FellowshipManager(new FakeHost(automation)); automation.AddTell(2u, "Alice", "startvote giveleader Bob"); manager.Tick(0.1d, enabled: true); automation.AddTell(3u, "Bob", "vote 1 yes"); manager.Tick(0.1d, enabled: true); manager.Tick(120d, enabled: true); Assert.Equal(3u, automation.AssignedLeaderObjectId); Assert.Contains(automation.Submitted, value => value.Contains( "passed (2/0)", StringComparison.Ordinal)); } private static PluginFellowMember Member(uint id, string name) => new( id, name, 100u, 100u, 100u, 100u, 100u, 100u, 0f); private sealed class FakeHost(FakeAutomation automation) : IPluginHost { public bool HasUi => false; public IPluginLogger Log { get; } = new StubLogger(); public IGameState State { get; } = new StubState(); public IEvents Events { get; } = new StubEvents(); public ISelectionService Selection { get; } = new StubSelection(); public IUiRegistry Ui => NoOpUiRegistry.Instance; public IAutomationSurface Automation { get; } = automation; } private sealed class StubLogger : IPluginLogger { public void Info(string message) { } public void Warn(string message) { } public void Error(string message, Exception? exception = null) { } } private sealed class StubState : IGameState { public IReadOnlyList Entities => []; } private sealed class StubEvents : IEvents { public event Action EntitySpawned { add { } remove { } } public event Action Tick { add { } remove { } } } private sealed class StubSelection : ISelectionService { public uint? SelectedObjectId => null; public uint? PreviousObjectId => null; public event Action Changed { add { } remove { } } public bool Select(uint objectId) => false; public bool Clear() => false; } private sealed class FakeAutomation : IAutomationSurface, ICharacterInfo, IPluginChat, IFellowshipAutomation, INavigationAutomation { private ulong _sequence; private readonly List _messages = []; public bool IsAvailable => true; public ICharacterInfo Character => this; public ISpellCatalog Spells => NoOpAutomationSurface.Instance; public IMagicCommands Magic => NoOpAutomationSurface.Instance; public IPluginChat Chat => this; public IFellowshipAutomation Fellowship => this; public INavigationAutomation Navigation => this; public bool IsInWorld => true; public string Name => "Leader"; public uint ObjectId => 1u; public uint CurrentHealth => 100u; public uint MaxHealth => 100u; public uint CurrentStamina => 100u; public uint MaxStamina => 100u; public uint CurrentMana => 100u; public uint MaxMana => 100u; public IReadOnlyList Skills => []; public IReadOnlyList Attributes => []; public IReadOnlyList ActiveEnchantments => []; public bool TryGetSkill(uint skillId, out PluginSkillInfo skill) { skill = default; return false; } public bool IsInFellowship => true; string IFellowshipAutomation.Name => "Test Fellow"; public uint LeaderObjectId => 1u; public bool IsOpen { get; private set; } = true; public bool IsLocked => false; public int MemberCount => Roster.Count; public List Roster { get; } = [Member(1u, "Leader")]; public IReadOnlyList CaptureMembers() => Roster.Where(static member => member.ObjectId != 1u).ToArray(); public IReadOnlyList CaptureRoster() => Roster.ToArray(); public uint RecruitedObjectId { get; private set; } public uint DismissedObjectId { get; private set; } public uint AssignedLeaderObjectId { get; private set; } public PluginFellowshipCommandResult Recruit(uint targetObjectId) { RecruitedObjectId = targetObjectId; return new(PluginFellowshipCommandStatus.Accepted); } public PluginFellowshipCommandResult Dismiss(uint targetObjectId) { DismissedObjectId = targetObjectId; return new(PluginFellowshipCommandStatus.Accepted); } public PluginFellowshipCommandResult AssignLeader(uint targetObjectId) { AssignedLeaderObjectId = targetObjectId; return new(PluginFellowshipCommandStatus.Accepted); } public PluginFellowshipCommandResult SetOpen(bool isOpen) { IsOpen = isOpen; return new(PluginFellowshipCommandStatus.Accepted); } public PluginNavigationSnapshot Snapshot => new( true, false, 1u, Position(0d), false, false); public bool TryGetObject(uint objectId, out PluginNavigationObject value) { value = objectId == 2u ? new PluginNavigationObject(2u, "Alice", Position(0.02d)) : default; return value.ObjectId != 0u; } public PluginNavigationCommandStatus SetMovementIntent( in PluginMovementIntent intent) => PluginNavigationCommandStatus.Accepted; public PluginNavigationCommandStatus ClearMovementIntent() => PluginNavigationCommandStatus.Accepted; public List Submitted { get; } = []; public IReadOnlyList CaptureMessages(ulong afterSequence) => _messages.Where(value => value.Sequence > afterSequence).ToArray(); public void PostSystemMessage(string text) { } public bool Submit(string text) { Submitted.Add(text); return true; } public void AddTell(uint senderId, string sender, string text) => _messages.Add(new PluginChatMessage( ++_sequence, senderId, 3, sender, text, string.Empty)); private static PluginNavigationPosition Position(double eastWest) => new( 0x00010001u, eastWest, 0d, 0d, 0f, true); } }