版本 1.0.0-rc8:卖货橱窗重构、收款币种、Gas 档位速度、区块自然增长、钱包持仓
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
018ee7d0d2
commit
93e26fc4f5
20 files changed
+582
-498
No files matched your search
+275
-300
@@ -32,9 +32,6 @@ namespace Decentralized
|
||||
private enum Page { Home, Bank, Poinbase, Web3, Mining, Gray, Wallets }
|
||||
|
||||
private static Page _page = Page.Home;
|
||||
private sealed class EditPrice { public GameItem Item; public BmSale Sale; public string Id; public long Ref; }
|
||||
private static EditPrice _edit;
|
||||
private static string _editPrice = "";
|
||||
private static string _amount = "";
|
||||
private static string _message;
|
||||
|
||||
@@ -59,7 +56,7 @@ namespace Decentralized
|
||||
TabRows(b, Enum.GetValues(typeof(Page)).Cast<Page>().Where(p => p != Page.Gray || GrayAvailable).Select(p =>
|
||||
{
|
||||
var page = p;
|
||||
return new TabDef { Name = L.T("tab." + p.ToString().ToLowerInvariant()), On = page == _page, Click = () => { _page = page; _message = null; _edit = null; Open(); } };
|
||||
return new TabDef { Name = L.T("tab." + p.ToString().ToLowerInvariant()), On = page == _page, Click = () => { _page = page; _message = null; Open(); } };
|
||||
}).ToList(), true);
|
||||
ConnectPicker(b);
|
||||
b.AddSeparator();
|
||||
@@ -86,7 +83,7 @@ namespace Decentralized
|
||||
Log.Guard("购物车按钮", () => CartFab(win));
|
||||
}
|
||||
|
||||
private static void Go(Page p) { _page = p; _message = null; _edit = null; Open(); }
|
||||
private static void Go(Page p) { _page = p; _message = null; Open(); }
|
||||
|
||||
/// <summary>钱包那边交易落地后刷新终端(开着的话),带上结果消息。</summary>
|
||||
public static void Refresh(string message)
|
||||
@@ -1169,7 +1166,7 @@ namespace Decentralized
|
||||
private static void WalletsPage(CustomUIBuilder b)
|
||||
{
|
||||
var d = Session.Data;
|
||||
TabRows(b, Enum.GetValues(typeof(WalletTab)).Cast<WalletTab>().Select(t => { var tab = t; return new TabDef { Name = L.T("wallets.sub." + t.ToString().ToLowerInvariant()), On = tab == _walletTab, Click = () => { _walletTab = tab; _message = null; _edit = null; Open(); } }; }).ToList(), false);
|
||||
TabRows(b, Enum.GetValues(typeof(WalletTab)).Cast<WalletTab>().Select(t => { var tab = t; return new TabDef { Name = L.T("wallets.sub." + t.ToString().ToLowerInvariant()), On = tab == _walletTab, Click = () => { _walletTab = tab; _message = null; Open(); } }; }).ToList(), false);
|
||||
if (_walletTab == WalletTab.Explorer) { ExplorerPage(b); return; }
|
||||
b.AddLabel(L.T("wallets.count", d.Wallets.Count, Tuning.WalletMax), "wallets_count_dim");
|
||||
if (d.Wallets.Count == 0) { b.AddLabel(L.T("wallets.none"), "wallets_none_dim"); return; }
|
||||
@@ -1220,6 +1217,13 @@ namespace Decentralized
|
||||
b.End();
|
||||
b.End();
|
||||
}
|
||||
if (d.Connected)
|
||||
{
|
||||
// 选中的钱包:下面列出它持有的币和数量
|
||||
b.AddSeparator();
|
||||
b.AddHeader(L.T("web3.wallet.h") + " " + WalletWindow.NameOf(d.Hot));
|
||||
Holdings(b, d.Hot, a => true);
|
||||
}
|
||||
if (_renamePick != null) RenameRow(b);
|
||||
}
|
||||
|
||||
@@ -1329,7 +1333,7 @@ namespace Decentralized
|
||||
if (_grayTab == GrayTab.Mixer && !Unlock.MixerUnlocked) _grayTab = GrayTab.Black;
|
||||
if (_grayTab == GrayTab.Black && !Unlock.BlackUnlocked) _grayTab = GrayTab.Mixer;
|
||||
if (Unlock.MixerUnlocked && Unlock.BlackUnlocked)
|
||||
TabRows(b, Enum.GetValues(typeof(GrayTab)).Cast<GrayTab>().Select(t => { var tab = t; return new TabDef { Name = L.T("gray.sub." + t.ToString().ToLowerInvariant()), On = tab == _grayTab, Click = () => { _grayTab = tab; _message = null; _edit = null; Open(); } }; }).ToList(), false);
|
||||
TabRows(b, Enum.GetValues(typeof(GrayTab)).Cast<GrayTab>().Select(t => { var tab = t; return new TabDef { Name = L.T("gray.sub." + t.ToString().ToLowerInvariant()), On = tab == _grayTab, Click = () => { _grayTab = tab; _message = null; Open(); } }; }).ToList(), false);
|
||||
if (!Session.Data.Connected) b.AddLabel(L.T("web3.err.noconn"), "term_note");
|
||||
if (_grayTab == GrayTab.Mixer) MixerPage(b); else BlackPage(b);
|
||||
}
|
||||
@@ -1341,7 +1345,7 @@ namespace Decentralized
|
||||
if (_web3Tab == Web3Tab.Predict && !PredictGame.Available(StoreStation.GetDayCounter())) _web3Tab = Web3Tab.Trade;
|
||||
TabRows(b, Enum.GetValues(typeof(Web3Tab)).Cast<Web3Tab>()
|
||||
.Where(t => t != Web3Tab.Predict || PredictGame.Available(StoreStation.GetDayCounter()))
|
||||
.Select(t => { var tab = t; return new TabDef { Name = L.T("web3.sub." + t.ToString().ToLowerInvariant()), On = tab == _web3Tab, Click = () => { _web3Tab = tab; _message = null; _edit = null; Open(); } }; }).ToList(), false);
|
||||
.Select(t => { var tab = t; return new TabDef { Name = L.T("web3.sub." + t.ToString().ToLowerInvariant()), On = tab == _web3Tab, Click = () => { _web3Tab = tab; _message = null; Open(); } }; }).ToList(), false);
|
||||
if (!Session.Data.Connected) b.AddLabel(L.T("web3.err.noconn"), "term_note");
|
||||
if (_web3Tab == Web3Tab.Stake) { StakePage(b); return; }
|
||||
if (_web3Tab == Web3Tab.Predict) { PredictPage(b); return; }
|
||||
@@ -1481,13 +1485,11 @@ namespace Decentralized
|
||||
{
|
||||
var tab = t;
|
||||
string name = L.T("black.sub." + t.ToString().ToLowerInvariant());
|
||||
PledgerSkin.Pick(b, name, tab == _blackTab, () => { _blackTab = tab; _message = null; _edit = null; _pay = null; _checkout = false; Open(); }, _picked);
|
||||
PledgerSkin.Pick(b, name, tab == _blackTab, () => { _blackTab = tab; _message = null; _sellView = SellView.Showcase; _stage.Clear(); _delist.Clear(); _coinsOpen = false; _checkout = false; Open(); }, _picked);
|
||||
}
|
||||
b.End();
|
||||
if (d.Black.Closed(day)) { b.AddLabel(L.T("black.rugged", d.Black.ClosedUntil - day + 1), "black_rugged_note"); return; }
|
||||
if (_checkout) { CheckoutPage(b, d); return; }
|
||||
if (_pay != null && _blackTab != BlackTab.Buy) { PayPanel(b); return; }
|
||||
if (_edit != null) { EditPage(b); return; }
|
||||
if (_blackTab == BlackTab.Orders) { OrdersPage(b, day); return; }
|
||||
if (_blackTab == BlackTab.Sell) { SellPage(b, day); return; }
|
||||
BuyPage(b, d, rep);
|
||||
@@ -1545,142 +1547,142 @@ namespace Decentralized
|
||||
});
|
||||
}
|
||||
|
||||
// ——— 付款面板 ———
|
||||
|
||||
/// <summary>一次付款的草稿:买什么、收款地址、商家收哪些币、可选数量与付款方式、合计,以及付款成功后做的事。</summary>
|
||||
private sealed class PayDraft
|
||||
{
|
||||
public string Title, Address, Asset, SwapFrom;
|
||||
public string[] Accepted;
|
||||
public int MaxQty = 1, Qty = 1;
|
||||
public bool Modes;
|
||||
public PayMode Mode = PayMode.Escrow;
|
||||
public Func<int, PayMode, double> Credits;
|
||||
public Action<string, int, PayMode, Wallet> Execute;
|
||||
}
|
||||
|
||||
private static PayDraft _pay;
|
||||
|
||||
private static void OpenPay(PayDraft p)
|
||||
{
|
||||
var w = Session.Data.Hot;
|
||||
p.Asset = p.Accepted.FirstOrDefault(a => w.Avail(a) * Session.Price(a) >= p.Credits(1, p.Mode)) ?? p.Accepted[0];
|
||||
p.SwapFrom = null;
|
||||
_pay = p;
|
||||
_message = null;
|
||||
Open();
|
||||
}
|
||||
|
||||
private static string Summary(PayDraft p) => p.Title + (p.Qty > 1 ? " ×" + p.Qty : "");
|
||||
|
||||
/// <summary>付款面板:数量、付款方式、合计、付款币种(商家收的币)、生成账单;下面是自动换币。</summary>
|
||||
private static void PayPanel(CustomUIBuilder b)
|
||||
{
|
||||
var d = Session.Data;
|
||||
var w = d.Hot;
|
||||
var p = _pay;
|
||||
if (p.Qty > p.MaxQty) p.Qty = p.MaxQty;
|
||||
b.AddHeader(L.T("bpay.h"));
|
||||
b.AddLabel(p.Title, "bpay_title");
|
||||
if (p.MaxQty > 1)
|
||||
{
|
||||
b.BeginRow(4f);
|
||||
b.AddLabel(L.T("bpay.qty"));
|
||||
foreach (var q in Qtys)
|
||||
{
|
||||
if (q > p.MaxQty && q != Qtys[0]) continue;
|
||||
int qq = q;
|
||||
PledgerSkin.Pick(b, "×" + qq, qq == p.Qty, () => { p.Qty = qq; Open(); }, _picked);
|
||||
}
|
||||
b.End();
|
||||
}
|
||||
if (p.Modes)
|
||||
{
|
||||
b.BeginRow(4f);
|
||||
foreach (PayMode m in Enum.GetValues(typeof(PayMode)))
|
||||
{
|
||||
var mode = m;
|
||||
PledgerSkin.Pick(b, L.T("black.mode." + m), mode == p.Mode, () => { p.Mode = mode; Open(); }, _picked);
|
||||
}
|
||||
b.End();
|
||||
}
|
||||
double credits = p.Credits(p.Qty, p.Mode);
|
||||
b.AddLabel(L.T("bpay.total", Fmt.Credits(credits)), "bpay_total");
|
||||
b.AddSeparator();
|
||||
b.AddLabel(L.T("bpay.coin"), "bpay_coin_dim");
|
||||
b.BeginRow(4f);
|
||||
foreach (var a in p.Accepted)
|
||||
{
|
||||
string asset = a;
|
||||
PledgerSkin.Pick(b, asset, asset == p.Asset, () => { p.Asset = asset; Open(); }, _picked);
|
||||
}
|
||||
b.End();
|
||||
double need = WalletWindow.RoundUp(credits / Session.Price(p.Asset));
|
||||
b.AddLabel(L.T("bpay.need", WalletWindow.Amt(need), p.Asset, Fmt.Num(w.Avail(p.Asset))), "bpay_need_dim");
|
||||
b.AddButton(L.T("bpay.invoice"), () =>
|
||||
{
|
||||
string asset = p.Asset; int q = p.Qty; var mode = p.Mode;
|
||||
string summary = Summary(p);
|
||||
_pay = null;
|
||||
BeginPay(p.Address, summary, asset, credits, pw => p.Execute(asset, q, mode, pw));
|
||||
});
|
||||
b.AddSeparator();
|
||||
// 自动换币:从钱包里别的币里换出商家收的币,多付滑点;是合约调用,签名后一步完成
|
||||
b.AddLabel(L.T("bpay.swap.h", p.Asset), "bpay_swap_dim");
|
||||
double slipped = credits * (1 + Tuning.BmAutoSwapSlip);
|
||||
var sources = w.Balances.Keys.Where(a => !p.Accepted.Contains(a) && a != Assets.Ponero && Session.Price(a) > 0 && w.Avail(a) * Session.Price(a) >= slipped).ToList();
|
||||
if (sources.Count == 0) b.AddLabel(L.T("bpay.swap.none"), "bpay_swap_none_dim");
|
||||
else
|
||||
{
|
||||
if (p.SwapFrom == null || !sources.Contains(p.SwapFrom)) p.SwapFrom = sources[0];
|
||||
b.BeginRow(4f);
|
||||
foreach (var s in sources)
|
||||
{
|
||||
string src = s;
|
||||
PledgerSkin.Pick(b, src, src == p.SwapFrom, () => { p.SwapFrom = src; Open(); }, _picked);
|
||||
}
|
||||
b.End();
|
||||
double needSrc = WalletWindow.RoundUp(slipped / Session.Price(p.SwapFrom));
|
||||
b.AddLabel(L.T("bpay.swap.need", WalletWindow.Amt(needSrc), p.SwapFrom, (Tuning.BmAutoSwapSlip * 100).ToString("0", CultureInfo.InvariantCulture)), "bpay_swap_need_dim");
|
||||
b.AddButton(L.T("bpay.swap.btn"), () => SwapAndPay(p, credits, needSrc));
|
||||
}
|
||||
b.AddSpace(4f);
|
||||
b.AddButton(L.T("bpay.back"), () => { _pay = null; _message = null; Open(); });
|
||||
}
|
||||
|
||||
/// <summary>自动换币并付款:一次合约调用,先把别的币按滑点换成商家收的币,再下单。</summary>
|
||||
private static void SwapAndPay(PayDraft p, double credits, double needSrc)
|
||||
{
|
||||
var d = Session.Data;
|
||||
string src = p.SwapFrom, asset = p.Asset; int q = p.Qty; var mode = p.Mode;
|
||||
_pay = null;
|
||||
_message = L.T("hw.wait");
|
||||
Open();
|
||||
WalletWindow.OpenForBill(new WalletWindow.Bill
|
||||
{
|
||||
Contract = true, Asset = src, Amount = needSrc, Spend = needSrc,
|
||||
Summary = L.T("bpay.swap.sum", Summary(p), src, WalletWindow.Amt(needSrc), asset),
|
||||
Execute = pw =>
|
||||
{
|
||||
if (pw.Avail(src) + 1e-9 < needSrc) { _message = L.T("msg.balance"); Open(); return; }
|
||||
double got = credits / Session.Price(asset) * 1.002;
|
||||
pw.Add(src, -needSrc);
|
||||
pw.Add(asset, got);
|
||||
pw.RecordBuy(asset, got, credits);
|
||||
CandleChart.Trade(src, credits); CandleChart.Trade(asset, credits);
|
||||
Log.Info($"自动换币:{needSrc:0.####} {src} → {got:0.####} {asset}({credits:0} 信用点等值)");
|
||||
p.Execute(asset, q, mode, pw);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private static double _sellMult = 1.0;
|
||||
|
||||
/// <summary>出售:储藏区里能上架的货做成货架,选标价倍率后上架。</summary>
|
||||
private static void SellPage(CustomUIBuilder b, int day)
|
||||
private enum SellView { Showcase, Add, Fee }
|
||||
private static SellView _sellView = SellView.Showcase;
|
||||
private static readonly Dictionary<string, int> _stage = new Dictionary<string, int>();
|
||||
private static readonly Dictionary<string, int> _delist = new Dictionary<string, int>();
|
||||
private static bool _coinsOpen;
|
||||
private static string _feeCoin;
|
||||
|
||||
/// <summary>储藏区里同一种货(标识、型号、真伪、成色都相同)聚成一组。</summary>
|
||||
private sealed class SellGroup
|
||||
{
|
||||
public string Key, Id, Model, FakeOf;
|
||||
public double Health;
|
||||
public long Ref;
|
||||
public GoodKind Kind;
|
||||
public readonly List<GameItem> Items = new List<GameItem>();
|
||||
}
|
||||
|
||||
private static List<SellGroup> SellGroups()
|
||||
{
|
||||
var map = new Dictionary<string, SellGroup>();
|
||||
var list = new List<SellGroup>();
|
||||
foreach (var it in BlackDelivery.Sellable())
|
||||
{
|
||||
string id = BlackDelivery.SellId(it);
|
||||
if (id == null) continue;
|
||||
var kind = BlackDelivery.KindOf(id);
|
||||
var u = kind == GoodKind.Gpu ? Items.ToUnit(it) : null;
|
||||
string key = BmSale.KeyOf(id, u?.Model, u?.FakeOf, u?.Health ?? 0);
|
||||
if (!map.TryGetValue(key, out var g))
|
||||
{
|
||||
g = new SellGroup { Key = key, Id = id, Model = u?.Model, FakeOf = u?.FakeOf, Health = u?.Health ?? 0, Ref = BlackDelivery.RefValue(it), Kind = kind };
|
||||
map[key] = g; list.Add(g);
|
||||
}
|
||||
g.Items.Add(it);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static double MultFor(string key) => Session.Data.Black.MultOf(key) ?? _sellMult;
|
||||
private static int Staged(string key) => _stage.TryGetValue(key, out int n) ? n : 0;
|
||||
|
||||
/// <summary>这一组预备数量再加一件会不会超限:返回提示文字,可以加返回 null。</summary>
|
||||
private static string StageBlock(SellGroup g)
|
||||
{
|
||||
var d = Session.Data;
|
||||
var items = BlackDelivery.Sellable();
|
||||
int have = d.Black.ActiveOf(g.Key), staged = Staged(g.Key);
|
||||
if (have + staged + 1 > Tuning.BmMaxStack) return L.T("black.sell.limit.stack", Tuning.BmMaxStack);
|
||||
if (have == 0 && staged == 0)
|
||||
{
|
||||
int kinds = d.Black.ActiveKinds + _stage.Count(kv => kv.Value > 0 && d.Black.ActiveOf(kv.Key) == 0);
|
||||
if (kinds >= Tuning.BmMaxKinds) return L.T("black.sell.limit.kinds", Tuning.BmMaxKinds);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static BmGood GoodOf(string id, GoodKind kind, string model, double health, int price) =>
|
||||
new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Gpu ? 2 : Merchants.Info(id).Level, Model = model, Health = health, Price = price };
|
||||
|
||||
/// <summary>卖货:默认是已上架的橱窗;"上架更多"进入备货页,"货架"付平台费后上架。</summary>
|
||||
private static void SellPage(CustomUIBuilder b, int day)
|
||||
{
|
||||
if (_sellView == SellView.Add) { SellAddPage(b); return; }
|
||||
if (_sellView == SellView.Fee) { SellFeePage(b); return; }
|
||||
ShowcasePage(b);
|
||||
}
|
||||
|
||||
private static void ShowcasePage(CustomUIBuilder b)
|
||||
{
|
||||
var d = Session.Data;
|
||||
var listed = d.Black.Sales.Where(x => x.State == SaleState.Listed).GroupBy(x => x.Key).ToList();
|
||||
b.AddLabel(L.T("black.sell.rating", d.Black.Rating) + " " + L.T("black.sell.kinds", d.Black.ActiveKinds, Tuning.BmMaxKinds), "sell_status_dim");
|
||||
b.BeginRow(6f);
|
||||
b.AddButton(L.T("black.sell.add"), () => { _sellView = SellView.Add; _delist.Clear(); _message = null; Open(); });
|
||||
PledgerSkin.Pick(b, L.T("black.sell.coins"), _coinsOpen, () => { _coinsOpen = !_coinsOpen; Open(); }, _picked);
|
||||
b.End();
|
||||
if (_coinsOpen)
|
||||
{
|
||||
b.BeginRow(4f);
|
||||
foreach (var c in BlackMarket.SellCoins)
|
||||
{
|
||||
string coin = c;
|
||||
PledgerSkin.Pick(b, coin, d.Black.AcceptedCoins.Contains(coin), () =>
|
||||
{
|
||||
if (!d.Black.ToggleCoin(coin)) _message = L.T("black.sell.nocoin");
|
||||
else { _message = null; Session.Save(); }
|
||||
Open();
|
||||
}, _picked);
|
||||
}
|
||||
b.End();
|
||||
}
|
||||
if (listed.Count == 0) { b.AddLabel(L.T("black.sell.empty"), "sell_none_dim"); return; }
|
||||
var cards = new Dictionary<BmGood, IGrouping<string, BmSale>>();
|
||||
Shelf(b, listed.Select(g =>
|
||||
{
|
||||
var s = g.First();
|
||||
var kind = BlackDelivery.KindOf(s.Item);
|
||||
var good = GoodOf(s.Item, kind, s.Model, s.Health, s.ListPrice);
|
||||
cards[good] = g;
|
||||
return good;
|
||||
}).ToList(), (c, good) =>
|
||||
{
|
||||
var g = cards[good];
|
||||
string key = g.Key; int count = g.Count();
|
||||
GoodHead(c, good, L.T("black.sell.have", count));
|
||||
c.AddLabel(L.T("black.price", good.Price));
|
||||
c.AddFlexibleSpace();
|
||||
int n = _delist.TryGetValue(key, out int cur) ? cur : 0;
|
||||
c.AddButton(n > 0 ? L.T("black.sell.off.n", n) : L.T("black.sell.off"), () => { _delist[key] = (n + 1) % (count + 1); _message = null; Open(); });
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfirmDelist()
|
||||
{
|
||||
var d = Session.Data;
|
||||
int n = 0;
|
||||
foreach (var kv in _delist.Where(kv => kv.Value > 0).ToList())
|
||||
foreach (var s in d.Black.Sales.Where(x => x.State == SaleState.Listed && x.Key == kv.Key).Take(kv.Value).ToList())
|
||||
{
|
||||
d.Black.Delist(s);
|
||||
Items.Give(BlackDelivery.Make(s.Item, s.Model, s.FakeOf, s.Health));
|
||||
n++;
|
||||
}
|
||||
_delist.Clear();
|
||||
_message = L.T("black.sell.delisted", n);
|
||||
Session.Save();
|
||||
Open();
|
||||
}
|
||||
|
||||
private static void SellAddPage(CustomUIBuilder b)
|
||||
{
|
||||
var d = Session.Data;
|
||||
var groups = SellGroups();
|
||||
_stage.Keys.Where(k => groups.All(g => g.Key != k)).ToList().ForEach(k => _stage.Remove(k));
|
||||
foreach (var g in groups) if (Staged(g.Key) > g.Items.Count) _stage[g.Key] = g.Items.Count;
|
||||
b.BeginRow(8f, TextAnchor.UpperLeft);
|
||||
b.BeginPanel(SellerW + 8f, -1f, TextAnchor.UpperLeft, 0f, 2f);
|
||||
_clear.Add(b.Last.image);
|
||||
@@ -1688,79 +1690,111 @@ namespace Decentralized
|
||||
foreach (var m in Tuning.BmSellMults)
|
||||
{
|
||||
double mult = m;
|
||||
TableRow(b, SellerW, new[] { 70f, SellerW - 78f }, new[] { TextAnchor.MiddleLeft, TextAnchor.MiddleRight },
|
||||
new[] { "×" + m.ToString("0.0", CultureInfo.InvariantCulture), L.T("black.sell.days", Math.Round(BlackMarket.ExpectedDays(m))) },
|
||||
TableRow(b, SellerW, new[] { SellerW - 8f }, new[] { TextAnchor.MiddleLeft },
|
||||
new[] { "×" + m.ToString("0.0", CultureInfo.InvariantCulture) },
|
||||
Math.Abs(mult - _sellMult) < 1e-9, () => { _sellMult = mult; Open(); });
|
||||
}
|
||||
b.AddSpace(4f);
|
||||
b.AddLabel(L.T("black.sell.rating", d.Black.Rating), "sell_status_dim");
|
||||
b.AddLabel(L.T("black.sell.slots", d.Black.ActiveListings, Tuning.BmMaxListings), "sell_status_dim");
|
||||
b.AddLabel(L.T("black.sell.kinds", d.Black.ActiveKinds, Tuning.BmMaxKinds), "sell_status_dim");
|
||||
b.AddSpace(4f);
|
||||
b.AddButton(L.T("black.sell.all"), () =>
|
||||
{
|
||||
double mult = _sellMult;
|
||||
double fee = BlackDelivery.Sellable().Sum(it => BlackMarket.ListFee(BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), mult)));
|
||||
OpenPay(new PayDraft
|
||||
{
|
||||
Title = L.T("black.sell.all") + L.T("pay.listfee", mult.ToString("0.0", CultureInfo.InvariantCulture)), Address = d.Address("platform"), Accepted = Merchants.PlatformAccepts,
|
||||
Credits = (q, m) => fee, Execute = (asset, q, m, pw) => ListAll(asset, mult, pw),
|
||||
});
|
||||
});
|
||||
b.AddButton(L.T("black.sell.back"), () => { _sellView = SellView.Showcase; _stage.Clear(); _message = null; Open(); });
|
||||
b.End();
|
||||
b.BeginPanel(ShelfW, -1f, TextAnchor.UpperLeft, 0f, 4f);
|
||||
_clear.Add(b.Last.image);
|
||||
if (items.Count == 0) b.AddLabel(L.T("black.sell.none"), "sell_none_dim");
|
||||
else Shelf(b, items.Select(it =>
|
||||
if (groups.Count == 0) b.AddLabel(L.T("black.sell.none"), "sell_none_dim");
|
||||
else
|
||||
{
|
||||
string id = BlackDelivery.SellId(it);
|
||||
var kind = BlackDelivery.KindOf(id);
|
||||
var g = new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Gpu ? 2 : Merchants.Info(id).Level };
|
||||
if (kind == GoodKind.Gpu) { var uu = Items.ToUnit(it); g.Health = uu.Health; g.Model = uu.Model; }
|
||||
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];
|
||||
GoodHead(c, g, "");
|
||||
c.AddLabel(L.T("black.price", g.Price));
|
||||
c.AddFlexibleSpace();
|
||||
c.AddButton(L.T("black.sell.list"), () => BeginEdit(new EditPrice { Item = it, Id = g.Item, Ref = BlackDelivery.RefValue(it) }, g.Price));
|
||||
});
|
||||
b.End();
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static readonly System.Collections.Generic.Dictionary<BmGood, GameItem> SellCards = new System.Collections.Generic.Dictionary<BmGood, GameItem>();
|
||||
|
||||
private static void ListAll(string asset, double mult, Wallet pw)
|
||||
{
|
||||
var d = Session.Data;
|
||||
int n = 0;
|
||||
foreach (var it in BlackDelivery.Sellable())
|
||||
{
|
||||
string id = BlackDelivery.SellId(it);
|
||||
if (id == null) continue;
|
||||
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), mult, pw, asset, StoreStation.GetDayCounter(), Session.Price);
|
||||
if (s == null) break;
|
||||
it.Destroy();
|
||||
n++;
|
||||
var cards = new Dictionary<BmGood, SellGroup>();
|
||||
Shelf(b, groups.Select(g =>
|
||||
{
|
||||
var good = GoodOf(g.Id, g.Kind, g.Model, g.Health, BlackMarket.ListPriceOf(g.Ref, MultFor(g.Key)));
|
||||
cards[good] = g;
|
||||
return good;
|
||||
}).ToList(), (c, good) =>
|
||||
{
|
||||
var g = cards[good];
|
||||
GoodHead(c, good, "");
|
||||
c.AddLabel(L.T("black.price", good.Price));
|
||||
c.AddLabel(L.T("black.stock", g.Items.Count), "good_line2_dim");
|
||||
c.AddFlexibleSpace();
|
||||
c.BeginRow(4f, TextAnchor.MiddleCenter);
|
||||
RowKey(c, "-", () => { if (Staged(g.Key) > 0) _stage[g.Key] = Staged(g.Key) - 1; _message = null; Open(); });
|
||||
Cell(c, 34f, Staged(g.Key).ToString(), false, TextAnchor.MiddleCenter);
|
||||
RowKey(c, "+", () =>
|
||||
{
|
||||
string block = Staged(g.Key) >= g.Items.Count ? null : StageBlock(g);
|
||||
if (block != null) { _message = block; Open(); return; }
|
||||
if (Staged(g.Key) < g.Items.Count) _stage[g.Key] = Staged(g.Key) + 1;
|
||||
_message = null; Open();
|
||||
});
|
||||
c.End();
|
||||
});
|
||||
}
|
||||
_message = L.T("black.sell.listedall", n);
|
||||
Session.Save();
|
||||
Open();
|
||||
b.End();
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static void ListItem(GameItem it, double mult, string asset, Wallet pw)
|
||||
private static double StageFee(List<SellGroup> groups) =>
|
||||
groups.Sum(g => Staged(g.Key) * (double)BlackMarket.ListFee(BlackMarket.ListPriceOf(g.Ref, MultFor(g.Key))));
|
||||
|
||||
/// <summary>货架:左栏是准备上架的清单,右栏选收款币付平台费,付完才上架。</summary>
|
||||
private static void SellFeePage(CustomUIBuilder b)
|
||||
{
|
||||
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), mult, pw, asset, StoreStation.GetDayCounter(), Session.Price);
|
||||
if (s == null) { _message = d.Black.ActiveListings >= Tuning.BmMaxListings ? L.T("black.sell.full", Tuning.BmMaxListings) : L.T("msg.balance"); Open(); return; }
|
||||
it.Destroy();
|
||||
_message = L.T("black.sell.listed", ItemName(id), s.ListPrice);
|
||||
var w = d.Hot;
|
||||
var groups = SellGroups().Where(g => Staged(g.Key) > 0).ToList();
|
||||
if (groups.Count == 0) { _sellView = SellView.Add; Open(); return; }
|
||||
double fee = StageFee(groups);
|
||||
var acc = Merchants.PlatformAccepts;
|
||||
if (_feeCoin == null || !acc.Contains(_feeCoin)) _feeCoin = acc[0];
|
||||
Columns(b, l =>
|
||||
{
|
||||
l.AddHeader(L.T("black.fee.h"));
|
||||
foreach (var g in groups)
|
||||
l.AddLabel(L.T("black.fee.line", ItemName(g.Id), Staged(g.Key), BlackMarket.ListPriceOf(g.Ref, MultFor(g.Key)), "×" + MultFor(g.Key).ToString("0.0", CultureInfo.InvariantCulture)), "cart_shop");
|
||||
l.AddSeparator();
|
||||
l.AddLabel(L.T("black.fee.total", Fmt.Credits(fee)), "cart_total");
|
||||
}, r =>
|
||||
{
|
||||
r.AddHeader(L.T("bpay.h"));
|
||||
r.AddLabel(L.T("bpay.coin"), "bpay_coin_dim");
|
||||
r.BeginRow(4f);
|
||||
foreach (var a in acc) { string asset = a; PledgerSkin.Pick(r, asset, asset == _feeCoin, () => { _feeCoin = asset; Open(); }, _picked); }
|
||||
r.End();
|
||||
r.AddLabel(L.T("bpay.need", WalletWindow.Amt(WalletWindow.RoundUp(fee / Session.Price(_feeCoin))), _feeCoin, Fmt.Num(w.Avail(_feeCoin))), "bpay_need_dim");
|
||||
r.AddButton(L.T("bpay.invoice"), () =>
|
||||
{
|
||||
string coin = _feeCoin;
|
||||
string summary = L.T("black.fee.sum", groups.Sum(g => Staged(g.Key)));
|
||||
BeginPay(d.Address("platform"), summary, coin, fee, pw => ListStaged(coin, pw));
|
||||
});
|
||||
r.AddSpace(4f);
|
||||
r.AddButton(L.T("black.sell.back"), () => { _sellView = SellView.Add; _message = null; Open(); });
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>付完平台费后:按预备数量逐件上架,货从储藏区拿走。</summary>
|
||||
private static void ListStaged(string asset, Wallet pw)
|
||||
{
|
||||
var d = Session.Data;
|
||||
int day = StoreStation.GetDayCounter(), ok = 0, fail = 0;
|
||||
foreach (var g in SellGroups())
|
||||
{
|
||||
int n = Staged(g.Key);
|
||||
if (n <= 0) continue;
|
||||
double mult = MultFor(g.Key);
|
||||
foreach (var it in g.Items.Take(n))
|
||||
{
|
||||
var s = d.Black.List(g.Id, g.Model, g.FakeOf, g.Health, g.Ref, mult, pw, asset, day, Session.Price);
|
||||
if (s == null) { fail++; continue; }
|
||||
it.Destroy();
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
_stage.Clear();
|
||||
_sellView = SellView.Showcase;
|
||||
_message = fail == 0 ? L.T("black.sell.listedall", ok) : L.T("black.sell.partial", ok, fail);
|
||||
Session.Save();
|
||||
Open();
|
||||
}
|
||||
@@ -1775,7 +1809,7 @@ namespace Decentralized
|
||||
private static readonly Dictionary<int, string> _ckGroupCoin = new Dictionary<int, string>();
|
||||
|
||||
/// <summary>读档或换局时清空购物车(货架对象已经不是同一批了)。</summary>
|
||||
public static void ClearCart() { _cart.Clear(); _checkout = false; _ckMode.Clear(); _ckGroupCoin.Clear(); _ckSwap.Clear(); }
|
||||
public static void ClearCart() { _stage.Clear(); _delist.Clear(); _sellView = SellView.Showcase; _cart.Clear(); _checkout = false; _ckMode.Clear(); _ckGroupCoin.Clear(); _ckSwap.Clear(); }
|
||||
|
||||
private static int StockOf(BmGood g) => g is BmOffer o ? o.Qty : ((BmListing)g).Qty;
|
||||
|
||||
@@ -1808,12 +1842,33 @@ namespace Decentralized
|
||||
private static double GroupCredits(SaveData d, int seller) => _cart.Where(l => l.Seller == seller).Sum(l => LineCredits(d, l));
|
||||
|
||||
/// <summary>悬浮购物车:商店页右下角一个常驻的小按钮,显示件数,点开进结算页。</summary>
|
||||
private sealed class Fab { public string Text; public bool On; public Action Click; }
|
||||
|
||||
/// <summary>右下角悬浮按钮:买货页是购物车,卖货的备货页是货架,橱窗里有预备下架时是确定下架;其余页面没有。</summary>
|
||||
private static Fab FabNow()
|
||||
{
|
||||
if (_page != Page.Gray || _grayTab != GrayTab.Black || _checkout || WalletWindow.Pending != null || !Unlock.BlackUnlocked) return null;
|
||||
if (_blackTab == BlackTab.Buy) return new Fab { Text = L.T("cart.fab", CartCount()), On = CartCount() > 0, Click = () => { _checkout = true; _message = null; Open(); } };
|
||||
if (_blackTab != BlackTab.Sell) return null;
|
||||
if (_sellView == SellView.Add)
|
||||
{
|
||||
int n = _stage.Values.Sum();
|
||||
return new Fab { Text = L.T("shelf.fab", n), On = n > 0, Click = () => { if (n > 0) { _sellView = SellView.Fee; _message = null; Open(); } } };
|
||||
}
|
||||
if (_sellView == SellView.Showcase)
|
||||
{
|
||||
int n = _delist.Values.Sum();
|
||||
return n > 0 ? new Fab { Text = L.T("delist.fab", n), On = true, Click = ConfirmDelist } : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CartFab(CustomUIWindow win)
|
||||
{
|
||||
if (win?.root == null) return;
|
||||
var old = win.root.transform.Find("pd_cart_fab");
|
||||
bool show = _page == Page.Gray && _grayTab == GrayTab.Black && !_checkout && WalletWindow.Pending == null && Unlock.BlackUnlocked;
|
||||
if (!show) { if (old != null) UnityEngine.Object.Destroy(old.gameObject); return; }
|
||||
var fab = FabNow();
|
||||
if (fab == null) { if (old != null) UnityEngine.Object.Destroy(old.gameObject); return; }
|
||||
if (old != null) UnityEngine.Object.DestroyImmediate(old.gameObject);
|
||||
var go = new GameObject("pd_cart_fab", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button), typeof(LayoutElement));
|
||||
go.transform.SetParent(win.root.transform, false);
|
||||
@@ -1821,20 +1876,20 @@ namespace Decentralized
|
||||
var rt = (RectTransform)go.transform;
|
||||
rt.anchorMin = rt.anchorMax = new Vector2(1f, 0f);
|
||||
rt.pivot = new Vector2(1f, 0f);
|
||||
rt.sizeDelta = new Vector2(132f, 36f);
|
||||
rt.sizeDelta = new Vector2(Mathf.Max(132f, fab.Text.Length * 9f + 40f), 36f);
|
||||
rt.anchoredPosition = new Vector2(-34f, 34f);
|
||||
var img = go.GetComponent<Image>();
|
||||
PledgerSkin.KeyShape(img, CartCount() > 0 ? PledgerSkin.Ink : PledgerSkin.Key);
|
||||
PledgerSkin.KeyShape(img, fab.On ? PledgerSkin.Ink : PledgerSkin.Key);
|
||||
var btn = go.GetComponent<Button>();
|
||||
btn.transition = Selectable.Transition.None;
|
||||
btn.onClick.AddListener(() => { _checkout = true; _message = null; Open(); });
|
||||
btn.onClick.AddListener(() => fab.Click());
|
||||
var tgo = new GameObject("label", typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
|
||||
tgo.transform.SetParent(go.transform, false);
|
||||
var trt = (RectTransform)tgo.transform;
|
||||
trt.anchorMin = Vector2.zero; trt.anchorMax = Vector2.one; trt.offsetMin = trt.offsetMax = Vector2.zero;
|
||||
var tmp = tgo.GetComponent<TextMeshProUGUI>();
|
||||
if (win.frame?.titleText != null) { tmp.font = win.frame.titleText.font; tmp.fontSharedMaterial = win.frame.titleText.fontSharedMaterial; }
|
||||
tmp.text = L.T("cart.fab", CartCount());
|
||||
tmp.text = fab.Text;
|
||||
tmp.fontSize = 15f;
|
||||
tmp.fontStyle = FontStyles.Bold;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
@@ -2128,106 +2183,26 @@ namespace Decentralized
|
||||
Open();
|
||||
}
|
||||
|
||||
private static void BeginEdit(EditPrice e, int initial)
|
||||
{
|
||||
_edit = e;
|
||||
_editPrice = initial.ToString(CultureInfo.InvariantCulture);
|
||||
_message = null;
|
||||
Open();
|
||||
}
|
||||
|
||||
private static int MinPrice(long refValue) => (int)Math.Ceiling(Tuning.BmSellMin * refValue - 1e-9);
|
||||
private static int MaxPrice(long refValue) => (int)Math.Floor(Tuning.BmSellMax * refValue + 1e-9);
|
||||
|
||||
/// <summary>设定标价:显示参考价,档位按钮只是帮忙填价格,价格也可以手输;按 标价 / 参考价 的比例算成交和污点。</summary>
|
||||
private static void EditPage(CustomUIBuilder b)
|
||||
{
|
||||
var e = _edit;
|
||||
b.AddHeader(ItemName(e.Id));
|
||||
b.AddLabel(L.T("black.edit.ref", e.Ref), "edit_ref_dim");
|
||||
b.BeginPanel(RightW, -1f, TextAnchor.MiddleCenter, 0f, 0f);
|
||||
if (b.Last != null && b.Last.image != null) _clear.Add(b.Last.image);
|
||||
b.AddInput(L.T("black.edit.input"), _editPrice, s => _editPrice = s, "price", s => _editPrice = s);
|
||||
b.End();
|
||||
b.BeginRow(4f);
|
||||
foreach (var m in Tuning.BmSellMults)
|
||||
{
|
||||
double mult = m;
|
||||
string label = "×" + m.ToString("0.0", CultureInfo.InvariantCulture);
|
||||
int targetPrice = BlackMarket.ListPriceOf(e.Ref, mult);
|
||||
bool sel = int.TryParse((_editPrice ?? "").Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int curP) && curP == targetPrice;
|
||||
PledgerSkin.Pick(b, label, sel, () => { _editPrice = targetPrice.ToString(CultureInfo.InvariantCulture); Open(); }, _picked);
|
||||
}
|
||||
b.End();
|
||||
if (int.TryParse((_editPrice ?? "").Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int p) && p > 0)
|
||||
b.AddLabel(L.T("black.edit.price", p, (p / (double)e.Ref).ToString("0.00", CultureInfo.InvariantCulture)));
|
||||
b.BeginRow(6f);
|
||||
b.AddButton(L.T("black.edit.ok"), () =>
|
||||
{
|
||||
if (!int.TryParse((_editPrice ?? "").Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int price) || price < MinPrice(e.Ref) || price > MaxPrice(e.Ref))
|
||||
{ _message = L.T("black.edit.range", MinPrice(e.Ref), MaxPrice(e.Ref)); Open(); return; }
|
||||
double mult = price / (double)e.Ref;
|
||||
var d = Session.Data;
|
||||
int fee = e.Sale != null ? BlackMarket.RepriceFee(price) : BlackMarket.ListFee(price);
|
||||
_edit = null;
|
||||
OpenPay(new PayDraft
|
||||
{
|
||||
Title = ItemName(e.Id) + L.T(e.Sale != null ? "pay.repricefee" : "pay.listfee", mult.ToString("0.00", CultureInfo.InvariantCulture)), Address = d.Address("platform"), Accepted = Merchants.PlatformAccepts,
|
||||
Credits = (q, m) => fee,
|
||||
Execute = (asset, q, m, pw) => { if (e.Sale != null) RepriceSale(e.Sale, mult, asset, pw); else ListItem(e.Item, mult, asset, pw); },
|
||||
});
|
||||
});
|
||||
b.AddButton(L.T("black.edit.cancel"), () => { _edit = null; _message = null; Open(); });
|
||||
b.End();
|
||||
}
|
||||
|
||||
private static void RepriceSale(BmSale s, double mult, string asset, Wallet pw)
|
||||
{
|
||||
var d = Session.Data;
|
||||
if (!d.Black.Reprice(s, mult, pw, asset, StoreStation.GetDayCounter(), Session.Price)) { _message = L.T("msg.balance"); Open(); return; }
|
||||
_message = L.T("black.sell.repriced", ItemName(s.Item), s.ListPrice);
|
||||
Session.Save();
|
||||
Open();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>我的订单与论坛情报。</summary>
|
||||
/// <summary>在途订单:买入还没取货的订单,和卖出已成交、货款在托管中的订单;每条显示状态与到期日。</summary>
|
||||
private static void OrdersPage(CustomUIBuilder b, int day)
|
||||
{
|
||||
var d = Session.Data;
|
||||
var buys = d.Black.Orders.Where(o => o.State == OrderState.Pending || o.State == OrderState.Ready).OrderByDescending(x => x.PlacedDay).ToList();
|
||||
var sells = d.Black.Sales.Where(x => x.State == SaleState.Sold).OrderByDescending(x => x.ListedDay).ToList();
|
||||
b.BeginRow(8f, TextAnchor.UpperLeft);
|
||||
b.BeginPanel((SellerW + 8f + ShelfW) * 0.45f, -1f, TextAnchor.UpperLeft, 4f, 4f);
|
||||
b.BeginPanel((SellerW + 8f + ShelfW) * 0.5f, -1f, TextAnchor.UpperLeft, 4f, 4f);
|
||||
_clear.Add(b.Last.image);
|
||||
b.AddHeader(L.T("black.orders.in"));
|
||||
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))
|
||||
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));
|
||||
if (buys.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_in_none_dim");
|
||||
foreach (var o in buys)
|
||||
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.DueDay));
|
||||
b.End();
|
||||
b.BeginPanel((SellerW + 8f + ShelfW) * 0.55f, -1f, TextAnchor.UpperLeft, 4f, 4f);
|
||||
b.BeginPanel((SellerW + 8f + ShelfW) * 0.5f, -1f, TextAnchor.UpperLeft, 4f, 4f);
|
||||
_clear.Add(b.Last.image);
|
||||
b.AddHeader(L.T("black.orders.out"));
|
||||
if (d.Black.Sales.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_out_none_dim");
|
||||
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 : ""));
|
||||
b.AddLabel(L.T("black.mine.sale", ItemName(sale.Item), sale.Ref, sale.ListPrice, sale.Mult.ToString("0.00", CultureInfo.InvariantCulture), st));
|
||||
if (sale.State == SaleState.Listed)
|
||||
{
|
||||
b.BeginRow(6f);
|
||||
b.AddButton(L.T("black.sell.reprice"), () => BeginEdit(new EditPrice { Sale = sale, Id = sale.Item, Ref = sale.Ref }, sale.ListPrice));
|
||||
b.AddButton(L.T("black.sell.delist"), () => Delist(sale));
|
||||
b.End();
|
||||
}
|
||||
}
|
||||
if (sells.Count == 0) b.AddLabel(L.T("black.mine.none"), "orders_out_none_dim");
|
||||
foreach (var sale in sells)
|
||||
b.AddLabel(L.T("black.mine.sale", ItemName(sale.Item), sale.Ref, sale.ListPrice, sale.Mult.ToString("0.00", CultureInfo.InvariantCulture), L.T("black.sale.Sold", sale.PayDay, sale.PayAsset ?? Assets.CRT)));
|
||||
b.End();
|
||||
b.End();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user