167 lines
10 KiB
C#
167 lines
10 KiB
C#
#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<string, ReplacedCardDefinition> Cards { get; } = new(StringComparer.Ordinal);
|
|
public Dictionary<string, ReplacedEndCardDefinition> EndCards { get; } = new(StringComparer.Ordinal);
|
|
public Dictionary<string, ReplacedEnemyDefinition> Enemies { get; } = new(StringComparer.Ordinal);
|
|
public Dictionary<string, IReplacedPersonEffectResolver> 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<string>(value.NormalDeck), EndDeck = new List<string>(value.EndDeck),
|
|
RewardIds = new List<string>(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<string> { "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<string>(playerDeck.EndCards), RewardIds = new List<string> { "ornament.gold-eye" }
|
|
});
|
|
result.Enemies.Add("greed.full", new ReplacedEnemyDefinition
|
|
{
|
|
Id = "greed.full", Name = "贪婪·葛朗台", MaxHealth = 30,
|
|
NormalDeck = new List<string> { "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<string>(playerDeck.EndCards), RewardIds = new List<string> { "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<string>
|
|
{
|
|
"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<string> { "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<string> Validate(ReplacedDeck deck, ReplacedContentCatalog catalog)
|
|
{
|
|
var errors = new List<string>();
|
|
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;
|
|
}
|
|
}
|
|
}
|