Black market: 18 merchant types from the game's items and player professions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 21:02:54 +08:00
1 parent 378203d04d
commit 61ec91b34d
11 files changed
+132 -52

No files matched your search

+1
View File
@@ -27,6 +27,7 @@
- 收尾核算(`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 万)。 - 订单改成商家货架(用户意见):黑市页"订单"改名"商家",每周从 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 万)。
- 商家类型扩到 18 种 + 大宗(用户意见,对照游戏的物品目录和玩家职业):全新矿卡经销商、二手矿卡贩子、矿架与能源、模组、军火(枪、手雷、炸药、刀具、电击枪)、枪匠配件(枪械配件、3D 打印芯片与模组、枪械零件)、证件芯片、药物(药片、注射器、血袋、手术工具)、化学品(化学用品、助熔剂、化学设备模组、量筒试剂瓶)、酿酒(酵母、荧光莓、啤酒、酒瓶)、水培农场(种子、水培模组、植物分析仪)、养殖(鱼卵、鱼子酱、循环水养殖系统、血清、屠宰机)、矿工(矿石、金属锭、电子元件、拾荒扫描仪)、水务(滤水、净水、储水、检测)、仓储(走私暗格、储藏区扩建、军用背包)、工具(涡轮增压器、等离子切割机、X 射线扫描仪、心智重编程器)、奢侈品(香水、精华液、香烟)。每周 7 家:2 家从矿卡 / 矿架 / 模组 4 类里抽(保证矿工路线总有货),其余 5 家从其余类型里抽;每家 3 种货,库存 2–5 件,单件基础价超过 4000 的不上。游戏里抽样 60 周货架总值平均 13.2k(目标 1.4 万)。药物改成并入药物商家,现货柜台的货不变。所有货都能转手出售。
- 同时上架最多 10 件(用户意见),出售页显示"上架 n/10",超出时提示。 - 同时上架最多 10 件(用户意见),出售页显示"上架 n/10",超出时提示。
- 出售:货款污点随近 7 天成交额加重(成交额达 1.4 万时概率与比例各加 30%);出售页加"全部上架"按钮。套利核算(`bm_econ.py`,商家系数均值 0.825 再加批量折扣):普通商家提前放款进货 + ×1.5 出售每件约 +21%,大宗商家约 +33%,一周可买的货约 1.3 万,带污点的货款约够吃满 Ponero 日额度。 - 出售:货款污点随近 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,封条完好,不会是假卡),标签显示"全新"。全新卡只有单元测试,没有在游戏里见过。
+1 -7
View File
@@ -5,7 +5,7 @@ using System.Linq;
namespace Decentralized.Domain namespace Decentralized.Domain
{ {
/// <summary>货的种类:违禁品、高科技物品、显卡。</summary> /// <summary>货的种类:违禁品、高科技物品、显卡。</summary>
public enum GoodKind { Contraband, Tech, Gpu } public enum GoodKind { Contraband, Tech, Gpu, Goods }
/// <summary>黑市货架上的一种货。显卡的 Item 是 "gpu:型号",Model 是标称型号,FakeOf 非空表示背后是别的芯片(买家看不到)。</summary> /// <summary>黑市货架上的一种货。显卡的 Item 是 "gpu:型号",Model 是标称型号,FakeOf 非空表示背后是别的芯片(买家看不到)。</summary>
public class BmGood public class BmGood
@@ -47,12 +47,6 @@ 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 =
{ {
+70
View File
@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Linq;
namespace Decentralized.Domain
{
/// <summary>商家货架上的一项货:游戏物品 id、货的种类、违禁等级(只对违禁品有意义,随黑市声望放开)。</summary>
public struct BmEntry
{
public string Id;
public GoodKind Kind;
public int Level;
}
/// <summary>
/// 商家类型的货表。除了矿卡,其余都按玩家能走的路线配货:矿工、酿酒师、化学师、药剂师、枪械师、水培农场主、养殖户、水务、仓储、工具、奢侈品,
/// 加上违禁品(军火、证件芯片)。大宗商家什么都卖。
/// </summary>
public static class Merchants
{
private static BmEntry C(string id, int level) => new BmEntry { Id = id, Kind = GoodKind.Contraband, Level = level };
private static BmEntry T(string id) => new BmEntry { Id = id, Kind = GoodKind.Tech, Level = 3 };
private static BmEntry G(string id) => new BmEntry { Id = id, Kind = GoodKind.Goods, Level = 0 };
private static BmEntry[] Gs(params string[] ids) => ids.Select(G).ToArray();
public static readonly Dictionary<MerchantType, BmEntry[]> Table = new Dictionary<MerchantType, BmEntry[]>
{
[MerchantType.Rigs] = new[] { T("pd_rig"), T("energy_credit_ext"), T("energy_credit_hyp"), T("energy_credit_breeder"), T("powerblock"), T("recharger"), T("current_converter") },
[MerchantType.Modules] = new[] { T("system_module_overclock"), T("system_module_performance"), T("system_module_quality"), T("system_module_eco"), T("system_module_efficiancy"),
T("system_module_fineness"), T("system_module_streamlining"), T("module_extractor_advanced"), T("module_bay_expansion_kit") },
[MerchantType.Arms] = new[] { C("handmade_pistol", 1), C("printed_gun", 1), C("stun_baton", 1), C("revolver", 2), C("heavy_handmade_pistol", 2), C("flashbang_grenade", 2), C("smoke_grenade", 2),
C("shotgun", 3), C("smg", 3), C("c4", 3), G("combat_knife"), G("combat_machete"), G("hatchet"), G("crowbar"), G("kitchen_cleaver"), G("skinning_knife"), G("stun_gun"), G("tazer") },
[MerchantType.GunMods] = Gs("silencer2", "laser2", "match_barrel", "trigger_group", "mag_10mm_high", "mag_22_high", "mag_10mm_smg_high", "rds", "compensator", "stock", "grip", "bipod", "barrel",
"printer_chip_gun_part", "printer_chip_silencer", "printer_chip_handmade_pistol_frame", "printer_chip_heavy_handmade_pistol_frame", "printer_module_cnc", "printer_module_metal", "gun_part", "printer_plastic"),
[MerchantType.Docs] = new[] { C("eng_keycard", 1), C("med_keycard", 1), C("ser_keycard", 1), C("sup_keycard", 1), C("crypto_module_ser", 1), C("crypto_module_sup", 1),
C("sci_keycard", 2), C("sec_keycard", 2), C("crypto_module_eng", 2), C("crypto_module_med", 2), C("cmd_keycard", 3), C("crypto_module_sec", 3) },
[MerchantType.Pharma] = new[] { C("unlicensed_phagimycin_pill", 2), C("dream_dust", 2), C("fanta_pill", 3), C("nightmare_dust", 3), C("zyanide_pill", 2),
G("black_injector"), G("pure_white_injector"), G("large_purple_injector"), G("pink_injector"), G("pink_alt_injector"), G("blue_blood_bag"), G("surgery_tool"), G("zerochew"), G("phagimycin_pill") },
[MerchantType.Chem] = new[] { C("restricted_chemical", 2), G("common_chemical"), G("flux_agent"), G("advanced_flux_agent"), G("chem_module"), G("evaporator_module_solvent_recovery"),
G("chem_scanner"), G("med_bottle_blue"), G("med_bottle_orange"), G("med_bottle_red"), G("med_bottle_small_pink"), G("med_bottle_violet"), G("100ml_cylinder"), G("5ml_dropper") },
[MerchantType.Brewer] = new[] { G("wine_yeast"), G("wine_superyeast"), G("wine_yeast_infinite"), C("wine_yeast_red", 1), G("wine_berry"), G("bloomberry_seed"), G("nudka"), G("red_beer"), G("beerCase"), G("wine_bottle") },
[MerchantType.Farm] = new[] { G("nutrifruit_seed"), G("kotton_seed"), G("fillerweed_seed"), G("hydroreed_seed"), G("bloomberry_seed"), C("dream_cap_seed", 1),
G("farm_module_nutrifruit"), G("farm_module_kotton_loom"), G("farm_module_fillerweed_production"), G("farm_module_hydroreed_filter"), G("farm_module_dream_cap"), G("farm_module_smuggler"),
G("plant_analyser"), G("plant_dna_disk"), G("nutrient_tablet"), G("hydroreed_filter") },
[MerchantType.Husbandry] = Gs("tilapia_roe", "sturgeon_roe", "caviar", "ras", "serum_green", "serum_red",
"livestock_feed_dispenser", "auto_butcher", "industrial_fish_feed", "animal_cage"),
[MerchantType.Miner] = Gs("common_ore", "rare_ore", "metal_ingot", "scrap_metal", "common_electronic", "rare_electronic", "hypertech_electronic", "metal_scanner", "wire", "advanced_flux_agent"),
[MerchantType.Water] = Gs("water_filter_adv", "portable_water_purifier", "water_tank", "uv_filter", "water_tablet_container", "water_test_container", "aquascan", "bottled_water_premium", "water_jug", "water_ration"),
[MerchantType.Storage] = Gs("smuggler_bay", "smuggler_bay_mod", "smuggler_bay_mini", "storage_bay_large", "storage_bay_expansion_kit", "hypertech_bay", "machine_bay_ext", "storage_bay_chem",
"storage_bay_gun", "backpack_large_military"),
[MerchantType.Tools] = Gs("turbo_booster", "turbo_booster_adv", "turbo_booster_hypertech", "welder", "xray_scanner", "magnifier", "labeler_blue", "toolbox", "wire_cutter", "mind_reprogrammer"),
[MerchantType.Luxury] = new[] { C("pheromone_perfume", 2), G("neuroactive_perfume"), G("self_refilling_neuroactive_perfume"), G("skincare_cream"), G("packet_red_cigarette"), G("gamblers_coin"), G("kotton_fabric"), G("armor_lining") },
};
private static readonly Dictionary<string, BmEntry> ById = BuildIndex();
private static Dictionary<string, BmEntry> BuildIndex()
{
var d = new Dictionary<string, BmEntry>();
foreach (var e in Table.Values.SelectMany(x => x)) d[e.Id] = e;
foreach (var kv in BlackMarket.Pool) d[kv.Key] = new BmEntry { Id = kv.Key, Kind = GoodKind.Contraband, Level = kv.Value };
foreach (var id in BlackMarket.TechPool) d[id] = new BmEntry { Id = id, Kind = GoodKind.Tech, Level = 3 };
return d;
}
/// <summary>这个物品 id 在黑市上是不是一件货(现货柜台、商家货架里出现过的,都能转手出售)。</summary>
public static bool IsGood(string id) => ById.ContainsKey(id);
public static BmEntry Info(string id) => ById[id];
}
}
+20 -35
View File
@@ -5,8 +5,8 @@ using System.Linq;
namespace Decentralized.Domain namespace Decentralized.Domain
{ {
public enum PayMode { Escrow, Advance } public enum PayMode { Escrow, Advance }
/// <summary>商家类型:全新矿卡经销商、二手矿卡贩子、模组高科技、军火、证件芯片、药物,大宗商家随机出现。</summary> /// <summary>商家类型:矿卡(全新 / 二手)、矿架能源、模组、军火、枪匠配件、证件芯片、药物、化学、酿酒、水培、养殖、矿工、水务、仓储、工具、奢侈品;大宗商家随机出现。</summary>
public enum MerchantType { GpuNew, GpuUsed, Mods, Arms, Docs, Pharma, Bulk } public enum MerchantType { GpuNew, GpuUsed, Rigs, Modules, Arms, GunMods, Docs, Pharma, Chem, Brewer, Farm, Husbandry, Miner, Water, Storage, Tools, Luxury, Bulk }
public enum OrderState { Pending, Ready, Done, Refunded, Lost } public enum OrderState { Pending, Ready, Done, Refunded, Lost }
/// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary> /// <summary>黑市上的卖家:成交数与好评率公开;是不是骗子是隐藏的。</summary>
@@ -104,33 +104,28 @@ namespace Decentralized.Domain
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);
private static readonly MerchantType[] Regular = { MerchantType.GpuNew, MerchantType.GpuUsed, MerchantType.Mods, MerchantType.Arms, MerchantType.Docs, MerchantType.Pharma }; /// <summary>每周必有的"矿工路线"商家类型(从中抽 2 家),其余按全部类型抽。</summary>
private static readonly MerchantType[] Core = { MerchantType.GpuNew, MerchantType.GpuUsed, MerchantType.Rigs, MerchantType.Modules };
private static readonly MerchantType[] Others = { MerchantType.Arms, MerchantType.GunMods, MerchantType.Docs, MerchantType.Pharma, MerchantType.Chem, MerchantType.Brewer,
MerchantType.Farm, MerchantType.Husbandry, MerchantType.Miner, MerchantType.Water, MerchantType.Storage, MerchantType.Tools, MerchantType.Luxury };
private static double F(Rng rng, double lo, double hi) => lo + rng.NextDouble() * (hi - lo); private static double F(Rng rng, double lo, double hi) => lo + rng.NextDouble() * (hi - lo);
/// <summary>商家类型对应的货(含货种、库存),按声望放开违禁等级。</summary> /// <summary>商家类型对应的货(含货种、库存),违禁品按声望放开等级。</summary>
private List<BmListing> MerchantGoods(int idx, BmSeller s, int day, int rep, Rng rng, Func<string, long> value) private List<BmListing> MerchantGoods(int idx, BmSeller s, int day, int rep, Rng rng, Func<string, long> value)
{ {
var list = new List<BmListing>(); var list = new List<BmListing>();
int n = s.Type == MerchantType.Bulk ? 2 : Tuning.BmMerchantListings; int n = s.Type == MerchantType.Bulk ? 2 : Tuning.BmMerchantListings;
var contra = new List<KeyValuePair<string, int>>(); bool gpuKind = s.Type == MerchantType.GpuNew || s.Type == MerchantType.GpuUsed;
switch (s.Type) var entries = s.Type == MerchantType.Bulk ? Merchants.Table.Values.SelectMany(x => x).GroupBy(x => x.Id).Select(g => g.First()).ToList()
{ : gpuKind ? new List<BmEntry>() : Merchants.Table[s.Type].ToList();
case MerchantType.Arms: contra = Pool.Where(x => ArmsIds.Contains(x.Key)).ToList(); break; entries = entries.Where(e => e.Kind != GoodKind.Contraband || e.Level <= MaxLevel(rep)).ToList();
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); var gpus = UsedDealers.Pool(day);
for (int i = 0; i < n; i++) 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); 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); double jitter = F(rng, 0.97, 1.03);
BmListing l = null; BmListing l;
bool gpuKind = s.Type == MerchantType.GpuNew || s.Type == MerchantType.GpuUsed;
if (s.Type == MerchantType.Bulk) gpuKind = false;
if (gpuKind) if (gpuKind)
{ {
bool fresh = s.Type == MerchantType.GpuNew; bool fresh = s.Type == MerchantType.GpuNew;
@@ -138,23 +133,14 @@ namespace Decentralized.Domain
if (fresh) c.Health = 100; 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)) }; 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 else
{ {
if (contra.Count == 0) continue; if (entries.Count == 0) break;
int k = rng.Range(0, contra.Count - 1); int k = rng.Range(0, entries.Count - 1);
var pick = contra[k]; contra.RemoveAt(k); var e = entries[k]; entries.RemoveAt(k);
long v = value(pick.Key); long v = value(e.Id);
if (v <= 0) continue; if (v <= 0 || v > Tuning.BmMaxUnitValue) { i--; 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 = new BmListing { Item = e.Id, Kind = e.Kind, Level = e.Level, Price = (int)Math.Max(1, Math.Round(v * s.Factor * jitter)) };
} }
l.Seller = idx; l.Qty = qty; l.Seller = idx; l.Qty = qty;
list.Add(l); list.Add(l);
@@ -162,13 +148,12 @@ namespace Decentralized.Domain
return list; 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> /// <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();
var types = Regular.OrderBy(_ => rng.NextDouble()).Take(Tuning.BmMerchantCount).ToList(); var types = Core.OrderBy(_ => rng.NextDouble()).Take(Tuning.BmCoreMerchants).ToList();
types.AddRange(Others.OrderBy(_ => rng.NextDouble()).Take(Tuning.BmMerchantCount - Tuning.BmCoreMerchants));
if (rng.Chance(Tuning.BmBulkChance)) types.Add(MerchantType.Bulk); if (rng.Chance(Tuning.BmBulkChance)) types.Add(MerchantType.Bulk);
foreach (var t in types) foreach (var t in types)
{ {
+4 -2
View File
@@ -106,10 +106,12 @@ 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, BmMaxListings = 10; public const int BmSaleHold = 3, BmRatingStart = 90, BmRatingFakeLoss = 10, BmMaxListings = 10;
/// <summary>商家货架:每周的商家数(从 6 种类型里抽)、每家的货种数、每种货的库存范围;大宗商家出现概率、库存范围、送货天数; /// <summary>商家货架:每周的商家数(其中 2 家从矿卡 / 矿架 / 模组 4 类里抽,其余从其他类型里抽)、每家的货种数、每种货的库存范围;大宗商家出现概率、库存范围、送货天数;
/// 各类商家的价格系数范围(订单价 = 基础价 × 系数);批量折扣档(件数、折扣)。</summary> /// 各类商家的价格系数范围(订单价 = 基础价 × 系数);批量折扣档(件数、折扣)。</summary>
public const int BmMerchantCount = 5, BmMerchantListings = 4, BmQtyMin = 3, BmQtyMax = 6, BmBulkQtyMin = 10, BmBulkQtyMax = 25, BmBulkDays = 6; public const int BmMerchantCount = 7, BmCoreMerchants = 2, BmMerchantListings = 3, BmQtyMin = 2, BmQtyMax = 5, 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 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;
/// <summary>商家货架上单件基础价的上限(过贵的货不上,免得一件把周转量撑歪)。</summary>
public const long BmMaxUnitValue = 4000;
public const int BmBulk1Count = 5, BmBulk2Count = 10; public const int BmBulk1Count = 5, BmBulk2Count = 10;
public const double BmBulk1Discount = 0.05, BmBulk2Discount = 0.10; public const double BmBulk1Discount = 0.05, BmBulk2Discount = 0.10;
/// <summary>出售量越大货款越容易带污点:近 7 天成交额达到参考量时,污点概率与比例各加最多 BmVolumeTaintMax。</summary> /// <summary>出售量越大货款越容易带污点:近 7 天成交额达到参考量时,污点概率与比例各加最多 BmVolumeTaintMax。</summary>
+2 -2
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.PharmaPool.Any(x => x.Key == id) || BlackMarket.TechPool.Contains(id) ? id : null; return Merchants.IsGood(id) ? id : null;
} }
public static System.Collections.Generic.List<GameItem> Sellable() => public static System.Collections.Generic.List<GameItem> Sellable() =>
@@ -71,7 +71,7 @@ namespace Decentralized
return Value(id); return Value(id);
} }
public static GoodKind KindOf(string id) => id.StartsWith("gpu:") ? GoodKind.Gpu : BlackMarket.TechPool.Contains(id) ? GoodKind.Tech : GoodKind.Contraband; public static GoodKind KindOf(string id) => id.StartsWith("gpu:") ? GoodKind.Gpu : Merchants.Info(id).Kind;
/// <summary>每周刷新:现货与订单目录一起。返回是否刷新了。</summary> /// <summary>每周刷新:现货与订单目录一起。返回是否刷新了。</summary>
public static bool Restock(SaveData d, int day, bool force = false) public static bool Restock(SaveData d, int day, bool force = false)
+4
View File
@@ -69,6 +69,10 @@ namespace Decentralized
BlackDelivery.Restock(d, day, true); BlackDelivery.Restock(d, day, true);
vals.Add(d.Black.CatalogValue); vals.Add(d.Black.CatalogValue);
} }
var missing = Merchants.Table.SelectMany(kv => kv.Value.Select(e => (kv.Key, e.Id))).Where(x => BlackDelivery.Value(x.Item2) <= 0).Select(x => x.Item1 + ":" + x.Item2).ToList();
var costs = Merchants.Table.SelectMany(kv => kv.Value.Select(e => (kv.Key, e.Id))).Select(x => BlackDelivery.Value(x.Item2)).Where(v => v > 0).OrderBy(v => v).ToList();
Log.Info("黑市抽样:基础价 >2500 的货:" + string.Join(", ", Merchants.Table.SelectMany(kv => kv.Value.Select(e => (kv.Key, e.Id))).Select(x => (id: x.Item2, v: BlackDelivery.Value(x.Item2))).Where(x => x.v > 2500).Select(x => x.id + "=" + x.v)));
Log.Info($"黑市抽样:无法造出或价格为 0 的货 {missing.Count} 个:{string.Join(", ", missing)};基础价中位 {costs[costs.Count / 2]},最高 {costs.Last()}");
Log.Info($"黑市抽样:60 周货架总值 平均 {vals.Average():0},最小 {vals.Min():0},最大 {vals.Max():0}"); 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("黑市事件(测试)", () =>
+13 -1
View File
@@ -310,7 +310,6 @@ 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.GpuNew,New GPUs
merchant.GpuUsed,Used GPUs merchant.GpuUsed,Used GPUs
merchant.Mods,Mods & tech
merchant.Arms,Arms merchant.Arms,Arms
merchant.Docs,Keycards & chips merchant.Docs,Keycards & chips
merchant.Pharma,Pharma merchant.Pharma,Pharma
@@ -320,3 +319,16 @@ black.sell.all,List all
black.sell.listedall,"Listed {0} items." black.sell.listedall,"Listed {0} items."
black.sell.slots,"Listed {0}/{1}" black.sell.slots,"Listed {0}/{1}"
black.sell.full,"At most {0} listings at a time." black.sell.full,"At most {0} listings at a time."
merchant.Rigs,Rigs & power
merchant.Modules,Modules
merchant.GunMods,Gunsmith parts
merchant.Chem,Chemicals
merchant.Brewer,Brewing
merchant.Farm,Hydroponics
merchant.Husbandry,Husbandry
merchant.Miner,Mining supplies
merchant.Water,Water
merchant.Storage,Storage
merchant.Tools,Tools
merchant.Luxury,Luxury
black.kind.Goods,Goods
1 # Probably Decentralized localization table: English (en)
310 black.kind.gpunew
311 merchant.GpuNew
312 merchant.GpuUsed
merchant.Mods
313 merchant.Arms
314 merchant.Docs
315 merchant.Pharma
319 black.sell.listedall
320 black.sell.slots
321 black.sell.full
322 merchant.Rigs
323 merchant.Modules
324 merchant.GunMods
325 merchant.Chem
326 merchant.Brewer
327 merchant.Farm
328 merchant.Husbandry
329 merchant.Miner
330 merchant.Water
331 merchant.Storage
332 merchant.Tools
333 merchant.Luxury
334 black.kind.Goods
+13 -1
View File
@@ -309,7 +309,6 @@ black.sale.Delisted.fake,买家看穿假卡退货
black.kind.gpunew,显卡 全新 black.kind.gpunew,显卡 全新
merchant.GpuNew,全新矿卡 merchant.GpuNew,全新矿卡
merchant.GpuUsed,二手矿卡 merchant.GpuUsed,二手矿卡
merchant.Mods,模组高科技
merchant.Arms,军火 merchant.Arms,军火
merchant.Docs,证件芯片 merchant.Docs,证件芯片
merchant.Pharma,药物 merchant.Pharma,药物
@@ -319,3 +318,16 @@ black.sell.all,全部上架
black.sell.listedall,"已上架 {0} 件。" black.sell.listedall,"已上架 {0} 件。"
black.sell.slots,"上架 {0}/{1}" black.sell.slots,"上架 {0}/{1}"
black.sell.full,"最多同时上架 {0} 件。" black.sell.full,"最多同时上架 {0} 件。"
merchant.Rigs,矿架与能源
merchant.Modules,模组
merchant.GunMods,枪匠配件
merchant.Chem,化学品
merchant.Brewer,酿酒
merchant.Farm,水培农场
merchant.Husbandry,养殖
merchant.Miner,矿工
merchant.Water,水务
merchant.Storage,仓储
merchant.Tools,工具
merchant.Luxury,奢侈品
black.kind.Goods,商品
1 # Probably Decentralized 本地化表:简体中文(zh)
309 black.kind.gpunew
310 merchant.GpuNew
311 merchant.GpuUsed
merchant.Mods
312 merchant.Arms
313 merchant.Docs
314 merchant.Pharma
318 black.sell.listedall
319 black.sell.slots
320 black.sell.full
321 merchant.Rigs
322 merchant.Modules
323 merchant.GunMods
324 merchant.Chem
325 merchant.Brewer
326 merchant.Farm
327 merchant.Husbandry
328 merchant.Miner
329 merchant.Water
330 merchant.Storage
331 merchant.Tools
332 merchant.Luxury
333 black.kind.Goods
+1 -1
View File
@@ -370,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.Concat(BlackMarket.PharmaPool).First(x => x.Key == id).Value : 3 }; var g = new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Gpu ? 2 : Merchants.Info(id).Level };
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 };
+3 -3
View File
@@ -737,10 +737,10 @@ internal static class Program
} }
double avg = sum / n; double avg = sum / n;
Check(avg > 8000 && avg < 40000, $"每周货架总值(游戏里实测平均约 1.4 万,这里用统一基础价 250):平均 {avg:0}"); 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};七种类型都出现过"); Check(bulk > n * 0.15 && bulk < n * 0.35 && types.Count >= 18, $"大宗商家约 {Tuning.BmBulkChance:P0} 的周出现:{bulk}/{n};七种类型都出现过");
// 批量下单:折扣、库存扣减 // 批量下单:折扣、库存扣减
Func<string, double> price = a => 1; 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 m = new BlackMarket(); m.Sellers.Add(new BmSeller { Name = "a", Trades = 100, Type = MerchantType.Modules, Factor = 0.8 });
var l = new BmListing { Seller = 0, Item = "x", Price = 100, Qty = 8 }; var l = new BmListing { Seller = 0, Item = "x", Price = 100, Qty = 8 };
m.Catalog.Add(l); m.Catalog.Add(l);
var w = new Wallet(); w.Add(Assets.CRT, 5000); var w = new Wallet(); w.Add(Assets.CRT, 5000);
@@ -748,7 +748,7 @@ internal static class Program
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(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), "超过库存不能订;订完下架"); 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 }; var bs = new BmSeller { Type = MerchantType.Bulk };
Check(bs.Days == Tuning.BmBulkDays && new BmSeller { Type = MerchantType.Mods }.Days == Tuning.BmDoorDays, "大宗商家送货更久"); Check(bs.Days == Tuning.BmBulkDays && new BmSeller { Type = MerchantType.Modules }.Days == Tuning.BmDoorDays, "大宗商家送货更久");
// 出售量推高污点 // 出售量推高污点
var b2 = new BlackMarket(); var b2 = new BlackMarket();
double v0 = b2.VolumeTaint(10); double v0 = b2.VolumeTaint(10);