diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4cc5c50..e90b040 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,3 +7,4 @@
- mod 本体(第一阶段的第一部分):人脉网络"矿业渠道"(300)交付交易终端、矿架和一张 RZX 50;矿架是带电池槽和 4 个显卡槽的机器,每晚从电池扣电挖 PSCoin;显卡按型号显示算力、耗电与健康度,每晚损耗并可能烧毁;交易终端有钱包、交易(四个交易对、存入信用点、提取 CRC 过合规审查)、报告三页;夜间结算写晨间日志;PSCoin 交易所开业与减半上报纸;状态存进游戏的 mod 存档数据。中英双语。
- 物品图:art/sprites.py 生成显卡(Probidia、PMD、烧毁)、矿架、交易终端。
- 矿架加入游戏原有的通用模块槽:性能、效率、品质三项百分比折算 1/4 后作用于算力、耗电和显卡损耗,算力最多 +20%、省电最多 25%、损耗最多 −30%;性能加成同时提高损耗与暴毙率。取代原计划的 mod 自带超频模块。
+- 杰克逊博士上货(游戏给 mod 的进货钩子,夜间去他店里或他每周来本店摆摊时):没有矿架时必有一个,有了之后 30% 再上;基础显卡每次两张;进阶显卡第 10 天起 60%(再 30% 多一张);旗舰第 30 天起 25%,特供货事件时第 20 天起必有。全新真货,价格为物品价值加他的加价。解锁矿业渠道后才上货。开发者模式 F9 有测试入口。
diff --git a/src/Decentralized.Domain/Dealer.cs b/src/Decentralized.Domain/Dealer.cs
new file mode 100644
index 0000000..e64d4cb
--- /dev/null
+++ b/src/Decentralized.Domain/Dealer.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+
+namespace Decentralized.Domain
+{
+ ///
+ /// 正规渠道(杰克逊博士)每次进货时上架的挖矿设备:全新、真货。返回物品种类:矿架用 ,显卡用型号。
+ ///
+ public static class Dealer
+ {
+ public const string Rig = "RIG";
+
+ public static readonly string[] Basic = { "RZX50", "RZX60", "RZ470", "RZ580" };
+ public static readonly string[] Advanced = { "RZX70", "RZX80Ti", "RZ6800" };
+ public static readonly string[] Flagship = { "RZX90", "RZ7900" };
+
+ public const int AdvancedFromDay = 10, FlagshipFromDay = 30, SpecialFlagshipFromDay = 20;
+ public const double RigRestockChance = 0.30, AdvancedChance = 0.60, AdvancedSecondChance = 0.30, FlagshipChance = 0.25;
+
+ public static List Stock(int day, bool ownsRig, bool specialStock, Rng rng)
+ {
+ var list = new List();
+ if (!ownsRig || rng.Chance(RigRestockChance)) list.Add(Rig);
+ for (int i = 0; i < 2; i++) list.Add(Pick(Basic, rng));
+ if (day >= AdvancedFromDay && rng.Chance(AdvancedChance))
+ {
+ list.Add(Pick(Advanced, rng));
+ if (rng.Chance(AdvancedSecondChance)) list.Add(Pick(Advanced, rng));
+ }
+ if (specialStock && day >= SpecialFlagshipFromDay) list.Add(Pick(Flagship, rng));
+ else if (day >= FlagshipFromDay && rng.Chance(FlagshipChance)) list.Add(Pick(Flagship, rng));
+ return list;
+ }
+
+ private static string Pick(string[] from, Rng rng) => from[rng.Range(0, from.Length - 1)];
+ }
+}
diff --git a/src/Decentralized/DealerStock.cs b/src/Decentralized/DealerStock.cs
new file mode 100644
index 0000000..1dc4f94
--- /dev/null
+++ b/src/Decentralized/DealerStock.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Linq;
+using Decentralized.Domain;
+
+namespace Decentralized
+{
+ ///
+ /// 杰克逊博士进货时(夜间去他店里,或他每周来本店摆摊)加上挖矿设备。用游戏给 mod 的进货钩子和他自己的上架函数,
+ /// 价格是物品价值加上他的加价(难度规则与夜间事件决定)。解锁矿业渠道之后才上货。
+ ///
+ public static class DealerStock
+ {
+ public static void Subscribe()
+ {
+ ModHook.OnPlaceInventorInventoryItemLate += OnPlace;
+ // 开发者模式(F9)里的测试入口:像到了杰克逊店里一样,清空并扩大柜台后上货
+ ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Dr. Jackson stock (test)", () =>
+ {
+ EmporiumEntry.Instance.ClearFrontInv();
+ EmporiumEntry.Instance.ExpandFrontCounterGrid();
+ StoreClientList.PlaceInventorInventory();
+ });
+ }
+
+ public static void Unsubscribe()
+ {
+ ModHook.OnPlaceInventorInventoryItemLate -= OnPlace;
+ ModHelper.RemoveDebugFunctions(ModInfo.Id);
+ }
+
+ private static void OnPlace(List owned)
+ {
+ if (Session.Data == null || !Unlock.IsUnlocked) return;
+ Log.Guard("杰克逊博士上货", () =>
+ {
+ bool ownsRig = owned != null && owned.Any(i => i.identifier == Items.Rig);
+ bool special = AfterhourEvents.IsActive("special_stock");
+ var placed = new List();
+ foreach (var kind in Dealer.Stock(StoreStation.GetDayCounter(), ownsRig, special, Session.Data.Rng))
+ {
+ var item = ModHelper.SpawnItem(kind == Dealer.Rig ? Items.Rig : Items.GpuId(kind));
+ bool ok = PlayerStore.Instance.TryAddDirectSellingItemToTable(item);
+ placed.Add(ok ? $"{kind}#{item.uniqueId}" : kind + "(柜台已满)");
+ }
+ Log.Info("杰克逊博士上货:" + string.Join(",", placed));
+ });
+ }
+ }
+}
diff --git a/src/Decentralized/ModEntry.cs b/src/Decentralized/ModEntry.cs
index 7ed25b1..c308e90 100644
--- a/src/Decentralized/ModEntry.cs
+++ b/src/Decentralized/ModEntry.cs
@@ -14,6 +14,7 @@ namespace Decentralized
Items.OpenTerminal = TerminalWindow.Open;
Unlock.Register();
Night.Subscribe();
+ DealerStock.Subscribe();
ModHook.OnGameLoadedLate += Session.Load;
ModHook.OnNewGameStarted += OnNewGame;
Log.Info($"{ModInfo.Name} {ModInfo.Version} 已启用");
@@ -23,6 +24,7 @@ namespace Decentralized
{
Items.Unsubscribe();
Night.Unsubscribe();
+ DealerStock.Unsubscribe();
ModHook.OnGameLoadedLate -= Session.Load;
ModHook.OnNewGameStarted -= OnNewGame;
}
diff --git a/tests/Decentralized.Domain.Tests/Program.cs b/tests/Decentralized.Domain.Tests/Program.cs
index eaa56ef..61cb889 100644
--- a/tests/Decentralized.Domain.Tests/Program.cs
+++ b/tests/Decentralized.Domain.Tests/Program.cs
@@ -290,6 +290,28 @@ internal static class Program
Check(Near(dr.Received, 900 * (1 - Tuning.ExchangeFee), 1e-6), "CRC 脱锚时按市价兑出");
});
+ Test("杰克逊博士:没有矿架必上一个;基础卡每次两张;进阶第 10 天起、旗舰第 30 天起;特供货第 20 天起必有旗舰", () =>
+ {
+ var rng = new Rng(11);
+ var first = Dealer.Stock(1, false, false, rng);
+ Check(first.Count(k => k == Dealer.Rig) == 1 && first.Count(k => Dealer.Basic.Contains(k)) == 2 && first.Count == 3, $"第 1 天:矿架 + 两张基础卡:{string.Join(",", first)}");
+ int adv = 0, flag = 0, rigs = 0, n = 4000;
+ for (int i = 0; i < n; i++)
+ {
+ var s9 = Dealer.Stock(9, true, false, rng);
+ Check(!s9.Any(k => Dealer.Advanced.Contains(k) || Dealer.Flagship.Contains(k)), "第 9 天没有进阶与旗舰");
+ var s40 = Dealer.Stock(40, true, false, rng);
+ if (s40.Any(k => Dealer.Advanced.Contains(k))) adv++;
+ if (s40.Any(k => Dealer.Flagship.Contains(k))) flag++;
+ if (s40.Contains(Dealer.Rig)) rigs++;
+ }
+ Check(Near(adv / (double)n, 0.60, 0.03) && Near(flag / (double)n, 0.25, 0.03) && Near(rigs / (double)n, 0.30, 0.03),
+ $"第 40 天:进阶 {adv / (double)n:P0}(60%),旗舰 {flag / (double)n:P0}(25%),已有矿架时再上 {rigs / (double)n:P0}(30%)");
+ Check(Dealer.Stock(20, true, true, rng).Any(k => Dealer.Flagship.Contains(k)) && !Dealer.Stock(19, true, true, new Rng(1)).Any(k => Dealer.Flagship.Contains(k)),
+ "特供货:第 20 天起必有旗舰,第 19 天没有");
+ Check(Dealer.Basic.Concat(Dealer.Advanced).Concat(Dealer.Flagship).All(m => Hardware.Specs.ContainsKey(m) && !Hardware.Specs[m].Asic), "上架的型号都是登记过的显卡");
+ });
+
Test("存档:市场、稳定币、全网、钱包、交易所、随机数序列化后读回,之后的结果一致", () =>
{
var m = Market.CreateDefault(3);