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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user