diff --git a/art/__pycache__/sprites.cpython-314.pyc b/art/__pycache__/sprites.cpython-314.pyc
index 7fc7d1b..c0d9531 100644
Binary files a/art/__pycache__/sprites.cpython-314.pyc and b/art/__pycache__/sprites.cpython-314.pyc differ
diff --git a/art/sprites.py b/art/sprites.py
index 426dc21..bdfbd0e 100644
--- a/art/sprites.py
+++ b/art/sprites.py
@@ -30,6 +30,11 @@ C = {
'Y': (0xC8, 0xA4, 0x4A), # 金手指
'c': (0x2E, 0x2A, 0x2A), # 焦黑
'e': (0x6B, 0x4A, 0x3A), # 焦痕
+ 'u': (0x4F, 0x86, 0xD0), # 蓝
+ 'U': (0x2B, 0x4A, 0x82), # 深蓝
+ 'v': (0x9B, 0x76, 0xD6), # 紫
+ 'V': (0x5E, 0x45, 0x8F), # 深紫
+ 'y': (0xF0, 0xCF, 0x64), # 亮金
'p': (0xC0, 0x7A, 0x4A), # 铜
'q': (0x8A, 0x4F, 0x34), # 暗铜
'w': (0xE6, 0xE2, 0xD0), # 白
@@ -514,6 +519,76 @@ def key(model):
return model.replace(' ', '').lower()
+def ui_bank():
+ """银行图标(32×32):带山花的石柱建筑,门前一枚金币。"""
+ c = Canvas(32, 32)
+ for k in range(0, 14): # 山花(三角)
+ c.rect(16 - k, 4 + k // 2, 16 + k, 4 + k // 2, 'h' if k < 3 else 'l')
+ c.rect(2, 11, 29, 12, 'b'); c.rect(2, 13, 29, 13, 'd') # 檐口
+ for x0 in (4, 10, 16, 22): # 四根柱子
+ c.rect(x0, 14, x0 + 3, 24, 'l')
+ c.rect(x0, 14, x0, 24, 'h'); c.rect(x0 + 3, 14, x0 + 3, 24, 'd')
+ c.rect(x0 - 1, 14, x0 + 4, 14, 'b'); c.rect(x0 - 1, 24, x0 + 4, 24, 'b')
+ c.rect(1, 25, 30, 26, 'b'); c.rect(0, 27, 31, 28, 'm'); c.rect(0, 29, 31, 29, 'd')
+ c.rect(14, 8, 17, 9, 'y'); c.put(15, 8, 'w') # 山花里的小徽标
+ for yy in range(19, 27): # 金币
+ for xx in range(23, 31):
+ d = (xx - 26.5) ** 2 + (yy - 22.5) ** 2
+ if d <= 14:
+ c.put(xx, yy, 'e' if d > 9 else 'y')
+ c.put(25, 21, 'w'); c.put(26, 21, 'w'); c.put(26, 22, 'y')
+ outline(c)
+ return c
+
+
+def ui_poinbase():
+ """Poinbase 图标(32×32):蓝色盾牌,中间一枚金色 P 币,两侧一上一下的箭头。"""
+ c = Canvas(32, 32)
+ for y in range(3, 29): # 盾牌
+ half = 12 if y < 16 else max(0, 12 - (y - 16) * 12 // 12)
+ for x in range(16 - half, 16 + half + 1):
+ c.put(x, y, 'u' if x < 16 else 'U')
+ c.put(16 - half, y, 'h') if half else None
+ for x in range(4, 29):
+ c.put(x, 3, 'h'); c.put(x, 4, 'l')
+ for y in range(7, 21): # 币
+ for x in range(9, 24):
+ d = (x - 16) ** 2 + (y - 13.5) ** 2
+ if d <= 36:
+ c.put(x, y, 'e' if d > 28 else 'y')
+ c.put(12, 9, 'w'); c.put(13, 8, 'w'); c.put(12, 10, 'w')
+ c.rect(13, 9, 14, 18, 'U'); c.rect(15, 9, 18, 10, 'U'); c.rect(18, 11, 19, 13, 'U'); c.rect(15, 14, 18, 15, 'U') # P
+ for k in range(4): # 下方两个箭头:买入、卖出
+ c.put(9 + k, 24 - k, 'w') if k < 3 else None
+ for x in range(8, 13): c.put(x, 22, 'w')
+ for x in range(19, 24): c.put(x, 22, 'w')
+ c.put(19, 23, 'w'); c.put(20, 24, 'w'); c.put(23, 23, 'w'); c.put(22, 24, 'w')
+ outline(c)
+ return c
+
+
+def ui_web3():
+ """Web3 图标(32×32):六边形节点网络,紫色底。"""
+ c = Canvas(32, 32)
+ nodes = [(16, 7), (7, 14), (25, 14), (11, 24), (21, 24), (16, 16)]
+ edges = [(0, 1), (0, 2), (1, 3), (2, 4), (3, 4), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4)]
+ for (a, b) in edges:
+ (x0, y0), (x1, y1) = nodes[a], nodes[b]
+ n = max(abs(x1 - x0), abs(y1 - y0))
+ for i in range(n + 1):
+ px, py = x0 + (x1 - x0) * i // n, y0 + (y1 - y0) * i // n
+ c.put(px, py, 'v'); c.put(px + 1, py, 'v'); c.put(px, py + 1, 'v')
+ for i, (x, y) in enumerate(nodes):
+ r = 3 if i == 5 else 2
+ for dy in range(-r, r + 1):
+ for dx in range(-r, r + 1):
+ if dx * dx + dy * dy <= r * r + 1:
+ c.put(x + dx, y + dy, 'V' if (dx + dy) > 0 else 'h' if i != 5 else 'y')
+ c.put(x, y, 'w')
+ outline(c)
+ return c
+
+
def guide():
"""鉴定手册(16×16,一格):竖放的小册子,封面带品牌色条,书脊在左。"""
c = Canvas(16, 16)
@@ -542,6 +617,8 @@ for _tier in range(4):
SPRITES['gpu_probidia_f_t%d' % _tier] = (lambda t=_tier: gpu_front('G', 'g2', tier=t))
SPRITES['gpu_pmd_f_t%d' % _tier] = (lambda t=_tier: gpu_front('R', 'r2', tier=t))
+UI_ICONS = {'ui_bank': ui_bank, 'ui_poinbase': ui_poinbase, 'ui_web3': ui_web3}
+
# 叠加图:和底图同尺寸,位置 (0,0) 对齐,只画变化的像素,不参与判格
OVERLAYS = {
'gpu_ov_fin1': lambda: ov_fingers(1), 'gpu_ov_fin2': lambda: ov_fingers(2),
@@ -581,6 +658,8 @@ if __name__ == '__main__':
if oc.px[y][x] is not None:
b.px[y][x] = oc.px[y][x]
return b
+ for name, make in UI_ICONS.items():
+ make().png(os.path.join(sys.argv[2], name + '.png'), scale=8, bg=(0x2a, 0x2c, 0x34))
stack('gpu_probidia_f_t1', 'gpu_ov_fan2_2', 'gpu_ov_fin1', 'gpu_ov_brk1').png(os.path.join(sys.argv[2], 'prev_front_worn.png'), scale=6, bg=(0xA0, 0xA8, 0x9C))
print(sys.argv[2])
else:
@@ -594,6 +673,8 @@ if __name__ == '__main__':
canvas.png(os.path.join(out, name + '.png'))
for name, make in OVERLAYS.items():
make().png(os.path.join(out, name + '.png'))
+ for name, make in UI_ICONS.items():
+ make().png(os.path.join(out, name + '.png'))
import big, big_cases
for name, make in big.BIG.items():
make().png(os.path.join(out, name + '.png'))
diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv
index 8551bc3..9081b60 100644
--- a/src/Decentralized/ModFiles/Localization/en.csv
+++ b/src/Decentralized/ModFiles/Localization/en.csv
@@ -18,8 +18,6 @@ item.gpu.desc,"Rated hashrate {0} MH/s. Rated draw {1} kJ per day. Condition: {2
item.gpu.burnt.name,Burnt {0} {1}
item.gpu.burnt.desc,This card no longer works.
term.title,Trading Terminal
-tab.wallet,Wallet
-tab.trade,Trade
tab.report,Report
wallet.line,"{0}: {1} (about {2} credits)"
wallet.empty,The wallet is empty.
@@ -129,3 +127,24 @@ lbl.ev.print.1,"Print shows another model"
lbl.ev.print.2,"Print was redone"
news.prehalving.title,"PSC Halving Ahead"
news.prehalving.desc,"Miners will receive half as many coins per night in three days. Holders expect the price to rise."
+tab.home,"Home"
+tab.bank,"Bank"
+tab.poinbase,"Poinbase"
+tab.web3,"Web3"
+home.enter,"Enter"
+home.summary,"Credits {0} | On-chain assets about {1} credits"
+home.lastnight,"Night of day {0}: mined {1} PSC, {2} / {3} rigs powered"
+entry.bank,"Bank"
+entry.bank.l1,"Deposits, withdrawals, rent"
+entry.bank.l2,"Credits {0}"
+entry.poinbase,"Poinbase"
+entry.poinbase.l1,"Compliant exchange"
+entry.poinbase.l2,"PSC {0} credits"
+entry.web3,"Web3"
+entry.web3.l1,"Wallet and decentralised trading"
+entry.web3.l2,"About {0} credits"
+bank.cash,"Credits: {0}"
+bank.rent,"Rent: {0} credits, due in {1} days"
+bank.crc,"Poinbase account (CRC): {0}"
+bank.hint,"Deposit turns credits into CRC; withdrawing turns CRC back into credits and passes compliance review."
+poin.account,"Poinbase account: CRC {0}, PSC {1}"
diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv
index 82822ba..6f85c3b 100644
--- a/src/Decentralized/ModFiles/Localization/zh.csv
+++ b/src/Decentralized/ModFiles/Localization/zh.csv
@@ -17,8 +17,6 @@ item.gpu.desc,标称算力 {0} MH/s。标称耗电 {1} kJ/天。成色 {2}。
item.gpu.burnt.name,烧毁的 {0} {1}
item.gpu.burnt.desc,此显卡已无法工作。
term.title,交易终端
-tab.wallet,钱包
-tab.trade,交易
tab.report,报告
wallet.line,{0}:{1}(约 {2} 信用点)
wallet.empty,钱包为空。
@@ -128,3 +126,24 @@ lbl.ev.print.1,"丝印印着另一个型号"
lbl.ev.print.2,"丝印重印过"
news.prehalving.title,"PSC 即将减半"
news.prehalving.desc,"三天后矿工每晚获得的币减半。持币者预期币价上行。"
+tab.home,"首页"
+tab.bank,"银行"
+tab.poinbase,"Poinbase"
+tab.web3,"Web3"
+home.enter,"进入"
+home.summary,"信用点 {0} | 链上资产约 {1} 信用点"
+home.lastnight,"第 {0} 天夜间:挖出 {1} PSC,{2} / {3} 个矿架有电"
+entry.bank,"银行"
+entry.bank.l1,"信用点存取、房租"
+entry.bank.l2,"信用点 {0}"
+entry.poinbase,"Poinbase"
+entry.poinbase.l1,"合规交易所"
+entry.poinbase.l2,"PSC {0} 信用点"
+entry.web3,"Web3"
+entry.web3.l1,"钱包与去中心化交易"
+entry.web3.l2,"约 {0} 信用点"
+bank.cash,"信用点:{0}"
+bank.rent,"房租:{0} 信用点,{1} 天后支付"
+bank.crc,"Poinbase 账户(CRC):{0}"
+bank.hint,"存入把信用点换成 CRC,提取把 CRC 换回信用点,提取要过合规审查。"
+poin.account,"Poinbase 账户:CRC {0},PSC {1}"
diff --git a/src/Decentralized/ModFiles/Textures/Items/ui_bank.png b/src/Decentralized/ModFiles/Textures/Items/ui_bank.png
new file mode 100644
index 0000000..c06305c
Binary files /dev/null and b/src/Decentralized/ModFiles/Textures/Items/ui_bank.png differ
diff --git a/src/Decentralized/ModFiles/Textures/Items/ui_poinbase.png b/src/Decentralized/ModFiles/Textures/Items/ui_poinbase.png
new file mode 100644
index 0000000..82f6d6b
Binary files /dev/null and b/src/Decentralized/ModFiles/Textures/Items/ui_poinbase.png differ
diff --git a/src/Decentralized/ModFiles/Textures/Items/ui_web3.png b/src/Decentralized/ModFiles/Textures/Items/ui_web3.png
new file mode 100644
index 0000000..ea60ced
Binary files /dev/null and b/src/Decentralized/ModFiles/Textures/Items/ui_web3.png differ
diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs
index e91f087..9ce6825 100644
--- a/src/Decentralized/TerminalWindow.cs
+++ b/src/Decentralized/TerminalWindow.cs
@@ -6,13 +6,16 @@ using Decentralized.Domain;
namespace Decentralized
{
- /// 交易终端窗口:钱包、交易、报告三页。每次操作后按当前页重建窗口。
+ ///
+ /// 交易终端窗口:首页三个入口(银行、Poinbase、Web3)加报告。银行是真实的信用点(存取、房租);
+ /// Poinbase 是合规交易所;Web3 是去中心化交易和链上钱包。每次操作后按当前页重建窗口。
+ ///
public static class TerminalWindow
{
public const string Id = "pd_terminal";
- private enum Page { Wallet, Trade, Report }
+ private enum Page { Home, Bank, Poinbase, Web3, Report }
- private static Page _page = Page.Wallet;
+ private static Page _page = Page.Home;
private static string _amount = "";
private static string _message;
@@ -21,7 +24,7 @@ namespace Decentralized
if (Session.Data == null) return;
var b = CustomUIManager.Window(Id, L.T("term.title"));
if (b == null) return;
- b.SetSize(620, 560).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
+ b.SetSize(640, 580).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
b.BeginRow();
foreach (Page p in Enum.GetValues(typeof(Page)))
{
@@ -31,11 +34,13 @@ namespace Decentralized
}
b.End();
b.AddSeparator();
- b.BeginScroll(440);
+ b.BeginScroll(450);
switch (_page)
{
- case Page.Wallet: WalletPage(b); break;
- case Page.Trade: TradePage(b); break;
+ case Page.Home: HomePage(b); break;
+ case Page.Bank: BankPage(b); break;
+ case Page.Poinbase: ExchangePage(b, Venue.Exchange); break;
+ case Page.Web3: Web3Page(b); break;
default: ReportPage(b); break;
}
b.End();
@@ -43,32 +48,67 @@ namespace Decentralized
b.Show();
}
- private static void WalletPage(CustomUIBuilder b)
+ private static void Go(Page p) { _page = p; _message = null; Open(); }
+
+ private static void Entry(CustomUIBuilder b, string icon, string name, string line1, string line2, Page target)
{
- var w = Session.Data.Hot;
- if (w.Balances.Count == 0) b.AddLabel(L.T("wallet.empty"));
- foreach (var kv in w.Balances.OrderBy(k => k.Key))
- b.AddLabel(L.T("wallet.line", kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))));
- b.AddSeparator();
- b.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
- if (w.Taint > 0.5) b.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
+ b.BeginPanel(190f, 250f, TextAnchor.UpperCenter, 10f, 6f);
+ b.AddImage(ModInfo.Id + ":" + icon, 96f, 96f);
+ b.AddHeader(name);
+ b.AddLabel(line1);
+ b.AddLabel(line2);
+ b.AddFlexibleSpace();
+ b.AddButton(L.T("home.enter"), () => Go(target));
+ b.End();
}
- private static void TradePage(CustomUIBuilder b)
+ private static void HomePage(CustomUIBuilder b)
+ {
+ var d = Session.Data;
+ double chain = d.Hot.Value(Session.Price);
+ b.AddLabel(L.T("home.summary", PlayerStore.Instance.playerCash, Fmt.Credits(chain)));
+ b.AddSpace(6f);
+ b.BeginRow(8f, TextAnchor.UpperCenter);
+ Entry(b, "ui_bank", L.T("entry.bank"), L.T("entry.bank.l1"), L.T("entry.bank.l2", PlayerStore.Instance.playerCash), Page.Bank);
+ Entry(b, "ui_poinbase", L.T("entry.poinbase"), L.T("entry.poinbase.l1"), L.T("entry.poinbase.l2", Fmt.Num(d.Market.Price)), Page.Poinbase);
+ Entry(b, "ui_web3", L.T("entry.web3"), L.T("entry.web3.l1"), L.T("entry.web3.l2", Fmt.Credits(chain)), Page.Web3);
+ b.End();
+ b.AddSpace(8f);
+ b.AddSeparator();
+ var r = d.LastNight;
+ b.AddLabel(r == null ? L.T("report.none") : L.T("home.lastnight", r.Day, Fmt.Num(r.Mined), r.PoweredRigs, r.Rigs));
+ }
+
+ private static void AmountRow(CustomUIBuilder b)
{
- b.AddLabel(L.T("trade.hint"));
b.BeginRow();
b.AddLabel(L.T("trade.amount"));
b.BeginPanel(240f, -1f, TextAnchor.MiddleCenter, 0f, 0f); // 给输入框一个固定宽度,容得下大额数字
b.AddInput("0", _amount, s => _amount = s, "amount", s => _amount = s);
b.End();
b.End();
+ }
+
+ /// 银行:真实的信用点。现金、房租,以及信用点与 Poinbase 账户(CRC)之间的存取。
+ private static void BankPage(CustomUIBuilder b)
+ {
+ var ps = PlayerStore.Instance;
+ b.AddHeader(L.T("entry.bank"));
+ b.AddLabel(L.T("bank.cash", ps.playerCash));
+ b.AddLabel(L.T("bank.rent", Session.Rent(), ps.GetRentDayCounter()));
+ b.AddLabel(L.T("bank.crc", Fmt.Num(Session.Data.Hot.Get(Assets.CRC))));
+ b.AddSeparator();
+ b.AddLabel(L.T("bank.hint"));
+ AmountRow(b);
b.BeginRow();
b.AddButton(L.T("trade.deposit"), Deposit);
b.AddButton(L.T("trade.withdraw"), Withdraw);
b.End();
- b.AddSeparator();
- foreach (var pool in Session.Data.Market.Pools)
+ }
+
+ private static void TradeRows(CustomUIBuilder b, Venue venue)
+ {
+ foreach (var pool in Session.Data.Market.Pools.Where(x => x.Venue == venue))
{
var p = pool;
b.AddLabel(L.T("trade.pair", p.Base, p.Quote, L.T("venue." + p.Venue)) + " " + L.T("trade.price", Fmt.Num(p.Price)));
@@ -79,6 +119,35 @@ namespace Decentralized
}
}
+ /// Poinbase:合规交易所。账户余额与交易对;信用点的存取在银行。
+ private static void ExchangePage(CustomUIBuilder b, Venue venue)
+ {
+ var w = Session.Data.Hot;
+ b.AddHeader(L.T("entry.poinbase"));
+ b.AddLabel(L.T("poin.account", Fmt.Num(w.Get(Assets.CRC)), Fmt.Num(w.Get(Assets.PSC))));
+ b.AddLabel(L.T("trade.hint"));
+ AmountRow(b);
+ b.AddSeparator();
+ TradeRows(b, venue);
+ }
+
+ /// Web3:链上钱包与去中心化交易。
+ private static void Web3Page(CustomUIBuilder b)
+ {
+ var w = Session.Data.Hot;
+ b.AddHeader(L.T("entry.web3"));
+ if (w.Balances.Count == 0) b.AddLabel(L.T("wallet.empty"));
+ foreach (var kv in w.Balances.OrderBy(k => k.Key))
+ b.AddLabel(L.T("wallet.line", kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))));
+ b.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
+ if (w.Taint > 0.5) b.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
+ b.AddSeparator();
+ b.AddLabel(L.T("trade.hint"));
+ AmountRow(b);
+ b.AddSeparator();
+ TradeRows(b, Venue.Dex);
+ }
+
private static void ReportPage(CustomUIBuilder b)
{
var r = Session.Data.LastNight;