using System; using System.Collections.Generic; using System.Linq; namespace Decentralized.Domain { /// 显卡或矿机的型号。算力内部单位 H(1 H = 10 MH/s),耗电单位 kJ/天。 public sealed class HardwareSpec { public string Model; public string Brand; public double Hash; public double Kj; public int Price; /// 暴毙概率的品牌系数。 /// 档次:0 低端、1 中端、2 高端、3 旗舰(显卡)。实物特征(供电口、风扇数)只看档次。 public int Tier; /// 废片:只会作为假卡背后的真实芯片出现,不上架、不单独出现。低档次的卡也能被冒充靠它。 public bool Scrap; public double DeathFactor; /// 每天损耗的系数。 public double WearFactor; /// 矿机(ASIC):独立机器;false 为插在矿架上的显卡。 public bool Asic; /// 显示用算力:显卡 MH/s,矿机 GH/s。 public double DisplayHash => Asic ? Hash * 10 / 1000 : Hash * 10; } public static class Hardware { /// 各档次的实物特征:供电口针数(旗舰是两个 8 针)与风扇数量。 public static readonly string[] PowerPins = { "6", "8", "8", "8+8" }; public static readonly int[] FanCount = { 1, 2, 3, 3 }; public const string Probidia = "Probidia", Pmd = "PMD", RatDigg = "RatDigg"; public static readonly IReadOnlyDictionary Specs = new[] { // 四个档次(低端、中端、高端、旗舰),两个牌子通用:型号只是显示名,性能和价格由「牌子 + 档次」决定 Gpu("RTX60", Probidia, 0, 1.0, 4, 220), Gpu("RTX70", Probidia, 1, 5.0, 12, 800), Gpu("RTX80", Probidia, 2, 8.0, 18, 1300), Gpu("RTX90", Probidia, 3, 20.0, 30, 5000), Gpu("RX70", Pmd, 0, 0.8, 2.5, 170), Gpu("RX80", Pmd, 1, 4.0, 8, 680), Gpu("RX800", Pmd, 2, 8.0, 14, 1500), Gpu("RX900", Pmd, 3, 15.0, 20, 4000), Scrap("RX10", Pmd, 0, 0.3, 2, 60), Asic("M1", 50, 60, 11000), Asic("M2", 120, 110, 27000), Asic("M3", 300, 220, 72000), }.ToDictionary(s => s.Model); private static HardwareSpec Gpu(string model, string brand, int tier, double h, double kj, int price) => new HardwareSpec { Model = model, Brand = brand, Tier = tier, Hash = h, Kj = kj, Price = price, DeathFactor = brand == Probidia ? 0.7 : 1.0, WearFactor = brand == Probidia ? 1.0 : 1.2, }; private static HardwareSpec Scrap(string model, string brand, int tier, double h, double kj, int price) { var s = Gpu(model, brand, tier, h, kj, price); s.Scrap = true; return s; } private static HardwareSpec Asic(string model, double h, double kj, int price) => new HardwareSpec { Model = model, Brand = RatDigg, Hash = h, Kj = kj, Price = price, DeathFactor = 0.5, WearFactor = 0.8, Asic = true, }; } /// /// 矿架上游戏模块的效果。机器汇总的三项百分比(性能、效率、品质)各除以 后生效, /// 并有上限,避免模块把挖矿收益放大到破坏经济:算力最多 +20%,省电最多 25%,损耗最多降 30%。 /// 性能加成同时按比例提高损耗与暴毙率。负数(例如节能模块的负性能、超频模块的负效率)照常生效,耗电增加不设上限。 /// public struct ModuleBonus { public double Hash; // 算力乘数 public double Kj; // 耗电乘数 public double Wear; // 每日损耗乘数 public double Death; // 暴毙率乘数 public static ModuleBonus None => new ModuleBonus { Hash = 1, Kj = 1, Wear = 1, Death = 1 }; public static ModuleBonus From(int performance, int efficiency, int quality) { double r = Tuning.ModuleRatio * 100.0; double hash = Clamp(performance / r, -0.5, Tuning.ModuleHashCap); double save = Clamp(efficiency / r, -1.0, Tuning.ModuleKjCap); double wearCut = Clamp(quality / r, -1.0, Tuning.ModuleWearCap); double heat = 1 + Math.Max(0, hash); return new ModuleBonus { Hash = 1 + hash, Kj = 1 - save, Wear = (1 - wearCut) * heat, Death = heat }; } private static double Clamp(double v, double lo, double hi) => Math.Max(lo, Math.Min(hi, v)); } /// 一张显卡或一台矿机的实例状态(存进物品标签)。 public sealed class Unit { public string Model; /// 假卡:标着 Model,芯片实为 FakeOf。 public string FakeOf; public double Health = 100; public bool Dead; public bool Refurbished; /// 外观;新卡(正规渠道)没有记录,按全新画。 public CardLook Look; /// 按当前健康度算出的外观。 public CardLook Appearance => Look.Appearance(Health); public HardwareSpec Label => Hardware.Specs[Model]; public HardwareSpec Chip => Hardware.Specs[FakeOf ?? Model]; /// 当前算力:健康 ≥ 50 时满算力,低于 50 线性降到健康 0 时的 60%。 public double Hash(ModuleBonus b) { if (Dead) return 0; return Chip.Hash * (Health >= 50 ? 1.0 : 0.6 + 0.4 * Health / 50) * b.Hash * Appearance.HashFactor; } public double Kj(ModuleBonus b) => Dead ? 0 : Chip.Kj * b.Kj; /// 今晚的暴毙概率。 public double DeathChance(ModuleBonus b) { double p = Tuning.DeathBase + Tuning.DeathWorn * Math.Pow(1 - Health / 100, 2); return p * Label.DeathFactor * (FakeOf != null ? Tuning.FakeDeathFactor : 1) * b.Death * Appearance.DeathFactor; } /// 一夜的损耗与暴毙判定;返回是否在今晚暴毙。 public bool Night(ModuleBonus b, Rng rng) { if (Dead) return false; Health = Math.Max(0, Health - Label.WearFactor * b.Wear * Appearance.WearFactor); if (rng.Chance(DeathChance(b))) { Dead = true; return true; } return false; } /// 翻新:每张卡一次,健康 +30。 public bool Refurbish() { if (Dead || Refurbished) return false; Refurbished = true; Health = Math.Min(100, Health + Tuning.RefurbishHealth); return true; } } /// 全网:NPC 背景算力、出块奖励与减半。 public sealed class Network { public double NpcHash = Tuning.NpcHash0; /// NPC 矿工每 H 的耗电(kJ):矿机时代到来后减半,他们能挖得更久,全网算力均衡点升高。 public double NpcKj = Tuning.NpcKjPerH; public int EraDay = -1, EraWarnDay = -1; public bool EraWarned, EraDone; public double Reward = Tuning.BlockReward; /// 停电、动荡等事件对 NPC 算力的可用比例。 public double Availability = 1.0; /// 按算力分得今晚的出块(已扣矿池抽成)。 public double Mine(double playerHash) { if (playerHash <= 0) return 0; double total = NpcHash * Availability + playerHash; return Reward * playerHash / total * (1 - Tuning.PoolFee); } public enum EraStep { None, Warn, Start } /// /// "矿机时代":第一次调用时在 EraMin–EraMax 天之间随机定一天(至少留出预兆的天数),提前 4–7 天预兆,到点 NPC 耗电效率翻倍,永久。 /// 返回今晚发生的一步(预兆 / 开始)。 /// public EraStep EraNight(int day, Rng rng) { if (EraDone) return EraStep.None; if (EraDay < 0) { EraDay = Math.Max(day + Tuning.EraWarnMax + 1, rng.Range(Tuning.EraMin, Tuning.EraMax)); EraWarnDay = EraDay - rng.Range(Tuning.EraWarnMin, Tuning.EraWarnMax); } if (day >= EraDay) { NpcKj *= Tuning.EraKjMult; EraDone = true; return EraStep.Start; } if (!EraWarned && day >= EraWarnDay) { EraWarned = true; return EraStep.Warn; } return EraStep.None; } /// /// 夜间:第 HalvingDays 天的倍数时减半;NPC 矿工按"每 H 收益 / 电费"与目标比的偏离扩张或关机,每晚最多 ±5%。 /// public void Night(int day, double price, double playerHash) { if (day > 0 && day % Tuning.HalvingDays == 0) Reward /= 2; double total = NpcHash * Availability + playerHash; double perH = Reward * price / total; double ratio = perH / (NpcKj * Tuning.ElecPrice); double adj = Math.Max(-Tuning.NpcAdj, Math.Min(Tuning.NpcAdj, Tuning.NpcAdj * (ratio / Tuning.NpcTargetRatio - 1))); NpcHash *= 1 + adj; } } }