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 20:13:18 +08:00
|
|
|
|
private enum Page { Home, Bank, Poinbase, Web3 }
|
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 20:06:32 +08:00
|
|
|
|
b.SetSize(780, 660).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 20:06:32 +08:00
|
|
|
|
b.BeginScroll(540);
|
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 20:13:18 +08:00
|
|
|
|
default: break;
|
2026-11-03 09:11:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
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:41:42 +08:00
|
|
|
|
b.BeginPanel(220f, 250f, TextAnchor.UpperCenter, 10f, 6f);
|
2026-11-03 15:23:15 +08:00
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:41:42 +08:00
|
|
|
|
private const float LeftW = 430f, RightW = 300f;
|
|
|
|
|
|
|
|
|
|
|
|
private static void Cell(CustomUIBuilder b, float w, string text, bool header = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
b.BeginPanel(w, 30f, TextAnchor.MiddleLeft, 6f, 0f);
|
|
|
|
|
|
b.AddLabel(header ? "<b>" + text + "</b>" : text);
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void TableRow(CustomUIBuilder b, float[] widths, params string[] cells)
|
|
|
|
|
|
{
|
|
|
|
|
|
b.BeginRow(2f);
|
|
|
|
|
|
for (int i = 0; i < widths.Length; i++) Cell(b, widths[i], i < cells.Length ? cells[i] : "");
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void TableHeader(CustomUIBuilder b, float[] widths, params string[] cells)
|
|
|
|
|
|
{
|
|
|
|
|
|
b.BeginRow(2f);
|
|
|
|
|
|
for (int i = 0; i < widths.Length; i++) Cell(b, widths[i], cells[i], true);
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>左右分栏:左边是账户与持仓表格,右边是操作区。</summary>
|
|
|
|
|
|
private static void Columns(CustomUIBuilder b, Action<CustomUIBuilder> left, Action<CustomUIBuilder> right)
|
|
|
|
|
|
{
|
|
|
|
|
|
b.BeginRow(10f);
|
|
|
|
|
|
b.BeginPanel(LeftW, -1f, TextAnchor.UpperLeft, 8f, 4f);
|
|
|
|
|
|
left(b);
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
b.BeginPanel(RightW, -1f, TextAnchor.UpperLeft, 8f, 6f);
|
|
|
|
|
|
right(b);
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
b.End();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:23:15 +08:00
|
|
|
|
private static void AmountRow(CustomUIBuilder b)
|
2026-11-03 09:11:46 +08:00
|
|
|
|
{
|
|
|
|
|
|
b.AddLabel(L.T("trade.amount"));
|
2026-11-03 15:41:42 +08:00
|
|
|
|
b.BeginPanel(RightW - 30f, -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:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>银行:真实的信用点。现金、房租,以及信用点与 Poinbase 账户(CRC)之间的存取。</summary>
|
|
|
|
|
|
private static void BankPage(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
var ps = PlayerStore.Instance;
|
2026-11-03 15:41:42 +08:00
|
|
|
|
var w = new[] { 200f, 220f };
|
|
|
|
|
|
Columns(b, l =>
|
|
|
|
|
|
{
|
|
|
|
|
|
l.AddHeader(L.T("entry.bank"));
|
|
|
|
|
|
TableRow(l, w, L.T("bank.cash.k"), ps.playerCash.ToString());
|
|
|
|
|
|
TableRow(l, w, L.T("bank.rent.k"), L.T("bank.rent.v", Session.Rent(), ps.GetRentDayCounter()));
|
|
|
|
|
|
TableRow(l, w, L.T("bank.crc.k"), Fmt.Num(Session.Data.Hot.Get(Assets.CRC)));
|
|
|
|
|
|
}, r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AddHeader(L.T("bank.ops"));
|
|
|
|
|
|
AmountRow(r);
|
2026-11-03 15:44:49 +08:00
|
|
|
|
AmlPreview(r);
|
2026-11-03 15:41:42 +08:00
|
|
|
|
r.AddButton(L.T("trade.deposit"), Deposit);
|
|
|
|
|
|
r.AddButton(L.T("trade.withdraw"), Withdraw);
|
|
|
|
|
|
});
|
2026-11-03 15:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 15:44:49 +08:00
|
|
|
|
/// <summary>提取前的合规审查提示:本周已提取的累计、污点比例,以及按当前数量估算的审查概率。</summary>
|
|
|
|
|
|
private static void AmlPreview(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
int day = StoreStation.GetDayCounter();
|
|
|
|
|
|
double rent = Session.Rent();
|
|
|
|
|
|
double recent = d.Exchange.History.Where(h => h.Key > day - Tuning.AmlWindowDays).Sum(h => h.Value);
|
|
|
|
|
|
b.AddSeparator();
|
|
|
|
|
|
b.AddLabel(L.T("aml.window", Tuning.AmlWindowDays, Fmt.Credits(recent), Fmt.Credits(Tuning.AmlCumRents * rent)));
|
|
|
|
|
|
double total = d.Hot.Value(Session.Price);
|
|
|
|
|
|
double taintRatio = total > 0 ? d.Hot.Taint / total : 0;
|
|
|
|
|
|
b.AddLabel(L.T("aml.taint", (int)Math.Round(taintRatio * 100)));
|
|
|
|
|
|
if (double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double v) && v > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
double value = Math.Min(v, d.Hot.Get(Assets.CRC)) * Session.Price(Assets.CRC);
|
|
|
|
|
|
double taint = total > 0 ? d.Hot.Taint * Math.Min(1, value / total) : 0;
|
|
|
|
|
|
double chance = Exchange.HitChance(value, taint, recent, rent, d.Hot.Flagged);
|
|
|
|
|
|
b.AddLabel(L.T("aml.chance", (int)Math.Round(chance * 100)));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly float[] StableW = { 80f, 100f, 220f };
|
|
|
|
|
|
|
|
|
|
|
|
private static string StableStatus(Stablecoins st, string asset, int day)
|
|
|
|
|
|
{
|
|
|
|
|
|
double p = st.Price(asset);
|
|
|
|
|
|
if (asset == Assets.CRA && st.CraWarning(day)) return L.T("stable.warn");
|
|
|
|
|
|
if (asset == Assets.CRA && p < 0.97) return L.T("stable.spiral");
|
|
|
|
|
|
return p < 0.99 ? L.T("stable.depeg") : L.T("stable.ok");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>稳定币行情:价格与状态(脱锚、预兆)。</summary>
|
|
|
|
|
|
private static void StableTable(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
var st = Session.Data.Stable;
|
|
|
|
|
|
int day = StoreStation.GetDayCounter();
|
|
|
|
|
|
b.AddSeparator();
|
|
|
|
|
|
TableHeader(b, StableW, L.T("col.asset"), L.T("col.peg"), L.T("col.status"));
|
|
|
|
|
|
foreach (var a in new[] { Assets.CRC, Assets.CRT, Assets.CRA })
|
|
|
|
|
|
TableRow(b, StableW, a, Fmt.Num(st.Price(a)), StableStatus(st, a, day));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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:41:42 +08:00
|
|
|
|
private static readonly float[] HoldW = { 80f, 100f, 110f, 110f };
|
|
|
|
|
|
|
|
|
|
|
|
private static void Holdings(CustomUIBuilder b, Wallet w, Func<string, bool> show)
|
|
|
|
|
|
{
|
|
|
|
|
|
TableHeader(b, HoldW, L.T("col.asset"), L.T("col.amount"), L.T("col.value"), L.T("col.avg"));
|
|
|
|
|
|
bool any = false;
|
|
|
|
|
|
foreach (var kv in w.Balances.OrderBy(k => k.Key).Where(k => show(k.Key)))
|
|
|
|
|
|
{
|
|
|
|
|
|
any = true;
|
|
|
|
|
|
TableRow(b, HoldW, kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key)), AvgText(w, kv.Key));
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!any) b.AddLabel(L.T("wallet.empty"));
|
2026-11-03 16:51:12 +08:00
|
|
|
|
foreach (var kv in w.Frozen.OrderBy(k => k.Key).Where(k => show(k.Key)))
|
|
|
|
|
|
b.AddLabel(L.T("wallet.frozen", kv.Key, Fmt.Num(kv.Value), w.FrozenUntil.TryGetValue(kv.Key, out var u) ? u : 0));
|
2026-11-03 15:41:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Poinbase:合规交易所。左边账户持仓,右边交易;信用点的存取在银行。</summary>
|
2026-11-03 15:23:15 +08:00
|
|
|
|
private static void ExchangePage(CustomUIBuilder b, Venue venue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var w = Session.Data.Hot;
|
2026-11-03 15:41:42 +08:00
|
|
|
|
Columns(b, l =>
|
|
|
|
|
|
{
|
|
|
|
|
|
l.AddHeader(L.T("poin.account.h"));
|
|
|
|
|
|
Holdings(l, w, a => a == Assets.CRC || a == Assets.PSC);
|
|
|
|
|
|
}, r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AddHeader(L.T("trade.h"));
|
|
|
|
|
|
AmountRow(r);
|
|
|
|
|
|
TradeRows(r, venue);
|
|
|
|
|
|
});
|
2026-11-03 15:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Web3:链上钱包与去中心化交易。</summary>
|
|
|
|
|
|
private static void Web3Page(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
var w = Session.Data.Hot;
|
2026-11-03 16:43:30 +08:00
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
|
if (_web3Tab == Web3Tab.Mixer && !Unlock.MixerUnlocked) _web3Tab = Web3Tab.Trade;
|
|
|
|
|
|
if (_web3Tab == Web3Tab.Black && !Unlock.BlackUnlocked) _web3Tab = Web3Tab.Trade;
|
2026-11-03 16:43:30 +08:00
|
|
|
|
b.BeginRow();
|
2026-11-03 20:06:32 +08:00
|
|
|
|
foreach (Web3Tab t in Enum.GetValues(typeof(Web3Tab)))
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((t == Web3Tab.Mixer && !Unlock.MixerUnlocked) || (t == Web3Tab.Black && !Unlock.BlackUnlocked)) continue;
|
|
|
|
|
|
var tab = t;
|
|
|
|
|
|
string name = L.T("web3.sub." + t.ToString().ToLowerInvariant());
|
|
|
|
|
|
b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; Open(); });
|
|
|
|
|
|
}
|
2026-11-03 16:43:30 +08:00
|
|
|
|
b.End();
|
2026-11-03 20:13:18 +08:00
|
|
|
|
if (_web3Tab == Web3Tab.Mining) { ReportPage(b); return; }
|
2026-11-03 20:06:32 +08:00
|
|
|
|
if (_web3Tab == Web3Tab.Mixer) { MixerPage(b); return; }
|
|
|
|
|
|
if (_web3Tab == Web3Tab.Black) { BlackPage(b); return; }
|
2026-11-03 16:43:30 +08:00
|
|
|
|
}
|
2026-11-03 15:41:42 +08:00
|
|
|
|
Columns(b, l =>
|
|
|
|
|
|
{
|
|
|
|
|
|
l.AddHeader(L.T("web3.wallet.h"));
|
|
|
|
|
|
Holdings(l, w, a => true);
|
|
|
|
|
|
l.AddSeparator();
|
|
|
|
|
|
l.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
|
|
|
|
|
|
if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
|
2026-11-03 15:44:49 +08:00
|
|
|
|
StableTable(l);
|
2026-11-03 15:41:42 +08:00
|
|
|
|
}, r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AddHeader(L.T("trade.h"));
|
|
|
|
|
|
AmountRow(r);
|
|
|
|
|
|
TradeRows(r, Venue.Dex);
|
|
|
|
|
|
});
|
2026-11-03 15:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 20:23:21 +08:00
|
|
|
|
private enum BlackTab { Shop, Orders, Sell, Mine, Desk }
|
2026-11-03 19:48:26 +08:00
|
|
|
|
private static BlackTab _blackTab = BlackTab.Shop;
|
|
|
|
|
|
private static PayMode _mode = PayMode.Escrow;
|
2026-11-03 19:35:48 +08:00
|
|
|
|
private static string _deskAsset = Assets.PSC;
|
|
|
|
|
|
private static bool _deskIn = true;
|
|
|
|
|
|
private static string _payAsset = Assets.CRT;
|
2026-11-03 20:06:32 +08:00
|
|
|
|
private static readonly System.Collections.Generic.Dictionary<string, Sprite> IconCache = new System.Collections.Generic.Dictionary<string, Sprite>();
|
2026-11-03 19:35:48 +08:00
|
|
|
|
|
2026-11-03 20:06:32 +08:00
|
|
|
|
/// <summary>货架上的商品图:游戏物品自己的贴图(本 mod 的显卡用本 mod 的贴图)。</summary>
|
|
|
|
|
|
private static Sprite Icon(string id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IconCache.TryGetValue(id, out var cached)) return cached;
|
|
|
|
|
|
Sprite sprite = null;
|
|
|
|
|
|
var it = ModHelper.SpawnItem(BlackDelivery.GameId(id));
|
|
|
|
|
|
if (it != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try { sprite = it.useSpriteFromMod ? Items.ModSprite(it.spritePath) : RenderHandler.LoadFromAtlas(it.spriteAtlasPath, it.spritePath); }
|
|
|
|
|
|
catch (Exception ex) { Log.Info($"商品图读取失败 {id}:{ex.Message}"); }
|
|
|
|
|
|
it.Destroy();
|
|
|
|
|
|
}
|
|
|
|
|
|
IconCache[id] = sprite;
|
|
|
|
|
|
return sprite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Web3 页里的黑市:黑市派系的链上实体。货架式商城(现货、订单)、我的订单与情报、Ponero 兑换处。</summary>
|
2026-11-03 19:35:48 +08:00
|
|
|
|
private static void BlackPage(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
int rep = StoreReputation.GetBMReputation();
|
|
|
|
|
|
int day = StoreStation.GetDayCounter();
|
|
|
|
|
|
var d = Session.Data;
|
2026-11-03 20:35:28 +08:00
|
|
|
|
if (Night.ActiveEvents().Contains(MarketEvent.Crackdown)) { b.AddLabel(L.T("black.offline")); return; }
|
2026-11-03 19:35:48 +08:00
|
|
|
|
if (!BlackMarket.Open(rep)) { b.AddLabel(L.T("black.closed", Tuning.BmOpenRep, rep)); return; }
|
|
|
|
|
|
bool desk = BlackMarket.DeskOpen(rep);
|
2026-11-03 19:48:26 +08:00
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
foreach (BlackTab t in Enum.GetValues(typeof(BlackTab)))
|
2026-11-03 19:35:48 +08:00
|
|
|
|
{
|
2026-11-03 19:48:26 +08:00
|
|
|
|
if (t == BlackTab.Desk && !desk) continue;
|
|
|
|
|
|
var tab = t;
|
|
|
|
|
|
string name = L.T("black.sub." + t.ToString().ToLowerInvariant());
|
|
|
|
|
|
b.AddButton(tab == _blackTab ? $"[{name}]" : name, () => { _blackTab = tab; _message = null; Open(); });
|
2026-11-03 19:35:48 +08:00
|
|
|
|
}
|
2026-11-03 19:48:26 +08:00
|
|
|
|
b.End();
|
|
|
|
|
|
if (d.Black.Closed(day)) { b.AddLabel(L.T("black.rugged", d.Black.ClosedUntil - day + 1)); return; }
|
|
|
|
|
|
if (_blackTab == BlackTab.Desk && desk) { DeskPage(b, rep, day); return; }
|
|
|
|
|
|
if (_blackTab == BlackTab.Mine) { MinePage(b, day); return; }
|
2026-11-03 20:23:21 +08:00
|
|
|
|
if (_blackTab == BlackTab.Sell) { SellPage(b, day); return; }
|
2026-11-03 20:06:32 +08:00
|
|
|
|
bool orders = _blackTab == BlackTab.Orders;
|
2026-11-03 19:35:48 +08:00
|
|
|
|
var w = d.Hot;
|
2026-11-03 20:06:32 +08:00
|
|
|
|
b.AddLabel(L.T("black.rep", rep, BlackMarket.MaxLevel(rep)) + " " + L.T("mixer.balance", _payAsset, Fmt.Num(w.Avail(_payAsset))) + (w.Taint > 0.5 ? " " + L.T("wallet.taint", Fmt.Credits(w.Taint)) : ""));
|
|
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
foreach (var a in new[] { Assets.PSC, Assets.CRT, Assets.CRA, Assets.Ponero })
|
2026-11-03 19:35:48 +08:00
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
|
var asset = a;
|
|
|
|
|
|
b.AddButton(asset == _payAsset ? $"[{asset}]" : asset, () => { _payAsset = asset; Open(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
if (orders)
|
|
|
|
|
|
foreach (PayMode m in Enum.GetValues(typeof(PayMode)))
|
2026-11-03 19:35:48 +08:00
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
|
var mode = m;
|
|
|
|
|
|
string n = L.T("black.mode." + m);
|
|
|
|
|
|
b.AddButton(mode == _mode ? $"[{n}]" : n, () => { _mode = mode; Open(); });
|
2026-11-03 19:35:48 +08:00
|
|
|
|
}
|
2026-11-03 20:06:32 +08:00
|
|
|
|
b.End();
|
|
|
|
|
|
b.AddSeparator();
|
|
|
|
|
|
if (!orders)
|
2026-11-03 19:35:48 +08:00
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
|
if (d.Black.Offers.Count == 0) b.AddLabel(L.T("black.empty"));
|
|
|
|
|
|
Shelf(b, d.Black.Offers.Cast<BmGood>().ToList(), (c, g) =>
|
2026-11-03 19:35:48 +08:00
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
|
var offer = (BmOffer)g;
|
|
|
|
|
|
GoodHead(c, g, L.T("black.stock", offer.Qty));
|
|
|
|
|
|
c.AddLabel(L.T("black.price", offer.Price));
|
|
|
|
|
|
c.AddFlexibleSpace();
|
|
|
|
|
|
c.AddButton(L.T("black.buy"), () => BuyOffer(offer));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (d.Black.Catalog.Count == 0) b.AddLabel(L.T("black.empty"));
|
|
|
|
|
|
Shelf(b, d.Black.Catalog.Cast<BmGood>().ToList(), (c, g) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var listing = (BmListing)g;
|
|
|
|
|
|
var seller = d.Black.Sellers[listing.Seller];
|
|
|
|
|
|
GoodHead(c, g, L.T("black.seller", seller.Name, seller.Trades, seller.Rating));
|
|
|
|
|
|
c.AddLabel(L.T("black.price", BlackMarket.Pay(listing.Price, _mode)));
|
|
|
|
|
|
c.AddFlexibleSpace();
|
|
|
|
|
|
bool ok = _mode == PayMode.Escrow || seller.Advance;
|
|
|
|
|
|
c.AddButton(ok ? L.T("black.order", Tuning.BmDoorDays) : L.T("black.noadvance"), () => { if (ok) PlaceOrder(listing); });
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 20:23:21 +08:00
|
|
|
|
private static double _sellMult = 1.0;
|
|
|
|
|
|
private static readonly double[] SellMults = { 0.6, 0.8, 0.9, 1.0, 1.2, 1.5, 1.8 };
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>出售:储藏区里能上架的货做成货架,选标价倍率后上架。</summary>
|
|
|
|
|
|
private static void SellPage(CustomUIBuilder b, int day)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
b.BeginRow();
|
|
|
|
|
|
foreach (var m in SellMults)
|
|
|
|
|
|
{
|
|
|
|
|
|
double mult = m;
|
|
|
|
|
|
string n = m.ToString("0.0", CultureInfo.InvariantCulture);
|
|
|
|
|
|
b.AddButton(Math.Abs(mult - _sellMult) < 1e-9 ? $"[×{n}]" : "×" + n, () => { _sellMult = mult; Open(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
b.End();
|
2026-11-03 20:25:06 +08:00
|
|
|
|
b.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(d.Hot.Avail(_payAsset))) + " " + L.T("black.sell.rating", d.Black.Rating));
|
2026-11-03 20:23:21 +08:00
|
|
|
|
b.AddSeparator();
|
|
|
|
|
|
var items = BlackDelivery.Sellable();
|
|
|
|
|
|
if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; }
|
|
|
|
|
|
Shelf(b, items.Select(it =>
|
|
|
|
|
|
{
|
|
|
|
|
|
string id = BlackDelivery.SellId(it);
|
|
|
|
|
|
var kind = BlackDelivery.KindOf(id);
|
|
|
|
|
|
var g = new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Contraband ? BlackMarket.Pool.First(x => x.Key == id).Value : 3 };
|
|
|
|
|
|
if (kind == GoodKind.Gpu) g.Health = Items.ToUnit(it).Health;
|
|
|
|
|
|
g.Price = BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult);
|
|
|
|
|
|
return new { g, it };
|
|
|
|
|
|
}).Select(x => { SellCards[x.g] = x.it; return x.g; }).ToList(), (c, g) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var it = SellCards[g];
|
2026-11-03 20:25:06 +08:00
|
|
|
|
GoodHead(c, g, "");
|
2026-11-03 20:23:21 +08:00
|
|
|
|
c.AddLabel(L.T("black.price", g.Price));
|
|
|
|
|
|
c.AddFlexibleSpace();
|
|
|
|
|
|
c.AddButton(L.T("black.sell.list"), () => ListItem(it));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly System.Collections.Generic.Dictionary<BmGood, GameItem> SellCards = new System.Collections.Generic.Dictionary<BmGood, GameItem>();
|
|
|
|
|
|
|
|
|
|
|
|
private static void ListItem(GameItem it)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
string id = BlackDelivery.SellId(it);
|
|
|
|
|
|
if (id == null) { Open(); return; }
|
|
|
|
|
|
var u = Items.IsGpu(it) ? Items.ToUnit(it) : null;
|
|
|
|
|
|
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), _sellMult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
|
|
|
|
|
|
if (s == null) { _message = L.T("msg.balance"); Open(); return; }
|
|
|
|
|
|
it.Destroy();
|
|
|
|
|
|
_message = L.T("black.sell.listed", ItemName(id), s.ListPrice);
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 20:06:32 +08:00
|
|
|
|
private const int ShelfCols = 4;
|
|
|
|
|
|
private const float CardW = 172f, CardH = 268f;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>货架:一行 4 张商品卡。</summary>
|
|
|
|
|
|
private static void Shelf(CustomUIBuilder b, System.Collections.Generic.List<BmGood> goods, Action<CustomUIBuilder, BmGood> card)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < goods.Count; i += ShelfCols)
|
|
|
|
|
|
{
|
|
|
|
|
|
b.BeginRow(6f);
|
|
|
|
|
|
for (int k = i; k < Math.Min(goods.Count, i + ShelfCols); k++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var g = goods[k];
|
|
|
|
|
|
b.BeginPanel(CardW, CardH, TextAnchor.UpperCenter, 6f, 3f);
|
|
|
|
|
|
card(b, g);
|
|
|
|
|
|
b.End();
|
2026-11-03 19:35:48 +08:00
|
|
|
|
}
|
2026-11-03 20:06:32 +08:00
|
|
|
|
b.End();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void GoodHead(CustomUIBuilder c, BmGood g, string line2)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sprite = Icon(g.Item);
|
|
|
|
|
|
if (sprite != null) c.AddImage(sprite, 64f, 64f); else c.AddSpace(64f);
|
|
|
|
|
|
c.AddLabel("<b>" + ItemName(g.Item) + "</b>");
|
|
|
|
|
|
c.AddLabel(g.Kind == GoodKind.Gpu ? L.T("black.kind.gpu", (int)Math.Round(g.Health)) : L.T("black.kind." + g.Kind + (g.Kind == GoodKind.Contraband ? "." + g.Level : "")));
|
2026-11-03 20:25:06 +08:00
|
|
|
|
if (!string.IsNullOrEmpty(line2)) c.AddLabel(line2);
|
2026-11-03 19:35:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 19:48:26 +08:00
|
|
|
|
private static string ItemName(string id) => BlackDelivery.ItemName(id);
|
2026-11-03 19:35:48 +08:00
|
|
|
|
|
|
|
|
|
|
private static void BuyOffer(BmOffer o)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
2026-11-03 20:06:32 +08:00
|
|
|
|
string item = o.Item, model = o.Model, fake = o.FakeOf;
|
|
|
|
|
|
double health = o.Health;
|
2026-11-03 19:35:48 +08:00
|
|
|
|
if (!d.Black.Buy(o, d.Hot, _payAsset, Session.Price)) { _message = L.T("msg.balance"); Open(); return; }
|
2026-11-03 20:06:32 +08:00
|
|
|
|
Items.Give(BlackDelivery.Make(item, model, fake, health));
|
|
|
|
|
|
_message = L.T("black.bought", ItemName(item));
|
2026-11-03 19:35:48 +08:00
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 19:48:26 +08:00
|
|
|
|
private static void PlaceOrder(BmListing l)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
2026-11-03 20:06:32 +08:00
|
|
|
|
var o = d.Black.Place(l, _mode, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
|
2026-11-03 19:48:26 +08:00
|
|
|
|
if (o == null) { _message = L.T("msg.balance"); Open(); return; }
|
|
|
|
|
|
_message = L.T("black.ordered", ItemName(o.Item), o.DueDay);
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 20:23:21 +08:00
|
|
|
|
private static void Delist(BmSale s)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
d.Black.Delist(s);
|
|
|
|
|
|
Items.Give(BlackDelivery.Make(s.Item, s.Model, s.FakeOf, s.Health));
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 19:48:26 +08:00
|
|
|
|
/// <summary>我的订单与论坛情报。</summary>
|
|
|
|
|
|
private static void MinePage(CustomUIBuilder b, int day)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
Columns(b, l =>
|
|
|
|
|
|
{
|
|
|
|
|
|
l.AddHeader(L.T("black.mine.h"));
|
2026-11-03 20:23:21 +08:00
|
|
|
|
if (d.Black.Orders.Count == 0 && d.Black.Sales.Count == 0) l.AddLabel(L.T("black.mine.none"));
|
|
|
|
|
|
foreach (var sl in d.Black.Sales.Where(x => x.State != SaleState.Delisted || x.Why == "fake").OrderByDescending(x => x.ListedDay))
|
|
|
|
|
|
{
|
|
|
|
|
|
var sale = sl;
|
|
|
|
|
|
string st = sale.State == SaleState.Sold ? L.T("black.sale.Sold", sale.PayDay) : L.T("black.sale." + sale.State + (sale.State == SaleState.Delisted && sale.Why != null ? "." + sale.Why : ""));
|
|
|
|
|
|
l.AddLabel(L.T("black.mine.sale", ItemName(sale.Item), sale.Mult.ToString("0.0", CultureInfo.InvariantCulture), st));
|
|
|
|
|
|
if (sale.State == SaleState.Listed) l.AddButton(L.T("black.sell.delist"), () => Delist(sale));
|
|
|
|
|
|
}
|
2026-11-03 19:48:26 +08:00
|
|
|
|
foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay))
|
2026-11-03 20:06:32 +08:00
|
|
|
|
l.AddLabel(L.T("black.mine.line", ItemName(o.Item), 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));
|
2026-11-03 19:48:26 +08:00
|
|
|
|
}, r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AddHeader(L.T("black.forum.h"));
|
|
|
|
|
|
if (d.Black.Forum.Count == 0) r.AddLabel(L.T("black.forum.quiet"));
|
|
|
|
|
|
foreach (var f in d.Black.Forum)
|
2026-11-03 20:13:18 +08:00
|
|
|
|
r.AddLabel(f.StartsWith("scam:") ? L.T("black.forum.scam", f.Substring(5)) : f.StartsWith("busted:") ? L.T("black.forum.busted", f.Substring(7)) : L.T("black.forum." + f));
|
2026-11-03 19:48:26 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 19:35:48 +08:00
|
|
|
|
private static void DeskPage(CustomUIBuilder b, int rep, int day)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
var w = d.Hot;
|
|
|
|
|
|
Columns(b, l =>
|
|
|
|
|
|
{
|
|
|
|
|
|
l.AddHeader(L.T("black.desk.h"));
|
|
|
|
|
|
l.AddLabel(L.T("black.desk.left", Fmt.Credits(d.Black.DeskLeft(day)), Fmt.Credits(Tuning.PoneroDailyCap)));
|
|
|
|
|
|
l.AddLabel(L.T("black.desk.price", Fmt.Num(Session.Price(Assets.Ponero))));
|
|
|
|
|
|
Holdings(l, w, a => a == Assets.Ponero || a == _deskAsset);
|
|
|
|
|
|
}, r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AddHeader(L.T("black.desk.swap"));
|
|
|
|
|
|
r.BeginRow();
|
|
|
|
|
|
r.AddButton(_deskIn ? $"[{L.T("black.desk.in")}]" : L.T("black.desk.in"), () => { _deskIn = true; Open(); });
|
|
|
|
|
|
r.AddButton(_deskIn ? L.T("black.desk.out") : $"[{L.T("black.desk.out")}]", () => { _deskIn = false; Open(); });
|
|
|
|
|
|
r.End();
|
|
|
|
|
|
r.BeginRow();
|
|
|
|
|
|
foreach (var a in new[] { Assets.PSC, Assets.CRT, Assets.CRA })
|
|
|
|
|
|
{
|
|
|
|
|
|
var asset = a;
|
|
|
|
|
|
r.AddButton(asset == _deskAsset ? $"[{asset}]" : asset, () => { _deskAsset = asset; Open(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
r.End();
|
|
|
|
|
|
r.AddLabel(_deskIn ? L.T("mixer.balance", _deskAsset, Fmt.Num(w.Avail(_deskAsset))) : L.T("mixer.balance", Assets.Ponero, Fmt.Num(w.Avail(Assets.Ponero))));
|
|
|
|
|
|
AmountRow(r);
|
|
|
|
|
|
r.AddButton(L.T("black.desk.go"), DeskSwap);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void DeskSwap()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ReadAmount(out double v)) return;
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
int day = StoreStation.GetDayCounter();
|
|
|
|
|
|
double got = _deskIn ? d.Black.ToPonero(d.Hot, _deskAsset, v, day, Session.Price) : d.Black.FromPonero(d.Hot, _deskAsset, v, day, Session.Price);
|
|
|
|
|
|
if (got < 0) { _message = L.T("black.desk.fail"); Open(); return; }
|
|
|
|
|
|
// 从 Ponero 出来的币来源不明:出金审查按"来自混币器"加重
|
|
|
|
|
|
if (!_deskIn) d.Mixer.LastReturnDay = day;
|
|
|
|
|
|
_message = _deskIn ? L.T("msg.trade", Fmt.Num(v), _deskAsset, Fmt.Num(got), Assets.Ponero) : L.T("msg.trade", Fmt.Num(v), Assets.Ponero, Fmt.Num(got), _deskAsset);
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 20:13:18 +08:00
|
|
|
|
private enum Web3Tab { Trade, Mining, Mixer, Black }
|
2026-11-03 20:06:32 +08:00
|
|
|
|
private static Web3Tab _web3Tab = Web3Tab.Trade;
|
2026-11-03 16:39:40 +08:00
|
|
|
|
private static string _mixAsset = Assets.PSC;
|
|
|
|
|
|
private static int _mixRounds = 2;
|
|
|
|
|
|
private static readonly float[] MixW = { 70f, 100f, 100f, 80f };
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>混币器:把币存进去,几轮之后提回同一个地址,污点降低,每轮扣手续费。</summary>
|
|
|
|
|
|
private static void MixerPage(CustomUIBuilder b)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
var w = d.Hot;
|
|
|
|
|
|
int day = StoreStation.GetDayCounter();
|
|
|
|
|
|
Columns(b, l =>
|
|
|
|
|
|
{
|
|
|
|
|
|
l.AddHeader(L.T("mixer.h"));
|
|
|
|
|
|
l.AddLabel(d.Mixer.Sanctioned(day) ? L.T("mixer.sanctioned", d.Mixer.SanctionedUntil - day + 1) : L.T("mixer.ok"));
|
|
|
|
|
|
if (w.Taint > 0.5) l.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
|
|
|
|
|
|
l.AddSeparator();
|
|
|
|
|
|
l.AddHeader(L.T("mixer.pending"));
|
|
|
|
|
|
if (d.Mixer.Pending.Count == 0) l.AddLabel(L.T("mixer.none"));
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
TableHeader(l, MixW, L.T("col.asset"), L.T("col.amount"), L.T("mixer.col.taint"), L.T("mixer.col.ready"));
|
|
|
|
|
|
foreach (var p in d.Mixer.Pending)
|
|
|
|
|
|
TableRow(l, MixW, p.Asset, Fmt.Num(p.Amount), Fmt.Credits(p.Taint), L.T("mixer.day", p.ReadyDay));
|
|
|
|
|
|
}
|
|
|
|
|
|
}, r =>
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AddHeader(L.T("mixer.deposit"));
|
|
|
|
|
|
r.BeginRow();
|
|
|
|
|
|
foreach (var a in new[] { Assets.PSC, Assets.CRC, Assets.CRT, Assets.CRA })
|
|
|
|
|
|
{
|
|
|
|
|
|
var asset = a;
|
|
|
|
|
|
r.AddButton(asset == _mixAsset ? $"[{asset}]" : asset, () => { _mixAsset = asset; Open(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
r.End();
|
2026-11-03 16:51:12 +08:00
|
|
|
|
r.AddLabel(L.T("mixer.balance", _mixAsset, Fmt.Num(w.Avail(_mixAsset))));
|
2026-11-03 16:39:40 +08:00
|
|
|
|
AmountRow(r);
|
|
|
|
|
|
r.AddLabel(L.T("mixer.rounds"));
|
|
|
|
|
|
r.BeginRow();
|
|
|
|
|
|
for (int n = 1; n <= Tuning.MixerMaxRounds; n++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int rounds = n;
|
|
|
|
|
|
r.AddButton(rounds == _mixRounds ? $"[{rounds}]" : rounds.ToString(), () => { _mixRounds = rounds; Open(); });
|
|
|
|
|
|
}
|
|
|
|
|
|
r.End();
|
|
|
|
|
|
if (double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double v) && v > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
double rate = Mixer.RoundRate(v * Session.Price(_mixAsset));
|
|
|
|
|
|
r.AddLabel(L.T("mixer.preview", Fmt.Num(v * Math.Pow(1 - Tuning.MixerFee, _mixRounds)), _mixAsset, (int)Math.Round((1 - Math.Pow(1 - rate, _mixRounds)) * 100), day + _mixRounds));
|
|
|
|
|
|
}
|
|
|
|
|
|
r.AddButton(L.T("mixer.go"), MixDeposit);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void MixDeposit()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!ReadAmount(out double v)) return;
|
|
|
|
|
|
var d = Session.Data;
|
|
|
|
|
|
int day = StoreStation.GetDayCounter();
|
2026-11-03 16:51:12 +08:00
|
|
|
|
if (d.Hot.Avail(_mixAsset) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
|
2026-11-03 16:39:40 +08:00
|
|
|
|
var dep = d.Mixer.Deposit(d.Hot, _mixAsset, v, _mixRounds, day, Session.Price);
|
|
|
|
|
|
_message = dep == null ? L.T("mixer.min", Tuning.MixerMin) : L.T("mixer.done", Fmt.Num(v), _mixAsset, dep.ReadyDay);
|
|
|
|
|
|
Session.Save();
|
|
|
|
|
|
Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 16:51:12 +08:00
|
|
|
|
private static bool frozenPay(Wallet w, string asset) => w.Frozen.ContainsKey(asset);
|
|
|
|
|
|
|
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; }
|
2026-11-03 16:39:40 +08:00
|
|
|
|
var r = d.Exchange.Cashout(d.Hot, v, Session.Price, StoreStation.GetDayCounter(), Session.Rent(), d.Mixer.RecentlyMixed(StoreStation.GetDayCounter()), d.Rng);
|
2026-11-03 09:11:46 +08:00
|
|
|
|
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;
|
2026-11-03 16:51:12 +08:00
|
|
|
|
if (w.Avail(pay) + 1e-9 < v) { _message = L.T(frozenPay(w, pay) ? "msg.frozen" : "msg.balance"); Open(); return; }
|
2026-11-03 09:11:46 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|