2026-11-03 09:11:46 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
|
using System.Linq;
|
2026-11-03 15:15:19 +08:00
|
|
|
|
using UnityEngine;
|
2026-11-03 09:11:46 +08:00
|
|
|
|
using Decentralized.Domain;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Decentralized
|
|
|
|
|
|
{
|
2026-11-03 15:23:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 交易终端窗口:首页三个入口(银行、Poinbase、Web3)加报告。银行是真实的信用点(存取、房租);
|
|
|
|
|
|
/// Poinbase 是合规交易所;Web3 是去中心化交易和链上钱包。每次操作后按当前页重建窗口。
|
|
|
|
|
|
/// </summary>
|
2026-11-03 09:11:46 +08:00
|
|
|
|
public static class TerminalWindow
|
|
|
|
|
|
{
|
|
|
|
|
|
public const string Id = "pd_terminal";
|
2026-11-03 15:23:15 +08:00
|
|
|
|
private enum Page { Home, Bank, Poinbase, Web3, Report }
|
2026-11-03 09:11:46 +08:00
|
|
|
|
|
2026-11-03 15:23:15 +08:00
|
|
|
|
private static Page _page = Page.Home;
|
2026-11-03 09:11:46 +08:00
|
|
|
|
private static string _amount = "";
|
|
|
|
|
|
private static string _message;
|
|
|
|
|
|
|
|
|
|
|
|
public static void Open()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Session.Data == null) return;
|
|
|
|
|
|
var b = CustomUIManager.Window(Id, L.T("term.title"));
|
|
|
|
|
|
if (b == null) return;
|
2026-11-03 15:23:15 +08:00
|
|
|
|
b.SetSize(640, 580).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
|
2026-11-03 09:11:46 +08:00
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
foreach (Page p in Enum.GetValues(typeof(Page)))
|
|
|
|
|
|
{
|
|
|
|
|
|
var page = p;
|
|
|
|
|
|
string name = L.T("tab." + p.ToString().ToLowerInvariant());
|
|
|
|
|
|
b.AddButton(page == _page ? $"[{name}]" : name, () => { _page = page; _message = null; Open(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
b.AddSeparator();
|
2026-11-03 15:23:15 +08:00
|
|
|
|
b.BeginScroll(450);
|
2026-11-03 09:11:46 +08:00
|
|
|
|
switch (_page)
|
|
|
|
|
|
{
|
2026-11-03 15:23:15 +08:00
|
|
|
|
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;
|
2026-11-03 09:11:46 +08:00
|
|
|
|
default: ReportPage(b); break;
|
|
|
|
|
|
}
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
if (!string.IsNullOrEmpty(_message)) b.AddLabel(_message);
|
|
|
|
|
|
b.Show();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:23:15 +08:00
|
|
|
|
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)
|
2026-11-03 09:11:46 +08:00
|
|
|
|
{
|
2026-11-03 15:23:15 +08:00
|
|
|
|
b.BeginPanel(190f, 250f, TextAnchor.UpperCenter, 10f, 6f);
|
|
|
|
|
|
b.AddImage(ModInfo.Id + ":" + icon, 96f, 96f);
|
|
|
|
|
|
b.AddHeader(name);
|
|
|
|
|
|
b.AddLabel(line1);
|
2026-11-03 15:35:48 +08:00
|
|
|
|
if (line2 != null) b.AddLabel(line2);
|
2026-11-03 15:23:15 +08:00
|
|
|
|
b.AddFlexibleSpace();
|
|
|
|
|
|
b.AddButton(L.T("home.enter"), () => Go(target));
|
|
|
|
|
|
b.End();
|
2026-11-03 09:11:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:23:15 +08:00
|
|
|
|
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);
|
2026-11-03 15:35:48 +08:00
|
|
|
|
Entry(b, "ui_poinbase", L.T("entry.poinbase"), L.T("entry.poinbase.l1"), null, Page.Poinbase);
|
2026-11-03 15:23:15 +08:00
|
|
|
|
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)
|
2026-11-03 09:11:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
b.AddLabel(L.T("trade.amount"));
|
2026-11-03 15:15:19 +08:00
|
|
|
|
b.BeginPanel(240f, -1f, TextAnchor.MiddleCenter, 0f, 0f); // 给输入框一个固定宽度,容得下大额数字
|
2026-11-03 09:11:46 +08:00
|
|
|
|
b.AddInput("0", _amount, s => _amount = s, "amount", s => _amount = s);
|
|
|
|
|
|
b.End();
|
2026-11-03 15:15:19 +08:00
|
|
|
|
b.End();
|
2026-11-03 15:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>银行:真实的信用点。现金、房租,以及信用点与 Poinbase 账户(CRC)之间的存取。</summary>
|
|
|
|
|
|
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);
|
2026-11-03 09:11:46 +08:00
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
b.AddButton(L.T("trade.deposit"), Deposit);
|
|
|
|
|
|
b.AddButton(L.T("trade.withdraw"), Withdraw);
|
|
|
|
|
|
b.End();
|
2026-11-03 15:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void TradeRows(CustomUIBuilder b, Venue venue)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var pool in Session.Data.Market.Pools.Where(x => x.Venue == venue))
|
2026-11-03 09:11:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
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)));
|
|
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
b.AddButton(L.T("trade.buy"), () => Trade(p, buy: true));
|
|
|
|
|
|
b.AddButton(L.T("trade.sell"), () => Trade(p, buy: false));
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:23:15 +08:00
|
|
|
|
/// <summary>Poinbase:合规交易所。账户余额与交易对;信用点的存取在银行。</summary>
|
|
|
|
|
|
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))));
|
2026-11-03 15:35:48 +08:00
|
|
|
|
b.AddLabel(L.T("avg.line", Assets.PSC, AvgText(w, Assets.PSC)));
|
2026-11-03 15:23:15 +08:00
|
|
|
|
b.AddLabel(L.T("trade.hint"));
|
|
|
|
|
|
AmountRow(b);
|
|
|
|
|
|
b.AddSeparator();
|
|
|
|
|
|
TradeRows(b, venue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Web3:链上钱包与去中心化交易。</summary>
|
|
|
|
|
|
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))
|
2026-11-03 15:35:48 +08:00
|
|
|
|
b.AddLabel(L.T("wallet.line", kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))) + " " + L.T("avg.short", AvgText(w, kv.Key)));
|
2026-11-03 15:23:15 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:35:48 +08:00
|
|
|
|
private static string AvgText(Wallet w, string asset) => w.AvgBuy(asset) is double a ? Fmt.Num(a) : L.T("avg.none");
|
|
|
|
|
|
|
2026-11-03 09:11:46 +08:00
|
|
|
|
private static void ReportPage(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
var r = Session.Data.LastNight;
|
|
|
|
|
|
if (r == null) { b.AddLabel(L.T("report.none")); return; }
|
|
|
|
|
|
b.AddHeader(L.T("report.day", r.Day));
|
|
|
|
|
|
b.AddLabel(L.T("report.rigs", r.PoweredRigs, r.Rigs));
|
|
|
|
|
|
b.AddLabel(L.T("report.hash", Fmt.Num(r.Hash * 10), Fmt.Num(r.NetworkHash * 10)));
|
|
|
|
|
|
b.AddLabel(L.T("report.mined", Fmt.Num(r.Mined), Fmt.Credits(r.Mined * r.Price)));
|
|
|
|
|
|
b.AddLabel(L.T("report.energy", Fmt.Num(r.Kj)));
|
|
|
|
|
|
b.AddLabel(L.T("report.burnt", r.Burnt.Count));
|
|
|
|
|
|
foreach (var n in r.Burnt) b.AddLabel(" " + n);
|
|
|
|
|
|
b.AddLabel(L.T("report.price", Fmt.Num(r.Price)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static bool ReadAmount(out double v)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out v) || v <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_message = L.T("msg.amount");
|
|
|
|
|
|
Open();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void Deposit()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ReadAmount(out double v)) return;
|
|
|
|
|
|
int credits = (int)Math.Floor(v);
|
|
|
|
|
|
if (credits <= 0 || PlayerStore.Instance.playerCash < credits) { _message = L.T("msg.balance"); Open(); return; }
|
|
|
|
|
|
ModHelper.AddCredit(-credits, false);
|
|
|
|
|
|
var w = Session.Data.Hot;
|
|
|
|
|
|
w.Add(Assets.CRC, credits / Session.Data.Stable.Crc);
|
2026-11-03 15:35:48 +08:00
|
|
|
|
w.RecordBuy(Assets.CRC, credits / Session.Data.Stable.Crc, credits);
|
2026-11-03 09:11:46 +08:00
|
|
|
|
w.Basis += credits;
|
|
|
|
|
|
_message = L.T("msg.deposit", credits);
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void Withdraw()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ReadAmount(out double v)) return;
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
if (d.Hot.Get(Assets.CRC) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
|
|
|
|
|
|
var r = d.Exchange.Cashout(d.Hot, v, Session.Price, StoreStation.GetDayCounter(), Session.Rent(), false, d.Rng);
|
|
|
|
|
|
int got = (int)Math.Floor(r.Received - r.Tax);
|
|
|
|
|
|
ModHelper.AddCredit(got, true);
|
|
|
|
|
|
_message = L.T("msg.cashout", got, Fmt.Credits(r.Tax));
|
|
|
|
|
|
if (r.Outcome == AmlOutcome.Light) _message += "\n" + L.T("msg.aml_light", Fmt.Credits(r.Confiscated));
|
|
|
|
|
|
if (r.Outcome == AmlOutcome.Heavy)
|
|
|
|
|
|
{
|
|
|
|
|
|
_message += "\n" + L.T("msg.aml_heavy", Fmt.Credits(r.Confiscated));
|
|
|
|
|
|
PlayerStore.Instance.secData.CommitCrime("FENCING", (int)Math.Round(r.Crime));
|
|
|
|
|
|
}
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void Trade(Pool p, bool buy)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ReadAmount(out double v)) return;
|
|
|
|
|
|
var w = Session.Data.Hot;
|
|
|
|
|
|
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote;
|
|
|
|
|
|
if (w.Get(pay) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
|
|
|
|
|
|
double got = buy ? p.BuyBase(v) : p.SellBase(v);
|
2026-11-03 15:35:48 +08:00
|
|
|
|
double spent = v * Session.Price(pay);
|
2026-11-03 09:11:46 +08:00
|
|
|
|
w.Add(pay, -v);
|
|
|
|
|
|
w.Add(get, got);
|
2026-11-03 15:35:48 +08:00
|
|
|
|
w.RecordBuy(get, got, spent);
|
2026-11-03 09:11:46 +08:00
|
|
|
|
_message = L.T("msg.trade", Fmt.Num(v), pay, Fmt.Num(got), get);
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|