2026-11-03 19:48:26 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace Decentralized.Domain
|
|
|
|
|
{
|
|
|
|
|
public enum PayMode { Escrow, Advance }
|
|
|
|
|
public enum OrderState { Pending, Ready, Done, Refunded, Lost }
|
|
|
|
|
|
|
|
|
|
/// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary>
|
|
|
|
|
public sealed class BmSeller
|
|
|
|
|
{
|
|
|
|
|
public string Name;
|
|
|
|
|
public int Trades;
|
|
|
|
|
public int Rating; // 好评率 %
|
|
|
|
|
public bool Scam;
|
|
|
|
|
/// <summary>只有成交数够多的卖家才接受提前放款。</summary>
|
|
|
|
|
public bool Advance => Trades >= Tuning.BmAdvanceMinTrades;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>订单目录里的一件货:卖家、货、价格(订单价,已折扣)。</summary>
|
2026-11-03 20:06:32 +08:00
|
|
|
public sealed class BmListing : BmGood
|
2026-11-03 19:48:26 +08:00
|
|
|
{
|
|
|
|
|
public int Seller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public sealed class BmOrder
|
|
|
|
|
{
|
|
|
|
|
public string Item;
|
|
|
|
|
public string Seller;
|
|
|
|
|
public bool SellerScam;
|
|
|
|
|
public PayMode Mode;
|
2026-11-03 20:06:32 +08:00
|
|
|
public string Model, FakeOf;
|
|
|
|
|
public double Health;
|
2026-11-03 19:48:26 +08:00
|
|
|
public int Price;
|
|
|
|
|
public string Asset;
|
|
|
|
|
public double Qty;
|
|
|
|
|
/// <summary>付款时留在卖家那边的污点与成本:退款时一并还回,免得"付脏币再退款"洗钱。</summary>
|
|
|
|
|
public double Taint, Basis;
|
|
|
|
|
public int PlacedDay, DueDay;
|
2026-11-03 20:13:18 +08:00
|
|
|
/// <summary>被截获后执法选择了上门:到货时快递员是执法人员,签收即记罪证。</summary>
|
|
|
|
|
public bool Controlled;
|
2026-11-03 19:48:26 +08:00
|
|
|
public OrderState State = OrderState.Pending;
|
|
|
|
|
/// <summary>终态的原因:done / scam / rug / intercept / theft / refund。</summary>
|
|
|
|
|
public string Why;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 20:13:18 +08:00
|
|
|
/// <summary>一笔会被追查的交易:到期日、金额(信用点)、来源(honeypot / seller / controlled)。</summary>
|
|
|
|
|
public sealed class BmTrap
|
|
|
|
|
{
|
|
|
|
|
public int Day;
|
|
|
|
|
public double Amount;
|
|
|
|
|
public string Why;
|
|
|
|
|
public string Item;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 19:48:26 +08:00
|
|
|
/// <summary>夜间发生的订单事件,供日志与新闻使用。</summary>
|
|
|
|
|
public sealed class BmEvent
|
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
public string Kind; // ready / scam_refund / scam_loss / rug / intercept / reopen
|
2026-11-03 19:48:26 +08:00
|
|
|
public BmOrder Order;
|
|
|
|
|
public double Amount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public sealed partial class BlackMarket
|
|
|
|
|
{
|
|
|
|
|
public List<BmSeller> Sellers = new List<BmSeller>();
|
|
|
|
|
public List<BmListing> Catalog = new List<BmListing>();
|
|
|
|
|
public List<BmOrder> Orders = new List<BmOrder>();
|
|
|
|
|
public List<string> Forum = new List<string>();
|
|
|
|
|
/// <summary>平台跑路后关闭到这一天(含)。</summary>
|
|
|
|
|
public int ClosedUntil = -1;
|
2026-11-03 20:13:18 +08:00
|
|
|
/// <summary>执法接管平台的最后一天(含);期间的交易之后会被追查。</summary>
|
|
|
|
|
public int HoneypotUntil = -1;
|
|
|
|
|
public List<BmTrap> Traps = new List<BmTrap>();
|
|
|
|
|
|
|
|
|
|
public bool Honeypot(int day) => day <= HoneypotUntil;
|
|
|
|
|
|
|
|
|
|
public static double HoneypotChance(int secRep) => Math.Min(Tuning.BmHoneypotMax, Tuning.BmHoneypotBase + Math.Max(0, secRep) * Tuning.BmHoneypotPerSecRep);
|
2026-11-03 19:48:26 +08:00
|
|
|
|
|
|
|
|
private static readonly string[] NameA = { "rat", "grey", "static", "null", "dust", "iron", "pale", "wire", "salt", "ghost" };
|
|
|
|
|
private static readonly string[] NameB = { "king", "mule", "broker", "hand", "cat", "fox", "tooth", "lamp", "nine", "dealer" };
|
|
|
|
|
|
|
|
|
|
public bool Closed(int day) => day <= ClosedUntil;
|
|
|
|
|
|
|
|
|
|
/// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary>
|
|
|
|
|
public double Held => Orders.Where(o => o.State == OrderState.Pending).Sum(o => (double)o.Price);
|
|
|
|
|
|
2026-11-03 20:06:32 +08:00
|
|
|
public static int OrderPrice(long baseValue, GoodKind k = GoodKind.Contraband) => (int)Math.Max(1, Math.Round(baseValue * (1 + Extra(k)) * (1 - Tuning.BmOrderDiscount)));
|
2026-11-03 19:48:26 +08:00
|
|
|
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>
|
2026-11-03 20:06:32 +08:00
|
|
|
public void RefreshOrders(int day, int rep, Rng rng, Func<string, long> value)
|
2026-11-03 19:48:26 +08:00
|
|
|
{
|
|
|
|
|
Sellers.Clear(); Catalog.Clear(); Forum.Clear();
|
|
|
|
|
for (int i = 0; i < Tuning.BmSellerCount; i++)
|
|
|
|
|
{
|
|
|
|
|
bool scam = rng.Chance(Tuning.BmScamSellerShare);
|
|
|
|
|
// 骗子多半是新号,也有刷过好评的老号
|
|
|
|
|
bool fresh = scam ? rng.Chance(0.6) : rng.Chance(0.25);
|
|
|
|
|
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,
|
|
|
|
|
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);
|
|
|
|
|
}
|
2026-11-03 20:06:32 +08:00
|
|
|
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 });
|
2026-11-03 19:48:26 +08:00
|
|
|
if (RugChance > Tuning.BmRugBase * 3) Forum.Add("rug");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>下单:钱包里的币按实付折算扣除,污点与成本按比例留在卖家那边(记在订单上)。成功返回订单。</summary>
|
2026-11-03 20:06:32 +08:00
|
|
|
public BmOrder Place(BmListing l, PayMode mode, Wallet w, string asset, int day, Func<string, double> price)
|
2026-11-03 19:48:26 +08:00
|
|
|
{
|
|
|
|
|
if (l == null || 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);
|
|
|
|
|
double qty = credits / p;
|
|
|
|
|
if (w.Avail(asset) + 1e-9 < qty) return null;
|
|
|
|
|
double total = w.Value(price);
|
|
|
|
|
double share = total > 0 ? Math.Min(1, credits / total) : 0;
|
|
|
|
|
double taint = w.Taint * share, basis = w.Basis * share;
|
|
|
|
|
w.Taint -= taint; w.Basis -= basis;
|
|
|
|
|
w.Add(asset, -qty);
|
|
|
|
|
var o = new BmOrder
|
|
|
|
|
{
|
2026-11-03 20:06:32 +08:00
|
|
|
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,
|
2026-11-03 19:48:26 +08:00
|
|
|
};
|
|
|
|
|
Orders.Add(o);
|
2026-11-03 20:13:18 +08:00
|
|
|
if (Honeypot(day)) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = credits, Why = "honeypot", Item = l.Item });
|
2026-11-03 19:48:26 +08:00
|
|
|
return o;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void Refund(Wallet w, BmOrder o)
|
|
|
|
|
{
|
|
|
|
|
w.Add(o.Asset, o.Qty);
|
|
|
|
|
w.Taint += o.Taint; w.Basis += o.Basis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>每晚推进订单:平台跑路、到期的订单(卖家跑路、被截获、死投被挖走)、转入可取状态。镇压期间到期顺延一天。</summary>
|
2026-11-03 20:13:18 +08:00
|
|
|
public List<BmEvent> Night(int day, bool crackdown, Wallet w, Rng rng, int secRep = 0)
|
2026-11-03 19:48:26 +08:00
|
|
|
{
|
|
|
|
|
var ev = new List<BmEvent>();
|
2026-11-03 20:13:18 +08:00
|
|
|
if (!Honeypot(day) && rng.Chance(HoneypotChance(secRep))) { HoneypotUntil = day + Tuning.BmHoneypotDays; if (!Forum.Contains("easy")) Forum.Add("easy"); }
|
2026-11-03 19:48:26 +08:00
|
|
|
if (ClosedUntil == day) ev.Add(new BmEvent { Kind = "reopen" });
|
|
|
|
|
if (!Closed(day) && Orders.Any(o => o.State == OrderState.Pending) && rng.Chance(RugChance))
|
|
|
|
|
{
|
|
|
|
|
foreach (var o in Orders.Where(o => o.State == OrderState.Pending))
|
|
|
|
|
{
|
|
|
|
|
o.State = OrderState.Lost; o.Why = "rug";
|
|
|
|
|
ev.Add(new BmEvent { Kind = "rug", Order = o, Amount = o.Price });
|
|
|
|
|
}
|
|
|
|
|
ClosedUntil = day + Tuning.BmRugClosedDays;
|
|
|
|
|
Offers.Clear(); Catalog.Clear(); Sellers.Clear();
|
|
|
|
|
return ev;
|
|
|
|
|
}
|
|
|
|
|
foreach (var o in Orders.Where(x => x.State == OrderState.Pending).ToList())
|
|
|
|
|
{
|
|
|
|
|
if (day < o.DueDay) continue;
|
|
|
|
|
if (crackdown) { o.DueDay = day + 1; continue; }
|
|
|
|
|
if (o.SellerScam)
|
|
|
|
|
{
|
|
|
|
|
if (o.Mode == PayMode.Escrow) { Refund(w, o); o.State = OrderState.Refunded; o.Why = "scam"; ev.Add(new BmEvent { Kind = "scam_refund", Order = o, Amount = o.Price }); }
|
|
|
|
|
else { o.State = OrderState.Lost; o.Why = "scam"; ev.Add(new BmEvent { Kind = "scam_loss", Order = o, Amount = o.Price }); }
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-11-03 20:06:32 +08:00
|
|
|
double ic = Tuning.BmInterceptDoor + (crackdown ? Tuning.BmInterceptCrackdown : 0);
|
2026-11-03 20:13:18 +08:00
|
|
|
if (rng.Chance(ic))
|
|
|
|
|
{
|
|
|
|
|
if (rng.Chance(Tuning.BmControlled)) { o.Controlled = true; o.State = OrderState.Ready; ev.Add(new BmEvent { Kind = "ready", Order = o }); continue; }
|
|
|
|
|
o.State = OrderState.Lost; o.Why = "intercept"; ev.Add(new BmEvent { Kind = "intercept", Order = o, Amount = o.Price }); continue;
|
|
|
|
|
}
|
2026-11-03 19:48:26 +08:00
|
|
|
o.State = OrderState.Ready;
|
|
|
|
|
ev.Add(new BmEvent { Kind = "ready", Order = o });
|
|
|
|
|
}
|
2026-11-03 20:13:18 +08:00
|
|
|
// 卖家落网:近期成交过的卖家各有小概率被抓,他们留存的订单记录之后会连累买家
|
|
|
|
|
foreach (var name in Orders.Where(x => x.State == OrderState.Done && day - x.DueDay <= 14).Select(x => x.Seller).Distinct().ToList())
|
|
|
|
|
{
|
|
|
|
|
if (!rng.Chance(Tuning.BmSellerBust)) continue;
|
|
|
|
|
foreach (var o in Orders.Where(x => x.State == OrderState.Done && x.Seller == name && day - x.DueDay <= 14))
|
|
|
|
|
Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = o.Price, Why = "seller", Item = o.Item });
|
|
|
|
|
Forum.Add("busted:" + name);
|
|
|
|
|
}
|
|
|
|
|
foreach (var t in Traps.Where(x => day >= x.Day).ToList())
|
|
|
|
|
{
|
|
|
|
|
Traps.Remove(t);
|
|
|
|
|
ev.Add(new BmEvent { Kind = "trap", Amount = t.Amount, Order = new BmOrder { Item = t.Item, Seller = t.Why } });
|
|
|
|
|
}
|
2026-11-03 19:48:26 +08:00
|
|
|
Orders.RemoveAll(o => o.State != OrderState.Pending && o.State != OrderState.Ready && day - o.DueDay > 14);
|
|
|
|
|
return ev;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 20:13:18 +08:00
|
|
|
/// <summary>受控交付:签收则当场记罪证(返回金额)并查封,不交货;拒签则损失货款,不留罪证。</summary>
|
|
|
|
|
public double Sign(BmOrder o, bool accept)
|
|
|
|
|
{
|
|
|
|
|
o.State = OrderState.Lost;
|
|
|
|
|
o.Why = accept ? "seized" : "refused";
|
|
|
|
|
return accept ? o.Price : 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 19:48:26 +08:00
|
|
|
public void Deliver(BmOrder o) { o.State = OrderState.Done; o.Why = "done"; }
|
|
|
|
|
|
2026-11-03 20:06:32 +08:00
|
|
|
public IEnumerable<BmOrder> Ready => Orders.Where(o => o.State == OrderState.Ready);
|
2026-11-03 19:48:26 +08:00
|
|
|
}
|
|
|
|
|
}
|