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:
1 parent
74f0dac57e
commit
f43cd487e5
11 files changed
+366
-1
No files matched your search
@@ -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.
|
||||
|
@@ -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,兑换失败:余额、额度或数量不对。
|
||||
|
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user