Rig: game module bay with damped, capped effects

The rig gets the game's universal module bay (module adjacency included).
The machine's summed performance, efficiency and quality percentages are
divided by 4, like the ratios vanilla machines use, and capped: hashrate
at most +20%, power saving at most 25%, card wear at most -30%. Performance
also raises wear and burn-out chance by the same amount; negative
efficiency (overclock module, neural cores) raises power draw. Replaces the
planned mod-specific overclock module. Tested in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-03 09:16:46 +08:00
1 parent 750b26e4b3
commit 79d4c686a0
9 files changed
+113 -44

No files matched your search

+35 -10
View File
@@ -48,6 +48,33 @@ namespace Decentralized.Domain
};
}
/// <summary>
/// 矿架上游戏模块的效果。机器汇总的三项百分比(性能、效率、品质)各除以 <see cref="Tuning.ModuleRatio"/> 后生效,
/// 并有上限,避免模块把挖矿收益放大到破坏经济:算力最多 +20%,省电最多 25%,损耗最多降 30%。
/// 性能加成同时按比例提高损耗与暴毙率。负数(例如节能模块的负性能、超频模块的负效率)照常生效,耗电增加不设上限。
/// </summary>
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));
}
/// <summary>一张显卡或一台矿机的实例状态(存进物品标签)。</summary>
public sealed class Unit
{
@@ -62,29 +89,27 @@ namespace Decentralized.Domain
public HardwareSpec Chip => Hardware.Specs[FakeOf ?? Model];
/// <summary>当前算力:健康 ≥ 50 时满算力,低于 50 线性降到健康 0 时的 60%。</summary>
public double Hash(bool overclock)
public double Hash(ModuleBonus b)
{
if (Dead) return 0;
double h = Chip.Hash * (Health >= 50 ? 1.0 : 0.6 + 0.4 * Health / 50);
return overclock ? h * Tuning.OcHash : h;
return Chip.Hash * (Health >= 50 ? 1.0 : 0.6 + 0.4 * Health / 50) * b.Hash;
}
public double Kj(bool overclock) => Dead ? 0 : Chip.Kj * (overclock ? Tuning.OcKj : 1);
public double Kj(ModuleBonus b) => Dead ? 0 : Chip.Kj * b.Kj;
/// <summary>今晚的暴毙概率。</summary>
public double DeathChance(bool overclock)
public double DeathChance(ModuleBonus b)
{
double p = Tuning.DeathBase + Tuning.DeathWorn * Math.Pow(1 - Health / 100, 2);
p *= Label.DeathFactor * (FakeOf != null ? Tuning.FakeDeathFactor : 1) * (overclock ? Tuning.OcDeath : 1);
return p;
return p * Label.DeathFactor * (FakeOf != null ? Tuning.FakeDeathFactor : 1) * b.Death;
}
/// <summary>一夜的损耗与暴毙判定;返回是否在今晚暴毙。</summary>
public bool Night(bool overclock, Rng rng)
public bool Night(ModuleBonus b, Rng rng)
{
if (Dead) return false;
Health = Math.Max(0, Health - Label.WearFactor * (overclock ? Tuning.OcWear : 1));
if (rng.Chance(DeathChance(overclock)))
Health = Math.Max(0, Health - Label.WearFactor * b.Wear);
if (rng.Chance(DeathChance(b)))
{
Dead = true;
return true;
+2 -1
View File
@@ -16,7 +16,8 @@ namespace Decentralized.Domain
public const double NpcAdj = 0.05;
public const double PoolFee = 0.03;
public const int RigPrice = 400, RigKj = 2, RigSlots = 4;
public const double OcHash = 1.25, OcKj = 1.40, OcWear = 1.3, OcDeath = 1.2;
/// <summary>矿架上模块百分比的折算系数(原版机器用 2–4),以及各项上限。</summary>
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;
public const double RefurbishHealth = 30;
+42 -18
View File
@@ -120,9 +120,11 @@ namespace Decentralized
private static GameItem CreateRig()
{
var battery = new GameSlotInventory();
var modules = new GameGridInventory(MachineHelper.DEFAULT_MODULE_BAY_CONFIG, 7);
var cards = new GameGridInventory(2, 4);
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"));
var rig = ItemDirectory.CreateEmptyItem(Rig).SetContentWindow(window);
rig.SetSpriteAndShapeFromMod(ModInfo.Id, "rig");
@@ -132,41 +134,63 @@ namespace Decentralized
rig.EnableTag("CONTAINER_TAG");
rig.EnableTag("STANDARD_MACHINE_TAG");
MachineryHelper.InitMachinery(rig, Tuning.RigKj);
// 游戏的通用模块槽(含模块相邻效果);模块变动后游戏会按原版公式改写耗电,再由 UpdateRig 按本 mod 的规则重算
MachineHelper.SetupModuleBay(modules, rig, () => UpdateRig(rig, modules, cards));
cards.mayInventoryAddItemFunc = (item, _) => GeneralHelper.IsItemOwned(item) && IsGpu(item) && !item.IsTag(DeadTag);
cards.onSlotAddItemEndFunc = (_, _, _) => UpdateRig(rig, cards);
cards.onSlotRemoveItemEndFunc = (_, _, _) => UpdateRig(rig, cards);
rig.onLoaded = _ => UpdateRig(rig, cards);
rig.onCycleEndEarlySlotItemFunc = (_, _, _) => Log.Guard("矿架扣电", () => NightDraw(rig, battery, cards));
cards.onSlotAddItemEndFunc = (_, _, _) => UpdateRig(rig, modules, cards);
cards.onSlotRemoveItemEndFunc = (_, _, _) => UpdateRig(rig, modules, cards);
rig.onLoaded = _ => UpdateRig(rig, modules, cards);
rig.onCycleEndEarlySlotItemFunc = (_, _, _) => Log.Guard("矿架扣电", () => NightDraw(rig, battery, modules, cards));
ModHelper.SetSource(rig, ModInfo.Id, ModInfo.Name);
UpdateRig(rig, cards);
UpdateRig(rig, modules, cards);
return rig;
}
public static List<GameItem> Cards(GameInventory cards) => cards.childItems.Where(IsGpu).ToList();
private static int RigKj(GameInventory cards) =>
Tuning.RigKj + Mathf.CeilToInt((float)Cards(cards).Select(ToUnit).Sum(u => u.Kj(false)));
private static void UpdateRig(GameItem rig, GameInventory cards)
/// <summary>按模块槽里的模块重新汇总三项百分比(游戏自己的公式,含相邻效果),换算成本 mod 的模块效果。</summary>
public static ModuleBonus Bonus(GameItem rig, GameInventory modules)
{
int kj = RigKj(cards);
rig.ModifyTag(MachineryHelper.MACHINERY_DRAWN_POWER, s => s.Enable().SetInt(kj));
double hash = Cards(cards).Select(ToUnit).Sum(u => u.Hash(false));
rig.shortDescription = L.T("item.rig.desc", Fmt.Num(hash * 10), kj);
ModuleHelper.ComputeModuleEffect(modules.childItems, rig);
return ModuleBonus.From(
rig.GetTagReadonly(ModuleHelper.TOTAL_PERCENTAGE_PERFORMANCE_BONUS_INT).GetInt(),
rig.GetTagReadonly(ModuleHelper.TOTAL_PERCENTAGE_POWER_BONUS_INT).GetInt(),
rig.GetTagReadonly(ModuleHelper.TOTAL_PERCENTAGE_QUALITY_BONUS_INT).GetInt());
}
/// <summary>夜间早段:按矿架上的显卡总耗电从电池扣电;扣电成功,登记今晚参与挖矿的显卡。</summary>
private static void NightDraw(GameItem rig, GameSlotInventory battery, GameInventory cards)
private static int RigKj(GameInventory cards, ModuleBonus b) =>
Tuning.RigKj + Mathf.CeilToInt((float)Cards(cards).Select(ToUnit).Sum(u => u.Kj(b)));
private static void UpdateRig(GameItem rig, GameInventory modules, GameInventory cards)
{
var b = Bonus(rig, modules);
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);
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));
}
private static string Pct(double mult)
{
int p = (int)Math.Round((mult - 1) * 100);
return p > 0 ? "+" + p + "%" : p + "%";
}
/// <summary>夜间早段:按矿架上的显卡总耗电(含模块效果)从电池扣电;扣电成功,登记今晚参与挖矿的显卡和模块效果。</summary>
private static void NightDraw(GameItem rig, GameSlotInventory battery, GameInventory modules, GameInventory cards)
{
var list = Cards(cards).Where(c => !c.IsTag(DeadTag)).ToList();
Session.TonightRigs++;
if (list.Count == 0) return;
int kj = RigKj(cards);
var b = Bonus(rig, modules);
int kj = RigKj(cards, b);
if (battery.childItems.Count == 0 || !PowerHelper.DrawPowerSource(battery.childItem, kj)) return;
Session.TonightPoweredRigs++;
Session.TonightKj += kj;
Session.TonightCards.AddRange(list);
UpdateRig(rig, cards);
foreach (var c in list) Session.TonightCards.Add(new KeyValuePair<GameItem, ModuleBonus>(c, b));
UpdateRig(rig, modules, cards);
}
/// <summary>
@@ -7,6 +7,8 @@ item.terminal.name,Trading Terminal
item.terminal.desc,Wallet, 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."
rig.label.modules,Modules
item.rig.modules,"Modules: hashrate {0}, power draw {1}, wear {2}."
rig.label.cards,Graphics cards
item.gpu.name,{0} {1}
item.gpu.desc,"Hashrate {0} MH/s. Draws {1} kJ per day. Condition {2}%."
1 # Probably Decentralized localization table: English (en)
7 item.terminal.desc
8 item.rig.name
9 item.rig.desc
10 rig.label.modules
11 item.rig.modules
12 rig.label.cards
13 item.gpu.name
14 item.gpu.desc
@@ -6,6 +6,8 @@ item.terminal.name,交易终端
item.terminal.desc,钱包、交易所与夜间挖矿报告。
item.rig.name,矿架
item.rig.desc,可装 4 张显卡。算力 {0} MH/s。耗电 {1} kJ/天。
rig.label.modules,模块
item.rig.modules,模块:算力 {0},耗电 {1},损耗 {2}。
rig.label.cards,显卡
item.gpu.name,{0} {1}
item.gpu.desc,算力 {0} MH/s。耗电 {1} kJ/天。健康度 {2}%。
1 # Probably Decentralized 本地化表:简体中文(zh)
6 item.terminal.desc,钱包、交易所与夜间挖矿报告。
7 item.rig.name,矿架
8 item.rig.desc,可装 4 张显卡。算力 {0} MH/s。耗电 {1} kJ/天。
9 rig.label.modules,模块
10 item.rig.modules,模块:算力 {0},耗电 {1},损耗 {2}。
11 rig.label.cards,显卡
12 item.gpu.name,{0} {1}
13 item.gpu.desc,算力 {0} MH/s。耗电 {1} kJ/天。健康度 {2}%。
+4 -4
View File
@@ -86,8 +86,8 @@ namespace Decentralized
d.ActiveEvents = events;
d.Network.Availability = (events.Contains(MarketEvent.Blackout) ? 0.8 : 1) * (events.Contains(MarketEvent.LowerUnrest) ? 0.9 : 1);
var units = Session.TonightCards.Select(c => (item: c, unit: Items.ToUnit(c))).ToList();
double hash = units.Sum(x => x.unit.Hash(false));
var units = Session.TonightCards.Select(c => (item: c.Key, unit: Items.ToUnit(c.Key), bonus: c.Value)).ToList();
double hash = units.Sum(x => x.unit.Hash(x.bonus));
double mined = d.Network.Mine(hash);
if (mined > 0) d.Hot.Add(Assets.PSC, mined);
@@ -96,9 +96,9 @@ namespace Decentralized
Day = day, Rigs = Session.TonightRigs, PoweredRigs = Session.TonightPoweredRigs, Kj = Session.TonightKj,
Hash = hash, NetworkHash = d.Network.NpcHash * d.Network.Availability + hash, Mined = mined,
};
foreach (var (item, unit) in units)
foreach (var (item, unit, bonus) in units)
{
bool died = unit.Night(false, d.Rng);
bool died = unit.Night(bonus, d.Rng);
Items.Store(item, unit);
if (died) report.Burnt.Add(item.name);
}
+1 -1
View File
@@ -37,7 +37,7 @@ namespace Decentralized
public static SaveData Data;
/// <summary>今晚有电的矿架上的显卡(矿架在夜间早段扣电时登记,结算时用)。</summary>
public static readonly List<GameItem> TonightCards = new List<GameItem>();
public static readonly List<KeyValuePair<GameItem, ModuleBonus>> TonightCards = new List<KeyValuePair<GameItem, ModuleBonus>>();
public static int TonightRigs, TonightPoweredRigs;
public static double TonightKj;