Black market in Web3 page, shelf UI, no dead drop, tech and GPU goods; rules moved to Web3 handbook

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:06:32 +08:00
1 parent 6bc479f710
commit e44a4439ed
12 files changed
+296 -245

No files matched your search

+69 -22
View File
@@ -4,18 +4,29 @@ using System.Linq;
namespace Decentralized.Domain
{
/// <summary>黑市现货柜台上的一件货:游戏物品 id、剩余件数、含溢价的单价(信用点)。</summary>
public sealed class BmOffer
/// <summary>货的种类:违禁品、高科技物品、显卡。</summary>
public enum GoodKind { Contraband, Tech, Gpu }
/// <summary>黑市货架上的一种货。显卡的 Item 是 "gpu:型号",Model 是标称型号,FakeOf 非空表示背后是别的芯片(买家看不到)。</summary>
public class BmGood
{
public string Item;
public int Qty;
public int Price;
public GoodKind Kind;
public int Level;
public int Price;
public string Model, FakeOf;
public double Health;
}
/// <summary>现货柜台上的一件货:剩余件数、含溢价的单价(信用点)。</summary>
public sealed class BmOffer : BmGood
{
public int Qty;
}
/// <summary>
/// Web3 黑市:黑市派系在链上的实体。现货柜台每周刷新一批违禁品(档次随黑市声望放开),只收币,脏币也收,
/// 付出去的币连同按比例的污点一起留在卖家那边;Ponero 兑换处把任意币兑成无法追踪的 Ponero(污点留在原地),有每日额度和手续费。
/// Web3 黑市:黑市派系在链上的实体。货架上主要是违禁品(档次随黑市声望放开),偶尔有高科技物品和显卡(加价),
/// 现货只收币,脏币也收,付出去的币连同按比例的污点一起留在卖家那边;Ponero 兑换处把任意币兑成无法追踪的 Ponero(这部分价值的污点被清除),有每日额度和手续费。
/// 镇压期间整个平台下线。
/// </summary>
public sealed partial class BlackMarket
@@ -26,7 +37,7 @@ namespace Decentralized.Domain
public double DeskToday;
public int DeskDay = -1;
/// <summary>现货池:游戏物品 id 与违禁等级(1 低、2 中、3 高)。全部来自游戏已有的违禁品。</summary>
/// <summary>违禁品池:游戏物品 id 与违禁等级(1 低、2 中、3 高)。全部来自游戏已有的违禁品。</summary>
public static readonly KeyValuePair<string, int>[] Pool =
{
P("small_pistol_ammo", 1), P("heavy_pistol_ammo", 1), P("handmade_pistol", 1), P("printed_gun", 1), P("stun_baton", 1),
@@ -38,6 +49,13 @@ namespace Decentralized.Domain
P("shotgun", 3), P("smg", 3), P("c4", 3), P("fanta_pill", 3), P("nightmare_dust", 3), P("cmd_keycard", 3), P("crypto_module_sec", 3),
};
/// <summary>高科技物品池(不是违禁品,黑市上要加价)。</summary>
public static readonly string[] TechPool =
{
"hand", "aug_chip", "energy_credit_hyp", "energy_credit_breeder", "makeshift_reactor", "mirage_projector", "printer",
"system_module_overclock", "system_module_performance", "system_module_quality", "module_extractor_advanced", "powerblock",
};
private static KeyValuePair<string, int> P(string id, int level) => new KeyValuePair<string, int>(id, level);
public static bool Open(int rep) => rep >= Tuning.BmOpenRep;
@@ -45,26 +63,55 @@ namespace Decentralized.Domain
public static bool DeskOpen(int rep) => rep >= Tuning.PoneroOpenRep;
public static int StockCount(int rep) => Tuning.BmStockBase + (int)Math.Max(0, rep / Tuning.BmStockPerRep);
/// <summary>溢价后的单价(信用点)。</summary>
public static int PriceOf(long baseValue) => (int)Math.Max(1, Math.Round(baseValue * (1 + Tuning.BmPremium)));
/// <summary>货的加价:违禁品 0,高科技、显卡各有额外加价(在现货溢价 / 订单折扣之外)。</summary>
public static double Extra(GoodKind k) => k == GoodKind.Tech ? Tuning.BmTechMarkup : k == GoodKind.Gpu ? Tuning.BmGpuMarkup : 0;
/// <summary>每周刷新一次(或首次开放时):按声望开放的档次抽 StockCount 种货,每种 1–3 件。value 给出物品的游戏基础价。</summary>
/// <summary>现货单价(信用点):基础价 ×(1 + 溢价 + 货的加价)。</summary>
public static int PriceOf(long baseValue, GoodKind k = GoodKind.Contraband) => (int)Math.Max(1, Math.Round(baseValue * (1 + Tuning.BmPremium + Extra(k))));
/// <summary>抽 count 种货:高科技(中档声望起)和显卡按概率换掉违禁品。value 给出游戏物品的基础价;显卡的基础价按标称型号与成色算。</summary>
private List<BmGood> Roll(int count, int day, int rep, Rng rng, Func<string, long> value, Func<long, GoodKind, int> price)
{
var goods = new List<BmGood>();
var pool = Pool.Where(x => x.Value <= MaxLevel(rep)).ToList();
var tech = TechPool.ToList();
var gpus = UsedDealers.Pool(day);
for (int i = 0; i < count; i++)
{
double r = rng.NextDouble();
if (r < Tuning.BmGpuChance)
{
var c = UsedDealers.Card(gpus, Tuning.BmGpuFake, 0, rng);
goods.Add(new BmGood { Item = "gpu:" + c.Model, Kind = GoodKind.Gpu, Level = 2, Model = c.Model, FakeOf = c.FakeOf, Health = c.Health, Price = price(CardValue.Of(c.Model, c.Health), GoodKind.Gpu) });
}
else if (r < Tuning.BmGpuChance + Tuning.BmTechChance && rep >= Tuning.BmMidRep && tech.Count > 0)
{
int k = rng.Range(0, tech.Count - 1);
string id = tech[k];
tech.RemoveAt(k);
long v = value(id);
if (v > 0) goods.Add(new BmGood { Item = id, Kind = GoodKind.Tech, Level = 3, Price = price(v, GoodKind.Tech) });
}
else if (pool.Count > 0)
{
int k = rng.Range(0, pool.Count - 1);
var pick = pool[k];
pool.RemoveAt(k);
long v = value(pick.Key);
if (v > 0) goods.Add(new BmGood { Item = pick.Key, Kind = GoodKind.Contraband, Level = pick.Value, Price = price(v, GoodKind.Contraband) });
}
}
return goods;
}
/// <summary>每周刷新一次(或首次开放时):按声望开放的档次抽 StockCount 种货,每种 1–3 件(高档与显卡、高科技 1 件)。</summary>
public bool Refresh(int day, int rep, Rng rng, Func<string, long> value)
{
if (!Open(rep) || day - RefreshedDay < Tuning.BmRefreshDays) return false;
RefreshedDay = day;
Offers.Clear();
var pool = Pool.Where(x => x.Value <= MaxLevel(rep)).ToList();
int n = Math.Min(StockCount(rep), pool.Count);
for (int i = 0; i < n; i++)
{
int k = rng.Range(0, pool.Count - 1);
var pick = pool[k];
pool.RemoveAt(k);
long v = value(pick.Key);
if (v <= 0) continue;
Offers.Add(new BmOffer { Item = pick.Key, Level = pick.Value, Qty = pick.Value == 3 ? 1 : rng.Range(1, 3), Price = PriceOf(v) });
}
foreach (var g in Roll(StockCount(rep), day, rep, rng, value, PriceOf))
Offers.Add(new BmOffer { Item = g.Item, Kind = g.Kind, Level = g.Level, Price = g.Price, Model = g.Model, FakeOf = g.FakeOf, Health = g.Health, Qty = g.Kind != GoodKind.Contraband || g.Level == 3 ? 1 : rng.Range(1, 3) });
return true;
}
@@ -91,7 +138,7 @@ namespace Decentralized.Domain
public double DeskLeft(int day) => Math.Max(0, Tuning.PoneroDailyCap - (DeskDay == day ? DeskToday : 0));
/// <summary>兑入 Ponero:from 币 amount 个,扣手续费后按 Ponero 价到账;这部分价值带的污点留在原地(不跟着 Ponero)。返回到账数量,额度或余额不足返回 -1。</summary>
/// <summary>兑入 Ponero:from 币 amount 个,扣手续费后按 Ponero 价到账;这部分价值带的污点被清除(不跟着 Ponero)。返回到账数量,额度或余额不足返回 -1。</summary>
public double ToPonero(Wallet w, string from, double amount, int day, Func<string, double> price)
{
double value = amount * price(from);
+14 -26
View File
@@ -5,7 +5,6 @@ using System.Linq;
namespace Decentralized.Domain
{
public enum PayMode { Escrow, Advance }
public enum Delivery { Door, Drop }
public enum OrderState { Pending, Ready, Done, Refunded, Lost }
/// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary>
@@ -20,12 +19,9 @@ namespace Decentralized.Domain
}
/// <summary>订单目录里的一件货:卖家、货、价格(订单价,已折扣)。</summary>
public sealed class BmListing
public sealed class BmListing : BmGood
{
public int Seller;
public string Item;
public int Level;
public int Price;
}
public sealed class BmOrder
@@ -34,7 +30,8 @@ namespace Decentralized.Domain
public string Seller;
public bool SellerScam;
public PayMode Mode;
public Delivery Delivery;
public string Model, FakeOf;
public double Health;
public int Price;
public string Asset;
public double Qty;
@@ -49,7 +46,7 @@ namespace Decentralized.Domain
/// <summary>夜间发生的订单事件,供日志与新闻使用。</summary>
public sealed class BmEvent
{
public string Kind; // ready / scam_refund / scam_loss / rug / intercept / theft / reopen
public string Kind; // ready / scam_refund / scam_loss / rug / intercept / reopen
public BmOrder Order;
public double Amount;
}
@@ -71,16 +68,15 @@ namespace Decentralized.Domain
/// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary>
public double Held => Orders.Where(o => o.State == OrderState.Pending).Sum(o => (double)o.Price);
public static int OrderPrice(long baseValue) => (int)Math.Max(1, Math.Round(baseValue * (1 - Tuning.BmOrderDiscount)));
public static int OrderPrice(long baseValue, GoodKind k = GoodKind.Contraband) => (int)Math.Max(1, Math.Round(baseValue * (1 + Extra(k)) * (1 - Tuning.BmOrderDiscount)));
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 static int Days(Delivery d) => d == Delivery.Door ? Tuning.BmDoorDays : Tuning.BmDropDays;
public double RugChance => Math.Min(Tuning.BmRugMax, Tuning.BmRugBase + Held / 10000.0 * Tuning.BmRugPerTenK);
/// <summary>每周重新生成卖家、目录和论坛情报(与现货同一天刷新)。</summary>
public void RefreshOrders(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();
for (int i = 0; i < Tuning.BmSellerCount; i++)
@@ -98,21 +94,14 @@ namespace Decentralized.Domain
Sellers.Add(s);
if (scam && rng.Chance(0.5)) Forum.Add("scam:" + s.Name);
}
var pool = Pool.Where(x => x.Value <= MaxLevel(rep)).ToList();
for (int i = 0; i < Tuning.BmSellerCount * 2 && pool.Count > 0; i++)
{
int k = rng.Range(0, pool.Count - 1);
var pick = pool[k];
pool.RemoveAt(k);
long v = value(pick.Key);
if (v <= 0) continue;
Catalog.Add(new BmListing { Seller = i % Sellers.Count, Item = pick.Key, Level = pick.Value, Price = OrderPrice(v) });
}
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 });
if (RugChance > Tuning.BmRugBase * 3) Forum.Add("rug");
}
/// <summary>下单:钱包里的币按实付折算扣除,污点与成本按比例留在卖家那边(记在订单上)。成功返回订单。</summary>
public BmOrder Place(BmListing l, PayMode mode, Delivery del, Wallet w, string asset, int day, Func<string, double> price)
public BmOrder Place(BmListing l, PayMode mode, Wallet w, string asset, int day, Func<string, double> price)
{
if (l == null || asset == Assets.CRC || Closed(day)) return null;
var seller = Sellers[l.Seller];
@@ -129,8 +118,8 @@ namespace Decentralized.Domain
w.Add(asset, -qty);
var o = new BmOrder
{
Item = l.Item, Seller = seller.Name, SellerScam = seller.Scam, Mode = mode, Delivery = del, Price = credits, Asset = asset, Qty = qty,
Taint = taint, Basis = basis, PlacedDay = day, DueDay = day + Days(del),
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,
};
Orders.Add(o);
return o;
@@ -168,9 +157,8 @@ namespace Decentralized.Domain
else { o.State = OrderState.Lost; o.Why = "scam"; ev.Add(new BmEvent { Kind = "scam_loss", Order = o, Amount = o.Price }); }
continue;
}
double ic = (o.Delivery == Delivery.Door ? Tuning.BmInterceptDoor : Tuning.BmInterceptDrop) + (crackdown ? Tuning.BmInterceptCrackdown : 0);
double ic = Tuning.BmInterceptDoor + (crackdown ? Tuning.BmInterceptCrackdown : 0);
if (rng.Chance(ic)) { o.State = OrderState.Lost; o.Why = "intercept"; ev.Add(new BmEvent { Kind = "intercept", Order = o, Amount = o.Price }); continue; }
if (o.Delivery == Delivery.Drop && rng.Chance(Tuning.BmDropTheft)) { o.State = OrderState.Lost; o.Why = "theft"; ev.Add(new BmEvent { Kind = "theft", Order = o, Amount = o.Price }); continue; }
o.State = OrderState.Ready;
ev.Add(new BmEvent { Kind = "ready", Order = o });
}
@@ -180,6 +168,6 @@ namespace Decentralized.Domain
public void Deliver(BmOrder o) { o.State = OrderState.Done; o.Why = "done"; }
public IEnumerable<BmOrder> ReadyFor(Delivery d) => Orders.Where(o => o.State == OrderState.Ready && o.Delivery == d);
public IEnumerable<BmOrder> Ready => Orders.Where(o => o.State == OrderState.Ready);
}
}
+7 -5
View File
@@ -89,11 +89,13 @@ namespace Decentralized.Domain
/// <summary>Ponero 兑换处:开放所需黑市声望、每次兑入 / 兑出的手续费、每天可兑换的额度(信用点)、对 PSC 的价格比。</summary>
public const int PoneroOpenRep = 30;
public const double PoneroFee = 0.08, PoneroDailyCap = 1000, PoneroPscRatio = 0.5;
/// <summary>黑市订单:订单价相对游戏基础价的折扣、托管费、提前放款再折扣、提前放款所需卖家成交数、上门 / 死投的天数、
/// 上门 / 死投被截获的概率(镇压期间再加)、死投被别人先挖走的概率、骗子卖家占比、每晚平台跑路基础概率与每万信用点托管资金加的概率(封顶)、跑路后关闭的天数。</summary>
public const double BmOrderDiscount = 0.15, BmEscrowFee = 0.05, BmAdvanceDiscount = 0.10, BmInterceptDoor = 0.08, BmInterceptDrop = 0.03, BmInterceptCrackdown = 0.15;
public const double BmDropTheft = 0.12, BmScamSellerShare = 0.15, BmRugBase = 0.002, BmRugPerTenK = 0.004, BmRugMax = 0.05;
public const int BmAdvanceMinTrades = 30, BmDoorDays = 3, BmDropDays = 2, BmRugClosedDays = 10, BmSellerCount = 4;
/// <summary>黑市订单:订单价相对游戏基础价的折扣、托管费、提前放款再折扣、提前放款所需卖家成交数、上门的天数、
/// 上门被截获的概率(镇压期间再加)、骗子卖家占比、每晚平台跑路基础概率与每万信用点托管资金加的概率(封顶)、跑路后关闭的天数。</summary>
public const double BmOrderDiscount = 0.15, 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;
/// <summary>货架上除违禁品外的两类货:高科技物品(需中档声望)和显卡的出现概率、加价(在现货溢价 / 订单价之外再加),显卡里夹假卡的比例。</summary>
public const double BmTechChance = 0.15, BmGpuChance = 0.20, BmTechMarkup = 0.5, BmGpuMarkup = 0.2, BmGpuFake = 0.20;
/// <summary>黑市人脉升级的价格。</summary>
public const int BmUnlockPrice = 1500;
/// <summary>单个金融违规事件记入的罪证上限。</summary>
+11 -6
View File
@@ -38,6 +38,16 @@ namespace Decentralized.Domain
return list.ToArray();
}
/// <summary>从池里抽一张:随机型号与成色;按 fakeShare 的概率是假卡(背后是便宜得多的真实芯片,废片只给低档标称)。</summary>
public static UsedCard Card(string[] pool, double fakeShare, int markupPct, Rng rng)
{
var label = pool[rng.Range(0, pool.Length - 1)];
var card = new UsedCard { Model = label, Health = 25 + rng.NextDouble() * 60, MarkupPct = markupPct };
var cheaper = Hardware.Specs.Values.Where(s => !s.Asic && s.Price * 2 <= Hardware.Specs[label].Price && (!s.Scrap || Hardware.Specs[label].Tier == 0)).Select(s => s.Model).ToArray();
if (cheaper.Length > 0 && rng.Chance(fakeShare)) card.FakeOf = cheaper[rng.Range(0, cheaper.Length - 1)];
return card;
}
public static List<UsedCard> Cards(DealerKind kind, int day, Rng rng)
{
var pool = Pool(day);
@@ -46,12 +56,7 @@ namespace Decentralized.Domain
for (int i = 0; i < count; i++)
{
double share = kind == DealerKind.Scammer ? FakeShareScammer : kind == DealerKind.Middleman ? FakeShareMiddleman : 0;
var label = pool[rng.Range(0, pool.Length - 1)];
var card = new UsedCard { Model = label, Health = 25 + rng.NextDouble() * 60 };
var cheaper = Hardware.Specs.Values.Where(s => !s.Asic && s.Price * 2 <= Hardware.Specs[label].Price && (!s.Scrap || Hardware.Specs[label].Tier == 0)).Select(s => s.Model).ToArray();
if (cheaper.Length > 0 && rng.Chance(share)) card.FakeOf = cheaper[rng.Range(0, cheaper.Length - 1)];
card.MarkupPct = kind == DealerKind.Middleman ? (int)Math.Round(MiddlemanMarkup * 100) : 0;
cards.Add(card);
cards.Add(Card(pool, share, kind == DealerKind.Middleman ? (int)Math.Round(MiddlemanMarkup * 100) : 0, rng));
}
return cards;
}