换币客户改在交易终端成交:按比例部分成交、买币随机出价、弹出钱包

- 对话只剩"到终端上谈 / 这单我不做":交易终端打开柜台交易页(标签"柜台",只在有客户时出现),客户在柜台前等着
- 柜台交易页:客户来意、数量、市值、出价/要价、单价、地址(卖币可"查地址"进浏览器),25/50/75/100% 或手输数量部分成交(Otc.Part),成交 / 放弃
- 买币客户随机出价(OtcBidMin −5% … OtcBidMax +30%),去掉三档报价与成交概率(OtcPremium、OtcAccept);成交后弹出 PLEDGER,账单 Prefill 填好客户地址
- 卖币:成交后付现金、币进已连接的钱包;客户说最后一句离开(DialogUIManager.StartDialogue)
- 客户被打发或离开时交易作废;不再有"当晚未付作废"
- 增量翻译:只是和已删旧键同文的新键照常翻译
- 实测:买币客户 25% 部分成交(先在 Web3 换出 CRA),PLEDGER 预填地址签名,到账 +668,客户离开;涡轮增压器作用于矿架立刻挖一轮

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-08 15:46:14 +08:00
1 parent 2badc45a5a
commit 9d95e1f760
16 files changed
+455 -223

No files matched your search

+12 -9
View File
@@ -8,7 +8,8 @@ namespace Decentralized.Domain
/// <summary>
/// 一位换币客户的报价。Sell:客户手里的币(Qty、按现价的市值 Value、污点比例 TaintRatio)和他要的现金 Cash;Sting 是便衣(收下就记罪证,币不会到)。
/// Buy:客户要买的币和数量,Value 是按现价的市值,成交价按玩家选的报价档加溢价。
/// Buy:客户要买的币和数量,Value 是按现价的市值,Cash 是他出的现金(市价加一个随机的溢价,可能低于市价)。
/// 玩家可以只成交一部分(<see cref="Otc.Part"/>),现金、市值按数量比例折算。
/// </summary>
public sealed class OtcOffer
{
@@ -33,7 +34,7 @@ namespace Decentralized.Domain
/// 换币客户(场外换币台):小偷灯切到"换币客户"后招来,一半来卖币、一半来买币。
/// 卖币沿用原来的收黑币:帮派火并、镇压期间多是 CRT,革命军袭击上层、下层动荡期间多是 PSC,这类币全额带污点;
/// 其他时候在 PSC、CRT、CRA 里随机,污点比例随机,现金价按污点比例打折;Ponero 兑换处开放后偶尔有人出手 Ponero(不带污点)。
/// 买币:什么币都可能要,玩家选报价档,溢价越高越难成交;卖出的币不追查污点。
/// 买币:什么币都可能要,出价是市价加一个随机溢价,玩家自己决定成不成交;卖出的币不追查污点。两边都可以只成交一部分。
/// </summary>
public static class Otc
{
@@ -81,7 +82,7 @@ namespace Decentralized.Domain
/// <summary>现金价占市值的比例:污点比例 0 时 OtcCutClean,1 时 OtcCutDirty,之间线性。</summary>
public static double CutFor(double taintRatio) => Tuning.OtcCutClean + (Tuning.OtcCutDirty - Tuning.OtcCutClean) * Math.Max(0, Math.Min(1, taintRatio));
/// <summary>买币客户的来意:随机一种币,市值 = 房租 × OtcBuyMinRents–OtcBuyMaxRents。</summary>
/// <summary>买币客户的来意:随机一种币,市值 = 房租 × OtcBuyMinRents–OtcBuyMaxRents,出价 = 市值 × (1 + OtcBidMin–OtcBidMax 之间的随机溢价)。</summary>
public static OtcOffer MakeBuy(Func<string, double> price, double rent, Rng rng)
{
if (rent <= 0) return null;
@@ -89,14 +90,16 @@ namespace Decentralized.Domain
double p = price(asset);
if (p <= 0) return null;
double value = rent * rng.Range(Tuning.OtcBuyMinRents, Tuning.OtcBuyMaxRents);
return new OtcOffer { Side = OtcSide.Buy, Asset = asset, Qty = value / p, Value = value };
return new OtcOffer { Side = OtcSide.Buy, Asset = asset, Qty = value / p, Value = value, Cash = (int)Math.Round(value * (1 + rng.Range(Tuning.OtcBidMin, Tuning.OtcBidMax))) };
}
/// <summary>买币客户在第 tier 档(0–2)的成交价(信用点):市值 × (1 + 溢价)。</summary>
public static int Quote(OtcOffer o, int tier) => (int)Math.Round(o.Value * (1 + Tuning.OtcPremium[tier]));
/// <summary>客户接不接受第 tier 档的报价。</summary>
public static bool Accepts(int tier, Rng rng) => rng.Chance(Tuning.OtcAccept[tier]);
/// <summary>只成交 qty(不超过报价的数量):现金、市值按数量比例折算,污点比例和便衣不变。qty 不合法返回 null。</summary>
public static OtcOffer Part(OtcOffer o, double qty)
{
if (o == null || qty <= 0 || o.Qty <= 0) return null;
double f = Math.Min(1, qty / o.Qty);
return new OtcOffer { Side = o.Side, Asset = o.Asset, Qty = o.Qty * f, Value = o.Value * f, TaintRatio = o.TaintRatio, Cash = (int)Math.Round(o.Cash * f), Sting = o.Sting };
}
/// <summary>收下卖币客户的币:币进钱包,成本是付出的现金;污点 = 市值 × 污点比例(带上限)。返回超出钱包价值、没加进去的污点。</summary>
public static double Receive(Wallet w, OtcOffer o, Func<string, double> price)