Stage 4: Web3 black market spot counter and Ponero desk

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 19:35:48 +08:00
1 parent 74f0dac57e
commit f43cd487e5
11 files changed
+366 -1

No files matched your search

+18
View File
@@ -47,6 +47,24 @@ namespace Decentralized
w.Add(Assets.PSC, 100); w.Add(Assets.CRT, 1000); w.Taint += 3000; w.Basis += 2000;
Log.Info($"钱包:PSC {w.Get(Assets.PSC):0.##},污点 {w.Taint:0}");
}));
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Terminal (test)", () => Log.Guard("终端(测试)", TerminalWindow.Open));
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Black market +50 rep, restock (test)", () => Log.Guard("黑市(测试)", () =>
{
StoreReputation.ModReputation("FACTION_BLACK_MARKET", 50);
if (!Unlock.BlackUnlocked) ModHelper.GetNetworkUpgrade(Unlock.BlackId)?.Unlock();
var d = Session.Data;
d.Black.RefreshedDay = -999;
d.Hot.Add(Assets.PSC, 20); d.Hot.Add(Assets.CRT, 3000); d.Hot.Taint += 2000; d.Hot.Basis += 1000;
d.Black.Refresh(StoreStation.GetDayCounter(), StoreReputation.GetBMReputation(), d.Rng, id =>
{
var it = ModHelper.SpawnItem(id);
if (it == null) return 0;
long v = it.GetBaseValue();
it.Destroy();
return v;
});
Log.Info($"黑市:声望 {StoreReputation.GetBMReputation()},上架 {d.Black.Offers.Count} 种:{string.Join(", ", d.Black.Offers.Select(o => o.Item + "×" + o.Qty + "@" + o.Price))},解锁 {Unlock.BlackUnlocked}");
}));
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Coin buyer (test)", () => Log.Guard("用币付款顾客(测试)", () =>
{
var c = new StoreClient
@@ -206,3 +206,27 @@ nightlog.crt_freeze,The black market handed over addresses during the crackdown:
nightlog.thaw,Frozen {0} {1} thawed.
news.crtfreeze.title,Black market freezes some CRT addresses for the crackdown
news.crtfreeze.desc,"Word is the black market gave security a list of CRT addresses in exchange for being left alone. Holders with low standing are likelier to be frozen."
tab.black,Black market
black.upgrade.title,Web3 Black Market
black.upgrade.desc,"Unlocks the terminal's black-market page: the black-market faction's on-chain arm. A spot counter restocks weekly with contraband, payable only in coin (PSC, CRT, CRA, Ponero), tainted coin accepted, at a premium; higher standing also opens a Ponero desk. Needs black-market standing; offline during crackdowns."
black.offline,The black market's on-chain entrance is offline during the crackdown.
black.closed,"You need black-market standing of {0} before anyone lets you in (currently {1})."
black.sub.shop,Spot counter
black.sub.desk,Ponero desk
black.shop.h,Spot counter
black.rep,"Black-market standing {0}, goods tier {1}/3 visible. Restocks weekly."
black.empty,Sold out for this week.
black.offer,"{0} x{1} {2} credits"
black.buy,"Buy with coin ({0})"
black.bought,"Deal done: {0} is in storage."
black.pay.h,Payment
black.hint,"Coin only, no CRC.|Prices are 30% above normal channels.|The coin you pay leaves its share of taint with the seller; the goods come without a stolen tag."
black.desk.h,Ponero desk
black.desk.hint,"Ponero is a privacy coin; it cannot be traced on-chain.|Swapping any coin into Ponero leaves that value's taint behind.|{0}% fee each way, with a daily limit.|Coin swapped out of Ponero has no origin; cash-out screening gets stricter."
black.desk.left,"Left today: {0} (limit {1})"
black.desk.price,"Ponero price: {0}"
black.desk.swap,Swap
black.desk.in,Into Ponero
black.desk.out,Out of Ponero
black.desk.go,Swap
black.desk.fail,Swap failed: balance, limit or amount is wrong.
1 # Probably Decentralized localization table: English (en)
206 nightlog.thaw
207 news.crtfreeze.title
208 news.crtfreeze.desc
209 tab.black
210 black.upgrade.title
211 black.upgrade.desc
212 black.offline
213 black.closed
214 black.sub.shop
215 black.sub.desk
216 black.shop.h
217 black.rep
218 black.empty
219 black.offer
220 black.buy
221 black.bought
222 black.pay.h
223 black.hint
224 black.desk.h
225 black.desk.hint
226 black.desk.left
227 black.desk.price
228 black.desk.swap
229 black.desk.in
230 black.desk.out
231 black.desk.go
232 black.desk.fail
@@ -205,3 +205,27 @@ nightlog.crt_freeze,镇压期间黑市交出了一批地址:你的 {0} CRT 被
nightlog.thaw,冻结的 {0} {1} 已解冻。
news.crtfreeze.title,黑市配合镇压冻结部分 CRT 地址
news.crtfreeze.desc,"传闻黑市为换取放行,向安保交出了一批 CRT 地址。声望低的持有者更容易被冻结。"
tab.black,黑市
black.upgrade.title,Web3 黑市
black.upgrade.desc,"解锁终端的黑市页:黑市派系在链上的实体。现货柜台每周上一批违禁品,只收币(PSC、CRT、CRA、Ponero),脏币也收,价格比正常渠道高;声望够高还有 Ponero 兑换处。需要黑市声望,镇压期间下线。"
black.offline,镇压期间,黑市的链上入口已经下线。
black.closed,"黑市声望达到 {0} 才会有人带你进去(现在 {1})。"
black.sub.shop,现货柜台
black.sub.desk,Ponero 兑换处
black.shop.h,现货柜台
black.rep,"黑市声望 {0},可见货物档次 {1}/3。每周刷新。"
black.empty,这周的货卖完了。
black.offer,"{0} ×{1} {2} 信用点等值"
black.buy,"用币购买({0})"
black.bought,"成交:{0} 已放进储藏区。"
black.pay.h,付款
black.hint,"只收币,不收 CRC。|价格比正常渠道高 30%。|付出去的币带着按比例的污点一起留在卖家那边,拿到手的货不带赃物标签。"
black.desk.h,Ponero 兑换处
black.desk.hint,"Ponero 是隐私币,链上无法追踪。|任何币兑入 Ponero 时,这部分价值的污点留在原地。|兑入、兑出各收 {0}% 手续费,每天有额度。|从 Ponero 兑出的币来路不明,出金审查会加重。"
black.desk.left,"今天还能兑换 {0}(上限 {1})"
black.desk.price,"Ponero 价格:{0}"
black.desk.swap,兑换
black.desk.in,兑入 Ponero
black.desk.out,兑出 Ponero
black.desk.go,兑换
black.desk.fail,兑换失败:余额、额度或数量不对。
1 # Probably Decentralized 本地化表:简体中文(zh)
205 nightlog.thaw
206 news.crtfreeze.title
207 news.crtfreeze.desc
208 tab.black
209 black.upgrade.title
210 black.upgrade.desc
211 black.offline
212 black.closed
213 black.sub.shop
214 black.sub.desk
215 black.shop.h
216 black.rep
217 black.empty
218 black.offer
219 black.buy
220 black.bought
221 black.pay.h
222 black.hint
223 black.desk.h
224 black.desk.hint
225 black.desk.left
226 black.desk.price
227 black.desk.swap
228 black.desk.in
229 black.desk.out
230 black.desk.go
231 black.desk.fail
+9
View File
@@ -101,6 +101,15 @@ namespace Decentralized
foreach (var t in d.Hot.Thaw(day)) PlayerStore.Instance.AddNightLog(L.T("nightlog.thaw", Fmt.Num(t.Value), t.Key));
if (!wasSanctioned && d.Mixer.Sanctioned(day) && Unlock.MixerUnlocked) News.Show("pd_news_sanction_" + day, L.T("news.sanction.title"), L.T("news.sanction.desc"), 3);
d.ActiveEvents = events;
if (Unlock.BlackUnlocked && !events.Contains(MarketEvent.Crackdown))
d.Black.Refresh(day, StoreReputation.GetBMReputation(), d.Rng, id =>
{
var it = ModHelper.SpawnItem(id);
if (it == null) return 0;
long v = it.GetBaseValue();
it.Destroy();
return v;
});
d.Network.Availability = (events.Contains(MarketEvent.Blackout) ? 0.8 : 1) * (events.Contains(MarketEvent.LowerUnrest) ? 0.9 : 1);
var units = Session.TonightCards.Select(c => (item: c.Key, unit: Items.ToUnit(c.Key), bonus: c.Value)).ToList();
+2
View File
@@ -26,6 +26,7 @@ namespace Decentralized
public Wallet Hot = new Wallet { Address = "hot" };
public Exchange Exchange = new Exchange();
public Mixer Mixer = new Mixer();
public BlackMarket Black = new BlackMarket();
public List<MarketEvent> ActiveEvents = new List<MarketEvent>();
public NightReport LastNight;
public bool OpeningNewsShown;
@@ -70,6 +71,7 @@ namespace Decentralized
{
if (asset == Assets.PSC) return Data.Market.Price;
var spec = Assets.Get(asset);
if (asset == Assets.Ponero) return BlackMarket.PoneroPrice(Data.Market.Price);
if (spec != null && spec.Stable) return Data.Stable.Price(asset);
return 0;
}
+117 -1
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, Report }
private enum Page { Home, Bank, Poinbase, Web3, Black, Report }
private static Page _page = Page.Home;
private static string _amount = "";
@@ -28,6 +28,7 @@ namespace Decentralized
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(); });
@@ -41,6 +42,7 @@ namespace Decentralized
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();
@@ -262,6 +264,120 @@ namespace Decentralized
});
}
private static bool _blackDesk;
private static string _deskAsset = Assets.PSC;
private static bool _deskIn = true;
private static string _payAsset = Assets.CRT;
/// <summary>Web3 黑市:黑市派系的链上实体。现货柜台(只收币)与 Ponero 兑换处。</summary>
private static void BlackPage(CustomUIBuilder b)
{
int rep = StoreReputation.GetBMReputation();
int day = StoreStation.GetDayCounter();
var d = Session.Data;
if (d.ActiveEvents.Contains(MarketEvent.Crackdown)) { b.AddLabel(L.T("black.offline")); return; }
if (!BlackMarket.Open(rep)) { b.AddLabel(L.T("black.closed", Tuning.BmOpenRep, rep)); return; }
bool desk = BlackMarket.DeskOpen(rep);
if (desk)
{
b.BeginRow();
b.AddButton(_blackDesk ? L.T("black.sub.shop") : $"[{L.T("black.sub.shop")}]", () => { _blackDesk = false; _message = null; Open(); });
b.AddButton(_blackDesk ? $"[{L.T("black.sub.desk")}]" : L.T("black.sub.desk"), () => { _blackDesk = true; _message = null; Open(); });
b.End();
}
if (desk && _blackDesk) { DeskPage(b, rep, day); return; }
var w = d.Hot;
Columns(b, l =>
{
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 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));
}
}, r =>
{
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 })
{
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))));
if (w.Taint > 0.5) r.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
});
}
private static string ItemName(string id)
{
var it = ModHelper.SpawnItem(id);
if (it == null) return id;
string n = it.name;
it.Destroy();
return n;
}
private static void BuyOffer(BmOffer o)
{
var d = Session.Data;
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));
Session.Save();
Open();
}
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"));
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);
}, 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();
}
private static bool _web3Mixer;
private static string _mixAsset = Assets.PSC;
private static int _mixRounds = 2;
+4
View File
@@ -15,6 +15,9 @@ namespace Decentralized
public const int MixerPrice = Domain.Tuning.MixerUnlockPrice;
public static bool MixerUnlocked => PlayerStore.Instance != null && ModHelper.IsNetworkUpgradeUnlocked(MixerId);
public const string BlackId = "PD_BLACKMARKET";
public static bool BlackUnlocked => PlayerStore.Instance != null && ModHelper.IsNetworkUpgradeUnlocked(BlackId);
public static bool IsUnlocked => PlayerStore.Instance != null && ModHelper.IsNetworkUpgradeUnlocked(Id);
public static void Register()
@@ -26,6 +29,7 @@ namespace Decentralized
// 每个升级单独 try,免得前一个抛异常时后面的没登记上。
Add(Id, Price, () => L.T("upgrade.title"), () => L.T("upgrade.desc"), OnPurchased, "terminal", null);
Add(MixerId, MixerPrice, () => L.T("mixer.upgrade.title"), () => L.T("mixer.upgrade.desc"), () => Log.Info("混币器已解锁"), "ui_web3", new[] { Id });
Add(BlackId, Domain.Tuning.BmUnlockPrice, () => L.T("black.upgrade.title"), () => L.T("black.upgrade.desc"), () => Log.Info("Web3 黑市已解锁"), "ui_web3", new[] { Id });
}
private static void Add(string id, int price, Func<string> title, Func<string> desc, Action onUnlock, string icon, string[] prerequisites)