Black market in Web3 page, shelf UI, no dead drop, tech and GPU goods; rules moved to Web3 handbook

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 20:06:32 +08:00
1 parent 6bc479f710
commit e44a4439ed
12 files changed
+296 -245

No files matched your search

+107 -93
View File
@@ -13,7 +13,7 @@ namespace Decentralized
public static class TerminalWindow
{
public const string Id = "pd_terminal";
private enum Page { Home, Bank, Poinbase, Web3, Black, Report }
private enum Page { Home, Bank, Poinbase, Web3, Report }
private static Page _page = Page.Home;
private static string _amount = "";
@@ -24,25 +24,23 @@ namespace Decentralized
if (Session.Data == null) return;
var b = CustomUIManager.Window(Id, L.T("term.title"));
if (b == null) return;
b.SetSize(780, 580).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
b.SetSize(780, 660).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle();
b.BeginRow();
foreach (Page p in Enum.GetValues(typeof(Page)))
{
if (p == Page.Black && !Unlock.BlackUnlocked) continue;
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();
b.BeginScroll(450);
b.BeginScroll(540);
switch (_page)
{
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;
case Page.Black: BlackPage(b); break;
default: ReportPage(b); break;
}
b.End();
@@ -139,7 +137,6 @@ namespace Decentralized
}, r =>
{
r.AddHeader(L.T("bank.ops"));
foreach (var line in L.T("bank.hint").Split('|')) r.AddLabel(line);
AmountRow(r);
AmlPreview(r);
r.AddButton(L.T("trade.deposit"), Deposit);
@@ -229,7 +226,6 @@ namespace Decentralized
}, r =>
{
r.AddHeader(L.T("trade.h"));
foreach (var line in L.T("trade.hint").Split('|')) r.AddLabel(line);
AmountRow(r);
TradeRows(r, venue);
});
@@ -239,13 +235,21 @@ namespace Decentralized
private static void Web3Page(CustomUIBuilder b)
{
var w = Session.Data.Hot;
if (Unlock.MixerUnlocked)
if (Unlock.MixerUnlocked || Unlock.BlackUnlocked)
{
if (_web3Tab == Web3Tab.Mixer && !Unlock.MixerUnlocked) _web3Tab = Web3Tab.Trade;
if (_web3Tab == Web3Tab.Black && !Unlock.BlackUnlocked) _web3Tab = Web3Tab.Trade;
b.BeginRow();
b.AddButton(_web3Mixer ? L.T("web3.sub.trade") : $"[{L.T("web3.sub.trade")}]", () => { _web3Mixer = false; _message = null; Open(); });
b.AddButton(_web3Mixer ? $"[{L.T("web3.sub.mixer")}]" : L.T("web3.sub.mixer"), () => { _web3Mixer = true; _message = null; Open(); });
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(); });
}
b.End();
if (_web3Mixer) { MixerPage(b); return; }
if (_web3Tab == Web3Tab.Mixer) { MixerPage(b); return; }
if (_web3Tab == Web3Tab.Black) { BlackPage(b); return; }
}
Columns(b, l =>
{
@@ -258,7 +262,6 @@ namespace Decentralized
}, r =>
{
r.AddHeader(L.T("trade.h"));
foreach (var line in L.T("trade.hint").Split('|')) r.AddLabel(line);
AmountRow(r);
TradeRows(r, Venue.Dex);
});
@@ -267,12 +270,28 @@ namespace Decentralized
private enum BlackTab { Shop, Orders, Mine, Desk }
private static BlackTab _blackTab = BlackTab.Shop;
private static PayMode _mode = PayMode.Escrow;
private static Delivery _delivery = Delivery.Door;
private static string _deskAsset = Assets.PSC;
private static bool _deskIn = true;
private static string _payAsset = Assets.CRT;
private static readonly System.Collections.Generic.Dictionary<string, Sprite> IconCache = new System.Collections.Generic.Dictionary<string, Sprite>();
/// <summary>Web3 黑市:黑市派系的链上实体。现货柜台(只收币)与 Ponero 兑换处。</summary>
/// <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>
private static void BlackPage(CustomUIBuilder b)
{
int rep = StoreReputation.GetBMReputation();
@@ -292,34 +311,80 @@ namespace Decentralized
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.Orders) { OrdersPage(b, day); return; }
if (_blackTab == BlackTab.Mine) { MinePage(b, day); return; }
bool orders = _blackTab == BlackTab.Orders;
var w = d.Hot;
Columns(b, l =>
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 })
{
l.AddHeader(L.T("black.shop.h"));
l.AddLabel(L.T("black.rep", rep, BlackMarket.MaxLevel(rep)));
if (d.Black.Offers.Count == 0) l.AddLabel(L.T("black.empty"));
foreach (var o in d.Black.Offers.ToList())
var asset = a;
b.AddButton(asset == _payAsset ? $"[{asset}]" : asset, () => { _payAsset = asset; Open(); });
}
if (orders)
foreach (PayMode m in Enum.GetValues(typeof(PayMode)))
{
var offer = o;
l.AddLabel(L.T("black.offer", ItemName(offer.Item), offer.Qty, offer.Price));
l.AddButton(L.T("black.buy", offer.Price), () => BuyOffer(offer));
var mode = m;
string n = L.T("black.mode." + m);
b.AddButton(mode == _mode ? $"[{n}]" : n, () => { _mode = mode; Open(); });
}
}, r =>
b.End();
b.AddSeparator();
if (!orders)
{
r.AddHeader(L.T("black.pay.h"));
foreach (var line in L.T("black.hint").Split('|')) r.AddLabel(line);
r.BeginRow();
foreach (var a in new[] { Assets.PSC, Assets.CRT, Assets.CRA, Assets.Ponero })
if (d.Black.Offers.Count == 0) b.AddLabel(L.T("black.empty"));
Shelf(b, d.Black.Offers.Cast<BmGood>().ToList(), (c, g) =>
{
var asset = a;
r.AddButton(asset == _payAsset ? $"[{asset}]" : asset, () => { _payAsset = asset; Open(); });
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); });
});
}
}
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();
}
r.End();
r.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(w.Avail(_payAsset))));
if (w.Taint > 0.5) r.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
});
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 : "")));
c.AddLabel(line2);
}
private static string ItemName(string id) => BlackDelivery.ItemName(id);
@@ -327,69 +392,19 @@ namespace Decentralized
private static void BuyOffer(BmOffer o)
{
var d = Session.Data;
string item = o.Item, model = o.Model, fake = o.FakeOf;
double health = o.Health;
if (!d.Black.Buy(o, d.Hot, _payAsset, Session.Price)) { _message = L.T("msg.balance"); Open(); return; }
Items.Give(ModHelper.SpawnItem(o.Item));
_message = L.T("black.bought", ItemName(o.Item));
Items.Give(BlackDelivery.Make(item, model, fake, health));
_message = L.T("black.bought", ItemName(item));
Session.Save();
Open();
}
private static string SellerText(BmSeller s) => L.T("black.seller", s.Name, s.Trades, s.Rating);
/// <summary>订单目录:比现货便宜,要等几天,可能被截获、被骗。</summary>
private static void OrdersPage(CustomUIBuilder b, int day)
{
var d = Session.Data;
var w = d.Hot;
Columns(b, l =>
{
l.AddHeader(L.T("black.orders.h"));
if (d.Black.Catalog.Count == 0) l.AddLabel(L.T("black.empty"));
foreach (var x in d.Black.Catalog.ToList())
{
var listing = x;
var seller = d.Black.Sellers[listing.Seller];
l.AddLabel(L.T("black.listing", ItemName(listing.Item), BlackMarket.Pay(listing.Price, _mode)));
l.AddLabel(" " + SellerText(seller));
bool ok = _mode == PayMode.Escrow || seller.Advance;
l.AddButton(ok ? L.T("black.order") : L.T("black.noadvance"), () => { if (ok) PlaceOrder(listing); });
}
}, r =>
{
r.AddHeader(L.T("black.pay.h"));
foreach (var line in L.T("black.orders.hint", (int)Math.Round(Tuning.BmEscrowFee * 100), (int)Math.Round(Tuning.BmAdvanceDiscount * 100), Tuning.BmAdvanceMinTrades).Split('|')) r.AddLabel(line);
r.BeginRow();
foreach (PayMode m in Enum.GetValues(typeof(PayMode)))
{
var mode = m;
string n = L.T("black.mode." + m);
r.AddButton(mode == _mode ? $"[{n}]" : n, () => { _mode = mode; Open(); });
}
r.End();
r.BeginRow();
foreach (Delivery dl in Enum.GetValues(typeof(Delivery)))
{
var del = dl;
string n = L.T("black.del." + dl, Tuning.BmDoorDays);
r.AddButton(del == _delivery ? $"[{n}]" : n, () => { _delivery = del; Open(); });
}
r.End();
r.AddLabel(L.T("black.del.hint." + _delivery, BlackMarket.Days(_delivery)));
r.BeginRow();
foreach (var a in new[] { Assets.PSC, Assets.CRT, Assets.CRA, Assets.Ponero })
{
var asset = a;
r.AddButton(asset == _payAsset ? $"[{asset}]" : asset, () => { _payAsset = asset; Open(); });
}
r.End();
r.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(w.Avail(_payAsset))));
});
}
private static void PlaceOrder(BmListing l)
{
var d = Session.Data;
var o = d.Black.Place(l, _mode, _delivery, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
var o = d.Black.Place(l, _mode, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
if (o == null) { _message = L.T("msg.balance"); Open(); return; }
_message = L.T("black.ordered", ItemName(o.Item), o.DueDay);
Session.Save();
@@ -405,7 +420,7 @@ namespace Decentralized
l.AddHeader(L.T("black.mine.h"));
if (d.Black.Orders.Count == 0) l.AddLabel(L.T("black.mine.none"));
foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay))
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, o.Delivery == Delivery.Drop && o.State == OrderState.Ready ? L.T("black.mine.drop") : ""));
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));
}, r =>
{
r.AddHeader(L.T("black.forum.h"));
@@ -422,7 +437,6 @@ namespace Decentralized
Columns(b, l =>
{
l.AddHeader(L.T("black.desk.h"));
foreach (var line in L.T("black.desk.hint", (int)Math.Round(Tuning.PoneroFee * 100)).Split('|')) l.AddLabel(line);
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);
@@ -460,7 +474,8 @@ namespace Decentralized
Open();
}
private static bool _web3Mixer;
private enum Web3Tab { Trade, Mixer, Black }
private static Web3Tab _web3Tab = Web3Tab.Trade;
private static string _mixAsset = Assets.PSC;
private static int _mixRounds = 2;
private static readonly float[] MixW = { 70f, 100f, 100f, 80f };
@@ -488,7 +503,6 @@ namespace Decentralized
}, r =>
{
r.AddHeader(L.T("mixer.deposit"));
foreach (var line in L.T("mixer.hint").Split('|')) r.AddLabel(line);
r.BeginRow();
foreach (var a in new[] { Assets.PSC, Assets.CRC, Assets.CRT, Assets.CRA })
{