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:
1 parent
d35425933c
commit
d6b344f70b
11 files changed
+279
-60
No files matched your search
@@ -26,6 +26,8 @@
|
||||
- 游戏内验证:货架显示显卡、模组、能量电池;买一张卡后出售页列出储藏区里的货,上架(标价 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 天一轮,资金占用),量小,暂不限制上架数。
|
||||
- 订单改成商家货架(用户意见):黑市页"订单"改名"商家",每周从 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,封条完好,不会是假卡),标签显示"全新"。全新卡只有单元测试,没有在游戏里见过。
|
||||
- 收尾验证(游戏内):黑市每周刷新(第 8 天自动刷出 5 种现货、8 种订单目录);蜜罐追查到期记罪证 300;假卡上架 8 件,有一件被看穿退货(好评下降);其余成交后到账,其中一笔带污点;受控交付选"签收。"记罪证 136、货被查封;镇压期间黑市页显示下线(改成实时读取游戏事件,原来要等过夜才更新)。
|
||||
- 没有在游戏里验证:平台跑路时订单和出售一起清零(单元测试覆盖)、死投点已删除。
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
/// <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>
|
||||
public static readonly string[] TechPool =
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Linq;
|
||||
namespace Decentralized.Domain
|
||||
{
|
||||
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 }
|
||||
|
||||
/// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary>
|
||||
@@ -14,14 +16,19 @@ namespace Decentralized.Domain
|
||||
public int Trades;
|
||||
public int Rating; // 好评率 %
|
||||
public bool Scam;
|
||||
public MerchantType Type;
|
||||
/// <summary>商家的价格系数(订单价 = 基础价 × 系数,各家不同)。</summary>
|
||||
public double Factor;
|
||||
public int Days => Type == MerchantType.Bulk ? Tuning.BmBulkDays : Tuning.BmDoorDays;
|
||||
/// <summary>只有成交数够多的卖家才接受提前放款。</summary>
|
||||
public bool Advance => Trades >= Tuning.BmAdvanceMinTrades;
|
||||
}
|
||||
|
||||
/// <summary>订单目录里的一件货:卖家、货、价格(订单价,已折扣)。</summary>
|
||||
/// <summary>商家货架上的一种货:卖家、货、单价(订单价)、库存。</summary>
|
||||
public sealed class BmListing : BmGood
|
||||
{
|
||||
public int Seller;
|
||||
public int Qty;
|
||||
}
|
||||
|
||||
public sealed class BmOrder
|
||||
@@ -30,6 +37,7 @@ namespace Decentralized.Domain
|
||||
public string Seller;
|
||||
public bool SellerScam;
|
||||
public PayMode Mode;
|
||||
public int Count = 1;
|
||||
public string Model, FakeOf;
|
||||
public double Health;
|
||||
public int Price;
|
||||
@@ -87,47 +95,114 @@ namespace Decentralized.Domain
|
||||
/// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary>
|
||||
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);
|
||||
/// <summary>下单实付(信用点):托管 = 订单价 + 托管费;提前放款 = 订单价打折。</summary>
|
||||
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);
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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 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
|
||||
{
|
||||
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),
|
||||
Rating = scam && !fresh ? rng.Range(94, 99) : fresh ? rng.Range(70, 100) : rng.Range(90, 99),
|
||||
};
|
||||
Sellers.Add(s);
|
||||
if (scam && rng.Chance(0.5)) Forum.Add("scam:" + s.Name);
|
||||
}
|
||||
int n = 0;
|
||||
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 });
|
||||
for (int i = 0; i < Sellers.Count; i++) Catalog.AddRange(MerchantGoods(i, Sellers[i], day, rep, rng, value));
|
||||
if (RugChance > Tuning.BmRugBase * 3) Forum.Add("rug");
|
||||
}
|
||||
|
||||
/// <summary>目录里全部货按订单价算的总值(信用点),用来核对每周周转量。</summary>
|
||||
public double CatalogValue => Catalog.Sum(l => (double)l.Price * l.Qty);
|
||||
|
||||
/// <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];
|
||||
if (mode == PayMode.Advance && !seller.Advance) return null;
|
||||
double p = price(asset);
|
||||
if (p <= 0) return null;
|
||||
int credits = Pay(l.Price, mode);
|
||||
int credits = Pay(OrderTotal(l.Price, count), mode);
|
||||
double qty = credits / p;
|
||||
if (w.Avail(asset) + 1e-9 < qty) return null;
|
||||
double total = w.Value(price);
|
||||
@@ -137,9 +212,11 @@ namespace Decentralized.Domain
|
||||
w.Add(asset, -qty);
|
||||
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,
|
||||
Taint = taint, Basis = basis, PlacedDay = day, DueDay = day + Tuning.BmDoorDays,
|
||||
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 + seller.Days,
|
||||
};
|
||||
l.Qty -= count;
|
||||
if (l.Qty <= 0) Catalog.Remove(l);
|
||||
Orders.Add(o);
|
||||
if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = credits, Why = "honeypot", Item = l.Item });
|
||||
return o;
|
||||
|
||||
@@ -46,6 +46,13 @@ namespace Decentralized.Domain
|
||||
/// <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));
|
||||
|
||||
/// <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 ListFee(int listPrice) => (int)Math.Ceiling(listPrice * Tuning.BmListFee);
|
||||
|
||||
@@ -88,7 +95,8 @@ namespace Decentralized.Domain
|
||||
}
|
||||
s.State = SaleState.Sold;
|
||||
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;
|
||||
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 });
|
||||
|
||||
@@ -91,9 +91,9 @@ namespace Decentralized.Domain
|
||||
public const double PoneroFee = 0.06, PoneroDailyCap = 1000, PoneroPscRatio = 0.5;
|
||||
/// <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 int BmAdvanceMinTrades = 30, BmDoorDays = 3, BmRugClosedDays = 10, BmSellerCount = 4;
|
||||
public const int BmAdvanceMinTrades = 30, BmDoorDays = 3, BmRugClosedDays = 10;
|
||||
/// <summary>蜜罐:每晚平台被执法接管的基础概率、每点治安声望加的概率、封顶、持续天数;之后订单被追查的延迟天数;
|
||||
/// 订单被截获时变成受控交付的概率;每晚某个近期卖家落网的概率。</summary>
|
||||
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 BmSaleTaintChanceMin = 0.2, BmSaleTaintChanceMax = 0.9, BmSaleTaintRatioMin = 0.2, BmSaleTaintRatioMax = 0.8;
|
||||
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>
|
||||
public const int BmUnlockPrice = 1500;
|
||||
/// <summary>单个金融违规事件记入的罪证上限。</summary>
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Decentralized
|
||||
if (it == null || it.identifier == null) return null;
|
||||
if (Items.IsGpu(it)) return "gpu:" + Items.ToUnit(it).Model;
|
||||
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() =>
|
||||
@@ -81,7 +81,7 @@ namespace Decentralized
|
||||
if (force) d.Black.RefreshedDay = -999;
|
||||
if (!d.Black.Refresh(day, rep, d.Rng, Value)) return false;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -195,9 +195,9 @@ namespace Decentralized
|
||||
var d = Session.Data;
|
||||
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);
|
||||
Log.Info($"黑市快递:{o.Item}{(o.FakeOf != null ? "(假卡,真身 " + o.FakeOf + ")" : "")} 已放进储藏区");
|
||||
Log.Info($"黑市快递:{o.Item} ×{o.Count}{(o.FakeOf != null ? "(假卡,真身 " + o.FakeOf + ")" : "")} 已放进储藏区");
|
||||
}
|
||||
Session.Save();
|
||||
}
|
||||
|
||||
@@ -58,6 +58,19 @@ namespace Decentralized
|
||||
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}");
|
||||
}));
|
||||
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("黑市事件(测试)", () =>
|
||||
{
|
||||
var d = Session.Data;
|
||||
@@ -68,7 +81,7 @@ namespace Decentralized
|
||||
var l = d.Black.Catalog.FirstOrDefault();
|
||||
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; }
|
||||
}
|
||||
Log.Info($"黑市事件(测试):追查 300 今晚到期,假卡上架 8 件,受控订单 {d.Black.Orders.Count(x => x.Controlled)} 个");
|
||||
|
||||
@@ -224,12 +224,12 @@ 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.
|
||||
black.sub.orders,Orders
|
||||
black.sub.orders,Merchants
|
||||
black.sub.mine,My orders & intel
|
||||
black.rugged,"The platform is down; nobody will reopen for {0} more days."
|
||||
black.seller,"{0}, {1} trades, {2}%"
|
||||
black.orders.h,Order catalog
|
||||
black.order,"Order ({0} days)"
|
||||
black.order,Order
|
||||
black.mode.Escrow,Escrow
|
||||
black.mode.Advance,Advance release
|
||||
black.ordered,"Ordered: {0}, due day {1}."
|
||||
@@ -270,7 +270,7 @@ black.kind.Contraband.3,Contraband (high)
|
||||
black.kind.Tech,High-tech (markup)
|
||||
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
|
||||
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.odd2,"(His hand stays in his pocket.) Sign here."
|
||||
courier.sign,Sign
|
||||
@@ -308,3 +308,13 @@ black.sale.Lost,platform rugged, gone
|
||||
black.sale.Delisted,taken down
|
||||
black.sale.Delisted.fake,buyer spotted the fake and returned it
|
||||
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."
|
||||
|
@@ -223,12 +223,12 @@ black.desk.in,兑入 Ponero
|
||||
black.desk.out,兑出 Ponero
|
||||
black.desk.go,兑换
|
||||
black.desk.fail,兑换失败:余额、额度或数量不对。
|
||||
black.sub.orders,订单
|
||||
black.sub.orders,商家
|
||||
black.sub.mine,我的订单与情报
|
||||
black.rugged,"平台出事了,入口关闭,还要 {0} 天才会有人重新开张。"
|
||||
black.seller,"{0},成交 {1},好评 {2}%"
|
||||
black.orders.h,订单目录
|
||||
black.order,"下单({0} 天到)"
|
||||
black.order,下单
|
||||
black.mode.Escrow,托管
|
||||
black.mode.Advance,提前放款
|
||||
black.ordered,"已下单:{0},预计第 {1} 天到。"
|
||||
@@ -269,7 +269,7 @@ black.kind.Contraband.3,违禁品(高)
|
||||
black.kind.Tech,高科技物品(加价)
|
||||
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
|
||||
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.odd2,"(他的手一直放在口袋里)在这儿签个字。"
|
||||
courier.sign,签收
|
||||
@@ -307,3 +307,13 @@ black.sale.Lost,平台跑路,没了
|
||||
black.sale.Delisted,已撤下
|
||||
black.sale.Delisted.fake,买家看穿假卡退货
|
||||
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} 件。"
|
||||
|
@@ -343,20 +343,7 @@ namespace Decentralized
|
||||
c.AddButton(L.T("black.buy"), () => BuyOffer(offer));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
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); });
|
||||
});
|
||||
}
|
||||
else MerchantShelf(b, d);
|
||||
}
|
||||
|
||||
private static double _sellMult = 1.0;
|
||||
@@ -375,6 +362,7 @@ namespace Decentralized
|
||||
}
|
||||
b.End();
|
||||
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();
|
||||
var items = BlackDelivery.Sellable();
|
||||
if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; }
|
||||
@@ -382,7 +370,7 @@ namespace Decentralized
|
||||
{
|
||||
string id = BlackDelivery.SellId(it);
|
||||
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;
|
||||
g.Price = BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult);
|
||||
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 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)
|
||||
{
|
||||
var d = Session.Data;
|
||||
@@ -412,6 +419,50 @@ namespace Decentralized
|
||||
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 float CardW = 172f, CardH = 268f;
|
||||
|
||||
@@ -455,12 +506,12 @@ namespace Decentralized
|
||||
Open();
|
||||
}
|
||||
|
||||
private static void PlaceOrder(BmListing l)
|
||||
private static void PlaceOrder(BmListing l, int count)
|
||||
{
|
||||
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; }
|
||||
_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();
|
||||
Open();
|
||||
}
|
||||
@@ -490,7 +541,7 @@ namespace Decentralized
|
||||
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))
|
||||
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.AddHeader(L.T("black.forum.h"));
|
||||
|
||||
@@ -591,44 +591,44 @@ internal static class Program
|
||||
Func<string, long> value = id => 200;
|
||||
var bm = new BlackMarket();
|
||||
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(BlackMarket.Pay(170, PayMode.Escrow) == 170 + 9 && BlackMarket.Pay(170, PayMode.Advance) == 153, "托管多付 5%,提前放款打九折");
|
||||
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 && 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 bad = new BmSeller { Name = "b", Trades = 100, Rating = 95, Scam = true };
|
||||
bm.Sellers.Clear(); bm.Sellers.Add(good); bm.Sellers.Add(bad);
|
||||
var lg = new BmListing { Seller = 0, Item = "x", Price = 1000 };
|
||||
var lb = new BmListing { Seller = 1, Item = "x", Price = 1000 };
|
||||
var lg = new BmListing { Qty = 99, Seller = 0, Item = "x", Price = 1000 };
|
||||
var lb = new BmListing { Qty = 99, Seller = 1, Item = "x", Price = 1000 };
|
||||
var w = new Wallet { Address = "t" };
|
||||
w.Add(Assets.CRT, 10000); w.Taint = 4000; w.Basis = 5000;
|
||||
var o = bm.Place(lb, 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");
|
||||
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, 1, PayMode.Escrow, w, Assets.CRC, 10, price) == null, "托管下单:扣币、污点留在订单上;不收 CRC");
|
||||
double tBefore = 4000;
|
||||
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), "骗子卖家 + 托管:退款,污点与成本一并还回(不能借退款洗钱)");
|
||||
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));
|
||||
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, "没到期不动");
|
||||
int ready = 0, lost = 0;
|
||||
for (int i = 0; i < 400; i++)
|
||||
{
|
||||
var t = new BlackMarket(); t.Sellers.Add(good);
|
||||
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));
|
||||
if (oo.State == OrderState.Ready) ready++; else lost++;
|
||||
}
|
||||
Check(ready > 340 && lost > 10, $"上门:约 {Tuning.BmInterceptDoor:P0} 被截获(跑路另算):到货 {ready},丢失 {lost}");
|
||||
var c = new BlackMarket(); c.Sellers.Add(good);
|
||||
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));
|
||||
Check(co.State == OrderState.Pending && co.DueDay == 5, "镇压期间到期顺延一天");
|
||||
// 蜜罐期间下单会被追查;受控交付签收记罪证、拒签不记
|
||||
var h = new BlackMarket(); h.Sellers.Add(good); h.HoneypotUntil = 12;
|
||||
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.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;
|
||||
@@ -636,7 +636,7 @@ internal static class Program
|
||||
{
|
||||
var t2 = new BlackMarket(); t2.Sellers.Add(good);
|
||||
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);
|
||||
if (o2b.Controlled) ctrl++;
|
||||
}
|
||||
@@ -648,10 +648,10 @@ internal static class Program
|
||||
// 跑路:托管资金多则概率高;跑路后关闭
|
||||
var r = new BlackMarket(); r.Sellers.Add(good);
|
||||
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(), "托管资金越多越容易跑路");
|
||||
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("黑市出售", () =>
|
||||
@@ -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%");
|
||||
});
|
||||
|
||||
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} 失败");
|
||||
return _failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user