第十一阶段 stage 2:场外换币台(暗灯招换币客户)
- 新人脉升级“场外换币台”(OtcUnlockPrice 500),附赠一盏暗灯 - 包装原版暗灯工厂:切换顺序末尾加目标 pd_otc(只接管进出这一步,其余交给原切换函数);顾客工厂写进公开的 StoreClientListDict.storeClientDict,顾客面板与概率走原版逻辑 - 换色:底图换成原版熄灭的暗灯,叠加层画黄绿色光晕(otc_lamp_on),不进存档 - 换币客户(OtcClient)一半卖币、一半买币:卖币并入原收黑币(Domain/Otc.cs 取代 HotCoins.cs,HotCoinSeller 删除),污点比例随机、越脏越便宜、地址来历写进账本;买币三档报价,PLEDGER 转账确认后现金到账,当晚未付作废 - 只在生成顾客的回调窗口里生成报价、写账本(原版取名字也会调用工厂) - 存档数据版本 4:旧存档出块奖励、NPC 算力按 MiningScale 放大 - 8 种语言文字、手册“换币客户”一节;单元测试 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc23ae056b
commit
ec578a39d9
25 files changed
+833
-321
No files matched your search
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>一笔黑币报价:卖家手里的币、按现价的市值和他要的现金。Sting 是钓鱼执法(收下就记罪证,币不会到)。</summary>
|
||||
public sealed class HotOffer
|
||||
{
|
||||
public string Asset;
|
||||
public double Qty, Value;
|
||||
public int Cash;
|
||||
public bool Sting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收黑币:游戏里出了事,手里有来路不明的币的人上门,折价换现金。
|
||||
/// 帮派火并、镇压期间黑市的人出手 CRT;革命军袭击上层、下层动荡期间有人出手抢来的 PSC;黑市声望够了以后偶尔有人出手勒索得来的 Ponero。
|
||||
/// PSC、CRT 链上可追,收下的币按市值全额带污点;Ponero 不可追、不带污点,折扣也小,出口只有 Ponero 兑换处。
|
||||
/// </summary>
|
||||
public static class HotCoins
|
||||
{
|
||||
/// <summary>今天哪种黑币可能有人上门(按游戏当前的事件),没有返回 null。poneroOpen:Ponero 兑换处已开放。</summary>
|
||||
public static string Pick(ICollection<MarketEvent> active, bool poneroOpen, Rng rng)
|
||||
{
|
||||
var c = new List<string>();
|
||||
if (active.Contains(MarketEvent.GangWar) || active.Contains(MarketEvent.Crackdown)) c.Add(Assets.CRT);
|
||||
if (active.Contains(MarketEvent.RevRaidUpper) || active.Contains(MarketEvent.LowerUnrest)) c.Add(Assets.PSC);
|
||||
if (c.Count > 0 && rng.Chance(Tuning.HotEventChance)) return c[rng.Range(0, c.Count - 1)];
|
||||
if (poneroOpen && rng.Chance(Tuning.HotPoneroChance)) return Assets.Ponero;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>开价:市值 = 房租 × 区间,现金要价 = 市值 × 折扣(Ponero 折扣小)。crackdown:镇压期间钓鱼执法更多。</summary>
|
||||
public static HotOffer Make(string asset, double coinPrice, double rent, bool crackdown, Rng rng)
|
||||
{
|
||||
if (coinPrice <= 0 || rent <= 0) return null;
|
||||
double value = rent * rng.Range(Tuning.HotValueMinRents, Tuning.HotValueMaxRents);
|
||||
bool ponero = asset == Assets.Ponero;
|
||||
double cut = ponero ? rng.Range(Tuning.HotPoneroCutMin, Tuning.HotPoneroCutMax) : rng.Range(Tuning.HotCutMin, Tuning.HotCutMax);
|
||||
return new HotOffer
|
||||
{
|
||||
Asset = asset, Qty = value / coinPrice, Value = value, Cash = (int)Math.Round(value * cut),
|
||||
Sting = rng.Chance(Tuning.HotSting + (crackdown ? Tuning.HotStingCrackdown : 0)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>收下:币进钱包,成本是付出的现金;PSC、CRT 按市值记污点,Ponero 不记。钓鱼执法不在这里处理。</summary>
|
||||
public static void Receive(Wallet w, HotOffer o)
|
||||
{
|
||||
w.Add(o.Asset, o.Qty);
|
||||
w.Basis += o.Cash;
|
||||
if (o.Asset != Assets.Ponero) w.Taint += o.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
/// <summary>换币客户的来意:卖币给玩家(玩家付现金收币),或者向玩家买币(玩家转币收现金)。</summary>
|
||||
public enum OtcSide { Sell, Buy }
|
||||
|
||||
/// <summary>
|
||||
/// 一位换币客户的报价。Sell:客户手里的币(Qty、按现价的市值 Value、污点比例 TaintRatio)和他要的现金 Cash;Sting 是便衣(收下就记罪证,币不会到)。
|
||||
/// Buy:客户要买的币和数量,Value 是按现价的市值,成交价按玩家选的报价档加溢价。
|
||||
/// </summary>
|
||||
public sealed class OtcOffer
|
||||
{
|
||||
public OtcSide Side;
|
||||
public string Asset;
|
||||
public double Qty, Value, TaintRatio;
|
||||
public int Cash;
|
||||
public bool Sting;
|
||||
}
|
||||
|
||||
/// <summary>换币台的累计统计(存档):成交额(信用点)、笔数、客户地址编号;混币器提议是否已经发过(第三阶段用)。</summary>
|
||||
public sealed class OtcDesk
|
||||
{
|
||||
public double Volume;
|
||||
public int Deals, Seq;
|
||||
public bool MixerOffered;
|
||||
|
||||
public void Record(double cash) { Volume += Math.Max(0, cash); Deals++; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 换币客户(场外换币台):小偷灯切到"换币客户"后招来,一半来卖币、一半来买币。
|
||||
/// 卖币沿用原来的收黑币:帮派火并、镇压期间多是 CRT,革命军袭击上层、下层动荡期间多是 PSC,这类币全额带污点;
|
||||
/// 其他时候在 PSC、CRT、CRA 里随机,污点比例随机,现金价按污点比例打折;Ponero 兑换处开放后偶尔有人出手 Ponero(不带污点)。
|
||||
/// 买币:什么币都可能要,玩家选报价档,溢价越高越难成交;卖出的币不追查污点。
|
||||
/// </summary>
|
||||
public static class Otc
|
||||
{
|
||||
/// <summary>客户卖币时可能出手的币(没有相关事件时随机)。</summary>
|
||||
public static readonly string[] SellAssets = { Assets.PSC, Assets.CRT, Assets.CRA };
|
||||
/// <summary>客户买币时可能要的币。</summary>
|
||||
public static readonly string[] BuyAssets = { Assets.PSC, Assets.CRC, Assets.CRT, Assets.CRA, Assets.SPARK };
|
||||
|
||||
public static OtcSide PickSide(Rng rng) => rng.Chance(Tuning.OtcSellShare) ? OtcSide.Sell : OtcSide.Buy;
|
||||
|
||||
/// <summary>和当前事件有关的币(帮派火并、镇压 → CRT;袭击上层、下层动荡 → PSC),没有返回 null。</summary>
|
||||
public static string EventAsset(ICollection<MarketEvent> active, Rng rng)
|
||||
{
|
||||
var c = new List<string>();
|
||||
if (active.Contains(MarketEvent.GangWar) || active.Contains(MarketEvent.Crackdown)) c.Add(Assets.CRT);
|
||||
if (active.Contains(MarketEvent.RevRaidUpper) || active.Contains(MarketEvent.LowerUnrest)) c.Add(Assets.PSC);
|
||||
return c.Count == 0 ? null : c[rng.Range(0, c.Count - 1)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卖币客户的报价。有相关事件时按 OtcEventChance 出手事件里来的币(全额污点);Ponero 兑换处开放时按 OtcPoneroChance 出手 Ponero;
|
||||
/// 否则在 PSC、CRT、CRA 里随机,污点比例在 OtcTaintMin–OtcTaintMax 之间。市值 = 房租 × 区间;现金价 = 市值 × 折扣,折扣按污点比例从 OtcCutClean 线性降到 OtcCutDirty。
|
||||
/// crackdown:镇压期间便衣更多。
|
||||
/// </summary>
|
||||
public static OtcOffer MakeSell(ICollection<MarketEvent> active, bool poneroOpen, Func<string, double> price, double rent, Rng rng)
|
||||
{
|
||||
if (rent <= 0) return null;
|
||||
string asset = null; double ratio;
|
||||
string ev = EventAsset(active, rng);
|
||||
if (ev != null && rng.Chance(Tuning.OtcEventChance)) { asset = ev; ratio = 1; }
|
||||
else if (poneroOpen && rng.Chance(Tuning.OtcPoneroChance)) { asset = Assets.Ponero; ratio = 0; }
|
||||
else { asset = SellAssets[rng.Range(0, SellAssets.Length - 1)]; ratio = rng.Range(Tuning.OtcTaintMin, Tuning.OtcTaintMax); }
|
||||
double p = price(asset);
|
||||
if (p <= 0) return null;
|
||||
double value = rent * rng.Range(Tuning.OtcValueMinRents, Tuning.OtcValueMaxRents);
|
||||
double cut = asset == Assets.Ponero ? rng.Range(Tuning.OtcPoneroCutMin, Tuning.OtcPoneroCutMax) : CutFor(ratio);
|
||||
bool crackdown = active.Contains(MarketEvent.Crackdown);
|
||||
return new OtcOffer
|
||||
{
|
||||
Side = OtcSide.Sell, Asset = asset, Qty = value / p, Value = value, TaintRatio = ratio, Cash = (int)Math.Round(value * cut),
|
||||
Sting = rng.Chance(Tuning.OtcSting + (crackdown ? Tuning.OtcStingCrackdown : 0)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public static OtcOffer MakeBuy(Func<string, double> price, double rent, Rng rng)
|
||||
{
|
||||
if (rent <= 0) return null;
|
||||
string asset = BuyAssets[rng.Range(0, BuyAssets.Length - 1)];
|
||||
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 };
|
||||
}
|
||||
|
||||
/// <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>收下卖币客户的币:币进钱包,成本是付出的现金;污点 = 市值 × 污点比例(带上限)。返回超出钱包价值、没加进去的污点。</summary>
|
||||
public static double Receive(Wallet w, OtcOffer o, Func<string, double> price)
|
||||
{
|
||||
w.Add(o.Asset, o.Qty);
|
||||
w.Basis += o.Cash;
|
||||
return o.TaintRatio > 0 ? w.AddTaintCapped(o.Value * o.TaintRatio, price) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户地址的来历(写进链上账本,玩家在浏览器里能查到):1–OtcHistoryMax 笔转入,每一笔按污点比例的概率来自"脏"的来源(混币器、黑市平台、骗局地址),否则来自 Poinbase 或 DEX。
|
||||
/// 返回 (来源地址, 数量占比) 列表;dirty 和 clean 由接入层给出地址。
|
||||
/// </summary>
|
||||
public static List<KeyValuePair<string, double>> History(OtcOffer o, IList<string> dirty, IList<string> clean, Rng rng)
|
||||
{
|
||||
var list = new List<KeyValuePair<string, double>>();
|
||||
int n = rng.Range(1, Tuning.OtcHistoryMax);
|
||||
double left = 1;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
bool isDirty = dirty.Count > 0 && rng.Chance(o.TaintRatio);
|
||||
var src = isDirty ? dirty[rng.Range(0, dirty.Count - 1)] : clean[rng.Range(0, clean.Count - 1)];
|
||||
double share = i == n - 1 ? left : left * rng.Range(0.3, 0.7);
|
||||
left -= share;
|
||||
list.Add(new KeyValuePair<string, double>(src, share));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ namespace Decentralized.Domain
|
||||
|
||||
// 挖矿
|
||||
public const double PscStartPrice = 100;
|
||||
/// <summary>第十一阶段把出块奖励和 NPC 初始算力同乘的倍数(100 → 1000、360 → 3600);读旧存档时按它放大全网。</summary>
|
||||
public const double MiningScale = 10;
|
||||
public const double BlockReward = 1000;
|
||||
public const int HalvingDays = 90;
|
||||
/// <summary>减半对币价的传导:每次减半在区间内随机一个指数,价格锚上移 2^指数 倍(0.15–0.85 即约 +11% 到 +80%)。
|
||||
@@ -289,10 +291,18 @@ namespace Decentralized.Domain
|
||||
public const double YieldPoolChance = 0.25, YieldRugNightly = 0.18, YieldCap0 = 4000, YieldCapPerDay = 120, YieldReserveDays = 25;
|
||||
public static readonly double[] YieldRates = { 0.01, 0.02, 0.03 }, YieldScamByRate = { 0.15, 0.40, 0.75 };
|
||||
public const double YieldScamTimelock = 0.25, YieldScamAudit = 0.20;
|
||||
/// <summary>收黑币:相关事件期间每天有人上门的概率(PSC、CRT),Ponero 兑换处开放后每天的概率;币的市值(房租的倍数);
|
||||
/// 现金要价占市值的比例(PSC、CRT 与 Ponero 分开);钓鱼执法的概率与镇压期间的加成。</summary>
|
||||
public const double HotEventChance = 0.35, HotPoneroChance = 0.05, HotValueMinRents = 4, HotValueMaxRents = 15;
|
||||
public const double HotCutMin = 0.45, HotCutMax = 0.60, HotPoneroCutMin = 0.70, HotPoneroCutMax = 0.80, HotSting = 0.05, HotStingCrackdown = 0.10;
|
||||
/// <summary>换币客户(场外换币台):人脉升级价格;来卖币的比例(其余来买币)。</summary>
|
||||
public const int OtcUnlockPrice = 500;
|
||||
public const double OtcSellShare = 0.5;
|
||||
/// <summary>卖币客户:有相关事件时出手事件里来的币(全额污点)的概率,Ponero 兑换处开放后出手 Ponero 的概率;币的市值(房租的倍数);
|
||||
/// 其他币的污点比例范围;现金价占市值的比例(污点比例 0 时、1 时,之间线性;Ponero 单独一档);便衣的概率与镇压期间的加成;客户地址来历的最多笔数。</summary>
|
||||
public const double OtcEventChance = 0.7, OtcPoneroChance = 0.15, OtcValueMinRents = 4, OtcValueMaxRents = 15;
|
||||
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>
|
||||
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 };
|
||||
/// <summary>假客服:每天上门的概率;伪造转账:用币付款的顾客里是骗子的比例;两者都从第几天起。</summary>
|
||||
public const double FakeSupportChance = 0.04, FakeTransferShare = 0.08;
|
||||
public const int ScamMinDay = 10;
|
||||
|
||||
@@ -249,16 +249,6 @@ namespace Decentralized.Domain
|
||||
["HalvingPassMin"] = Tuning.HalvingPassMin,
|
||||
["HalvingRamp"] = Tuning.HalvingRamp,
|
||||
["HalvingRampDays"] = Tuning.HalvingRampDays,
|
||||
["HotCutMax"] = Tuning.HotCutMax,
|
||||
["HotCutMin"] = Tuning.HotCutMin,
|
||||
["HotEventChance"] = Tuning.HotEventChance,
|
||||
["HotPoneroChance"] = Tuning.HotPoneroChance,
|
||||
["HotPoneroCutMax"] = Tuning.HotPoneroCutMax,
|
||||
["HotPoneroCutMin"] = Tuning.HotPoneroCutMin,
|
||||
["HotSting"] = Tuning.HotSting,
|
||||
["HotStingCrackdown"] = Tuning.HotStingCrackdown,
|
||||
["HotValueMaxRents"] = Tuning.HotValueMaxRents,
|
||||
["HotValueMinRents"] = Tuning.HotValueMinRents,
|
||||
["IntelBriefPrice"] = Tuning.IntelBriefPrice,
|
||||
["IntelFake"] = Tuning.IntelFake,
|
||||
["IntelRep"] = Tuning.IntelRep,
|
||||
@@ -268,6 +258,7 @@ namespace Decentralized.Domain
|
||||
["ManipFree"] = Tuning.ManipFree,
|
||||
["ManipMax"] = Tuning.ManipMax,
|
||||
["ManipSlope"] = Tuning.ManipSlope,
|
||||
["MiningScale"] = Tuning.MiningScale,
|
||||
["MixerCleanRate"] = Tuning.MixerCleanRate,
|
||||
["MixerFee"] = Tuning.MixerFee,
|
||||
["MixerMaxRounds"] = Tuning.MixerMaxRounds,
|
||||
@@ -285,6 +276,23 @@ namespace Decentralized.Domain
|
||||
["NpcHash0"] = Tuning.NpcHash0,
|
||||
["NpcKjPerH"] = Tuning.NpcKjPerH,
|
||||
["NpcTargetRatio"] = Tuning.NpcTargetRatio,
|
||||
["OtcBuyMaxRents"] = Tuning.OtcBuyMaxRents,
|
||||
["OtcBuyMinRents"] = Tuning.OtcBuyMinRents,
|
||||
["OtcCutClean"] = Tuning.OtcCutClean,
|
||||
["OtcCutDirty"] = Tuning.OtcCutDirty,
|
||||
["OtcEventChance"] = Tuning.OtcEventChance,
|
||||
["OtcHistoryMax"] = Tuning.OtcHistoryMax,
|
||||
["OtcPoneroChance"] = Tuning.OtcPoneroChance,
|
||||
["OtcPoneroCutMax"] = Tuning.OtcPoneroCutMax,
|
||||
["OtcPoneroCutMin"] = Tuning.OtcPoneroCutMin,
|
||||
["OtcSellShare"] = Tuning.OtcSellShare,
|
||||
["OtcSting"] = Tuning.OtcSting,
|
||||
["OtcStingCrackdown"] = Tuning.OtcStingCrackdown,
|
||||
["OtcTaintMax"] = Tuning.OtcTaintMax,
|
||||
["OtcTaintMin"] = Tuning.OtcTaintMin,
|
||||
["OtcUnlockPrice"] = Tuning.OtcUnlockPrice,
|
||||
["OtcValueMaxRents"] = Tuning.OtcValueMaxRents,
|
||||
["OtcValueMinRents"] = Tuning.OtcValueMinRents,
|
||||
["PerpAdlHaircut"] = Tuning.PerpAdlHaircut,
|
||||
["PerpBanDays"] = Tuning.PerpBanDays,
|
||||
["PerpDebtDays"] = Tuning.PerpDebtDays,
|
||||
@@ -480,6 +488,8 @@ 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),
|
||||
|
||||
Reference in new issue
Block a user