From 3a4572efb65578fc789cf6bfb683e2a0ab1b73e3 Mon Sep 17 00:00:00 2001 From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:18:19 +0800 Subject: [PATCH] Terminal and graphics cards redrawn; rig slots 2 to 5 with electronics Terminal: monitor with a top face and a trapezoid keyboard. Graphics card: a metal bracket with ports, two fans, brand stripe, gold fingers with a PCIe notch and power connectors on top, now 2x2 cells. Rig: two slots by default, an upgrade slot that takes the next level's electronics (4 common, 1 rare, 2 rare) and consumes them at once; the card grid reshapes with the slot count, which is kept on the rig's tags. Verified in game, including the slot limit, the upgrade items, five cards, and save and reload. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 + art/sprites.py | 147 ++++++++++++++---- src/Decentralized.Domain/RigSlots.cs | 58 +++++++ src/Decentralized.Domain/Tuning.cs | 2 +- src/Decentralized/Items.cs | 57 ++++++- .../ModFiles/Localization/en.csv | 6 +- .../ModFiles/Localization/zh.csv | 6 +- .../ModFiles/Textures/Items/gpu_burnt.png | Bin 229 -> 375 bytes .../ModFiles/Textures/Items/gpu_pmd.png | Bin 185 -> 329 bytes .../ModFiles/Textures/Items/gpu_probidia.png | Bin 185 -> 328 bytes .../ModFiles/Textures/Items/terminal.png | Bin 229 -> 259 bytes tests/Decentralized.Domain.Tests/Program.cs | 14 ++ 12 files changed, 252 insertions(+), 40 deletions(-) create mode 100644 src/Decentralized.Domain/RigSlots.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd8081..d5f30e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,6 @@ - 入口升级归入游戏原有的"服务"栏(和 Probably Automated 一样),图标用交易终端。 - 修复矿架、交易终端与别的物品重叠:游戏按 mod 物品图每格中心的一个像素判断这一格算不算占用,矿架的卡位之间有缝,缝正好落在中心行上,8 格被当成空格。矿架加了实心背板、终端支架拉通;art/sprites.py 生成时按游戏的方式检查每格中心,有透明格就报错。 - 矿架重画成和原版机器一样的略微俯视:顶面(带散热格栅和指示灯)、受光的左缘、暗的右侧面、正面三层显卡位和电源口、底部两只脚,轮廓削圆。 +- 交易终端和显卡重画成略微俯视:终端是带顶面的显示器加梯形键盘;显卡是一块真正的显卡(左端金属挡板带接口、两个风扇、品牌色条、底边金手指和 PCIe 缺口、顶上供电口),尺寸改为 2×2 格。 +- 矿架默认 2 个显卡槽,投入电子元件升级,最多 5 个:第 3 槽 4 个普通电子元件,第 4 槽 1 个稀有,第 5 槽 2 个稀有。窗口里新增"升级槽位",只收下一级需要的元件,放进去立即消耗并累计进度;显卡格子的形状随槽数变化,槽数与进度存在矿架标签上。 diff --git a/art/sprites.py b/art/sprites.py index bd9989a..02747c1 100644 --- a/art/sprites.py +++ b/art/sprites.py @@ -78,32 +78,75 @@ class Canvas: def gpu(accent, accent_dark, burnt=False): - """显卡(32×16,2×1 格):外壳、两个风扇、品牌色条、金手指。""" - c = Canvas(32, 16) - c.box(1, 2, 30, 12, 'm') - c.rect(2, 3, 29, 3, 'l') # 顶边受光 - c.rect(2, 11, 29, 11, 'd') # 底边暗面 - for cx in (9, 21): # 风扇 - for y in range(5, 11): - for x in range(cx - 4, cx + 5): - dx, dy = x - cx, y - 7.5 - if dx * dx + dy * dy <= 16: + """显卡(32×32,2×2 格),略微俯视:顶面是带两个风扇的散热罩,左端金属挡板(带接口), + 前沿露出 PCB 和金手指,右端一道暗侧面,顶上一排供电口。每格中心都不透明。""" + c = Canvas(32, 32) + TOP0, TOP1 = 4, 22 # 顶面行范围 + L, R = 6, 29 # 顶面左右(左边是挡板) + # 金属 I/O 挡板(左端竖条,带两个接口孔) + c.rect(1, 2, 5, 27, 'b') + c.rect(1, 2, 1, 27, 'h'); c.rect(5, 2, 5, 27, 'd') + for y in (6, 12): # 两个视频接口(深色矩形孔,带高光边) + c.rect(2, y, 4, y + 3, 'k') + c.rect(2, y, 4, y, 'g') + c.put(3, 20, 'k'); c.put(3, 22, 'k'); c.put(3, 24, 'k') # 下部散热孔 + # 散热罩顶面 + for y in range(TOP0, TOP1 + 1): + for x in range(L, R + 1): + c.put(x, y, 'm') + c.put(R, y, 'd'); c.put(R - 1, y, 'd') + for x in range(L, R + 1): + c.put(x, TOP0, 'l'); c.put(x, TOP0 + 1, 'b') + # 品牌色条(沿罩顶前沿) + for x in range(L + 1, R - 1): + c.put(x, TOP1 - 1, accent) + c.put(L + 1, TOP1, accent_dark); c.put(R - 2, TOP1, accent_dark) + # 两个风扇(圆形,叶片用十字 + 斜线) + for cx in (11, 22): + cy = 13 + for y in range(cy - 6, cy + 7): + for x in range(cx - 6, cx + 7): + d2 = (x - cx) ** 2 + (y - cy) ** 2 + if d2 <= 36: c.put(x, y, 'k') - if 6 <= dx * dx + dy * dy <= 16: + if 20 <= d2 <= 30: c.put(x, y, 'g') - c.put(cx, 7, 'b'); c.put(cx, 8, 'b') - c.rect(2, 4, 29, 4, accent) # 品牌色条 - c.put(2, 5, accent_dark); c.put(29, 5, accent_dark) - c.rect(6, 13, 25, 13, 'Y') # 金手指 - for x in range(6, 26, 2): - c.put(x, 14, 'Y') - c.rect(30, 3, 30, 11, 'k') # 挡板 + for k in range(-4, 5): + if abs(k) >= 1: + c.put(cx + k, cy, 'g'); c.put(cx, cy + k, 'g') + c.put(cx - 2, cy - 2, 'g'); c.put(cx + 2, cy + 2, 'g'); c.put(cx + 2, cy - 2, 'g'); c.put(cx - 2, cy + 2, 'g') + c.rect(cx - 1, cy - 1, cx + 1, cy + 1, 'b') + c.put(cx, cy, 'h') + # 前沿:PCB 绿色薄边 + 金手指 + for x in range(L, R + 1): + c.put(x, TOP1 + 1, 'g2') + c.put(x, TOP1 + 2, 'g2') + for x in range(L, R + 1): # 金手指:连续金色带,中间一道缺口(PCIe 键位),带深色触点线 + c.put(x, TOP1 + 3, 'Y'); c.put(x, TOP1 + 4, 'Y' if x % 2 == 0 else 'e') + for y in (TOP1 + 3, TOP1 + 4): + c.put(L + 13, y, 'k'); c.put(L + 14, y, 'k') + # 顶上一排供电口(两个黑色插口) + for x0 in (L + 3, L + 9): + c.rect(x0, TOP0 - 1, x0 + 4, TOP0 - 1, 'k') + # 保证每格中心不透明:下半部(第二行格子,y 16..31 的中心 y=24)落在金手指/PCB 上 + for x in range(L, R + 1): + for y in range(TOP1 + 1, 28): + if c.px[y][x] is None: + c.put(x, y, 'k') if burnt: - for x, y in [(7, 5), (8, 6), (10, 9), (19, 6), (20, 7), (22, 9), (14, 8), (15, 7), (24, 5)]: + for x, y in [(9, 8), (10, 9), (13, 17), (24, 8), (25, 9), (27, 18), (17, 12), (18, 11)]: c.put(x, y, 'c') - c.rect(2, 4, 29, 4, 'e') - for x, y in [(12, 3), (13, 3), (17, 11), (26, 10)]: + for x in range(L + 1, R - 1): + c.put(x, TOP1 - 1, 'e') + for x, y in [(15, 5), (16, 5), (20, 21), (26, 14)]: c.put(x, y, 'c') + # 轮廓 + solid = lambda v: v is not None + get = lambda x, y: c.px[y][x] if 0 <= x < c.w and 0 <= y < c.h else None + edge = [(x, y) for y in range(c.h) for x in range(c.w) + if solid(get(x, y)) and any(not solid(get(x + dx, y + dy)) for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)))] + for x, y in edge: + c.put(x, y, 'O') return c @@ -185,20 +228,58 @@ def rig(): def terminal(): - """交易终端(32×32,2×2 格):小型显示器 + 键盘,屏幕上是 K 线。""" + """交易终端(32×32,2×2 格),略微俯视:显示器是一个带顶面的盒子(正面是屏幕和 K 线), + 下面是键盘的顶面(梯形,露出按键),左亮右暗。每格中心都不透明。""" c = Canvas(32, 32) - c.box(3, 2, 28, 21, 'm') - c.rect(4, 3, 27, 3, 'l') - c.box(5, 4, 26, 18, 's') - candles = [(8, 12, 15), (11, 10, 14), (14, 11, 13), (17, 7, 12), (20, 8, 11), (23, 5, 10)] - for x, top, bot in candles: - c.rect(x, top, x, bot, 'S') - c.put(x, top - 1, 'T') - c.rect(3, 22, 28, 23, 'd') # 支架(拉通,保证每格中心点不透明) - c.box(2, 24, 29, 30, 'b') # 键盘 - for y in (26, 28): - for x in range(4, 28, 2): + # 显示器:顶面(浅色窄条,略收窄)+ 正面 + for v in range(4): + y = 3 + v + x0, x1 = 4 + (3 - v) // 3, 27 - (3 - v) // 3 + for x in range(x0, x1 + 1): + c.put(x, y, 'l' if v else 'h') + c.put(x1, y, 'd') + for y in range(7, 21): + for x in range(4, 28): c.put(x, y, 'm') + c.put(4, y, 'b'); c.put(27, y, 'd'); c.put(26, y, 'd') + # 屏幕(深青底、K 线) + for y in range(9, 19): + for x in range(7, 25): + c.put(x, y, 's') + candles = [(9, 13, 16), (12, 11, 15), (15, 12, 14), (18, 9, 13), (21, 10, 12), (23, 9, 11)] + for x, top, bot in candles: + for y in range(top, bot + 1): + c.put(x, y, 'S') + c.put(x, top - 1 if top > 9 else top, 'T') + c.put(7, 9, 'T'); c.put(8, 9, 'T') + # 支架 + 底座(拉通到键盘,保证 y=24 的格子中心不透明) + c.rect(13, 21, 18, 22, 'd') + # 键盘顶面:梯形(后窄前宽),按键网格 + for v in range(7): + y = 24 + (6 - v) # v=0 是前沿(y=30) + w = v // 3 + x0, x1 = 2 + w, 29 - w + for x in range(x0, x1 + 1): + c.put(x, y, 'b') + c.put(x0, y, 'h'); c.put(x1, y, 'd') + for y in (25, 27, 29): + for x in range(5, 27, 2): + c.put(x, y, 'm') + c.rect(10, 29, 21, 29, 'm') # 空格键 + # 键盘前沿(正面薄边) + for x in range(2, 30): + c.put(x, 31, 'd') + # 键盘与显示器之间的缝隙用桌面阴影色填实,避免 y=24 行出现透明格 + for y in range(22, 24): + for x in range(4, 28): + if c.px[y][x] is None: + c.put(x, y, 'k') + solid = lambda v: v is not None + get = lambda x, y: c.px[y][x] if 0 <= x < c.w and 0 <= y < c.h else None + edge = [(x, y) for y in range(c.h) for x in range(c.w) + if solid(get(x, y)) and any(not solid(get(x + dx, y + dy)) for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)))] + for x, y in edge: + c.put(x, y, 'O') return c diff --git a/src/Decentralized.Domain/RigSlots.cs b/src/Decentralized.Domain/RigSlots.cs new file mode 100644 index 0000000..8f7a9a2 --- /dev/null +++ b/src/Decentralized.Domain/RigSlots.cs @@ -0,0 +1,58 @@ +using System; + +namespace Decentralized.Domain +{ + /// + /// 矿架的显卡槽:默认 2 个,投入电子元件逐个升级,最多 5 个。每张显卡占 2×2 格,槽在矿架窗口里是一列 2 格宽的格子, + /// 每个槽两行;未解锁的行是不可放的格子。游戏里电子元件的价值是 33 / 350 / 2,500(普通 / 稀有 / 超科技)。 + /// + public static class RigSlots + { + public const int Base = 2, Max = 5; + public const int CellWidth = 2, CellsPerSlot = 2; + + /// 升级到第 n 个槽需要的电子元件(物品 id,件数)。n 从 3 开始。 + public static (string Item, int Count) Cost(int slot) + { + switch (slot) + { + case 3: return ("common_electronic", 4); + case 4: return ("rare_electronic", 1); + case 5: return ("rare_electronic", 2); + default: throw new ArgumentOutOfRangeException(nameof(slot)); + } + } + + /// 当前槽数下,下一次升级需要什么;已满时返回 null。 + public static (string Item, int Count)? Next(int slots) => slots >= Max ? ((string, int)?)null : Cost(slots + 1); + + /// + /// 投入 count 件 item:只收下一级需要的那种;累计到够用就解锁一个槽,多出的留作下一级进度(若下一级是同一种元件)。 + /// 返回实际收下的件数;其余退回(不收)。 + /// + public static int Insert(ref int slots, ref int progress, string item, int count) + { + if (slots >= Max || count <= 0) return 0; + var need = Cost(slots + 1); + if (item != need.Item) return 0; + int take = Math.Min(count, need.Count - progress); + progress += take; + if (progress >= need.Count) + { + slots++; + progress = 0; + } + return take; + } + + /// 显卡格子的形状字符串:宽 2,高 2 × 最大槽数;前 slots × 2 行可放(0),其余不可放(1)。 + public static string Shape(int slots) + { + var sb = new System.Text.StringBuilder(); + for (int row = 0; row < Max * CellsPerSlot; row++) + for (int col = 0; col < CellWidth; col++) + sb.Append(row < slots * CellsPerSlot ? '0' : '1'); + return sb.ToString(); + } + } +} diff --git a/src/Decentralized.Domain/Tuning.cs b/src/Decentralized.Domain/Tuning.cs index 0e52271..a6cc4eb 100644 --- a/src/Decentralized.Domain/Tuning.cs +++ b/src/Decentralized.Domain/Tuning.cs @@ -15,7 +15,7 @@ namespace Decentralized.Domain public const double NpcTargetRatio = 3.5; public const double NpcAdj = 0.05; public const double PoolFee = 0.03; - public const int RigPrice = 400, RigKj = 2, RigSlots = 4; + public const int RigPrice = 400, RigKj = 2; /// 矿架上模块百分比的折算系数(原版机器用 2–4),以及各项上限。 public const double ModuleRatio = 4, ModuleHashCap = 0.20, ModuleKjCap = 0.25, ModuleWearCap = 0.30; public const double DeathBase = 0.001, DeathWorn = 0.03, FakeDeathFactor = 2; diff --git a/src/Decentralized/Items.cs b/src/Decentralized/Items.cs index ea13fbf..2c7888a 100644 --- a/src/Decentralized/Items.cs +++ b/src/Decentralized/Items.cs @@ -117,15 +117,22 @@ namespace Decentralized // ---------- 矿架 ---------- + public const string SlotsTag = "PD_SLOTS", ProgressTag = "PD_SLOT_PROGRESS"; + + private static int SlotsOf(GameItem rig) => rig.IsTag(SlotsTag) ? rig.GetTagReadonly(SlotsTag).GetInt() : RigSlots.Base; + private static GameItem CreateRig() { var battery = new GameSlotInventory(); + var upgrade = new GameSlotInventory(); var modules = new GameGridInventory(MachineHelper.DEFAULT_MODULE_BAY_CONFIG, 7); - var cards = new GameGridInventory(2, 4); + // 显卡每张 2×2 格:一列 2 格宽的格子,每个槽两行;未解锁的行不可放 + var cards = new GameGridInventory(RigSlots.Shape(RigSlots.Base), RigSlots.CellWidth); var window = MachineHelper.CreateMachineryWindow(true, MachineHelper.PowerPanel(battery), MachineHelper.Panel(L.T("rig.label.modules"), modules, "module"), - MachineHelper.Panel(L.T("rig.label.cards"), cards, "cards")); + MachineHelper.Panel(L.T("rig.label.cards"), cards, "cards"), + MachineHelper.Panel(L.T("rig.label.upgrade"), upgrade, "upgrade")); var rig = ItemDirectory.CreateEmptyItem(Rig).SetContentWindow(window); rig.SetSpriteAndShapeFromMod(ModInfo.Id, "rig"); rig.name = L.T("item.rig.name"); @@ -139,13 +146,48 @@ namespace Decentralized cards.mayInventoryAddItemFunc = (item, _) => GeneralHelper.IsItemOwned(item) && IsGpu(item) && !item.IsTag(DeadTag); cards.onSlotAddItemEndFunc = (_, _, _) => UpdateRig(rig, modules, cards); cards.onSlotRemoveItemEndFunc = (_, _, _) => UpdateRig(rig, modules, cards); - rig.onLoaded = _ => UpdateRig(rig, modules, cards); + // 升级槽:只收下一级需要的电子元件,放进去立刻投入(计入进度),攒够解锁一个显卡槽 + upgrade.mayInventoryAddItemFunc = (item, _) => + { + var need = RigSlots.Next(SlotsOf(rig)); + return GeneralHelper.IsItemOwned(item) && need != null && item.identifier == need.Value.Item; + }; + upgrade.onSlotAddItemEndFunc = (item, _, _) => Log.Guard("矿架升级槽位", () => TakeUpgrade(rig, upgrade, cards, modules, item)); + rig.onLoaded = _ => { ApplySlots(rig, cards); UpdateRig(rig, modules, cards); }; rig.onCycleEndEarlySlotItemFunc = (_, _, _) => Log.Guard("矿架扣电", () => NightDraw(rig, battery, modules, cards)); ModHelper.SetSource(rig, ModInfo.Id, ModInfo.Name); + ApplySlots(rig, cards); UpdateRig(rig, modules, cards); return rig; } + /// 按标签里的槽数设置显卡格子的形状(游戏自己扩展模块槽也是 SetShape 加 Validate)。 + private static void ApplySlots(GameItem rig, GameGridInventory cards) + { + cards.SetShape(RigSlots.Shape(SlotsOf(rig)), RigSlots.CellWidth); + rig.contentWindow?.Validate(); + } + + private static void TakeUpgrade(GameItem rig, GameSlotInventory upgradeSlot, GameGridInventory cards, GameInventory modules, GameItem item) + { + int slots = SlotsOf(rig); + int progress = rig.IsTag(ProgressTag) ? rig.GetTagReadonly(ProgressTag).GetInt() : 0; + int before = slots; + int took = RigSlots.Insert(ref slots, ref progress, item.identifier, item.unitCount); + if (took <= 0) return; + rig.ModifyTag(SlotsTag, s => s.Enable().SetInt(slots)); + rig.ModifyTag(ProgressTag, s => s.Enable().SetInt(progress)); + // 消耗收下的件数;放不下的部分留在升级槽里(玩家取回) + if (took >= item.unitCount) { upgradeSlot.Expel(item); item.Destroy(); } + else item.GetSplitItem(took).Destroy(); + if (slots != before) + { + ApplySlots(rig, cards); + EmporiumAudioPlayer.Instance?.ratchet?.Play(); + } + UpdateRig(rig, (GameGridInventory)modules, cards); + } + public static List Cards(GameInventory cards) => cards.childItems.Where(IsGpu).ToList(); /// 按模块槽里的模块重新汇总三项百分比(游戏自己的公式,含相邻效果),换算成本 mod 的模块效果。 @@ -167,7 +209,14 @@ namespace Decentralized int kj = RigKj(cards, b); rig.ModifyTag(MachineryHelper.MACHINERY_DRAWN_POWER, s => s.Enable().SetInt(kj)); double hash = Cards(cards).Select(ToUnit).Sum(u => u.Hash(b)); - rig.shortDescription = L.T("item.rig.desc", Fmt.Num(hash * 10), kj); + int slots = SlotsOf(rig); + rig.shortDescription = L.T("item.rig.desc", slots, Fmt.Num(hash * 10), kj); + var next = RigSlots.Next(slots); + if (next != null) + { + int progress = rig.IsTag(ProgressTag) ? rig.GetTagReadonly(ProgressTag).GetInt() : 0; + rig.shortDescription += " " + L.T("item.rig.next", slots + 1, L.T("mat." + next.Value.Item), progress, next.Value.Count); + } if (b.Hash != 1 || b.Kj != 1 || b.Wear != 1) rig.shortDescription += " " + L.T("item.rig.modules", Pct(b.Hash), Pct(b.Kj), Pct(b.Wear)); } diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv index 559cdd2..32d824d 100644 --- a/src/Decentralized/ModFiles/Localization/en.csv +++ b/src/Decentralized/ModFiles/Localization/en.csv @@ -5,9 +5,13 @@ upgrade.desc,Members receive a trading terminal and an RZX 50 graphics card. The item.terminal.name,Trading Terminal item.terminal.desc,Wallet, the Poinbase exchange and the nightly mining report. item.rig.name,Mining Rig -item.rig.desc,"Holds up to four graphics cards. Hashrate {0} MH/s. Draws {1} kJ per day." +item.rig.desc,"Holds up to {0} graphics cards. Hashrate {1} MH/s. Draws {2} kJ per day." rig.label.modules,Modules item.rig.modules,"Modules: hashrate {0}, power draw {1}, wear {2}." +rig.label.upgrade,Upgrade slot +item.rig.next,"Slot {0}: put in {1} ({2}/{3})." +mat.common_electronic,Common electronics +mat.rare_electronic,Rare electronics rig.label.cards,Graphics cards item.gpu.name,{0} {1} item.gpu.desc,"Hashrate {0} MH/s. Draws {1} kJ per day. Condition {2}%." diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv index 3798c85..a9c8ca1 100644 --- a/src/Decentralized/ModFiles/Localization/zh.csv +++ b/src/Decentralized/ModFiles/Localization/zh.csv @@ -4,9 +4,13 @@ upgrade.desc,成员可获得一台交易终端和一张 RZX 50 显卡。终端 item.terminal.name,交易终端 item.terminal.desc,钱包、Poinbase 交易所与夜间挖矿报告。 item.rig.name,矿架 -item.rig.desc,可装 4 张显卡。算力 {0} MH/s。耗电 {1} kJ/天。 +item.rig.desc,最多装 {0} 张显卡。算力 {1} MH/s。耗电 {2} kJ/天。 rig.label.modules,模块 item.rig.modules,模块:算力 {0},耗电 {1},损耗 {2}。 +rig.label.upgrade,升级槽位 +item.rig.next,第 {0} 槽:投入{1}({2}/{3})。 +mat.common_electronic,普通电子元件 +mat.rare_electronic,稀有电子元件 rig.label.cards,显卡 item.gpu.name,{0} {1} item.gpu.desc,算力 {0} MH/s。耗电 {1} kJ/天。健康度 {2}%。 diff --git a/src/Decentralized/ModFiles/Textures/Items/gpu_burnt.png b/src/Decentralized/ModFiles/Textures/Items/gpu_burnt.png index c50a4e6a3cb6c46823798aa84068a91829bff11f..aeff9bf24233d0edafe82078a02e377c256edc8e 100644 GIT binary patch delta 361 zcmV-v0ha#d0rvus7k?lK1^@s6b9#F80003#Nkl$;d zI21P*!NIlA7ZGP4LBUCUhz`D$dVwqFHJ98$TM-)g2uZ(QemO2FO_IKS+Hf#_adi%G zJfA9Y012=$a1O9qZ@z-w?$??^^BP3~=vdMyO`SB}P=+1Ac7M081OSITZ$+5h8o+F_ ztRw-zGwni>*9-t-NHfh4$hQE@aUZ-(y(Rz}5FzLs4b10hPa#AAUW7CVDm2!5)d13N zc+XQE^NIlc6!0BTK)y3vT;o;MfUlS!SXn+?&x=v(MZc;DEg8^wv53{Dz*+-Z@Bf^F z(AlPUg61BeUSbn~bh}w@Lh1!@GeBqG0<72&K&xLvUj~Y7ZWg3D^xTvI2-f88O*OD6 zrmabpfu8`-3_LHHS$OHhd};tF8cUA=F`pNpYxTc_eZ%$!=s=Xka~By^00000NkvXX Hu0mjfdn1{> delta 214 zcmV;{04e|X0_6da7k>~41^@s6cL04^00022Nklkp%{wn6Lj?;FHus}Mo-AWBs&1vP~`N0FAWf@ zo@fV9nh0@)7={B}+Iq72Y}>=wFsA5k{tlzBZm$QW3zx5wIn$J*+3W@T@0J$_cjRiBStvPgEit&=44q zG%)CL!GM0;0W8Jha{x_iLu?_i!OM>7n4}P>GS;K2??L7f9RhaQW;DY@ zg}{I~067FsEHv>V8N&)4Lh6WiK(?0^Ni@s>gw$bm0KM^OcW@NI004W^qC|Yv0E_?t N002ovPDHLkV1fm1eGmWu delta 170 zcmV;b09F6V0=WT@7k>~41^@s6cL04^0001hNklX_1nVkj;LkfMHQIAFk|fL4hRs{>rzeCUcX95A3IAvSPgi6hs4YGRV>2bd;m(SsHM Y060YC*B5NA@Bjb+07*qoM6N<$f*q(qDgXcg diff --git a/src/Decentralized/ModFiles/Textures/Items/gpu_probidia.png b/src/Decentralized/ModFiles/Textures/Items/gpu_probidia.png index ba456e0f70c0a84be6e7358d328dd5bb0a0a4b66..247a3fc766368152777c23698482d3c54d49273b 100644 GIT binary patch delta 314 zcmV-A0mc5g0muT77k?lK1^@s6b9#F80003GNklIq72Y}>=wFsA5k{tlzBZm$QW3zx5wIn$J*+3W@T@0J$_cjRiBStvPgEit&=44q zG%)CL!GM0;0W8Jha{x_iLu?@sH^-0an4}P}%QmB`??L7f9ReW5w8BJ% zz<@XaIRs8D@iQVB!wMZj>WFqgwwD!2G|T~n)M0f1z42&wa1_7*0EAhioNBDT)Bpeg M07*qoM6N<$f-VSrL;wH) delta 170 zcmV;b09F6U0=WT@7k>~41^@s6cL04^0001hNklX_1nVkj;LkfMHQIAFk|fL4hRs{>rzeCUcX95A3IAvSPgi6hs4YGRV>2bd;m(SsHM Y0DSW1pMofW-T(jq07*qoM6N<$f^CUHTL1t6 diff --git a/src/Decentralized/ModFiles/Textures/Items/terminal.png b/src/Decentralized/ModFiles/Textures/Items/terminal.png index b4db295c6d91c438d4163b457e8ba20a2d13eda1..8a9bc6e2d3a920e1825cf1c81c689f885ba29e6b 100644 GIT binary patch delta 231 zcmVR}-SF|s-s8%C4u074caV| h0|qPv=!s$W0suo%&B1nr-+BN5002ovPDHLkV1hVYS&#q# delta 201 zcmV;)05<=F0_6daB!8?)L_t(|+GAiC1xP?s$>cvhFkT0&Shtj>n4kmVGcu^=)2{uy zQTQ;kiE@B?SO~>9ebrJpo2Cu``E)=XfNT-6V+OsTLu# zkulj0pu|$TIe-{UbqA1&sp0@i@PHJ>L)!tgz(hNMmU!?xU_wBfWG-$#|LK7TrvvB} z{5YclB-k;fi!KJjE2B~hQNU4f`I}6Pk-aKcV0?z00000NkvXXu0mjf D?MhTS diff --git a/tests/Decentralized.Domain.Tests/Program.cs b/tests/Decentralized.Domain.Tests/Program.cs index 61cb889..84555a2 100644 --- a/tests/Decentralized.Domain.Tests/Program.cs +++ b/tests/Decentralized.Domain.Tests/Program.cs @@ -128,6 +128,20 @@ internal static class Program Check(r.Refurbish() && r.Health == 80 && !r.Refurbish(), "翻新一次 +30,不能再翻新"); }); + Test("矿架槽位:默认 2 个,投入电子元件逐个升级到最多 5 个;只收下一级需要的;形状随槽数变化", () => + { + int slots = RigSlots.Base, prog = 0; + Check(slots == 2 && RigSlots.Next(2) == ("common_electronic", 4), "默认 2 槽,升到第 3 槽要 4 个普通电子元件"); + Check(RigSlots.Insert(ref slots, ref prog, "rare_electronic", 1) == 0 && slots == 2 && prog == 0, "第 3 槽不收稀有元件"); + Check(RigSlots.Insert(ref slots, ref prog, "common_electronic", 3) == 3 && slots == 2 && prog == 3, "先投 3 个:进度 3/4"); + Check(RigSlots.Insert(ref slots, ref prog, "common_electronic", 3) == 1 && slots == 3 && prog == 0, "再投 3 个只收 1 个,解锁第 3 槽,多的 2 个退回"); + Check(RigSlots.Next(3) == ("rare_electronic", 1) && RigSlots.Insert(ref slots, ref prog, "rare_electronic", 5) == 1 && slots == 4, "第 4 槽:1 个稀有元件"); + Check(RigSlots.Next(4) == ("rare_electronic", 2) && RigSlots.Insert(ref slots, ref prog, "rare_electronic", 1) == 1 && slots == 4 && prog == 1, "第 5 槽:2 个稀有元件,先投 1 个进度 1/2"); + Check(RigSlots.Insert(ref slots, ref prog, "rare_electronic", 1) == 1 && slots == 5, "投满解锁第 5 槽"); + Check(RigSlots.Next(5) == null && RigSlots.Insert(ref slots, ref prog, "rare_electronic", 1) == 0, "满 5 槽后不再收"); + Check(RigSlots.Shape(2) == "0000" + "0000" + new string('1', 12) && RigSlots.Shape(5) == new string('0', 20) && RigSlots.Shape(3).Length == 20, "形状:宽 2、共 10 行,前 2 × 槽数行可放"); + }); + Test("模块:折算系数 4,算力 +20%、省电 25%、损耗 −30% 封顶;性能加成提高损耗与暴毙", () => { var perf = ModuleBonus.From(16, 0, 0);