终端窗口可调大小(默认 1200×900)与布局放宽;mod 设置:界面、剧情、难度

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 15:34:38 +08:00
1 parent fa8f416545
commit 861acac240
26 files changed
+302 -45

No files matched your search

+2
View File
@@ -4,6 +4,8 @@
### 第九阶段:剧情与收尾 ### 第九阶段:剧情与收尾
- 交易终端窗口加大:默认 1200×900,mod 设置里可选 828×680(旧大小)、1200×900、1400×1000。窗口大时左右栏、表格列、货架列数、K 线图(3 倍像素)、首页卡片、订单两栏、钱包表按多出的宽度放宽,文字放大到约 1.24 倍(上限 1.3,行高和商品卡同步)。
- mod 设置(分类:界面、剧情、难度):终端窗口大小;Tumbler 剧情开关;风险倍率(骗局、跑路、钱包投毒、地址劫持、钓鱼概率)和挖矿产出倍率,档位 ×0.5 / ×1 / ×1.5 / ×2,领域层 `Scales` 读取。分类名要等本地化表可用后再登记。
- 多语言:新增 de fr es pl ru ja 六种语言表(连同 zh en 共 8 种,对应游戏主菜单的语言)。英文表是源文,其他语言由 Gemini 分块译出(术语表 `docs/i18n-glossary.md` 在运营仓库,游戏已有的词用官方译名),未经人工校对,文件头有说明。键集合、占位符、`§ ¶ \\n` 标记由单元测试逐语言核对,非中日语言里不得出现汉字或假名。 - 多语言:新增 de fr es pl ru ja 六种语言表(连同 zh en 共 8 种,对应游戏主菜单的语言)。英文表是源文,其他语言由 Gemini 分块译出(术语表 `docs/i18n-glossary.md` 在运营仓库,游戏已有的词用官方译名),未经人工校对,文件头有说明。键集合、占位符、`§ ¶ \\n` 标记由单元测试逐语言核对,非中日语言里不得出现汉字或假名。
- 多语言布局:表格单元、按钮文字放不下时自动缩小字号(最小 68%)再省略,商品名限定两行内缩放;de fr es pl ru 的钱包表、手册章节列表、硬件钱包粘贴键按语言放宽;ru de 等少数过长标签改短(永续“加保证金”键、收益池列头)。 - 多语言布局:表格单元、按钮文字放不下时自动缩小字号(最小 68%)再省略,商品名限定两行内缩放;de fr es pl ru 的钱包表、手册章节列表、硬件钱包粘贴键按语言放宽;ru de 等少数过长标签改短(永续“加保证金”键、收益池列头)。
- 中文“匿名币”改称“隐私币”(英文 privacy coin),手册、黑市标签同步。 - 中文“匿名币”改称“隐私币”(英文 privacy coin),手册、黑市标签同步。
+15
View File
@@ -13,6 +13,21 @@ All coins, exchanges and events are fictional and have no real-world value.
| `tests/Decentralized.Domain.Tests` | Console test runner: `dotnet run --project tests/Decentralized.Domain.Tests` | | `tests/Decentralized.Domain.Tests` | Console test runner: `dotnet run --project tests/Decentralized.Domain.Tests` |
| `art/sprites.py`, `art/big.py`, `art/big_cases.py` | Generate the item sprites, the large inspection art and its overlays, and the terminal icons into `src/Decentralized/ModFiles/Textures/Items`. | | `art/sprites.py`, `art/big.py`, `art/big_cases.py` | Generate the item sprites, the large inspection art and its overlays, and the terminal icons into `src/Decentralized/ModFiles/Textures/Items`. |
## Settings
Mod settings are in the game's mod list (select the mod, gear button); they apply to all saves.
| Section | Setting | Values |
|---|---|---|
| Interface | Trading terminal size | 828×680, 1200×900 (default), 1400×1000. Layout and text scale with the window; applies when the terminal is next opened. |
| Story | Tumbler storyline | on / off. Off stops new visits; progress already made is kept. |
| Difficulty | Risk multiplier | ×0.5 / ×1 / ×1.5 / ×2 on the chance of scams, rug pulls, wallet poisoning, address hijacking and phishing. |
| Difficulty | Mining reward multiplier | ×0.5 / ×1 / ×1.5 / ×2 on the block reward paid to the player's rigs. |
## Languages
The interface follows the game language: en, zh, de, fr, es, pl, ru, ja. English is the source text; zh is hand-written; the other six are AI translations and have not been proofread. Tables are in `src/Decentralized/ModFiles/Localization/<code>.csv`.
## License ## License
MIT, see `LICENSE`. MIT, see `LICENSE`.
+2 -2
View File
@@ -114,7 +114,7 @@ namespace Decentralized.Domain
/// <summary>下单实付(信用点):托管 = 订单价 + 托管费;提前放款 = 订单价打折。</summary> /// <summary>下单实付(信用点):托管 = 订单价 + 托管费;提前放款 = 订单价打折。</summary>
public static int Pay(int price, PayMode m) => m == PayMode.Escrow ? price + EscrowFee(price) : (int)Math.Round(price * (1 - Tuning.BmAdvanceDiscount)); public static int Pay(int price, PayMode m) => m == PayMode.Escrow ? price + EscrowFee(price) : (int)Math.Round(price * (1 - Tuning.BmAdvanceDiscount));
public double RugChance => Math.Min(Tuning.BmRugMax, Tuning.BmRugBase + Held / 10000.0 * Tuning.BmRugPerTenK); public double RugChance => Scales.R(Math.Min(Tuning.BmRugMax, Tuning.BmRugBase + Held / 10000.0 * Tuning.BmRugPerTenK));
/// <summary>每周必有的"矿工路线"商家类型(从中抽 2 家),其余按全部类型抽。</summary> /// <summary>每周必有的"矿工路线"商家类型(从中抽 2 家),其余按全部类型抽。</summary>
private static readonly MerchantType[] Core = { MerchantType.GpuNew, MerchantType.GpuUsed, MerchantType.Rigs, MerchantType.Modules }; private static readonly MerchantType[] Core = { MerchantType.GpuNew, MerchantType.GpuUsed, MerchantType.Rigs, MerchantType.Modules };
@@ -232,7 +232,7 @@ namespace Decentralized.Domain
if (scam && rng.Chance(Tuning.BmForumScamHint)) Forum.Add("scam:" + s.Name); if (scam && rng.Chance(Tuning.BmForumScamHint)) Forum.Add("scam:" + s.Name);
} }
for (int i = 0; i < Sellers.Count; i++) Catalog.AddRange(MerchantGoods(i, Sellers[i], day, rep, rng, value)); for (int i = 0; i < Sellers.Count; i++) Catalog.AddRange(MerchantGoods(i, Sellers[i], day, rep, rng, value));
if (RugChance > Tuning.BmRugBase * Tuning.BmForumRugFactor) Forum.Add("rug"); if (RugChance > Scales.R(Tuning.BmRugBase * Tuning.BmForumRugFactor)) Forum.Add("rug");
} }
/// <summary>目录里全部货按订单价算的总值(信用点),用来核对每周周转量。</summary> /// <summary>目录里全部货按订单价算的总值(信用点),用来核对每周周转量。</summary>
+1 -1
View File
@@ -165,7 +165,7 @@ namespace Decentralized.Domain
{ {
if (playerHash <= 0) return 0; if (playerHash <= 0) return 0;
double total = NpcHash * Availability + playerHash; double total = NpcHash * Availability + playerHash;
return Reward * playerHash / total * (1 - Tuning.PoolFee); return Reward * playerHash / total * (1 - Tuning.PoolFee) * Scales.Mining;
} }
public enum EraStep { None, Warn, Start } public enum EraStep { None, Warn, Start }
+1 -1
View File
@@ -81,7 +81,7 @@ namespace Decentralized.Domain
var results = new List<MixResult>(); var results = new List<MixResult>();
foreach (var d in Pending.ToList()) foreach (var d in Pending.ToList())
{ {
if (rng.Chance(Math.Min(1, Tuning.MixerRugChance * StoryMods.Current.MixerRug))) if (rng.Chance(Scales.R(Tuning.MixerRugChance * StoryMods.Current.MixerRug)))
{ {
Pending.Remove(d); Pending.Remove(d);
results.Add(new MixResult { Rugged = true, Asset = d.Asset, Amount = d.Amount }); results.Add(new MixResult { Rugged = true, Asset = d.Asset, Amount = d.Amount });
+26
View File
@@ -0,0 +1,26 @@
using System;
namespace Decentralized.Domain
{
/// <summary>mod 设置里的倍率(全局,不随存档)。接入层读设置后写进来;领域层在对应概率和产出处乘上。</summary>
public static class Scales
{
/// <summary>风险倍率:骗局、跑路、钱包投毒、地址劫持、钓鱼的发生概率。</summary>
public static double Risk = 1;
/// <summary>挖矿产出倍率:玩家矿机拿到的区块奖励。</summary>
public static double Mining = 1;
/// <summary>概率乘风险倍率,封顶 1。</summary>
public static double R(double chance) => Math.Min(1, chance * Risk);
/// <summary>设置里的倍率档位(“×1.5”)。</summary>
public static readonly string[] Choices = { "×0.5", "×1", "×1.5", "×2" };
public const string Default = "×1";
public static double Parse(string s)
{
if (s != null && s.StartsWith("×") && double.TryParse(s.Substring(1), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var v) && v >= 0) return v;
return 1;
}
}
}
+2 -2
View File
@@ -75,7 +75,7 @@ namespace Decentralized.Domain
/// <summary>新空投。钓鱼的名字常是正规名字的仿冒(一个字符不同),要求对一种币无上限授权,奖励看起来更高。</summary> /// <summary>新空投。钓鱼的名字常是正规名字的仿冒(一个字符不同),要求对一种币无上限授权,奖励看起来更高。</summary>
public Airdrop NewAirdrop(int day, Rng rng, string[] assets) public Airdrop NewAirdrop(int day, Rng rng, string[] assets)
{ {
bool scam = rng.Chance(Tuning.AirdropScam); bool scam = rng.Chance(Scales.R(Tuning.AirdropScam));
string name = AirdropNames[rng.Range(0, AirdropNames.Length - 1)]; string name = AirdropNames[rng.Range(0, AirdropNames.Length - 1)];
if (scam && rng.Chance(0.6)) name = Typo(name, rng); if (scam && rng.Chance(0.6)) name = Typo(name, rng);
string asset = assets[rng.Range(0, assets.Length - 1)]; string asset = assets[rng.Range(0, assets.Length - 1)];
@@ -183,7 +183,7 @@ namespace Decentralized.Domain
} }
foreach (var p in Pools.Where(x => !x.Closed).ToList()) foreach (var p in Pools.Where(x => !x.Closed).ToList())
{ {
if (p.Scam && p.Total > 0 && day > p.OpenDay && rng.Chance(Tuning.YieldRugNightly)) if (p.Scam && p.Total > 0 && day > p.OpenDay && rng.Chance(Scales.R(Tuning.YieldRugNightly)))
{ {
foreach (var kv in p.Deposits) evs.Add(new ScamEvent { Kind = "rug", Name = p.Name, Wallet = kv.Key, Asset = Assets.CRC, Amount = kv.Value }); foreach (var kv in p.Deposits) evs.Add(new ScamEvent { Kind = "rug", Name = p.Name, Wallet = kv.Key, Asset = Assets.CRC, Amount = kv.Value });
foreach (var q in p.Pending) evs.Add(new ScamEvent { Kind = "rug", Name = p.Name, Wallet = q.Wallet, Asset = Assets.CRC, Amount = q.Amount }); foreach (var q in p.Pending) evs.Add(new ScamEvent { Kind = "rug", Name = p.Name, Wallet = q.Wallet, Asset = Assets.CRC, Amount = q.Amount });
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Decentralized.Domain
/// <summary>今天来的贩子的类型:骗子越往后越多。</summary> /// <summary>今天来的贩子的类型:骗子越往后越多。</summary>
public static DealerKind Kind(int day, Rng rng) public static DealerKind Kind(int day, Rng rng)
{ {
double scam = Math.Min(Tuning.UsedScamMax, Tuning.UsedScamBase + day * Tuning.UsedScamPerDay); double scam = Scales.R(Math.Min(Tuning.UsedScamMax, Tuning.UsedScamBase + day * Tuning.UsedScamPerDay));
double r = rng.NextDouble(); double r = rng.NextDouble();
return r < scam ? DealerKind.Scammer : r < scam + Tuning.UsedMiddlemanShare ? DealerKind.Middleman : DealerKind.Honest; return r < scam ? DealerKind.Scammer : r < scam + Tuning.UsedMiddlemanShare ? DealerKind.Middleman : DealerKind.Honest;
} }
+1 -1
View File
@@ -39,7 +39,7 @@ namespace Decentralized
public static GameItem Make(string id, string model, string fakeOf, double health) public static GameItem Make(string id, string model, string fakeOf, double health)
{ {
if (id.StartsWith("gpu:")) return Hardware.Specs[model].Asic ? AsicItems.New(model, health) : Items.NewCard(model, health, fakeOf, Session.Data.Rng); if (id.StartsWith("gpu:")) return Hardware.Specs[model].Asic ? AsicItems.New(model, health) : Items.NewCard(model, health, fakeOf, Session.Data.Rng);
if (id == Items.HwWallet) return Items.NewHwWallet(Session.Data.Rng.Chance(Tuning.WalletPoisonShare)); if (id == Items.HwWallet) return Items.NewHwWallet(Session.Data.Rng.Chance(Scales.R(Tuning.WalletPoisonShare)));
return ModHelper.SpawnItem(id); return ModHelper.SpawnItem(id);
} }
+1 -1
View File
@@ -58,7 +58,7 @@ namespace Decentralized
public const int Days = 30, Step = 6, Body = 5, PriceH = 70, Gap = 3, VolH = 16, Pad = 2, AxisW = 22, DateH = 7; public const int Days = 30, Step = 6, Body = 5, PriceH = 70, Gap = 3, VolH = 16, Pad = 2, AxisW = 22, DateH = 7;
public const int W = Days * Step + Pad * 2 + AxisW, H = PriceH + Gap + VolH + Pad * 2 + DateH; public const int W = Days * Step + Pad * 2 + AxisW, H = PriceH + Gap + VolH + Pad * 2 + DateH;
public const float Scale = 2f; public static float Scale => TerminalWindow.ChartScale;
private static readonly Color32 Bg = new Color32(30, 31, 34, 255), Grid = new Color32(48, 50, 54, 255); private static readonly Color32 Bg = new Color32(30, 31, 34, 255), Grid = new Color32(48, 50, 54, 255);
private static readonly Color32 Up = new Color32(108, 190, 112, 255), Down = new Color32(214, 94, 88, 255); private static readonly Color32 Up = new Color32(108, 190, 112, 255), Down = new Color32(214, 94, 88, 255);
+1
View File
@@ -15,6 +15,7 @@ namespace Decentralized
Items.OpenHandbook = HandbookWindow.Open; Items.OpenHandbook = HandbookWindow.Open;
Items.AddWallet = TerminalWindow.AddWallet; Items.AddWallet = TerminalWindow.AddWallet;
Items.CanAddWallet = TerminalWindow.CanAddWallet; Items.CanAddWallet = TerminalWindow.CanAddWallet;
Settings.DefineWhenReady(ModHelper.GetConfig(ModInfo.Id));
Unlock.Register(); Unlock.Register();
Night.Subscribe(); Night.Subscribe();
DealerStock.Subscribe(); DealerStock.Subscribe();
@@ -809,3 +809,14 @@ cart.partial,{0} Artikel bezahlt; {1} fehlgeschlagen (Bestand oder Guthaben).
black.info.accepts,Akzeptiert: {0} black.info.accepts,Akzeptiert: {0}
black.info.advance.yes,Vorzeitige Freigabe akzeptiert black.info.advance.yes,Vorzeitige Freigabe akzeptiert
black.info.advance.no,Keine vorzeitige Freigabe black.info.advance.no,Keine vorzeitige Freigabe
settings.section.ui,Oberfläche
settings.section.story,Handlung
settings.section.difficulty,Schwierigkeit
settings.terminal_size,Größe des Handelsterminals
settings.terminal_size_desc,Fenstergröße in Oberflächeneinheiten (Breite × Höhe). Gilt beim nächsten Öffnen des Terminals.
settings.tumbler_story,Tumbler-Handlung
settings.tumbler_story_desc,Tumblers Besuche und der Auftrag mit der Schlüsselscheibe. Ausgeschaltet gibt es keine neuen Besuche; erreichter Fortschritt bleibt.
settings.risk_scale,Risikofaktor
settings.risk_scale_desc,"Multipliziert die Wahrscheinlichkeit von Betrug, Rug Pulls, Wallet-Vergiftung, Adressumleitung und Phishing. Gilt für alle Spielstände."
settings.mining_scale,Mining-Ertragsfaktor
settings.mining_scale_desc,Multipliziert die Blockbelohnung der Mining-Geräte. Gilt für alle Spielstände.
1 # Probably Decentralized localization table: German (de)
809 black.info.accepts
810 black.info.advance.yes
811 black.info.advance.no
812 settings.section.ui
813 settings.section.story
814 settings.section.difficulty
815 settings.terminal_size
816 settings.terminal_size_desc
817 settings.tumbler_story
818 settings.tumbler_story_desc
819 settings.risk_scale
820 settings.risk_scale_desc
821 settings.mining_scale
822 settings.mining_scale_desc
@@ -809,3 +809,14 @@ cart.partial,"Paid for {0} items; {1} failed (stock or balance)."
black.info.accepts,Takes: {0} black.info.accepts,Takes: {0}
black.info.advance.yes,advance release accepted black.info.advance.yes,advance release accepted
black.info.advance.no,no advance release black.info.advance.no,no advance release
settings.section.ui,Interface
settings.section.story,Story
settings.section.difficulty,Difficulty
settings.terminal_size,Trading terminal size
settings.terminal_size_desc,Window size in interface units (width × height). Applies the next time the terminal opens.
settings.tumbler_story,Tumbler storyline
settings.tumbler_story_desc,Tumbler's visits and the keydisk errand. Turning this off stops new visits; progress already made is kept.
settings.risk_scale,Risk multiplier
settings.risk_scale_desc,"Multiplies the chance of scams, rug pulls, wallet poisoning, address hijacking and phishing. Applies to all saves."
settings.mining_scale,Mining reward multiplier
settings.mining_scale_desc,Multiplies the block reward paid to mining rigs. Applies to all saves.
1 # Probably Decentralized localization table: English (en)
809 black.info.accepts
810 black.info.advance.yes
811 black.info.advance.no
812 settings.section.ui
813 settings.section.story
814 settings.section.difficulty
815 settings.terminal_size
816 settings.terminal_size_desc
817 settings.tumbler_story
818 settings.tumbler_story_desc
819 settings.risk_scale
820 settings.risk_scale_desc
821 settings.mining_scale
822 settings.mining_scale_desc
@@ -809,3 +809,14 @@ cart.partial,Artículos pagados: {0}; fallidos: {1} (stock o saldo).
black.info.accepts,Acepta: {0} black.info.accepts,Acepta: {0}
black.info.advance.yes,acepta liberación anticipada black.info.advance.yes,acepta liberación anticipada
black.info.advance.no,no acepta liberación anticipada black.info.advance.no,no acepta liberación anticipada
settings.section.ui,Interfaz
settings.section.story,Historia
settings.section.difficulty,Dificultad
settings.terminal_size,Tamaño del terminal de comercio
settings.terminal_size_desc,Tamaño de la ventana en unidades de interfaz (ancho × alto). Se aplica al abrir el terminal de nuevo.
settings.tumbler_story,Trama de Tumbler
settings.tumbler_story_desc,"Visitas de Tumbler y el encargo del disco de claves. Desactivada, no habrá visitas nuevas; el progreso logrado se conserva."
settings.risk_scale,Multiplicador de riesgo
settings.risk_scale_desc,"Multiplica la probabilidad de estafas, rug pulls, envenenamiento de billetera, secuestro de direcciones y phishing. Se aplica a todas las partidas."
settings.mining_scale,Multiplicador de minería
settings.mining_scale_desc,Multiplica la recompensa de bloque que reciben los equipos de minería. Se aplica a todas las partidas.
1 # Probably Decentralized localization table: Spanish (es)
809 black.info.accepts
810 black.info.advance.yes
811 black.info.advance.no
812 settings.section.ui
813 settings.section.story
814 settings.section.difficulty
815 settings.terminal_size
816 settings.terminal_size_desc
817 settings.tumbler_story
818 settings.tumbler_story_desc
819 settings.risk_scale
820 settings.risk_scale_desc
821 settings.mining_scale
822 settings.mining_scale_desc
@@ -809,3 +809,14 @@ cart.partial,Articles payés : {0} ; {1} en échec (stock ou solde).
black.info.accepts,Accepte : {0} black.info.accepts,Accepte : {0}
black.info.advance.yes,déblocage anticipé accepté black.info.advance.yes,déblocage anticipé accepté
black.info.advance.no,pas de déblocage anticipé black.info.advance.no,pas de déblocage anticipé
settings.section.ui,Interface
settings.section.story,Histoire
settings.section.difficulty,Difficulté
settings.terminal_size,Taille du terminal de trading
settings.terminal_size_desc,Taille de la fenêtre en unités d'interface (largeur × hauteur). Appliquée à la prochaine ouverture du terminal.
settings.tumbler_story,Intrigue Tumbler
settings.tumbler_story_desc,"Visites de Tumbler et mission de la clé. Désactivée, plus aucune nouvelle visite ; la progression acquise est conservée."
settings.risk_scale,Multiplicateur de risque
settings.risk_scale_desc,"Multiplie la probabilité d'arnaques, de rug pulls, d'empoisonnement de portefeuille, de détournement d'adresse et d'hameçonnage. S'applique à toutes les sauvegardes."
settings.mining_scale,Multiplicateur de gains de minage
settings.mining_scale_desc,Multiplie la récompense de bloc versée aux machines de minage. S'applique à toutes les sauvegardes.
1 # Probably Decentralized localization table: French (fr)
809 black.info.accepts
810 black.info.advance.yes
811 black.info.advance.no
812 settings.section.ui
813 settings.section.story
814 settings.section.difficulty
815 settings.terminal_size
816 settings.terminal_size_desc
817 settings.tumbler_story
818 settings.tumbler_story_desc
819 settings.risk_scale
820 settings.risk_scale_desc
821 settings.mining_scale
822 settings.mining_scale_desc
@@ -809,3 +809,14 @@ cart.partial,{0} 件を決済、{1} 件が失敗(在庫または残高不足
black.info.accepts,対応通貨:{0} black.info.accepts,対応通貨:{0}
black.info.advance.yes,事前リリース対応 black.info.advance.yes,事前リリース対応
black.info.advance.no,事前リリース非対応 black.info.advance.no,事前リリース非対応
settings.section.ui,インターフェース
settings.section.story,ストーリー
settings.section.difficulty,難易度
settings.terminal_size,取引ターミナルのサイズ
settings.terminal_size_desc,ウィンドウサイズ(幅×高さ、UI 単位)。次にターミナルを開いたときに反映されます。
settings.tumbler_story,Tumbler のストーリー
settings.tumbler_story_desc,Tumbler の訪問とキーディスクの依頼。オフにすると新たな訪問はなくなり、進行済みの分は保持されます。
settings.risk_scale,リスク倍率
settings.risk_scale_desc,詐欺、ラグプル、ウォレット汚染、アドレス乗っ取り、フィッシングの発生確率にかかる倍率。すべてのセーブに適用されます。
settings.mining_scale,採掘報酬倍率
settings.mining_scale_desc,採掘機器が受け取るブロック報酬にかかる倍率。すべてのセーブに適用されます。
1 # Probably Decentralized localization table: Japanese (ja)
809 black.info.accepts,対応通貨:{0}
810 black.info.advance.yes,事前リリース対応
811 black.info.advance.no,事前リリース非対応
812 settings.section.ui,インターフェース
813 settings.section.story,ストーリー
814 settings.section.difficulty,難易度
815 settings.terminal_size,取引ターミナルのサイズ
816 settings.terminal_size_desc,ウィンドウサイズ(幅×高さ、UI 単位)。次にターミナルを開いたときに反映されます。
817 settings.tumbler_story,Tumbler のストーリー
818 settings.tumbler_story_desc,Tumbler の訪問とキーディスクの依頼。オフにすると新たな訪問はなくなり、進行済みの分は保持されます。
819 settings.risk_scale,リスク倍率
820 settings.risk_scale_desc,詐欺、ラグプル、ウォレット汚染、アドレス乗っ取り、フィッシングの発生確率にかかる倍率。すべてのセーブに適用されます。
821 settings.mining_scale,採掘報酬倍率
822 settings.mining_scale_desc,採掘機器が受け取るブロック報酬にかかる倍率。すべてのセーブに適用されます。
@@ -809,3 +809,14 @@ cart.partial,Opłacono: {0} szt.; nieudane: {1} szt. (brak zapasów lub salda).
black.info.accepts,Akceptuje: {0} black.info.accepts,Akceptuje: {0}
black.info.advance.yes,akceptowana wypłata zaliczkowa black.info.advance.yes,akceptowana wypłata zaliczkowa
black.info.advance.no,brak wypłaty zaliczkowej black.info.advance.no,brak wypłaty zaliczkowej
settings.section.ui,Interfejs
settings.section.story,Fabuła
settings.section.difficulty,Poziom trudności
settings.terminal_size,Rozmiar terminala handlowego
settings.terminal_size_desc,Rozmiar okna w jednostkach interfejsu (szerokość × wysokość). Obowiązuje po następnym otwarciu terminala.
settings.tumbler_story,Wątek Tumblera
settings.tumbler_story_desc,Wizyty Tumblera i zlecenie z dyskiem kluczy. Po wyłączeniu nie będzie nowych wizyt; dotychczasowy postęp zostaje.
settings.risk_scale,Mnożnik ryzyka
settings.risk_scale_desc,"Mnoży szanse na oszustwa, rug pulle, zatrucie portfela, podmianę adresu i phishing. Dotyczy wszystkich zapisów."
settings.mining_scale,Mnożnik wydobycia
settings.mining_scale_desc,Mnoży nagrodę za blok wypłacaną koparkom. Dotyczy wszystkich zapisów.
1 # Probably Decentralized localization table: Polish (pl)
809 black.info.accepts
810 black.info.advance.yes
811 black.info.advance.no
812 settings.section.ui
813 settings.section.story
814 settings.section.difficulty
815 settings.terminal_size
816 settings.terminal_size_desc
817 settings.tumbler_story
818 settings.tumbler_story_desc
819 settings.risk_scale
820 settings.risk_scale_desc
821 settings.mining_scale
822 settings.mining_scale_desc
@@ -809,3 +809,14 @@ cart.partial,Оплачено: {0} шт.; не удалось: {1} шт. (нет
black.info.accepts,Принимает: {0} black.info.accepts,Принимает: {0}
black.info.advance.yes,досрочная выплата принимается black.info.advance.yes,досрочная выплата принимается
black.info.advance.no,без досрочной выплаты black.info.advance.no,без досрочной выплаты
settings.section.ui,Интерфейс
settings.section.story,Сюжет
settings.section.difficulty,Сложность
settings.terminal_size,Размер торгового терминала
settings.terminal_size_desc,Размер окна в единицах интерфейса (ширина × высота). Применяется при следующем открытии терминала.
settings.tumbler_story,Сюжет Tumbler
settings.tumbler_story_desc,Визиты Tumbler и поручение с ключ-диском. При отключении новых визитов не будет; достигнутый прогресс сохраняется.
settings.risk_scale,Множитель риска
settings.risk_scale_desc,"Умножает вероятность мошенничества, рагпулов, отравления кошелька, подмены адреса и фишинга. Действует для всех сохранений."
settings.mining_scale,Множитель добычи
settings.mining_scale_desc,"Умножает награду за блок, получаемую майнинг-устройствами. Действует для всех сохранений."
1 # Probably Decentralized localization table: Russian (ru)
809 black.info.accepts
810 black.info.advance.yes
811 black.info.advance.no
812 settings.section.ui
813 settings.section.story
814 settings.section.difficulty
815 settings.terminal_size
816 settings.terminal_size_desc
817 settings.tumbler_story
818 settings.tumbler_story_desc
819 settings.risk_scale
820 settings.risk_scale_desc
821 settings.mining_scale
822 settings.mining_scale_desc
@@ -808,3 +808,14 @@ cart.partial,"已结算 {0} 件,{1} 件没成(库存或余额不足)。"
black.info.accepts,收币:{0} black.info.accepts,收币:{0}
black.info.advance.yes,接受提前放款 black.info.advance.yes,接受提前放款
black.info.advance.no,不接受提前放款 black.info.advance.no,不接受提前放款
settings.section.ui,界面
settings.section.story,剧情
settings.section.difficulty,难度
settings.terminal_size,交易终端窗口大小
settings.terminal_size_desc,窗口尺寸(宽×高,界面单位)。下次打开终端时生效。
settings.tumbler_story,Tumbler 剧情
settings.tumbler_story_desc,Tumbler 上门与藏密钥盘的支线。关闭后不再有新的上门,已有进度保留。
settings.risk_scale,风险倍率
settings.risk_scale_desc,骗局、跑路、钱包投毒、地址劫持和钓鱼的发生概率倍数。对所有存档生效。
settings.mining_scale,挖矿产出倍率
settings.mining_scale_desc,挖矿设备拿到的区块奖励倍数。对所有存档生效。
1 # Probably Decentralized 本地化表:简体中文(zh)
808 black.info.accepts
809 black.info.advance.yes
810 black.info.advance.no
811 settings.section.ui
812 settings.section.story
813 settings.section.difficulty
814 settings.terminal_size
815 settings.terminal_size_desc
816 settings.tumbler_story
817 settings.tumbler_story_desc
818 settings.risk_scale
819 settings.risk_scale_desc
820 settings.mining_scale
821 settings.mining_scale_desc
+1 -1
View File
@@ -105,7 +105,7 @@ namespace Decentralized
TxRules.Record(w.History, new TxRecord { Addr = fake, Asset = Assets.PSC, Amount = 0, Day = day + 1, In = true }); TxRules.Record(w.History, new TxRecord { Addr = fake, Asset = Assets.PSC, Amount = 0, Day = day + 1, In = true });
Log.Info($"地址投毒:{p.Target} 的相似地址 {fake} 向 {w.Address} 转入 0 币"); Log.Info($"地址投毒:{p.Target} 的相似地址 {fake} 向 {w.Address} 转入 0 币");
} }
if (d.Rng.Chance(Tuning.TxPhishChance)) if (d.Rng.Chance(Scales.R(Tuning.TxPhishChance)))
{ {
string fake = ChainAddress.Make(d.Rng.NextUInt()); string fake = ChainAddress.Make(d.Rng.NextUInt());
if (Unlock.BlackUnlocked && d.Rng.Chance(0.5)) d.Black.Forum.Add("phish:" + fake); if (Unlock.BlackUnlocked && d.Rng.Chance(0.5)) d.Black.Forum.Add("phish:" + fake);
+7 -1
View File
@@ -184,7 +184,7 @@ namespace Decentralized
} }
/// <summary>应用整套 PLEDGER 皮肤:机身、边框、墨水屏、刻字、文字颜色、触控键、滚动条与数据表行。</summary> /// <summary>应用整套 PLEDGER 皮肤:机身、边框、墨水屏、刻字、文字颜色、触控键、滚动条与数据表行。</summary>
public static void Apply(CustomUIWindow win, RectTransform screen, Image bezel, Image paper, ICollection<GameObject> picked, ICollection<Image> clear, IEnumerable<RowDef> rows = null) public static void Apply(CustomUIWindow win, RectTransform screen, Image bezel, Image paper, ICollection<GameObject> picked, ICollection<Image> clear, IEnumerable<RowDef> rows = null, float fontScale = 1f)
{ {
if (win?.root == null) return; if (win?.root == null) return;
Slice(win.root.GetComponent<Image>(), "ui_hw_shell", 10); Slice(win.root.GetComponent<Image>(), "ui_hw_shell", 10);
@@ -210,6 +210,12 @@ namespace Decentralized
if (content != null) content.childAlignment = TextAnchor.UpperCenter; if (content != null) content.childAlignment = TextAnchor.UpperCenter;
if (screen == null) return; if (screen == null) return;
if (fontScale > 1.001f)
foreach (var t in screen.GetComponentsInChildren<TextMeshProUGUI>(true))
{
t.fontSize *= fontScale;
if (t.enableAutoSizing) { t.fontSizeMax *= fontScale; t.fontSizeMin *= fontScale; }
}
// 收集所有表格行 root,避免按钮样式覆盖整行 // 收集所有表格行 root,避免按钮样式覆盖整行
var rowRoots = new HashSet<GameObject>(); var rowRoots = new HashSet<GameObject>();
+81
View File
@@ -0,0 +1,81 @@
using Decentralized.Domain;
using UnityEngine;
namespace Decentralized
{
/// <summary>
/// mod 设置(游戏自带的模组配置:主菜单“改装件”里选中 mod 后点齿轮)。
/// 文件在 persistentDataPath/ModConfigs/probably_decentralized.json,全局生效,不随存档。
/// 分类(Section):界面、剧情、难度。
/// </summary>
public static class Settings
{
/// <summary>终端窗口尺寸档位(宽×高,界面单位)。第一项是旧版大小。</summary>
public static readonly string[] TerminalSizes = { "828×680", "1200×900", "1400×1000" };
public const string DefaultTerminalSize = "1200×900";
private static ConfigEntry<string> _size, _risk, _mining;
private static ConfigEntry<bool> _tumbler;
/// <summary>Tumbler 上门剧情开关:关掉后不再有新的上门,已有进度保留。</summary>
public static bool TumblerStory => _tumbler?.Value ?? true;
public static float TerminalW => Size().w;
public static float TerminalH => Size().h;
private static (float w, float h) Size()
{
var s = _size?.Value ?? DefaultTerminalSize;
var p = s.Split('×');
if (p.Length == 2 && float.TryParse(p[0], out var w) && float.TryParse(p[1], out var h)) return (w, h);
return (1200f, 900f);
}
/// <summary>
/// 登记设置项。本地化表要等主菜单起来才读得到,而分类名是字符串(不是回调),所以等表可用了再登记。
/// </summary>
public static void DefineWhenReady(ModConfig config)
{
var go = new GameObject("pd_settings_defer");
Object.DontDestroyOnLoad(go);
go.AddComponent<Waiter>().Config = config;
}
private sealed class Waiter : MonoBehaviour
{
public ModConfig Config;
private int _frames;
private void Update()
{
_frames++;
if (_frames < 300 || (L.T("settings.section.ui") == "settings.section.ui" && _frames < 1800)) return;
Log.Guard("登记 mod 设置", () => Define(Config));
Destroy(gameObject);
}
}
public static void Define(ModConfig config)
{
if (config == null) { Log.Warn("[Settings] 拿不到模组配置,用默认值"); return; }
string ui = L.T("settings.section.ui");
_size = config.Define("terminal_size", DefaultTerminalSize, ui, () => L.T("settings.terminal_size"), TerminalSizes,
() => L.T("settings.terminal_size_desc"));
_size.OnChanged += _ => { if (CustomUIManager.Instance != null && CustomUIManager.Instance.IsOpen(TerminalWindow.Id)) TerminalWindow.Open(); };
_tumbler = config.Define("tumbler_story", true, L.T("settings.section.story"), () => L.T("settings.tumbler_story"), () => L.T("settings.tumbler_story_desc"));
string hard = L.T("settings.section.difficulty");
_risk = config.Define("risk_scale", Scales.Default, hard, () => L.T("settings.risk_scale"), Scales.Choices, () => L.T("settings.risk_scale_desc"));
_mining = config.Define("mining_scale", Scales.Default, hard, () => L.T("settings.mining_scale"), Scales.Choices, () => L.T("settings.mining_scale_desc"));
_risk.OnChanged += _ => Apply();
_mining.OnChanged += _ => Apply();
Apply();
}
private static void Apply()
{
Scales.Risk = Scales.Parse(_risk?.Value);
Scales.Mining = Scales.Parse(_mining?.Value);
Log.Info($"[Settings] 风险 ×{Scales.Risk},挖矿 ×{Scales.Mining}"); }
}
}
+53 -31
View File
@@ -16,8 +16,19 @@ namespace Decentralized
public static class TerminalWindow public static class TerminalWindow
{ {
public const string Id = "pd_terminal"; public const string Id = "pd_terminal";
private const float WinW = 828f, WinH = 680f, ScreenW = 768f, ScreenH = 578f, Bezel = 6f; private const float Bezel = 6f;
private const float LeftW = 420f, RightW = 310f; // 窗口尺寸取自 mod 设置(828×680 是基准,各页布局按多出来的宽高等比放宽)
private static float WinW => Settings.TerminalW;
private static float WinH => Settings.TerminalH;
private static float ScreenW => WinW - 60f;
private static float ScreenH => WinH - 102f;
private static float Ext => ScreenW - 768f;
private static float LeftW => 420f + Ext * 0.6f;
private static float RightW => 310f + Ext * 0.4f;
/// <summary>大窗口里文字放大(行高、卡片同步放大):基准窗口 1,1200 宽约 1.24,上限 1.3。</summary>
private static float F => Mathf.Clamp(1f + Ext * 0.00065f, 1f, 1.3f);
public static float ChartScale => LeftW >= 634f ? 3f : 2f;
private static float[] Sc(float[] b) { float k = LeftW / 420f; return b.Select(x => x * k).ToArray(); }
private enum Page { Home, Bank, Poinbase, Web3, Wallets } private enum Page { Home, Bank, Poinbase, Web3, Wallets }
private static Page _page = Page.Home; private static Page _page = Page.Home;
@@ -69,7 +80,7 @@ namespace Decentralized
b.End(); b.End();
b.End(); b.End();
var win = b.Show(); var win = b.Show();
Log.Guard("交易终端外观", () => PledgerSkin.Apply(win, screen, bezel, paper, _picked, _clear, _rows)); Log.Guard("交易终端外观", () => PledgerSkin.Apply(win, screen, bezel, paper, _picked, _clear, _rows, F));
Log.Guard("购物车按钮", () => CartFab(win)); Log.Guard("购物车按钮", () => CartFab(win));
} }
@@ -84,7 +95,7 @@ namespace Decentralized
private static void Entry(CustomUIBuilder b, string icon, string name, string line1, string line2, Page target) private static void Entry(CustomUIBuilder b, string icon, string name, string line1, string line2, Page target)
{ {
b.BeginPanel(230f, 250f, TextAnchor.UpperCenter, 10f, 6f); b.BeginPanel((ScreenW - 80f) / 3f, 250f + (ScreenH - 578f) * 0.25f, TextAnchor.UpperCenter, 10f, 6f);
var cardImg = b.Last.image; var cardImg = b.Last.image;
cardImg.sprite = MachineryStyle.White; cardImg.sprite = MachineryStyle.White;
cardImg.color = PledgerSkin.RowNormal; cardImg.color = PledgerSkin.RowNormal;
@@ -116,7 +127,7 @@ namespace Decentralized
private static void Cell(CustomUIBuilder b, float w, string text, bool header = false, TextAnchor align = TextAnchor.MiddleLeft) private static void Cell(CustomUIBuilder b, float w, string text, bool header = false, TextAnchor align = TextAnchor.MiddleLeft)
{ {
b.BeginPanel(w, 26f, align, 0f, 0f); b.BeginPanel(w, 26f * F, align, 0f, 0f);
_clear.Add(b.Last.image); _clear.Add(b.Last.image);
b.AddLabel(header ? "<b>" + text + "</b>" : text); b.AddLabel(header ? "<b>" + text + "</b>" : text);
PledgerSkin.FitCurrent(b); PledgerSkin.FitCurrent(b);
@@ -125,7 +136,7 @@ namespace Decentralized
private static void TableRow(CustomUIBuilder b, float totalW, float[] widths, TextAnchor[] aligns, string[] cells, bool selected = false, Action onClick = null) private static void TableRow(CustomUIBuilder b, float totalW, float[] widths, TextAnchor[] aligns, string[] cells, bool selected = false, Action onClick = null)
{ {
b.BeginPanel(totalW, 28f, TextAnchor.MiddleLeft, 4f, 0f); b.BeginPanel(totalW, 28f * F, TextAnchor.MiddleLeft, 4f, 0f);
_rows.Add(new PledgerSkin.RowDef { Root = b.Last.root, Image = b.Last.image, Selected = selected, OnClick = onClick }); _rows.Add(new PledgerSkin.RowDef { Root = b.Last.root, Image = b.Last.image, Selected = selected, OnClick = onClick });
b.BeginRow(0f, TextAnchor.MiddleLeft); b.BeginRow(0f, TextAnchor.MiddleLeft);
for (int i = 0; i < widths.Length; i++) for (int i = 0; i < widths.Length; i++)
@@ -142,7 +153,7 @@ namespace Decentralized
private static void TableHeader(CustomUIBuilder b, float totalW, float[] widths, TextAnchor[] aligns, params string[] cells) private static void TableHeader(CustomUIBuilder b, float totalW, float[] widths, TextAnchor[] aligns, params string[] cells)
{ {
b.BeginPanel(totalW, 26f, TextAnchor.MiddleLeft, 4f, 0f); b.BeginPanel(totalW, 26f * F, TextAnchor.MiddleLeft, 4f, 0f);
_clear.Add(b.Last.image); _clear.Add(b.Last.image);
b.BeginRow(0f, TextAnchor.MiddleLeft); b.BeginRow(0f, TextAnchor.MiddleLeft);
for (int i = 0; i < widths.Length; i++) for (int i = 0; i < widths.Length; i++)
@@ -199,10 +210,10 @@ namespace Decentralized
AmlPreview(r); AmlPreview(r);
r.AddSpace(6f); r.AddSpace(6f);
r.AddButton(L.T("trade.deposit"), Deposit); r.AddButton(L.T("trade.deposit"), Deposit);
r.Last?.SetPreferredSize(RightW - 30f, 28f); r.Last?.SetPreferredSize(RightW - 30f, 28f * F);
r.AddSpace(4f); r.AddSpace(4f);
r.AddButton(L.T("trade.withdraw"), Withdraw); r.AddButton(L.T("trade.withdraw"), Withdraw);
r.Last?.SetPreferredSize(RightW - 30f, 28f); r.Last?.SetPreferredSize(RightW - 30f, 28f * F);
}); });
} }
@@ -216,7 +227,8 @@ namespace Decentralized
b.AddLabel(L.T("aml.taint", (int)Math.Round(taintRatio * 100)), taintRatio > 0.05 ? "aml_note" : "aml_dim"); b.AddLabel(L.T("aml.taint", (int)Math.Round(taintRatio * 100)), taintRatio > 0.05 ? "aml_note" : "aml_dim");
} }
private static readonly float[] StableW = { 80f, 100f, 220f }; private static readonly float[] StableWBase = new[] { 80f, 100f, 220f };
private static float[] StableW => Sc(StableWBase);
private static string StableStatus(Stablecoins st, string asset, int day) private static string StableStatus(Stablecoins st, string asset, int day)
{ {
@@ -250,7 +262,8 @@ namespace Decentralized
} }
} }
private static readonly float[] HoldW = { 80f, 100f, 110f, 110f }; private static readonly float[] HoldWBase = new[] { 80f, 100f, 110f, 110f };
private static float[] HoldW => Sc(HoldWBase);
private static readonly TextAnchor[] HoldAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static readonly TextAnchor[] HoldAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight };
private static void Holdings(CustomUIBuilder b, Wallet w, Func<string, bool> show) private static void Holdings(CustomUIBuilder b, Wallet w, Func<string, bool> show)
@@ -323,7 +336,8 @@ namespace Decentralized
private static string _perpProduct = "PSC"; private static string _perpProduct = "PSC";
private static bool _perpLong = true; private static bool _perpLong = true;
private static int _perpLev = 3; private static int _perpLev = 3;
private static readonly float[] PerpW = { 80f, 70f, 110f, 60f, 70f }; private static readonly float[] PerpWBase = new[] { 80f, 70f, 110f, 60f, 70f };
private static float[] PerpW => Sc(PerpWBase);
private static readonly TextAnchor[] PerpAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static readonly TextAnchor[] PerpAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight };
private static double PerpSpot(string p) => p == "SPARK" ? Session.Data.Stable.Spark : Session.Data.Market.Price; private static double PerpSpot(string p) => p == "SPARK" ? Session.Data.Stable.Spark : Session.Data.Market.Price;
@@ -409,7 +423,8 @@ namespace Decentralized
}); });
} }
private static readonly float[] PoolW = { 110f, 50f, 64f, 64f, 50f, 78f }; private static readonly float[] PoolWBase = new[] { 110f, 50f, 64f, 64f, 50f, 78f };
private static float[] PoolW => Sc(PoolWBase);
private static readonly TextAnchor[] PoolAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static readonly TextAnchor[] PoolAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight };
/// <summary>第三方收益池:日息、储备、时间锁、审计、我的存款,存入 / 取出(合约调用,签名)。是不是骗局看不见。</summary> /// <summary>第三方收益池:日息、储备、时间锁、审计、我的存款,存入 / 取出(合约调用,签名)。是不是骗局看不见。</summary>
@@ -804,7 +819,7 @@ namespace Decentralized
{ {
int tier = t; int tier = t;
r.AddButton(L.T("intel.tip.buy", (int)Math.Round(Tuning.IntelTipAcc[t] * 100), Fmt.Num(Tuning.IntelTipPrice[t] * m.MaxLoss)), () => IntelBuyTip(mk, tier)); r.AddButton(L.T("intel.tip.buy", (int)Math.Round(Tuning.IntelTipAcc[t] * 100), Fmt.Num(Tuning.IntelTipPrice[t] * m.MaxLoss)), () => IntelBuyTip(mk, tier));
r.Last?.SetPreferredSize(RightW - 30f, 28f); r.Last?.SetPreferredSize(RightW - 30f, 28f * F);
if (t < 2) r.AddSpace(2f); if (t < 2) r.AddSpace(2f);
} }
} }
@@ -985,9 +1000,11 @@ namespace Decentralized
{ {
var d = Session.Data; var d = Session.Data;
b.AddLabel(L.T("wallets.count", d.Wallets.Count, Tuning.WalletMax), "wallets_count_dim"); b.AddLabel(L.T("wallets.count", d.Wallets.Count, Tuning.WalletMax), "wallets_count_dim");
var cols = L.Wide ? new[] { 100f, 92f, 62f, 62f, 372f } : new[] { 100f, 110f, 85f, 70f, 375f }; bool wide = L.Wide && Ext < 100f;
float k = Ext < 100f ? 1f : (ScreenW - 28f) / 740f;
var cols = (wide ? new[] { 100f, 92f, 62f, 62f, 372f } : new[] { 100f, 110f, 85f, 70f, 375f }).Select(x => x * k).ToArray();
var aligns = new[] { TextAnchor.MiddleLeft, TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleLeft }; var aligns = new[] { TextAnchor.MiddleLeft, TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleLeft };
TableHeader(b, 740f, cols, aligns, L.T("wallets.col.name"), L.T("wallets.col.addr"), L.T("col.value"), L.T("wallets.col.taint"), ""); TableHeader(b, 740f * k, cols, aligns, L.T("wallets.col.name"), L.T("wallets.col.addr"), L.T("col.value"), L.T("wallets.col.taint"), "");
for (int i = 0; i < d.Wallets.Count; i++) for (int i = 0; i < d.Wallets.Count; i++)
{ {
var w = d.Wallets[i]; var w = d.Wallets[i];
@@ -995,7 +1012,7 @@ namespace Decentralized
bool isCurrent = idx == d.Current; bool isCurrent = idx == d.Current;
// 一行三块:数据格(整行反色的是这一块)、操作键(和顶栏页签一样的黑色触控键,放在纸面上)、连接状态靠右 // 一行三块:数据格(整行反色的是这一块)、操作键(和顶栏页签一样的黑色触控键,放在纸面上)、连接状态靠右
b.BeginRow(4f, TextAnchor.MiddleLeft); b.BeginRow(4f, TextAnchor.MiddleLeft);
b.BeginPanel(cols[0] + cols[1] + cols[2] + cols[3] + 8f, 32f, TextAnchor.MiddleLeft, 4f, 0f); b.BeginPanel(cols[0] + cols[1] + cols[2] + cols[3] + 8f, 32f * F, TextAnchor.MiddleLeft, 4f, 0f);
_rows.Add(new PledgerSkin.RowDef _rows.Add(new PledgerSkin.RowDef
{ {
Root = b.Last.root, Root = b.Last.root,
@@ -1010,7 +1027,7 @@ namespace Decentralized
Cell(b, cols[3], Fmt.Credits(w.Taint), false, TextAnchor.MiddleRight); Cell(b, cols[3], Fmt.Credits(w.Taint), false, TextAnchor.MiddleRight);
b.End(); b.End();
b.End(); b.End();
b.BeginPanel(cols[4] - (L.Wide ? 112f : 150f), 32f, TextAnchor.MiddleLeft, 0f, 0f); b.BeginPanel(cols[4] - (wide ? 112f : 150f) * k, 32f * F, TextAnchor.MiddleLeft, 0f, 0f);
_clear.Add(b.Last.image); _clear.Add(b.Last.image);
b.BeginRow(4f, TextAnchor.MiddleLeft); b.BeginRow(4f, TextAnchor.MiddleLeft);
RowKey(b, L.T("wallets.open"), () => WalletWindow.Open(w)); RowKey(b, L.T("wallets.open"), () => WalletWindow.Open(w));
@@ -1019,7 +1036,7 @@ namespace Decentralized
if (idx > 0) RowKey(b, _destroyPick == w.Address ? L.T("wallets.destroy.sure") : L.T("wallets.destroy"), () => DestroyWallet(w)); if (idx > 0) RowKey(b, _destroyPick == w.Address ? L.T("wallets.destroy.sure") : L.T("wallets.destroy"), () => DestroyWallet(w));
b.End(); b.End();
b.End(); b.End();
b.BeginPanel(L.Wide ? 92f : 84f, 32f, TextAnchor.MiddleRight, 0f, 0f); b.BeginPanel((wide ? 92f : 84f) * k, 32f * F, TextAnchor.MiddleRight, 0f, 0f);
_clear.Add(b.Last.image); _clear.Add(b.Last.image);
b.BeginRow(0f, TextAnchor.MiddleRight); b.BeginRow(0f, TextAnchor.MiddleRight);
if (idx != d.Current) RowKey(b, L.T("wallets.use"), () => { d.Current = idx; Session.Save(); Open(); }); if (idx != d.Current) RowKey(b, L.T("wallets.use"), () => { d.Current = idx; Session.Save(); Open(); });
@@ -1038,7 +1055,7 @@ namespace Decentralized
var go = b.Last.root; var go = b.Last.root;
if (go == null) return; if (go == null) return;
var le = go.GetComponent<LayoutElement>() ?? go.AddComponent<LayoutElement>(); var le = go.GetComponent<LayoutElement>() ?? go.AddComponent<LayoutElement>();
le.minHeight = 26f; le.preferredHeight = 26f; le.flexibleHeight = 0f; le.minHeight = 26f * F; le.preferredHeight = 26f * F; le.flexibleHeight = 0f;
} }
private static string _destroyPick, _renamePick, _renameText = ""; private static string _destroyPick, _renamePick, _renameText = "";
@@ -1230,7 +1247,8 @@ namespace Decentralized
b.AddLabel(f.StartsWith("phish:") ? L.T("black.forum.phish", f.Substring(6)) : f.StartsWith("scam:") ? L.T("black.forum.scam", BlackDelivery.DisplayName(f.Substring(5))) : f.StartsWith("busted:") ? L.T("black.forum.busted", f.Substring(7)) : L.T("black.forum." + f), "forum_dim"); b.AddLabel(f.StartsWith("phish:") ? L.T("black.forum.phish", f.Substring(6)) : f.StartsWith("scam:") ? L.T("black.forum.scam", BlackDelivery.DisplayName(f.Substring(5))) : f.StartsWith("busted:") ? L.T("black.forum.busted", f.Substring(7)) : L.T("black.forum." + f), "forum_dim");
} }
private const float SellerW = 176f, ShelfW = 560f; private static float SellerW => 176f * F;
private static float ShelfW => 560f + Ext * 0.78f;
private static void PlatformShelf(CustomUIBuilder b, SaveData d) private static void PlatformShelf(CustomUIBuilder b, SaveData d)
{ {
@@ -1242,7 +1260,7 @@ namespace Decentralized
c.AddLabel(L.T("black.price", offer.Price)); c.AddLabel(L.T("black.price", offer.Price));
c.AddFlexibleSpace(); c.AddFlexibleSpace();
c.AddButton(L.T("black.buy"), () => AddToCart(-1, offer)); c.AddButton(L.T("black.buy"), () => AddToCart(-1, offer));
}, 3); });
} }
// ——— 付款面板 ——— // ——— 付款面板 ———
@@ -1425,7 +1443,7 @@ namespace Decentralized
c.AddLabel(L.T("black.price", g.Price)); c.AddLabel(L.T("black.price", g.Price));
c.AddFlexibleSpace(); c.AddFlexibleSpace();
c.AddButton(L.T("black.sell.list"), () => BeginEdit(new EditPrice { Item = it, Id = g.Item, Ref = BlackDelivery.RefValue(it) }, g.Price)); c.AddButton(L.T("black.sell.list"), () => BeginEdit(new EditPrice { Item = it, Id = g.Item, Ref = BlackDelivery.RefValue(it) }, g.Price));
}, 3); });
b.End(); b.End();
b.End(); b.End();
} }
@@ -1725,12 +1743,14 @@ namespace Decentralized
}, 3); }, 3);
} }
private const int ShelfCols = 4; private static int ShelfCols => Ext < 100f ? 3 : Math.Max(3, (int)Math.Round(ShelfW / (172f * F)));
private const float CardW = 172f, CardH = 268f; private static float CardW => Ext < 100f ? 172f : (ShelfW - 6f * (ShelfCols - 1)) / ShelfCols;
private static float CardH => 268f * F;
/// <summary>货架:一行 4 张商品卡。</summary> /// <summary>货架:一行 4 张商品卡。</summary>
private static void Shelf(CustomUIBuilder b, System.Collections.Generic.List<BmGood> goods, Action<CustomUIBuilder, BmGood> card, int cols = ShelfCols) private static void Shelf(CustomUIBuilder b, System.Collections.Generic.List<BmGood> goods, Action<CustomUIBuilder, BmGood> card, int cols = -1)
{ {
if (cols <= 0) cols = ShelfCols;
for (int i = 0; i < goods.Count; i += cols) for (int i = 0; i < goods.Count; i += cols)
{ {
b.BeginRow(6f); b.BeginRow(6f);
@@ -1755,7 +1775,7 @@ namespace Decentralized
var sprite = Icon(g.Item); var sprite = Icon(g.Item);
if (sprite != null) c.AddImage(sprite, 64f, 64f); else c.AddSpace(64f); if (sprite != null) c.AddImage(sprite, 64f, 64f); else c.AddSpace(64f);
c.AddLabel("<b>" + ItemName(g.Item) + "</b>"); c.AddLabel("<b>" + ItemName(g.Item) + "</b>");
PledgerSkin.FitBox(c, 52f); PledgerSkin.FitBox(c, 52f * F);
bool asic = g.Kind == GoodKind.Gpu && g.Model != null && Hardware.Specs.TryGetValue(g.Model, out var gs) && gs.Asic; bool asic = g.Kind == GoodKind.Gpu && g.Model != null && Hardware.Specs.TryGetValue(g.Model, out var gs) && gs.Asic;
c.AddLabel(asic ? (g.Health >= 100 ? L.T("black.kind.asicnew") : L.T("black.kind.asic", (int)Math.Round(g.Health))) : g.Kind == GoodKind.Gpu ? (g.Health >= 100 ? L.T("black.kind.gpunew") : L.T("black.kind.gpu", (int)Math.Round(g.Health))) : L.T("black.kind." + g.Kind + (g.Kind == GoodKind.Contraband ? "." + g.Level : "")), "good_kind_dim"); c.AddLabel(asic ? (g.Health >= 100 ? L.T("black.kind.asicnew") : L.T("black.kind.asic", (int)Math.Round(g.Health))) : g.Kind == GoodKind.Gpu ? (g.Health >= 100 ? L.T("black.kind.gpunew") : L.T("black.kind.gpu", (int)Math.Round(g.Health))) : L.T("black.kind." + g.Kind + (g.Kind == GoodKind.Contraband ? "." + g.Level : "")), "good_kind_dim");
if (!string.IsNullOrEmpty(line2)) c.AddLabel(line2, "good_line2_dim"); if (!string.IsNullOrEmpty(line2)) c.AddLabel(line2, "good_line2_dim");
@@ -1902,14 +1922,14 @@ namespace Decentralized
{ {
var d = Session.Data; var d = Session.Data;
b.BeginRow(8f, TextAnchor.UpperLeft); b.BeginRow(8f, TextAnchor.UpperLeft);
b.BeginPanel(SellerW + 8f + 150f, -1f, TextAnchor.UpperLeft, 4f, 4f); b.BeginPanel((SellerW + 8f + ShelfW) * 0.45f, -1f, TextAnchor.UpperLeft, 4f, 4f);
_clear.Add(b.Last.image); _clear.Add(b.Last.image);
b.AddHeader(L.T("black.orders.in")); b.AddHeader(L.T("black.orders.in"));
if (d.Black.Orders.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_in_none_dim"); if (d.Black.Orders.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_in_none_dim");
foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay)) foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay))
b.AddLabel(L.T("black.mine.line", ItemName(o.Item) + (o.Count > 1 ? " ×" + o.Count : ""), BlackDelivery.DisplayName(o.Seller), L.T("black.mode." + o.Mode), L.T("black.state." + o.State + (o.Why != null && o.State != OrderState.Done ? "." + o.Why : "")), o.DueDay)); b.AddLabel(L.T("black.mine.line", ItemName(o.Item) + (o.Count > 1 ? " ×" + o.Count : ""), BlackDelivery.DisplayName(o.Seller), L.T("black.mode." + o.Mode), L.T("black.state." + o.State + (o.Why != null && o.State != OrderState.Done ? "." + o.Why : "")), o.DueDay));
b.End(); b.End();
b.BeginPanel(ShelfW - 150f, -1f, TextAnchor.UpperLeft, 4f, 4f); b.BeginPanel((SellerW + 8f + ShelfW) * 0.55f, -1f, TextAnchor.UpperLeft, 4f, 4f);
_clear.Add(b.Last.image); _clear.Add(b.Last.image);
b.AddHeader(L.T("black.orders.out")); b.AddHeader(L.T("black.orders.out"));
if (d.Black.Sales.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_out_none_dim"); if (d.Black.Sales.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_out_none_dim");
@@ -1979,7 +1999,8 @@ namespace Decentralized
private static Web3Tab _web3Tab = Web3Tab.Trade; private static Web3Tab _web3Tab = Web3Tab.Trade;
private static string _mixAsset = Assets.PSC; private static string _mixAsset = Assets.PSC;
private static int _mixRounds = 2; private static int _mixRounds = 2;
private static readonly float[] MixW = { 70f, 100f, 100f, 80f }; private static readonly float[] MixWBase = new[] { 70f, 100f, 100f, 80f };
private static float[] MixW => Sc(MixWBase);
private static readonly TextAnchor[] MixAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static readonly TextAnchor[] MixAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight };
/// <summary>混币器:把币存进去,几轮之后提回同一个地址,污点降低,每轮扣手续费。</summary> /// <summary>混币器:把币存进去,几轮之后提回同一个地址,污点降低,每轮扣手续费。</summary>
@@ -2048,7 +2069,8 @@ namespace Decentralized
Open(); Open();
} }
private static readonly float[] StakeW = { 110f, 90f, 200f }; private static readonly float[] StakeWBase = new[] { 110f, 90f, 200f };
private static float[] StakeW => Sc(StakeWBase);
private static readonly TextAnchor[] StakeAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight }; private static readonly TextAnchor[] StakeAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight };
/// <summary>质押页:革命军 CRA 质押池(日息、解锁排队)和平衡代币 SPARK 的铸销。都是合约调用,要在钱包上签名。</summary> /// <summary>质押页:革命军 CRA 质押池(日息、解锁排队)和平衡代币 SPARK 的铸销。都是合约调用,要在钱包上签名。</summary>
+1 -1
View File
@@ -18,7 +18,7 @@ namespace Decentralized
private static void OnGenerate(StoreClientManager manager) private static void OnGenerate(StoreClientManager manager)
{ {
var d = Session.Data; var d = Session.Data;
if (d == null || !Unlock.IsUnlocked) return; if (d == null || !Unlock.IsUnlocked || !Settings.TumblerStory) return;
Log.Guard("Tumbler", () => Log.Guard("Tumbler", () =>
{ {
int day = StoreStation.GetDayCounter(); int day = StoreStation.GetDayCounter();
+2 -2
View File
@@ -273,7 +273,7 @@ namespace Decentralized
{ {
string clip = Clipboard.Paste(); string clip = Clipboard.Paste();
// 被投毒的钱包:固件里的木马有时把粘贴的地址换成长得很像的骗子地址 // 被投毒的钱包:固件里的木马有时把粘贴的地址换成长得很像的骗子地址
if (w.Poisoned && ChainAddress.Valid(clip) && Session.Data.Rng.Chance(Tuning.TxHijackChance)) { clip = ChainAddress.Lookalike(clip, Session.Data.Rng); Log.Info($"剪贴板木马:{Clipboard.Paste()} → {clip}"); } if (w.Poisoned && ChainAddress.Valid(clip) && Session.Data.Rng.Chance(Scales.R(Tuning.TxHijackChance))) { clip = ChainAddress.Lookalike(clip, Session.Data.Rng); Log.Info($"剪贴板木马:{Clipboard.Paste()} → {clip}"); }
dev.To = clip; dev.Note = null; Open(dev); dev.To = clip; dev.Note = null; Open(dev);
}); });
b.End(); b.End();
@@ -546,7 +546,7 @@ namespace Decentralized
var mine = d.Own(tx.To); var mine = d.Own(tx.To);
if (mine != null) TxRules.Record(mine.History, new TxRecord { Addr = w.Address, Asset = tx.Asset, Amount = amt, Day = day, In = true }); if (mine != null) TxRules.Record(mine.History, new TxRecord { Addr = w.Address, Asset = tx.Asset, Amount = amt, Day = day, In = true });
// 地址投毒:链上公开的转账被人盯上,过一两天会收到一笔来自相似地址的 0 币转入 // 地址投毒:链上公开的转账被人盯上,过一两天会收到一笔来自相似地址的 0 币转入
else if (d.Rng.Chance(Tuning.TxPoisonChance)) d.PoisonPlans.Add(new PoisonPlan { Wallet = w.Address, Target = tx.To, Day = day + d.Rng.Range(1, Tuning.TxPoisonDelayMax) }); else if (d.Rng.Chance(Scales.R(Tuning.TxPoisonChance))) d.PoisonPlans.Add(new PoisonPlan { Wallet = w.Address, Target = tx.To, Day = day + d.Rng.Range(1, Tuning.TxPoisonDelayMax) });
} }
if (tx.Bill != null && !tx.Bill.Contract) if (tx.Bill != null && !tx.Bill.Contract)
{ {
@@ -1565,6 +1565,22 @@ internal static class Program
} }
}); });
Test("mod 设置倍率:解析、封顶、挖矿产出按倍率", () =>
{
Check(Scales.Parse("×1.5") == 1.5 && Scales.Parse("×0.5") == 0.5 && Scales.Parse("x") == 1 && Scales.Parse(null) == 1, "解析档位文字");
foreach (var c in Scales.Choices) Check(Scales.Parse(c) > 0, "档位都能解析:" + c);
try
{
Scales.Risk = 2; Check(Math.Abs(Scales.R(0.3) - 0.6) < 1e-9 && Scales.R(0.8) == 1, "风险倍率乘概率并封顶 1");
Scales.Risk = 1;
var n = new Network();
Scales.Mining = 1; double a = n.Mine(100);
Scales.Mining = 2; double b2 = n.Mine(100);
Check(a > 0 && Math.Abs(b2 - 2 * a) < 1e-9, "挖矿产出按倍率");
}
finally { Scales.Risk = 1; Scales.Mining = 1; }
});
Test("预测市场:先验概率", () => Test("预测市场:先验概率", () =>
{ {
Check(Math.Abs(PredictMath.PurityAtLeast(8500, 8000, 8999) - 0.5) < 1e-9, "纯度 8500 以上一半"); Check(Math.Abs(PredictMath.PurityAtLeast(8500, 8000, 8999) - 0.5) < 1e-9, "纯度 8500 以上一半");