using Vanguard.Content; using Vanguard.Rules.Cards; using Vanguard.Rules.Decks; using Vanguard.Server.Configuration; namespace Vanguard.Server.Tests; internal static class ServerTestData { private static readonly Lazy LazyCatalog = new( () => CatalogLoader.LoadDirectory(ContentPaths.Resolve(null)), isThreadSafe: true); private static readonly Lazy LazyTrialDecks = new( () => TrialDeckCatalog.Load( Path.Combine(Path.GetDirectoryName(ContentPaths.Resolve(null))!, "trial-decks.yaml")), isThreadSafe: true); public static CardCatalog Catalog => LazyCatalog.Value; public static TrialDeckCatalog TrialDecks => LazyTrialDecks.Value; /// Колода без Sentinel и Heal, ровно 50 карт, до 4 копий на nameId. public static List BuildDeck() { var deck = new List(); var counts = new Dictionary(StringComparer.Ordinal); void Add(string printingId, int limit) { var card = Catalog.CardOf(printingId); counts.TryGetValue(card.NameId, out var used); var room = Math.Min(Math.Min(4 - used, limit), 50 - deck.Count); for (var i = 0; i < room; i++) deck.Add(printingId); counts[card.NameId] = used + room; } // Сначала ровно 16 триггеров, Heal — не больше четырёх. var triggers = 0; var heals = 0; foreach (var group in Catalog.Printings .Where(p => !p.IsParallel) .GroupBy(p => p.NameId) .OrderBy(g => g.Key, StringComparer.Ordinal)) { if (triggers >= DeckValidator.RequiredTriggers) break; var card = Catalog.CardOf(group.First().PrintingId); if (card.IsSentinel || card.Trigger == TriggerType.None) continue; if (card.Trigger == TriggerType.Heal && heals >= DeckValidator.MaxHeal) continue; var before = deck.Count; Add(group.First().PrintingId, Math.Min( DeckValidator.RequiredTriggers - triggers, card.Trigger == TriggerType.Heal ? DeckValidator.MaxHeal - heals : DeckValidator.RequiredTriggers)); var added = deck.Count - before; triggers += added; if (card.Trigger == TriggerType.Heal) heals += added; } // Остальное — обычные воины. foreach (var group in Catalog.Printings .Where(p => !p.IsParallel) .GroupBy(p => p.NameId) .OrderBy(g => g.Key, StringComparer.Ordinal)) { if (deck.Count >= 50) break; var card = Catalog.CardOf(group.First().PrintingId); if (card.IsSentinel || card.Trigger != TriggerType.None) continue; Add(group.First().PrintingId, 4); } Assert.Equal(50, deck.Count); return deck; } public static GameOptions SmallOptions() => new() { TurnSeconds = 90, BankSeconds = 300, AbsenceSeconds = 300, ClockSyncSeconds = 5, TicketTtlSeconds = 60, ConnectTimeoutSeconds = 5, CompletedMatchTtlSeconds = 600, QueueExpirySeconds = 600, MmrInitialWindow = 100, MmrWindowStep = 50, MmrWindowIntervalSeconds = 5, MmrMaxWindow = 2000, }; } internal sealed class FakeTimeProvider : TimeProvider { private DateTimeOffset _now; public FakeTimeProvider(DateTimeOffset now) => _now = now; public override DateTimeOffset GetUtcNow() => _now; public void Advance(TimeSpan delta) => _now += delta; } internal sealed class FakeMatchConnection : Matches.IMatchConnection { private readonly object _gate = new(); private readonly List _frames = new(); public string Id { get; } = Guid.NewGuid().ToString("N"); public bool IsClosed { get; private set; } public int CloseCode { get; private set; } public IReadOnlyList Frames { get { lock (_gate) return _frames.ToList(); } } public Task SendAsync( Vanguard.Shared.Protocol.ServerFrame frame, CancellationToken cancellationToken = default) { lock (_gate) _frames.Add(frame); return Task.CompletedTask; } public Task CloseAsync(int code, string reason) { IsClosed = true; CloseCode = code; return Task.CompletedTask; } public T Last() where T : Vanguard.Shared.Protocol.ServerFrame { lock (_gate) return _frames.OfType().Last(); } public IReadOnlyList All() where T : Vanguard.Shared.Protocol.ServerFrame { lock (_gate) return _frames.OfType().ToList(); } } internal sealed class RecordingResultSink : Matches.IMatchResultSink { private readonly List _results = new(); public IReadOnlyList Results { get { lock (_results) return _results.ToList(); } } public Task RecordAsync(Matches.MatchResult result) { lock (_results) _results.Add(result); return Task.CompletedTask; } }