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

- 对话只剩"到终端上谈 / 这单我不做":交易终端打开柜台交易页(标签"柜台",只在有客户时出现),客户在柜台前等着
- 柜台交易页:客户来意、数量、市值、出价/要价、单价、地址(卖币可"查地址"进浏览器),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)
+2 -2
View File
@@ -300,9 +300,9 @@ namespace Decentralized.Domain
public const double OtcTaintMin = 0.2, OtcTaintMax = 0.9, OtcCutClean = 0.80, OtcCutDirty = 0.45, OtcPoneroCutMin = 0.70, OtcPoneroCutMax = 0.80;
public const double OtcSting = 0.05, OtcStingCrackdown = 0.10;
public const int OtcHistoryMax = 3;
/// <summary>买币客户:要买的币的市值(房租的倍数);三档报价的溢价与客户接受的概率。</summary>
/// <summary>买币客户:要买的币的市值(房租的倍数);出价相对市价的随机溢价范围(可能低于市价,成不成交由玩家决定)。</summary>
public const double OtcBuyMinRents = 2, OtcBuyMaxRents = 8;
public static readonly double[] OtcPremium = { 0.05, 0.15, 0.30 }, OtcAccept = { 0.95, 0.65, 0.30 };
public const double OtcBidMin = -0.05, OtcBidMax = 0.30;
/// <summary>假客服:每天上门的概率;伪造转账:用币付款的顾客里是骗子的比例;两者都从第几天起。</summary>
public const double FakeSupportChance = 0.04, FakeTransferShare = 0.08;
public const int ScamMinDay = 10;
+2 -2
View File
@@ -276,6 +276,8 @@ namespace Decentralized.Domain
["NpcHash0"] = Tuning.NpcHash0,
["NpcKjPerH"] = Tuning.NpcKjPerH,
["NpcTargetRatio"] = Tuning.NpcTargetRatio,
["OtcBidMax"] = Tuning.OtcBidMax,
["OtcBidMin"] = Tuning.OtcBidMin,
["OtcBuyMaxRents"] = Tuning.OtcBuyMaxRents,
["OtcBuyMinRents"] = Tuning.OtcBuyMinRents,
["OtcCutClean"] = Tuning.OtcCutClean,
@@ -488,8 +490,6 @@ namespace Decentralized.Domain
["CraSpiralPath"] = Array.ConvertAll(Tuning.CraSpiralPath, v => (double)v),
["IntelTipAcc"] = Array.ConvertAll(Tuning.IntelTipAcc, v => (double)v),
["IntelTipPrice"] = Array.ConvertAll(Tuning.IntelTipPrice, v => (double)v),
["OtcAccept"] = Array.ConvertAll(Tuning.OtcAccept, v => (double)v),
["OtcPremium"] = Array.ConvertAll(Tuning.OtcPremium, v => (double)v),
["PonziRates"] = Array.ConvertAll(Tuning.PonziRates, v => (double)v),
["SparkSpiralPath"] = Array.ConvertAll(Tuning.SparkSpiralPath, v => (double)v),
["UsedHealthMax"] = Array.ConvertAll(Tuning.UsedHealthMax, v => (double)v),