Black market merchant shelves, bulk discount, bulk merchants, volume-driven taint, list-all

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 20:50:58 +08:00
1 parent d35425933c
commit d6b344f70b
11 files changed
+279 -60

No files matched your search

+2
View File
@@ -26,6 +26,8 @@
- 游戏内验证:货架显示显卡、模组、能量电池;买一张卡后出售页列出储藏区里的货,上架(标价 400,上架费 20),过夜成交(货款 368),3 天后到账(CRT 2804 → 3172,这次随机没带污点)。没有在游戏里验证:假卡退货、跑路时的出售损失(只有单元测试)。 - 游戏内验证:货架显示显卡、模组、能量电池;买一张卡后出售页列出储藏区里的货,上架(标价 400,上架费 20),过夜成交(货款 368),3 天后到账(CRT 2804 → 3172,这次随机没带污点)。没有在游戏里验证:假卡退货、跑路时的出售损失(只有单元测试)。
- 收尾核算(`research/sim/bm_econ.py`,按 Tuning 常量算期望):Ponero 来回 15.4%,与混币器洗掉 80% 污点的 15–18% 相当,但当天完成、清得彻底、不受制裁,所以手续费 8% → 10%(来回 19%),日额度 1000 → 600(日额度按钱包污点占比估,约够清掉稳态的污点流入,后期会成为瓶颈);出售的最优倍率取决于玩家的资金机会成本:每天 1% 时 ×1.8,2–3% 时 ×1.5,倍率不是越高越好;订单:托管期望净 +0.02、提前放款 +0.07(现货 −0.30),所以订单是便宜进货,不是赚钱路线;订单进货再挂高价出售的套利每件约 +0.13–0.18(20 天一轮,资金占用),量小,暂不限制上架数。 - 收尾核算(`research/sim/bm_econ.py`,按 Tuning 常量算期望):Ponero 来回 15.4%,与混币器洗掉 80% 污点的 15–18% 相当,但当天完成、清得彻底、不受制裁,所以手续费 8% → 10%(来回 19%),日额度 1000 → 600(日额度按钱包污点占比估,约够清掉稳态的污点流入,后期会成为瓶颈);出售的最优倍率取决于玩家的资金机会成本:每天 1% 时 ×1.8,2–3% 时 ×1.5,倍率不是越高越好;订单:托管期望净 +0.02、提前放款 +0.07(现货 −0.30),所以订单是便宜进货,不是赚钱路线;订单进货再挂高价出售的套利每件约 +0.13–0.18(20 天一轮,资金占用),量小,暂不限制上架数。
- 订单改成商家货架(用户意见):黑市页"订单"改名"商家",每周从 6 种类型里抽 5 家(全新矿卡经销商、二手矿卡贩子、模组高科技、军火、证件芯片、药物),另有 25% 的周出现一家大宗商家(库存 10–25 件、价格系数 0.55–0.75、送货 6 天)。每家有自己的价格系数(订单价 = 基础价 × 系数,一般 0.70–0.95,全新卡 0.85–1.00,二手卡 0.65–0.90),每家 4 种货、库存 3–6 件。一次订 5 件以上 95 折、10 件以上 9 折。界面:件数按钮 ×1 / ×5 / ×10,商家按钮,所选商家的货架。药物(药片、梦尘、管制化学品)只在药物商家和大宗商家出现,也能转手出售。每周货架总值在游戏里抽样 60 周平均 13.2k(目标 1.4 万)。
- 出售:货款污点随近 7 天成交额加重(成交额达 1.4 万时概率与比例各加 30%);出售页加"全部上架"按钮。套利核算(`bm_econ.py`,商家系数均值 0.825 再加批量折扣):普通商家提前放款进货 + ×1.5 出售每件约 +21%,大宗商家约 +33%,一周可买的货约 1.3 万,带污点的货款约够吃满 Ponero 日额度。
- 调整(用户意见):Ponero 手续费 10% → 6%(一进一出约 12%),日额度 600 → 1000;混币器每轮手续费 4% → 3%。黑市货架上的显卡有 15% 是全新卡(成色 100,封条完好,不会是假卡),标签显示"全新"。全新卡只有单元测试,没有在游戏里见过。 - 调整(用户意见):Ponero 手续费 10% → 6%(一进一出约 12%),日额度 600 → 1000;混币器每轮手续费 4% → 3%。黑市货架上的显卡有 15% 是全新卡(成色 100,封条完好,不会是假卡),标签显示"全新"。全新卡只有单元测试,没有在游戏里见过。
- 收尾验证(游戏内):黑市每周刷新(第 8 天自动刷出 5 种现货、8 种订单目录);蜜罐追查到期记罪证 300;假卡上架 8 件,有一件被看穿退货(好评下降);其余成交后到账,其中一笔带污点;受控交付选"签收。"记罪证 136、货被查封;镇压期间黑市页显示下线(改成实时读取游戏事件,原来要等过夜才更新)。 - 收尾验证(游戏内):黑市每周刷新(第 8 天自动刷出 5 种现货、8 种订单目录);蜜罐追查到期记罪证 300;假卡上架 8 件,有一件被看穿退货(好评下降);其余成交后到账,其中一笔带污点;受控交付选"签收。"记罪证 136、货被查封;镇压期间黑市页显示下线(改成实时读取游戏事件,原来要等过夜才更新)。
- 没有在游戏里验证:平台跑路时订单和出售一起清零(单元测试覆盖)、死投点已删除。 - 没有在游戏里验证:平台跑路时订单和出售一起清零(单元测试覆盖)、死投点已删除。
+6
View File
@@ -47,6 +47,12 @@ namespace Decentralized.Domain
P("shotgun", 3), P("smg", 3), P("c4", 3), P("cmd_keycard", 3), P("crypto_module_sec", 3), P("shotgun", 3), P("smg", 3), P("c4", 3), P("cmd_keycard", 3), P("crypto_module_sec", 3),
}; };
/// <summary>药品池:只有药物商家卖,也能转手出售。</summary>
public static readonly KeyValuePair<string, int>[] PharmaPool =
{
P("unlicensed_phagimycin_pill", 2), P("dream_dust", 2), P("restricted_chemical", 2), P("fanta_pill", 3), P("nightmare_dust", 3),
};
/// <summary>高科技物品池(不是违禁品,黑市上要加价):矿机能用上的系统模组、能量电池、能量阵列,和矿架本身。</summary> /// <summary>高科技物品池(不是违禁品,黑市上要加价):矿机能用上的系统模组、能量电池、能量阵列,和矿架本身。</summary>
public static readonly string[] TechPool = public static readonly string[] TechPool =
{ {
+90 -13
View File
@@ -5,6 +5,8 @@ using System.Linq;
namespace Decentralized.Domain namespace Decentralized.Domain
{ {
public enum PayMode { Escrow, Advance } public enum PayMode { Escrow, Advance }
/// <summary>商家类型:全新矿卡经销商、二手矿卡贩子、模组高科技、军火、证件芯片、药物,大宗商家随机出现。</summary>
public enum MerchantType { GpuNew, GpuUsed, Mods, Arms, Docs, Pharma, Bulk }
public enum OrderState { Pending, Ready, Done, Refunded, Lost } public enum OrderState { Pending, Ready, Done, Refunded, Lost }
/// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary> /// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary>
@@ -14,14 +16,19 @@ namespace Decentralized.Domain
public int Trades; public int Trades;
public int Rating; // 好评率 % public int Rating; // 好评率 %
public bool Scam; public bool Scam;
public MerchantType Type;
/// <summary>商家的价格系数(订单价 = 基础价 × 系数,各家不同)。</summary>
public double Factor;
public int Days => Type == MerchantType.Bulk ? Tuning.BmBulkDays : Tuning.BmDoorDays;
/// <summary>只有成交数够多的卖家才接受提前放款。</summary> /// <summary>只有成交数够多的卖家才接受提前放款。</summary>
public bool Advance => Trades >= Tuning.BmAdvanceMinTrades; public bool Advance => Trades >= Tuning.BmAdvanceMinTrades;
} }
/// <summary>订单目录里的一件货:卖家、货、价格(订单价,已折扣)。</summary> /// <summary>商家货架上的一种货:卖家、货、单价(订单价)、库存。</summary>
public sealed class BmListing : BmGood public sealed class BmListing : BmGood
{ {
public int Seller; public int Seller;
public int Qty;
} }
public sealed class BmOrder public sealed class BmOrder
@@ -30,6 +37,7 @@ namespace Decentralized.Domain
public string Seller; public string Seller;
public bool SellerScam; public bool SellerScam;
public PayMode Mode; public PayMode Mode;
public int Count = 1;
public string Model, FakeOf; public string Model, FakeOf;
public double Health; public double Health;
public int Price; public int Price;
@@ -87,47 +95,114 @@ namespace Decentralized.Domain
/// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary> /// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary>
public double Held => Orders.Where(o => o.State == OrderState.Pending).Sum(o => (double)o.Price) + HeldSales; public double Held => Orders.Where(o => o.State == OrderState.Pending).Sum(o => (double)o.Price) + HeldSales;
public static int OrderPrice(long baseValue, GoodKind k = GoodKind.Contraband) => (int)Math.Max(1, Math.Round(baseValue * (1 + Extra(k)) * (1 - Tuning.BmOrderDiscount))); /// <summary>批量折扣:一次订 5 件以上 95 折,10 件以上 9 折。</summary>
public static double BulkDiscount(int count) => count >= Tuning.BmBulk2Count ? Tuning.BmBulk2Discount : count >= Tuning.BmBulk1Count ? Tuning.BmBulk1Discount : 0;
public static int OrderTotal(int unit, int count) => (int)Math.Max(1, Math.Round(unit * count * (1 - BulkDiscount(count))));
public static int EscrowFee(int price) => (int)Math.Ceiling(price * Tuning.BmEscrowFee); public static int EscrowFee(int price) => (int)Math.Ceiling(price * Tuning.BmEscrowFee);
/// <summary>下单实付(信用点):托管 = 订单价 + 托管费;提前放款 = 订单价打折。</summary> /// <summary>下单实付(信用点):托管 = 订单价 + 托管费;提前放款 = 订单价打折。</summary>
public static int Pay(int price, PayMode m) => m == PayMode.Escrow ? price + EscrowFee(price) : (int)Math.Round(price * (1 - Tuning.BmAdvanceDiscount)); public static int Pay(int price, PayMode m) => m == PayMode.Escrow ? price + EscrowFee(price) : (int)Math.Round(price * (1 - Tuning.BmAdvanceDiscount));
public double RugChance => Math.Min(Tuning.BmRugMax, Tuning.BmRugBase + Held / 10000.0 * Tuning.BmRugPerTenK); public double RugChance => Math.Min(Tuning.BmRugMax, Tuning.BmRugBase + Held / 10000.0 * Tuning.BmRugPerTenK);
/// <summary>每周重新生成卖家、目录和论坛情报(与现货同一天刷新)。</summary> private static readonly MerchantType[] Regular = { MerchantType.GpuNew, MerchantType.GpuUsed, MerchantType.Mods, MerchantType.Arms, MerchantType.Docs, MerchantType.Pharma };
private static double F(Rng rng, double lo, double hi) => lo + rng.NextDouble() * (hi - lo);
/// <summary>商家类型对应的货(含货种、库存),按声望放开违禁等级。</summary>
private List<BmListing> MerchantGoods(int idx, BmSeller s, int day, int rep, Rng rng, Func<string, long> value)
{
var list = new List<BmListing>();
int n = s.Type == MerchantType.Bulk ? 2 : Tuning.BmMerchantListings;
var contra = new List<KeyValuePair<string, int>>();
switch (s.Type)
{
case MerchantType.Arms: contra = Pool.Where(x => ArmsIds.Contains(x.Key)).ToList(); break;
case MerchantType.Docs: contra = Pool.Where(x => !ArmsIds.Contains(x.Key)).ToList(); break;
case MerchantType.Pharma: contra = PharmaPool.ToList(); break;
case MerchantType.Bulk: contra = Pool.Concat(PharmaPool).ToList(); break;
}
contra = contra.Where(x => x.Value <= MaxLevel(rep)).ToList();
var tech = TechPool.ToList();
var gpus = UsedDealers.Pool(day);
for (int i = 0; i < n; i++)
{
int qty = s.Type == MerchantType.Bulk ? rng.Range(Tuning.BmBulkQtyMin, Tuning.BmBulkQtyMax) : s.Type == MerchantType.GpuNew ? rng.Range(Math.Max(1, Tuning.BmQtyMin - 1), Tuning.BmQtyMax - 2) : rng.Range(Tuning.BmQtyMin, Tuning.BmQtyMax);
double jitter = F(rng, 0.97, 1.03);
BmListing l = null;
bool gpuKind = s.Type == MerchantType.GpuNew || s.Type == MerchantType.GpuUsed;
if (s.Type == MerchantType.Bulk) gpuKind = false;
if (gpuKind)
{
bool fresh = s.Type == MerchantType.GpuNew;
var c = UsedDealers.Card(gpus, fresh ? 0 : Tuning.BmGpuFake, 0, rng);
if (fresh) c.Health = 100;
l = new BmListing { Item = "gpu:" + c.Model, Kind = GoodKind.Gpu, Level = 2, Model = c.Model, FakeOf = c.FakeOf, Health = c.Health, Price = (int)Math.Max(1, Math.Round(CardValue.Of(c.Model, c.Health) * s.Factor * jitter)) };
}
else if (s.Type == MerchantType.Mods || (s.Type == MerchantType.Bulk && tech.Count > 0 && (contra.Count == 0 || rng.Chance(0.4))))
{
if (tech.Count == 0) continue;
int k = rng.Range(0, tech.Count - 1);
string id = tech[k]; tech.RemoveAt(k);
long v = value(id);
if (v <= 0) continue;
l = new BmListing { Item = id, Kind = GoodKind.Tech, Level = 3, Price = (int)Math.Max(1, Math.Round(v * s.Factor * jitter)) };
}
else
{
if (contra.Count == 0) continue;
int k = rng.Range(0, contra.Count - 1);
var pick = contra[k]; contra.RemoveAt(k);
long v = value(pick.Key);
if (v <= 0) continue;
l = new BmListing { Item = pick.Key, Kind = GoodKind.Contraband, Level = pick.Value, Price = (int)Math.Max(1, Math.Round(v * s.Factor * jitter)) };
}
l.Seller = idx; l.Qty = qty;
list.Add(l);
}
return list;
}
private static readonly HashSet<string> ArmsIds = new HashSet<string> { "handmade_pistol", "printed_gun", "stun_baton", "revolver", "heavy_handmade_pistol", "flashbang_grenade", "smoke_grenade", "shotgun", "smg", "c4" };
/// <summary>每周重新生成商家、货架和论坛情报(与现货同一天刷新):从 6 种类型里抽 BmMerchantCount 家,另有概率出现一家大宗商家。</summary>
public void RefreshOrders(int day, int rep, Rng rng, Func<string, long> value) public void RefreshOrders(int day, int rep, Rng rng, Func<string, long> value)
{ {
Sellers.Clear(); Catalog.Clear(); Forum.Clear(); Sellers.Clear(); Catalog.Clear(); Forum.Clear();
for (int i = 0; i < Tuning.BmSellerCount; i++) var types = Regular.OrderBy(_ => rng.NextDouble()).Take(Tuning.BmMerchantCount).ToList();
if (rng.Chance(Tuning.BmBulkChance)) types.Add(MerchantType.Bulk);
foreach (var t in types)
{ {
bool scam = rng.Chance(Tuning.BmScamSellerShare); bool scam = rng.Chance(Tuning.BmScamSellerShare);
// 骗子多半是新号,也有刷过好评的老号 // 骗子多半是新号,也有刷过好评的老号
bool fresh = scam ? rng.Chance(0.6) : rng.Chance(0.25); bool fresh = scam ? rng.Chance(0.6) : rng.Chance(0.25);
double lo = t == MerchantType.GpuNew ? Tuning.BmFactorNewMin : t == MerchantType.GpuUsed ? Tuning.BmFactorUsedMin : t == MerchantType.Bulk ? Tuning.BmFactorBulkMin : Tuning.BmFactorMin;
double hi = t == MerchantType.GpuNew ? Tuning.BmFactorNewMax : t == MerchantType.GpuUsed ? Tuning.BmFactorUsedMax : t == MerchantType.Bulk ? Tuning.BmFactorBulkMax : Tuning.BmFactorMax;
var s = new BmSeller var s = new BmSeller
{ {
Name = NameA[rng.Range(0, NameA.Length - 1)] + "_" + NameB[rng.Range(0, NameB.Length - 1)] + rng.Range(1, 99), Name = NameA[rng.Range(0, NameA.Length - 1)] + "_" + NameB[rng.Range(0, NameB.Length - 1)] + rng.Range(1, 99),
Scam = scam, Scam = scam, Type = t, Factor = F(rng, lo, hi),
Trades = fresh ? rng.Range(1, 12) : rng.Range(25, 220), Trades = fresh ? rng.Range(1, 12) : rng.Range(25, 220),
Rating = scam && !fresh ? rng.Range(94, 99) : fresh ? rng.Range(70, 100) : rng.Range(90, 99), Rating = scam && !fresh ? rng.Range(94, 99) : fresh ? rng.Range(70, 100) : rng.Range(90, 99),
}; };
Sellers.Add(s); Sellers.Add(s);
if (scam && rng.Chance(0.5)) Forum.Add("scam:" + s.Name); if (scam && rng.Chance(0.5)) Forum.Add("scam:" + s.Name);
} }
int n = 0; for (int i = 0; i < Sellers.Count; i++) Catalog.AddRange(MerchantGoods(i, Sellers[i], day, rep, rng, value));
foreach (var g in Roll(Tuning.BmSellerCount * 2, day, rep, rng, value, OrderPrice))
Catalog.Add(new BmListing { Seller = n++ % Sellers.Count, Item = g.Item, Kind = g.Kind, Level = g.Level, Price = g.Price, Model = g.Model, FakeOf = g.FakeOf, Health = g.Health });
if (RugChance > Tuning.BmRugBase * 3) Forum.Add("rug"); if (RugChance > Tuning.BmRugBase * 3) Forum.Add("rug");
} }
/// <summary>目录里全部货按订单价算的总值(信用点),用来核对每周周转量。</summary>
public double CatalogValue => Catalog.Sum(l => (double)l.Price * l.Qty);
/// <summary>下单:钱包里的币按实付折算扣除,污点与成本按比例留在卖家那边(记在订单上)。成功返回订单。</summary> /// <summary>下单:钱包里的币按实付折算扣除,污点与成本按比例留在卖家那边(记在订单上)。成功返回订单。</summary>
public BmOrder Place(BmListing l, PayMode mode, Wallet w, string asset, int day, Func<string, double> price) public BmOrder Place(BmListing l, int count, PayMode mode, Wallet w, string asset, int day, Func<string, double> price)
{ {
if (l == null || asset == Assets.CRC || Closed(day)) return null; if (l == null || count < 1 || count > l.Qty || asset == Assets.CRC || Closed(day)) return null;
var seller = Sellers[l.Seller]; var seller = Sellers[l.Seller];
if (mode == PayMode.Advance && !seller.Advance) return null; if (mode == PayMode.Advance && !seller.Advance) return null;
double p = price(asset); double p = price(asset);
if (p <= 0) return null; if (p <= 0) return null;
int credits = Pay(l.Price, mode); int credits = Pay(OrderTotal(l.Price, count), mode);
double qty = credits / p; double qty = credits / p;
if (w.Avail(asset) + 1e-9 < qty) return null; if (w.Avail(asset) + 1e-9 < qty) return null;
double total = w.Value(price); double total = w.Value(price);
@@ -137,9 +212,11 @@ namespace Decentralized.Domain
w.Add(asset, -qty); w.Add(asset, -qty);
var o = new BmOrder var o = new BmOrder
{ {
Item = l.Item, Seller = seller.Name, SellerScam = seller.Scam, Mode = mode, Model = l.Model, FakeOf = l.FakeOf, Health = l.Health, Price = credits, Asset = asset, Qty = qty, Item = l.Item, Seller = seller.Name, SellerScam = seller.Scam, Mode = mode, Count = count, Model = l.Model, FakeOf = l.FakeOf, Health = l.Health, Price = credits, Asset = asset, Qty = qty,
Taint = taint, Basis = basis, PlacedDay = day, DueDay = day + Tuning.BmDoorDays, Taint = taint, Basis = basis, PlacedDay = day, DueDay = day + seller.Days,
}; };
l.Qty -= count;
if (l.Qty <= 0) Catalog.Remove(l);
Orders.Add(o); Orders.Add(o);
if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = credits, Why = "honeypot", Item = l.Item }); if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = credits, Why = "honeypot", Item = l.Item });
return o; return o;
+9 -1
View File
@@ -46,6 +46,13 @@ namespace Decentralized.Domain
/// <summary>每晚成交的概率:1 / 期望天数,乘好评系数(好评 90 为 1,封顶 1.1),最多 1。</summary> /// <summary>每晚成交的概率:1 / 期望天数,乘好评系数(好评 90 为 1,封顶 1.1),最多 1。</summary>
public double SaleChance(double m) => Math.Min(1, 1 / ExpectedDays(m) * Math.Min(1.1, Rating / (double)Tuning.BmRatingStart)); public double SaleChance(double m) => Math.Min(1, 1 / ExpectedDays(m) * Math.Min(1.1, Rating / (double)Tuning.BmRatingStart));
/// <summary>近 7 天成交额推高的污点加成:出售量越大,货款越容易带污点、比例越高。</summary>
public double VolumeTaint(int day)
{
double v = Sales.Where(x => (x.State == SaleState.Sold || x.State == SaleState.Paid) && day - (x.PayDay - Tuning.BmSaleHold) <= 7).Sum(x => (double)x.Proceeds);
return Math.Min(Tuning.BmVolumeTaintMax, v / Tuning.BmVolumeRef * Tuning.BmVolumeTaintMax);
}
public static int ListPriceOf(long refValue, double m) => (int)Math.Max(1, Math.Round(refValue * m)); public static int ListPriceOf(long refValue, double m) => (int)Math.Max(1, Math.Round(refValue * m));
public static int ListFee(int listPrice) => (int)Math.Ceiling(listPrice * Tuning.BmListFee); public static int ListFee(int listPrice) => (int)Math.Ceiling(listPrice * Tuning.BmListFee);
@@ -88,7 +95,8 @@ namespace Decentralized.Domain
} }
s.State = SaleState.Sold; s.State = SaleState.Sold;
s.Proceeds = (int)Math.Round(s.ListPrice * (1 - Tuning.BmCommission)); s.Proceeds = (int)Math.Round(s.ListPrice * (1 - Tuning.BmCommission));
s.Taint = rng.Chance(TaintChance(s.Mult)) ? s.Proceeds * TaintRatio(s.Mult) : 0; double vb = VolumeTaint(day);
s.Taint = rng.Chance(Math.Min(1, TaintChance(s.Mult) + vb)) ? s.Proceeds * Math.Min(1, TaintRatio(s.Mult) + vb) : 0;
s.PayDay = day + Tuning.BmSaleHold; s.PayDay = day + Tuning.BmSaleHold;
Rating = Math.Min(100, Rating + 1); Rating = Math.Min(100, Rating + 1);
if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "honeypot", Item = s.Item }); if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "honeypot", Item = s.Item });
+10 -2
View File
@@ -91,9 +91,9 @@ namespace Decentralized.Domain
public const double PoneroFee = 0.06, PoneroDailyCap = 1000, PoneroPscRatio = 0.5; public const double PoneroFee = 0.06, PoneroDailyCap = 1000, PoneroPscRatio = 0.5;
/// <summary>黑市订单:订单价相对游戏基础价的折扣、托管费、提前放款再折扣、提前放款所需卖家成交数、上门的天数、 /// <summary>黑市订单:订单价相对游戏基础价的折扣、托管费、提前放款再折扣、提前放款所需卖家成交数、上门的天数、
/// 上门被截获的概率(镇压期间再加)、骗子卖家占比、每晚平台跑路基础概率与每万信用点托管资金加的概率(封顶)、跑路后关闭的天数。</summary> /// 上门被截获的概率(镇压期间再加)、骗子卖家占比、每晚平台跑路基础概率与每万信用点托管资金加的概率(封顶)、跑路后关闭的天数。</summary>
public const double BmOrderDiscount = 0.15, BmEscrowFee = 0.05, BmAdvanceDiscount = 0.10, BmInterceptDoor = 0.08, BmInterceptCrackdown = 0.15; public const double BmEscrowFee = 0.05, BmAdvanceDiscount = 0.10, BmInterceptDoor = 0.08, BmInterceptCrackdown = 0.15;
public const double BmScamSellerShare = 0.15, BmRugBase = 0.002, BmRugPerTenK = 0.004, BmRugMax = 0.05; public const double BmScamSellerShare = 0.15, BmRugBase = 0.002, BmRugPerTenK = 0.004, BmRugMax = 0.05;
public const int BmAdvanceMinTrades = 30, BmDoorDays = 3, BmRugClosedDays = 10, BmSellerCount = 4; public const int BmAdvanceMinTrades = 30, BmDoorDays = 3, BmRugClosedDays = 10;
/// <summary>蜜罐:每晚平台被执法接管的基础概率、每点治安声望加的概率、封顶、持续天数;之后订单被追查的延迟天数; /// <summary>蜜罐:每晚平台被执法接管的基础概率、每点治安声望加的概率、封顶、持续天数;之后订单被追查的延迟天数;
/// 订单被截获时变成受控交付的概率;每晚某个近期卖家落网的概率。</summary> /// 订单被截获时变成受控交付的概率;每晚某个近期卖家落网的概率。</summary>
public const double BmHoneypotBase = 0.01, BmHoneypotPerSecRep = 0.0004, BmHoneypotMax = 0.06, BmControlled = 0.35, BmSellerBust = 0.008; public const double BmHoneypotBase = 0.01, BmHoneypotPerSecRep = 0.0004, BmHoneypotMax = 0.06, BmControlled = 0.35, BmSellerBust = 0.008;
@@ -106,6 +106,14 @@ namespace Decentralized.Domain
public const double BmSellDaysFast = 1, BmSellDays1 = 2, BmSellDaysMax = 14; public const double BmSellDaysFast = 1, BmSellDays1 = 2, BmSellDaysMax = 14;
public const double BmSaleTaintChanceMin = 0.2, BmSaleTaintChanceMax = 0.9, BmSaleTaintRatioMin = 0.2, BmSaleTaintRatioMax = 0.8; public const double BmSaleTaintChanceMin = 0.2, BmSaleTaintChanceMax = 0.9, BmSaleTaintRatioMin = 0.2, BmSaleTaintRatioMax = 0.8;
public const int BmSaleHold = 3, BmRatingStart = 90, BmRatingFakeLoss = 10; public const int BmSaleHold = 3, BmRatingStart = 90, BmRatingFakeLoss = 10;
/// <summary>商家货架:每周的商家数(从 6 种类型里抽)、每家的货种数、每种货的库存范围;大宗商家出现概率、库存范围、送货天数;
/// 各类商家的价格系数范围(订单价 = 基础价 × 系数);批量折扣档(件数、折扣)。</summary>
public const int BmMerchantCount = 5, BmMerchantListings = 4, BmQtyMin = 3, BmQtyMax = 6, BmBulkQtyMin = 10, BmBulkQtyMax = 25, BmBulkDays = 6;
public const double BmBulkChance = 0.25, BmFactorMin = 0.70, BmFactorMax = 0.95, BmFactorNewMin = 0.85, BmFactorNewMax = 1.00, BmFactorUsedMin = 0.65, BmFactorUsedMax = 0.90, BmFactorBulkMin = 0.55, BmFactorBulkMax = 0.75;
public const int BmBulk1Count = 5, BmBulk2Count = 10;
public const double BmBulk1Discount = 0.05, BmBulk2Discount = 0.10;
/// <summary>出售量越大货款越容易带污点:近 7 天成交额达到参考量时,污点概率与比例各加最多 BmVolumeTaintMax。</summary>
public const double BmVolumeRef = 14000, BmVolumeTaintMax = 0.30;
/// <summary>黑市人脉升级的价格。</summary> /// <summary>黑市人脉升级的价格。</summary>
public const int BmUnlockPrice = 1500; public const int BmUnlockPrice = 1500;
/// <summary>单个金融违规事件记入的罪证上限。</summary> /// <summary>单个金融违规事件记入的罪证上限。</summary>
+4 -4
View File
@@ -56,7 +56,7 @@ namespace Decentralized
if (it == null || it.identifier == null) return null; if (it == null || it.identifier == null) return null;
if (Items.IsGpu(it)) return "gpu:" + Items.ToUnit(it).Model; if (Items.IsGpu(it)) return "gpu:" + Items.ToUnit(it).Model;
string id = it.identifier; string id = it.identifier;
return BlackMarket.Pool.Any(x => x.Key == id) || BlackMarket.TechPool.Contains(id) ? id : null; return BlackMarket.Pool.Any(x => x.Key == id) || BlackMarket.PharmaPool.Any(x => x.Key == id) || BlackMarket.TechPool.Contains(id) ? id : null;
} }
public static System.Collections.Generic.List<GameItem> Sellable() => public static System.Collections.Generic.List<GameItem> Sellable() =>
@@ -81,7 +81,7 @@ namespace Decentralized
if (force) d.Black.RefreshedDay = -999; if (force) d.Black.RefreshedDay = -999;
if (!d.Black.Refresh(day, rep, d.Rng, Value)) return false; if (!d.Black.Refresh(day, rep, d.Rng, Value)) return false;
d.Black.RefreshOrders(day, rep, d.Rng, Value); d.Black.RefreshOrders(day, rep, d.Rng, Value);
Log.Info($"黑市刷新(第 {day} 天):现货 {d.Black.Offers.Count} 种,订单目录 {d.Black.Catalog.Count} 种,高科技 {d.Black.Offers.Count(o => o.Kind == GoodKind.Tech)},显卡 {d.Black.Offers.Count(o => o.Kind == GoodKind.Gpu)}"); Log.Info($"黑市刷新(第 {day} 天):现货 {d.Black.Offers.Count} 种,商家 {d.Black.Sellers.Count} 家({string.Join("/", d.Black.Sellers.Select(x => x.Type))}),货架 {d.Black.Catalog.Count} 种共 {d.Black.CatalogValue:0} 信用点,高科技 {d.Black.Offers.Count(o => o.Kind == GoodKind.Tech)},显卡 {d.Black.Offers.Count(o => o.Kind == GoodKind.Gpu)}");
return true; return true;
} }
@@ -195,9 +195,9 @@ namespace Decentralized
var d = Session.Data; var d = Session.Data;
foreach (var o in d.Black.Ready.Where(x => !x.Controlled).ToList()) foreach (var o in d.Black.Ready.Where(x => !x.Controlled).ToList())
{ {
Items.Give(Make(o.Item, o.Model, o.FakeOf, o.Health)); for (int i = 0; i < o.Count; i++) Items.Give(Make(o.Item, o.Model, o.FakeOf, o.Health));
d.Black.Deliver(o); d.Black.Deliver(o);
Log.Info($"黑市快递:{o.Item}{(o.FakeOf != null ? "(假卡,真身 " + o.FakeOf + ")" : "")} 已放进储藏区"); Log.Info($"黑市快递:{o.Item} ×{o.Count}{(o.FakeOf != null ? "(假卡,真身 " + o.FakeOf + ")" : "")} 已放进储藏区");
} }
Session.Save(); Session.Save();
} }
+14 -1
View File
@@ -58,6 +58,19 @@ namespace Decentralized
BlackDelivery.Restock(d, StoreStation.GetDayCounter(), true); BlackDelivery.Restock(d, StoreStation.GetDayCounter(), true);
Log.Info($"黑市:声望 {StoreReputation.GetBMReputation()},上架 {d.Black.Offers.Count} 种:{string.Join(", ", d.Black.Offers.Select(o => o.Item + "×" + o.Qty + "@" + o.Price))},解锁 {Unlock.BlackUnlocked}"); 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, "Black market sample (test)", () => Log.Guard("黑市抽样(测试)", () =>
{
var d = Session.Data;
int day = StoreStation.GetDayCounter();
var vals = new System.Collections.Generic.List<double>();
var tech = new System.Collections.Generic.List<double>();
for (int i = 0; i < 60; i++)
{
BlackDelivery.Restock(d, day, true);
vals.Add(d.Black.CatalogValue);
}
Log.Info($"黑市抽样:60 周货架总值 平均 {vals.Average():0},最小 {vals.Min():0},最大 {vals.Max():0}");
}));
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Black market events (test)", () => Log.Guard("黑市事件(测试)", () => ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Black market events (test)", () => Log.Guard("黑市事件(测试)", () =>
{ {
var d = Session.Data; var d = Session.Data;
@@ -68,7 +81,7 @@ namespace Decentralized
var l = d.Black.Catalog.FirstOrDefault(); var l = d.Black.Catalog.FirstOrDefault();
if (l != null) if (l != null)
{ {
var o = d.Black.Place(l, PayMode.Escrow, w, Assets.CRT, day, Session.Price); var o = d.Black.Place(l, 1, PayMode.Escrow, w, Assets.CRT, day, Session.Price);
if (o != null) { o.State = OrderState.Ready; o.Controlled = true; } if (o != null) { o.State = OrderState.Ready; o.Controlled = true; }
} }
Log.Info($"黑市事件(测试):追查 300 今晚到期,假卡上架 8 件,受控订单 {d.Black.Orders.Count(x => x.Controlled)} 个"); Log.Info($"黑市事件(测试):追查 300 今晚到期,假卡上架 8 件,受控订单 {d.Black.Orders.Count(x => x.Controlled)} 个");
+13 -3
View File
@@ -224,12 +224,12 @@ black.desk.in,Into Ponero
black.desk.out,Out of Ponero black.desk.out,Out of Ponero
black.desk.go,Swap black.desk.go,Swap
black.desk.fail,Swap failed: balance, limit or amount is wrong. black.desk.fail,Swap failed: balance, limit or amount is wrong.
black.sub.orders,Orders black.sub.orders,Merchants
black.sub.mine,My orders & intel black.sub.mine,My orders & intel
black.rugged,"The platform is down; nobody will reopen for {0} more days." black.rugged,"The platform is down; nobody will reopen for {0} more days."
black.seller,"{0}, {1} trades, {2}%" black.seller,"{0}, {1} trades, {2}%"
black.orders.h,Order catalog black.orders.h,Order catalog
black.order,"Order ({0} days)" black.order,Order
black.mode.Escrow,Escrow black.mode.Escrow,Escrow
black.mode.Advance,Advance release black.mode.Advance,Advance release
black.ordered,"Ordered: {0}, due day {1}." black.ordered,"Ordered: {0}, due day {1}."
@@ -270,7 +270,7 @@ black.kind.Contraband.3,Contraband (high)
black.kind.Tech,High-tech (markup) black.kind.Tech,High-tech (markup)
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open." nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
item.web3guide.name,Web3 Handbook item.web3guide.name,Web3 Handbook
item.web3guide.desc,"Bank: depositing turns credits into CRC; withdrawing turns CRC back into credits and goes through compliance screening. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell page, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout; 5% listing fee, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Ponero desk: Ponero is a privacy coin and swapping in wipes the taint on that value; 6% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter." item.web3guide.desc,"Bank: depositing turns credits into CRC; withdrawing turns CRC back into credits and goes through compliance screening. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell page, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout, which also grows with how much you sold in the last 7 days; 5% listing fee, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Ponero desk: Ponero is a privacy coin and swapping in wipes the taint on that value; 6% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter."
courier.odd1,...Package is here. courier.odd1,...Package is here.
courier.odd2,"(His hand stays in his pocket.) Sign here." courier.odd2,"(His hand stays in his pocket.) Sign here."
courier.sign,Sign courier.sign,Sign
@@ -308,3 +308,13 @@ black.sale.Lost,platform rugged, gone
black.sale.Delisted,taken down black.sale.Delisted,taken down
black.sale.Delisted.fake,buyer spotted the fake and returned it black.sale.Delisted.fake,buyer spotted the fake and returned it
black.kind.gpunew,GPU brand new black.kind.gpunew,GPU brand new
merchant.GpuNew,New GPUs
merchant.GpuUsed,Used GPUs
merchant.Mods,Mods & tech
merchant.Arms,Arms
merchant.Docs,Keycards & chips
merchant.Pharma,Pharma
merchant.Bulk,Bulk
black.days,"{0} days delivery"
black.sell.all,List all
black.sell.listedall,"Listed {0} items."
1 # Probably Decentralized localization table: English (en)
224 black.desk.out
225 black.desk.go
226 black.desk.fail
227 black.sub.orders
228 black.sub.mine
229 black.rugged
230 black.seller
231 black.orders.h
232 black.order
233 black.mode.Escrow
234 black.mode.Advance
235 black.ordered
270 black.kind.Tech
271 nightlog.bm.ready
272 item.web3guide.name
273 item.web3guide.desc
274 courier.odd1
275 courier.odd2
276 courier.sign
308 black.sale.Delisted
309 black.sale.Delisted.fake
310 black.kind.gpunew
311 merchant.GpuNew
312 merchant.GpuUsed
313 merchant.Mods
314 merchant.Arms
315 merchant.Docs
316 merchant.Pharma
317 merchant.Bulk
318 black.days
319 black.sell.all
320 black.sell.listedall
+13 -3
View File
@@ -223,12 +223,12 @@ black.desk.in,兑入 Ponero
black.desk.out,兑出 Ponero black.desk.out,兑出 Ponero
black.desk.go,兑换 black.desk.go,兑换
black.desk.fail,兑换失败:余额、额度或数量不对。 black.desk.fail,兑换失败:余额、额度或数量不对。
black.sub.orders,订单 black.sub.orders,商家
black.sub.mine,我的订单与情报 black.sub.mine,我的订单与情报
black.rugged,"平台出事了,入口关闭,还要 {0} 天才会有人重新开张。" black.rugged,"平台出事了,入口关闭,还要 {0} 天才会有人重新开张。"
black.seller,"{0},成交 {1},好评 {2}%" black.seller,"{0},成交 {1},好评 {2}%"
black.orders.h,订单目录 black.orders.h,订单目录
black.order,"下单({0} 天到)" black.order,下单
black.mode.Escrow,托管 black.mode.Escrow,托管
black.mode.Advance,提前放款 black.mode.Advance,提前放款
black.ordered,"已下单:{0},预计第 {1} 天到。" black.ordered,"已下单:{0},预计第 {1} 天到。"
@@ -269,7 +269,7 @@ black.kind.Contraband.3,违禁品(高)
black.kind.Tech,高科技物品(加价) black.kind.Tech,高科技物品(加价)
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。" nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
item.web3guide.name,Web3 手册 item.web3guide.name,Web3 手册
item.web3guide.desc,"银行:存入把信用点换成 CRC;提取把 CRC 换回信用点,提取要过合规审查。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的出售页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高;上架收 5% 上架费,成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。Ponero 兑换处:Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 6% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。" item.web3guide.desc,"银行:存入把信用点换成 CRC;提取把 CRC 换回信用点,提取要过合规审查。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的出售页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;上架收 5% 上架费,成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。Ponero 兑换处:Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 6% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。"
courier.odd1,……东西到了。 courier.odd1,……东西到了。
courier.odd2,"(他的手一直放在口袋里)在这儿签个字。" courier.odd2,"(他的手一直放在口袋里)在这儿签个字。"
courier.sign,签收 courier.sign,签收
@@ -307,3 +307,13 @@ black.sale.Lost,平台跑路,没了
black.sale.Delisted,已撤下 black.sale.Delisted,已撤下
black.sale.Delisted.fake,买家看穿假卡退货 black.sale.Delisted.fake,买家看穿假卡退货
black.kind.gpunew,显卡 全新 black.kind.gpunew,显卡 全新
merchant.GpuNew,全新矿卡
merchant.GpuUsed,二手矿卡
merchant.Mods,模组高科技
merchant.Arms,军火
merchant.Docs,证件芯片
merchant.Pharma,药物
merchant.Bulk,大宗
black.days,"{0} 天到货"
black.sell.all,全部上架
black.sell.listedall,"已上架 {0} 件。"
1 # Probably Decentralized 本地化表:简体中文(zh)
223 black.desk.out
224 black.desk.go
225 black.desk.fail
226 black.sub.orders
227 black.sub.mine
228 black.rugged
229 black.seller
230 black.orders.h
231 black.order
232 black.mode.Escrow
233 black.mode.Advance
234 black.ordered
269 black.kind.Tech
270 nightlog.bm.ready
271 item.web3guide.name
272 item.web3guide.desc
273 courier.odd1
274 courier.odd2
275 courier.sign
307 black.sale.Delisted
308 black.sale.Delisted.fake
309 black.kind.gpunew
310 merchant.GpuNew
311 merchant.GpuUsed
312 merchant.Mods
313 merchant.Arms
314 merchant.Docs
315 merchant.Pharma
316 merchant.Bulk
317 black.days
318 black.sell.all
319 black.sell.listedall
+70 -19
View File
@@ -343,20 +343,7 @@ namespace Decentralized
c.AddButton(L.T("black.buy"), () => BuyOffer(offer)); c.AddButton(L.T("black.buy"), () => BuyOffer(offer));
}); });
} }
else else MerchantShelf(b, d);
{
if (d.Black.Catalog.Count == 0) b.AddLabel(L.T("black.empty"));
Shelf(b, d.Black.Catalog.Cast<BmGood>().ToList(), (c, g) =>
{
var listing = (BmListing)g;
var seller = d.Black.Sellers[listing.Seller];
GoodHead(c, g, L.T("black.seller", seller.Name, seller.Trades, seller.Rating));
c.AddLabel(L.T("black.price", BlackMarket.Pay(listing.Price, _mode)));
c.AddFlexibleSpace();
bool ok = _mode == PayMode.Escrow || seller.Advance;
c.AddButton(ok ? L.T("black.order", Tuning.BmDoorDays) : L.T("black.noadvance"), () => { if (ok) PlaceOrder(listing); });
});
}
} }
private static double _sellMult = 1.0; private static double _sellMult = 1.0;
@@ -375,6 +362,7 @@ namespace Decentralized
} }
b.End(); b.End();
b.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(d.Hot.Avail(_payAsset))) + " " + L.T("black.sell.rating", d.Black.Rating)); b.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(d.Hot.Avail(_payAsset))) + " " + L.T("black.sell.rating", d.Black.Rating));
b.AddButton(L.T("black.sell.all"), ListAll);
b.AddSeparator(); b.AddSeparator();
var items = BlackDelivery.Sellable(); var items = BlackDelivery.Sellable();
if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; } if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; }
@@ -382,7 +370,7 @@ namespace Decentralized
{ {
string id = BlackDelivery.SellId(it); string id = BlackDelivery.SellId(it);
var kind = BlackDelivery.KindOf(id); var kind = BlackDelivery.KindOf(id);
var g = new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Contraband ? BlackMarket.Pool.First(x => x.Key == id).Value : 3 }; var g = new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Contraband ? BlackMarket.Pool.Concat(BlackMarket.PharmaPool).First(x => x.Key == id).Value : 3 };
if (kind == GoodKind.Gpu) g.Health = Items.ToUnit(it).Health; if (kind == GoodKind.Gpu) g.Health = Items.ToUnit(it).Health;
g.Price = BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult); g.Price = BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult);
return new { g, it }; return new { g, it };
@@ -398,6 +386,25 @@ namespace Decentralized
private static readonly System.Collections.Generic.Dictionary<BmGood, GameItem> SellCards = new System.Collections.Generic.Dictionary<BmGood, GameItem>(); private static readonly System.Collections.Generic.Dictionary<BmGood, GameItem> SellCards = new System.Collections.Generic.Dictionary<BmGood, GameItem>();
private static void ListAll()
{
var d = Session.Data;
int n = 0;
foreach (var it in BlackDelivery.Sellable())
{
string id = BlackDelivery.SellId(it);
if (id == null) continue;
var u = Items.IsGpu(it) ? Items.ToUnit(it) : null;
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), _sellMult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
if (s == null) break;
it.Destroy();
n++;
}
_message = L.T("black.sell.listedall", n);
Session.Save();
Open();
}
private static void ListItem(GameItem it) private static void ListItem(GameItem it)
{ {
var d = Session.Data; var d = Session.Data;
@@ -412,6 +419,50 @@ namespace Decentralized
Open(); Open();
} }
private static int _merchant;
private static int _qty = 1;
private static readonly int[] Qtys = { 1, 5, 10 };
/// <summary>商家货架:选一家商家,看它这周的货,选件数下单。</summary>
private static void MerchantShelf(CustomUIBuilder b, SaveData d)
{
var sellers = d.Black.Sellers;
if (sellers.Count == 0) { b.AddLabel(L.T("black.empty")); return; }
if (_merchant >= sellers.Count) _merchant = 0;
b.BeginRow();
foreach (var m in Qtys)
{
int q = m;
b.AddButton(q == _qty ? $"[×{q}]" : "×" + q, () => { _qty = q; Open(); });
}
b.End();
for (int i = 0; i < sellers.Count; i += 3)
{
b.BeginRow();
for (int k = i; k < Math.Min(sellers.Count, i + 3); k++)
{
int idx = k;
string n = L.T("merchant." + sellers[k].Type) + " " + sellers[k].Name;
b.AddButton(idx == _merchant ? $"[{n}]" : n, () => { _merchant = idx; Open(); });
}
b.End();
}
var seller = sellers[_merchant];
b.AddLabel(L.T("black.seller", seller.Name, seller.Trades, seller.Rating) + " " + L.T("black.days", seller.Days));
var goods = d.Black.Catalog.Where(l => l.Seller == _merchant).Cast<BmGood>().ToList();
if (goods.Count == 0) { b.AddLabel(L.T("black.empty")); return; }
Shelf(b, goods, (c, g) =>
{
var listing = (BmListing)g;
int count = Math.Min(_qty, listing.Qty);
GoodHead(c, g, L.T("black.stock", listing.Qty));
c.AddLabel(L.T("black.price", BlackMarket.Pay(BlackMarket.OrderTotal(listing.Price, count), _mode)) + (count > 1 ? " ×" + count : ""));
c.AddFlexibleSpace();
bool ok = _mode == PayMode.Escrow || seller.Advance;
c.AddButton(ok ? L.T("black.order") : L.T("black.noadvance"), () => { if (ok) PlaceOrder(listing, count); });
});
}
private const int ShelfCols = 4; private const int ShelfCols = 4;
private const float CardW = 172f, CardH = 268f; private const float CardW = 172f, CardH = 268f;
@@ -455,12 +506,12 @@ namespace Decentralized
Open(); Open();
} }
private static void PlaceOrder(BmListing l) private static void PlaceOrder(BmListing l, int count)
{ {
var d = Session.Data; var d = Session.Data;
var o = d.Black.Place(l, _mode, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price); var o = d.Black.Place(l, count, _mode, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
if (o == null) { _message = L.T("msg.balance"); Open(); return; } if (o == null) { _message = L.T("msg.balance"); Open(); return; }
_message = L.T("black.ordered", ItemName(o.Item), o.DueDay); _message = L.T("black.ordered", ItemName(o.Item) + (o.Count > 1 ? " ×" + o.Count : ""), o.DueDay);
Session.Save(); Session.Save();
Open(); Open();
} }
@@ -490,7 +541,7 @@ namespace Decentralized
if (sale.State == SaleState.Listed) l.AddButton(L.T("black.sell.delist"), () => Delist(sale)); if (sale.State == SaleState.Listed) l.AddButton(L.T("black.sell.delist"), () => Delist(sale));
} }
foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay)) foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay))
l.AddLabel(L.T("black.mine.line", ItemName(o.Item), o.Seller, L.T("black.mode." + o.Mode), L.T("black.state." + o.State + (o.Why != null && o.State != OrderState.Done ? "." + o.Why : "")), o.DueDay)); l.AddLabel(L.T("black.mine.line", ItemName(o.Item) + (o.Count > 1 ? " ×" + o.Count : ""), o.Seller, L.T("black.mode." + o.Mode), L.T("black.state." + o.State + (o.Why != null && o.State != OrderState.Done ? "." + o.Why : "")), o.DueDay));
}, r => }, r =>
{ {
r.AddHeader(L.T("black.forum.h")); r.AddHeader(L.T("black.forum.h"));
+48 -14
View File
@@ -591,44 +591,44 @@ internal static class Program
Func<string, long> value = id => 200; Func<string, long> value = id => 200;
var bm = new BlackMarket(); var bm = new BlackMarket();
bm.RefreshOrders(10, 75, new Rng(5), value); bm.RefreshOrders(10, 75, new Rng(5), value);
Check(bm.Sellers.Count == Tuning.BmSellerCount && bm.Catalog.Count > 0 && bm.Catalog.Where(l => l.Kind == GoodKind.Contraband).All(l => l.Price == 170), "目录:卖家与订单价(基础价 × 0.85)"); Check(bm.Sellers.Count >= Tuning.BmMerchantCount && bm.Catalog.Count > 0 && bm.Catalog.All(l => l.Qty > 0 && l.Price >= 1) && bm.Catalog.Where(l => l.Kind != GoodKind.Gpu).All(l => l.Price >= 200 * bm.Sellers[l.Seller].Factor * 0.97 - 1 && l.Price <= 200 * bm.Sellers[l.Seller].Factor * 1.03 + 1), "目录:商家与单价(基础价 × 商家系数)");
Check(BlackMarket.Pay(170, PayMode.Escrow) == 170 + 9 && BlackMarket.Pay(170, PayMode.Advance) == 153, "托管多付 5%,提前放款打九折"); Check(BlackMarket.Pay(170, PayMode.Escrow) == 170 + 9 && BlackMarket.Pay(170, PayMode.Advance) == 153 && BlackMarket.OrderTotal(100, 5) == 475 && BlackMarket.OrderTotal(100, 10) == 900 && BlackMarket.OrderTotal(100, 4) == 400, "托管多付 5%,提前放款打九折");
var good = new BmSeller { Name = "g", Trades = 100, Rating = 95 }; var good = new BmSeller { Name = "g", Trades = 100, Rating = 95 };
var bad = new BmSeller { Name = "b", Trades = 100, Rating = 95, Scam = true }; var bad = new BmSeller { Name = "b", Trades = 100, Rating = 95, Scam = true };
bm.Sellers.Clear(); bm.Sellers.Add(good); bm.Sellers.Add(bad); bm.Sellers.Clear(); bm.Sellers.Add(good); bm.Sellers.Add(bad);
var lg = new BmListing { Seller = 0, Item = "x", Price = 1000 }; var lg = new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 1000 };
var lb = new BmListing { Seller = 1, Item = "x", Price = 1000 }; var lb = new BmListing { Qty = 99, Seller = 1, Item = "x", Price = 1000 };
var w = new Wallet { Address = "t" }; var w = new Wallet { Address = "t" };
w.Add(Assets.CRT, 10000); w.Taint = 4000; w.Basis = 5000; w.Add(Assets.CRT, 10000); w.Taint = 4000; w.Basis = 5000;
var o = bm.Place(lb, PayMode.Escrow, w, Assets.CRT, 10, price); var o = bm.Place(lb, 1, PayMode.Escrow, w, Assets.CRT, 10, price);
Check(o != null && Near(w.Get(Assets.CRT), 10000 - 1050, 1e-9) && w.Taint < 4000 && bm.Place(lg, PayMode.Escrow, w, Assets.CRC, 10, price) == null, "托管下单:扣币、污点留在订单上;不收 CRC"); Check(o != null && Near(w.Get(Assets.CRT), 10000 - 1050, 1e-9) && w.Taint < 4000 && bm.Place(lg, 1, PayMode.Escrow, w, Assets.CRC, 10, price) == null, "托管下单:扣币、污点留在订单上;不收 CRC");
double tBefore = 4000; double tBefore = 4000;
var ev = bm.Night(13, false, w, new Rng(1)); var ev = bm.Night(13, false, w, new Rng(1));
Check(ev.Any(e => e.Kind == "scam_refund") && Near(w.Get(Assets.CRT), 10000, 1e-6) && Near(w.Taint, tBefore, 1e-6) && Near(w.Basis, 5000, 1e-6), "骗子卖家 + 托管:退款,污点与成本一并还回(不能借退款洗钱)"); Check(ev.Any(e => e.Kind == "scam_refund") && Near(w.Get(Assets.CRT), 10000, 1e-6) && Near(w.Taint, tBefore, 1e-6) && Near(w.Basis, 5000, 1e-6), "骗子卖家 + 托管:退款,污点与成本一并还回(不能借退款洗钱)");
var o2 = bm.Place(lb, PayMode.Advance, w, Assets.CRT, 20, price); var o2 = bm.Place(lb, 1, PayMode.Advance, w, Assets.CRT, 20, price);
var ev2 = bm.Night(23, false, w, new Rng(2)); var ev2 = bm.Night(23, false, w, new Rng(2));
Check(o2.State == OrderState.Lost && Near(w.Get(Assets.CRT), 10000 - 900, 1e-6), "骗子卖家 + 提前放款:钱没了"); Check(o2.State == OrderState.Lost && Near(w.Get(Assets.CRT), 10000 - 900, 1e-6), "骗子卖家 + 提前放款:钱没了");
var o3 = bm.Place(lg, PayMode.Escrow, w, Assets.CRT, 30, price); var o3 = bm.Place(lg, 1, PayMode.Escrow, w, Assets.CRT, 30, price);
Check(bm.Night(31, false, w, new Rng(3)).Count == 0 && o3.State == OrderState.Pending, "没到期不动"); Check(bm.Night(31, false, w, new Rng(3)).Count == 0 && o3.State == OrderState.Pending, "没到期不动");
int ready = 0, lost = 0; int ready = 0, lost = 0;
for (int i = 0; i < 400; i++) for (int i = 0; i < 400; i++)
{ {
var t = new BlackMarket(); t.Sellers.Add(good); var t = new BlackMarket(); t.Sellers.Add(good);
var ww = new Wallet(); ww.Add(Assets.CRT, 5000); var ww = new Wallet(); ww.Add(Assets.CRT, 5000);
var oo = t.Place(new BmListing { Seller = 0, Item = "x", Price = 100 }, PayMode.Escrow, ww, Assets.CRT, 1, price); var oo = t.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 100 }, 1, PayMode.Escrow, ww, Assets.CRT, 1, price);
t.Night(oo.DueDay, false, ww, new Rng((ulong)i + 7)); t.Night(oo.DueDay, false, ww, new Rng((ulong)i + 7));
if (oo.State == OrderState.Ready) ready++; else lost++; if (oo.State == OrderState.Ready) ready++; else lost++;
} }
Check(ready > 340 && lost > 10, $"上门:约 {Tuning.BmInterceptDoor:P0} 被截获(跑路另算):到货 {ready},丢失 {lost}"); Check(ready > 340 && lost > 10, $"上门:约 {Tuning.BmInterceptDoor:P0} 被截获(跑路另算):到货 {ready},丢失 {lost}");
var c = new BlackMarket(); c.Sellers.Add(good); var c = new BlackMarket(); c.Sellers.Add(good);
var cw = new Wallet(); cw.Add(Assets.CRT, 5000); var cw = new Wallet(); cw.Add(Assets.CRT, 5000);
var co = c.Place(new BmListing { Seller = 0, Item = "x", Price = 100 }, PayMode.Escrow, cw, Assets.CRT, 1, price); var co = c.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 100 }, 1, PayMode.Escrow, cw, Assets.CRT, 1, price);
c.Night(co.DueDay, true, cw, new Rng(1)); c.Night(co.DueDay, true, cw, new Rng(1));
Check(co.State == OrderState.Pending && co.DueDay == 5, "镇压期间到期顺延一天"); Check(co.State == OrderState.Pending && co.DueDay == 5, "镇压期间到期顺延一天");
// 蜜罐期间下单会被追查;受控交付签收记罪证、拒签不记 // 蜜罐期间下单会被追查;受控交付签收记罪证、拒签不记
var h = new BlackMarket(); h.Sellers.Add(good); h.HoneypotUntil = 12; var h = new BlackMarket(); h.Sellers.Add(good); h.HoneypotUntil = 12;
var hw = new Wallet(); hw.Add(Assets.CRT, 5000); var hw = new Wallet(); hw.Add(Assets.CRT, 5000);
var ho = h.Place(new BmListing { Seller = 0, Item = "x", Price = 100 }, PayMode.Escrow, hw, Assets.CRT, 10, price); var ho = h.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 100 }, 1, PayMode.Escrow, hw, Assets.CRT, 10, price);
Check(h.Traps.Count == 1 && h.Traps[0].Day == 10 + Tuning.BmTrapDelay, "蜜罐期间下单:记一笔追查"); Check(h.Traps.Count == 1 && h.Traps[0].Day == 10 + Tuning.BmTrapDelay, "蜜罐期间下单:记一笔追查");
Check(h.Night(h.Traps[0].Day, false, hw, new Rng(3)).Any(e => e.Kind == "trap" && Near(e.Amount, ho.Price, 1e-9)) && h.Traps.Count == 0, "追查到期:产生罪证事件"); Check(h.Night(h.Traps[0].Day, false, hw, new Rng(3)).Any(e => e.Kind == "trap" && Near(e.Amount, ho.Price, 1e-9)) && h.Traps.Count == 0, "追查到期:产生罪证事件");
int ctrl = 0; int ctrl = 0;
@@ -636,7 +636,7 @@ internal static class Program
{ {
var t2 = new BlackMarket(); t2.Sellers.Add(good); var t2 = new BlackMarket(); t2.Sellers.Add(good);
var w2 = new Wallet(); w2.Add(Assets.CRT, 5000); var w2 = new Wallet(); w2.Add(Assets.CRT, 5000);
var o2b = t2.Place(new BmListing { Seller = 0, Item = "x", Price = 100 }, PayMode.Escrow, w2, Assets.CRT, 1, price); var o2b = t2.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 100 }, 1, PayMode.Escrow, w2, Assets.CRT, 1, price);
t2.Night(o2b.DueDay, false, w2, new Rng((ulong)i + 99), 0); t2.Night(o2b.DueDay, false, w2, new Rng((ulong)i + 99), 0);
if (o2b.Controlled) ctrl++; if (o2b.Controlled) ctrl++;
} }
@@ -648,10 +648,10 @@ internal static class Program
// 跑路:托管资金多则概率高;跑路后关闭 // 跑路:托管资金多则概率高;跑路后关闭
var r = new BlackMarket(); r.Sellers.Add(good); var r = new BlackMarket(); r.Sellers.Add(good);
var rw = new Wallet(); rw.Add(Assets.CRT, 1000000); var rw = new Wallet(); rw.Add(Assets.CRT, 1000000);
for (int i = 0; i < 20; i++) r.Place(new BmListing { Seller = 0, Item = "x", Price = 10000 }, PayMode.Escrow, rw, Assets.CRT, 1, price); for (int i = 0; i < 20; i++) r.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 10000 }, 1, PayMode.Escrow, rw, Assets.CRT, 1, price);
Check(r.RugChance > BlackMarketTestRug(), "托管资金越多越容易跑路"); Check(r.RugChance > BlackMarketTestRug(), "托管资金越多越容易跑路");
r.ClosedUntil = 40; r.ClosedUntil = 40;
Check(r.Closed(40) && !r.Closed(41) && r.Place(new BmListing { Seller = 0, Item = "x", Price = 1 }, PayMode.Escrow, rw, Assets.CRT, 30, price) == null, "关闭期间不能下单"); Check(r.Closed(40) && !r.Closed(41) && r.Place(new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 1 }, 1, PayMode.Escrow, rw, Assets.CRT, 30, price) == null, "关闭期间不能下单");
}); });
Test("黑市出售", () => Test("黑市出售", () =>
@@ -719,6 +719,40 @@ internal static class Program
Check(Near(Tuning.PoneroFee, 0.06, 1e-9) && Near(1 - Math.Pow(1 - Tuning.PoneroFee, 2), 0.12, 0.01), "Ponero 一进一出约 12%"); Check(Near(Tuning.PoneroFee, 0.06, 1e-9) && Near(1 - Math.Pow(1 - Tuning.PoneroFee, 2), 0.12, 0.01), "Ponero 一进一出约 12%");
}); });
Test("黑市商家", () =>
{
double sum = 0; int bulk = 0, n = 300;
var types = new System.Collections.Generic.HashSet<MerchantType>();
for (int i = 0; i < n; i++)
{
var bm = new BlackMarket();
bm.RefreshOrders(20, 75, new Rng((ulong)i + 5), id => 250);
sum += bm.CatalogValue;
if (bm.Sellers.Any(x => x.Type == MerchantType.Bulk)) bulk++;
foreach (var s in bm.Sellers) types.Add(s.Type);
if (bm.Sellers.Where(x => x.Type != MerchantType.Bulk).Select(x => x.Type).Distinct().Count() != bm.Sellers.Count(x => x.Type != MerchantType.Bulk)) throw new Exception("同类型商家重复");
}
double avg = sum / n;
Check(avg > 8000 && avg < 40000, $"每周货架总值(游戏里实测平均约 1.4 万,这里用统一基础价 250):平均 {avg:0}");
Check(bulk > n * 0.15 && bulk < n * 0.35 && types.Count == 7, $"大宗商家约 {Tuning.BmBulkChance:P0} 的周出现:{bulk}/{n};七种类型都出现过");
// 批量下单:折扣、库存扣减
Func<string, double> price = a => 1;
var m = new BlackMarket(); m.Sellers.Add(new BmSeller { Name = "a", Trades = 100, Type = MerchantType.Mods, Factor = 0.8 });
var l = new BmListing { Seller = 0, Item = "x", Price = 100, Qty = 8 };
m.Catalog.Add(l);
var w = new Wallet(); w.Add(Assets.CRT, 5000);
var o = m.Place(l, 5, PayMode.Advance, w, Assets.CRT, 1, price);
Check(o != null && o.Count == 5 && o.Price == (int)Math.Round(475 * 0.9) && l.Qty == 3 && m.Catalog.Contains(l), "订 5 件:95 折,再按提前放款打折,库存扣 5");
Check(m.Place(l, 4, PayMode.Escrow, w, Assets.CRT, 1, price) == null && m.Place(l, 3, PayMode.Escrow, w, Assets.CRT, 1, price) != null && !m.Catalog.Contains(l), "超过库存不能订;订完下架");
var bs = new BmSeller { Type = MerchantType.Bulk };
Check(bs.Days == Tuning.BmBulkDays && new BmSeller { Type = MerchantType.Mods }.Days == Tuning.BmDoorDays, "大宗商家送货更久");
// 出售量推高污点
var b2 = new BlackMarket();
double v0 = b2.VolumeTaint(10);
b2.Sales.Add(new BmSale { State = SaleState.Sold, Proceeds = 14000, PayDay = 12 });
Check(Near(v0, 0, 1e-9) && Near(b2.VolumeTaint(10), Tuning.BmVolumeTaintMax, 1e-9) && b2.VolumeTaint(30) == 0, "近 7 天成交额推高污点,过期不算");
});
Console.WriteLine($"\n{_passed} 通过,{_failed} 失败"); Console.WriteLine($"\n{_passed} 通过,{_failed} 失败");
return _failed == 0 ? 0 : 1; return _failed == 0 ? 0 : 1;
} }