#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; namespace ReplacedPerson.Core { public sealed class ReplacedContentCatalog { public Dictionary Cards { get; } = new(StringComparer.Ordinal); public Dictionary EndCards { get; } = new(StringComparer.Ordinal); public Dictionary Enemies { get; } = new(StringComparer.Ordinal); public Dictionary EffectResolvers { get; } = new(StringComparer.Ordinal); public ReplacedContentCatalog Clone() { var clone = new ReplacedContentCatalog(); foreach (var pair in Cards) clone.Cards.Add(pair.Key, Clone(pair.Value)); foreach (var pair in EndCards) clone.EndCards.Add(pair.Key, Clone(pair.Value)); foreach (var pair in Enemies) clone.Enemies.Add(pair.Key, Clone(pair.Value)); foreach (var pair in EffectResolvers) clone.EffectResolvers.Add(pair.Key, pair.Value); return clone; } public string ComputeHash() { var text = new StringBuilder(); foreach (var card in Cards.Values.OrderBy(value => value.Id, StringComparer.Ordinal)) text.Append("C|").Append(card.Id).Append('|').Append(card.Name).Append('|').Append((int)card.EffectType) .Append('|').Append(card.Cost).Append('|').Append(card.BaseValue).Append('|').Append(card.Factor) .Append('|').Append(card.SpecialEffectId).Append('\n'); foreach (var card in EndCards.Values.OrderBy(value => value.Id, StringComparer.Ordinal)) text.Append("E|").Append(card.Id).Append('|').Append(card.Name).Append('|').Append(card.Cost).Append('|') .Append(string.Join(",", card.SlotBase)).Append('|').Append(string.Join(",", card.SlotFactor)).Append('\n'); foreach (var enemy in Enemies.Values.OrderBy(value => value.Id, StringComparer.Ordinal)) text.Append("N|").Append(enemy.Id).Append('|').Append(enemy.Name).Append('|').Append(enemy.MaxHealth).Append('|') .Append(string.Join(",", enemy.NormalDeck)).Append('|').Append(string.Join(",", enemy.EndDeck)).Append('\n'); foreach (var effect in EffectResolvers.OrderBy(value => value.Key, StringComparer.Ordinal)) text.Append("R|").Append(effect.Key).Append('|').Append(effect.Value.GetType().FullName).Append('\n'); using var sha = SHA256.Create(); var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToString())); var hash = new StringBuilder(bytes.Length * 2); foreach (var value in bytes) hash.Append(value.ToString("x2")); return hash.ToString(); } private static ReplacedCardDefinition Clone(ReplacedCardDefinition value) => new() { Id = value.Id, Name = value.Name, Description = value.Description, EffectType = value.EffectType, Cost = value.Cost, BaseValue = value.BaseValue, Factor = value.Factor, SpecialEffectId = value.SpecialEffectId }; private static ReplacedEndCardDefinition Clone(ReplacedEndCardDefinition value) => new() { Id = value.Id, Name = value.Name, Description = value.Description, Cost = value.Cost, SlotBase = (int[])value.SlotBase.Clone(), SlotFactor = (int[])value.SlotFactor.Clone() }; private static ReplacedEnemyDefinition Clone(ReplacedEnemyDefinition value) => new() { Id = value.Id, Name = value.Name, MaxHealth = value.MaxHealth, NormalDeck = new List(value.NormalDeck), EndDeck = new List(value.EndDeck), RewardIds = new List(value.RewardIds) }; } public static class ReplacedBuiltInContent { public static ReplacedContentCatalog Create() { var result = new ReplacedContentCatalog(); AddCard(result, "attack.kill", "杀", ReplacedEffectType.Attack, 2, 2, 2, "直截了当的低费进攻。"); AddCard(result, "attack.elbow", "肘击", ReplacedEffectType.Attack, 3, 3, 2, "稳定进攻,适合试探对位。"); AddCard(result, "attack.break", "破局", ReplacedEffectType.Attack, 4, 3, 3, "高波动进攻。"); AddCard(result, "attack.iron", "铁山靠", ReplacedEffectType.Attack, 5, 5, 2, "高始数压制。"); AddCard(result, "attack.burst", "寸劲", ReplacedEffectType.Attack, 6, 4, 3, "高上限连击。"); AddCard(result, "attack.palm", "黄经极世掌", ReplacedEffectType.Attack, 7, 5, 3, "终盘重击。"); AddCard(result, "dodge.flash", "闪", ReplacedEffectType.Dodge, 2, 4, 2, "闪避成功后获得额外抽牌。"); AddCard(result, "dodge.phase", "虚化", ReplacedEffectType.Dodge, 3, 5, 3, "与反击合成可获得防御。"); AddCard(result, "counter.stance", "架势", ReplacedEffectType.Counter, 1, 2, 2, "低费反击,用来试探攻击并为高费反制保留 COST。"); AddCard(result, "counter.blood", "以血蒙眼", ReplacedEffectType.Counter, 3, 5, 2, "反制进攻并返还伤害。"); AddCard(result, "counter.instant", "一瞬千击", ReplacedEffectType.Counter, 5, 7, 3, "高费反击。"); AddCard(result, "greed.snob", "势利眼", ReplacedEffectType.Dodge, 3, 2, 2, "贪婪的闪避牌。", "greed.draw"); AddCard(result, "greed.turn-profit", "扭亏为盈", ReplacedEffectType.Counter, 4, 3, 3, "反击成功时恢复 COST。", "greed.refund"); AddCard(result, "greed.wage-cut", "降薪", ReplacedEffectType.Attack, 4, 2, 2, "命中后削减对方 COST。", "greed.wage-cut"); AddEnd(result, "end.inherit", "继承", 0, new[] { 0, 0, 0 }, new[] { 0, 0, 0 }, "将左侧普通牌效果继承到三个槽位。"); AddEnd(result, "end.ledger", "账本", 1, new[] { 1, 0, 1 }, new[] { 0, 1, 0 }, "稳定首尾槽。"); AddEnd(result, "end.severance", "断契", 1, new[] { 0, 2, 0 }, new[] { 1, 0, 1 }, "强化中槽始数与边槽因数。"); AddEnd(result, "end.black", "黑色遗嘱", 2, new[] { 1, 1, 1 }, new[] { 1, 0, 1 }, "全面强化,适合孤注一掷。"); var playerDeck = CreateStarterDeck(); result.Enemies.Add("greed.tutorial", new ReplacedEnemyDefinition { Id = "greed.tutorial", Name = "贪婪·影子", MaxHealth = 20, NormalDeck = new List { "attack.kill", "attack.kill", "attack.elbow", "attack.elbow", "dodge.flash", "dodge.flash", "counter.blood", "counter.blood", "greed.snob", "greed.snob", "greed.turn-profit", "greed.wage-cut" }, EndDeck = new List(playerDeck.EndCards), RewardIds = new List { "ornament.gold-eye" } }); result.Enemies.Add("greed.full", new ReplacedEnemyDefinition { Id = "greed.full", Name = "贪婪·葛朗台", MaxHealth = 30, NormalDeck = new List { "attack.break", "attack.break", "attack.iron", "attack.iron", "dodge.phase", "dodge.phase", "greed.snob", "greed.snob", "greed.turn-profit", "greed.turn-profit", "greed.wage-cut", "greed.wage-cut" }, EndDeck = new List(playerDeck.EndCards), RewardIds = new List { "ornament.gold-eye", "ornament.gold-chain", "ornament.worn-suit", "greed.snob", "greed.turn-profit", "greed.wage-cut" } }); return result; } public static ReplacedDeck CreateStarterDeck() => new() { NormalCards = new List { "counter.stance", "counter.stance", "counter.blood", "counter.blood", "counter.instant", "counter.instant", "dodge.flash", "dodge.flash", "attack.kill", "attack.kill", "attack.elbow", "attack.elbow" }, EndCards = new List { "end.inherit", "end.ledger", "end.severance", "end.black" } }; private static void AddCard(ReplacedContentCatalog target, string id, string name, ReplacedEffectType type, int cost, int baseValue, int factor, string description, string special = "") { target.Cards.Add(id, new ReplacedCardDefinition { Id = id, Name = name, EffectType = type, Cost = cost, BaseValue = baseValue, Factor = factor, Description = description, SpecialEffectId = special }); } private static void AddEnd(ReplacedContentCatalog target, string id, string name, int cost, int[] slotBase, int[] slotFactor, string description) { target.EndCards.Add(id, new ReplacedEndCardDefinition { Id = id, Name = name, Cost = cost, SlotBase = slotBase, SlotFactor = slotFactor, Description = description }); } } public static class ReplacedDeckValidator { public static IReadOnlyList Validate(ReplacedDeck deck, ReplacedContentCatalog catalog) { var errors = new List(); if (deck == null) return new[] { "deck.null" }; if (deck.NormalCards.Count != 12) errors.Add("deck.normal.count"); if (deck.EndCards.Count != 4) errors.Add("deck.end.count"); foreach (var group in deck.NormalCards.GroupBy(value => value, StringComparer.Ordinal)) { if (!catalog.Cards.ContainsKey(group.Key)) errors.Add("deck.normal.unknown:" + group.Key); if (group.Count() > 2) errors.Add("deck.normal.duplicate:" + group.Key); } foreach (var group in deck.EndCards.GroupBy(value => value, StringComparer.Ordinal)) { if (!catalog.EndCards.ContainsKey(group.Key)) errors.Add("deck.end.unknown:" + group.Key); if (group.Count() > 1) errors.Add("deck.end.duplicate:" + group.Key); } return errors; } } }