using System;
using System.Collections.Generic;
using System.Linq;
namespace Decentralized.Domain
{
/// 货的种类:违禁品、高科技物品、显卡。
public enum GoodKind { Contraband, Tech, Gpu, Goods }
/// 黑市货架上的一种货。显卡的 Item 是 "gpu:型号",Model 是标称型号,FakeOf 非空表示背后是别的芯片(买家看不到)。
public class BmGood
{
public string Item;
public GoodKind Kind;
public int Level;
public int Price;
public string Model, FakeOf;
public double Health;
}
/// 现货柜台上的一件货:剩余件数、含溢价的单价(信用点)。
public sealed class BmOffer : BmGood
{
public int Qty;
}
///
/// Web3 黑市:黑市派系在链上的实体。货架上主要是违禁品(档次随黑市声望放开),偶尔有高科技物品和显卡(加价),
/// 现货只收币,脏币也收,付出去的币连同按比例的污点一起留在卖家那边;Ponero 兑换处把任意币兑成无法追踪的 Ponero(这部分价值的污点被清除),有每日额度和手续费。
/// 镇压期间整个平台下线。
///
public sealed partial class BlackMarket
{
public List Offers = new List();
public int RefreshedDay = -999;
/// Ponero 兑换处当天已兑换的价值(信用点)及其日期。
public double DeskToday;
public int DeskDay = -1;
/// 违禁品池:游戏物品 id 与违禁等级(1 低、2 中、3 高)。都是能转手的货(枪械、钥匙卡、密码解序器芯片、爆炸物);弹药、毒品、药品玩家用不上,不上架。
public static readonly KeyValuePair[] Pool =
{
P("handmade_pistol", 1), P("printed_gun", 1), P("stun_baton", 1),
P("eng_keycard", 1), P("med_keycard", 1), P("ser_keycard", 1), P("sup_keycard", 1), P("crypto_module_ser", 1), P("crypto_module_sup", 1),
P("revolver", 2), P("heavy_handmade_pistol", 2), P("flashbang_grenade", 2), P("smoke_grenade", 2),
P("sci_keycard", 2), P("sec_keycard", 2), P("crypto_module_eng", 2), P("crypto_module_med", 2),
P("shotgun", 3), P("smg", 3), P("c4", 3), P("cmd_keycard", 3), P("crypto_module_sec", 3),
};
/// 高科技物品池(不是违禁品,黑市上要加价):矿机能用上的系统模组、能量电池、能量阵列,和矿架本身。
public static readonly string[] TechPool =
{
"system_module_overclock", "system_module_performance", "system_module_quality", "system_module_eco", "system_module_efficiancy",
"system_module_fineness", "system_module_streamlining", "energy_credit_ext", "energy_credit_hyp", "energy_credit_breeder",
"powerblock", "module_extractor_advanced", "pd_rig",
};
private static KeyValuePair P(string id, int level) => new KeyValuePair(id, level);
public static bool Open(int rep) => rep >= Tuning.BmOpenRep;
public static int MaxLevel(int rep) => rep >= Tuning.BmHighRep ? 3 : rep >= Tuning.BmMidRep ? 2 : rep >= Tuning.BmOpenRep ? 1 : 0;
public static bool DeskOpen(int rep) => rep >= Tuning.PoneroOpenRep;
public static int StockCount(int rep) => Tuning.BmStockBase + (int)Math.Max(0, rep / Tuning.BmStockPerRep);
/// 货的加价:违禁品 0,高科技、显卡各有额外加价(在现货溢价 / 订单折扣之外)。
public static double Extra(GoodKind k) => k == GoodKind.Tech ? Tuning.BmTechMarkup : k == GoodKind.Gpu ? Tuning.BmGpuMarkup : 0;
/// 现货单价(信用点):基础价 ×(1 + 溢价 + 货的加价)。
public static int PriceOf(long baseValue, GoodKind k = GoodKind.Contraband) => (int)Math.Max(1, Math.Round(baseValue * (1 + Tuning.BmPremium + Extra(k))));
/// 抽 count 种货:高科技(中档声望起)和显卡按概率换掉违禁品。value 给出游戏物品的基础价;显卡的基础价按标称型号与成色算。
private List Roll(int count, int day, int rep, Rng rng, Func value, Func price)
{
var goods = new List();
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)
{
bool fresh = rng.Chance(Tuning.BmGpuNew);
var c = UsedDealers.Card(gpus, fresh ? 0 : Tuning.BmGpuFake, 0, rng);
if (fresh) c.Health = 100;
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 && 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;
}
/// 最近一个该上新的夜晚(不晚于 day):夜间结算时 day 还是当天,第二天早上才看到货,所以周一的货在周日夜里上。
public static int LastRefreshNight(int day)
{
for (int d = day; d > day - 7; d--)
if (Tuning.BmRefreshWeekdays.Contains((d + 1) % 7)) return d;
return day;
}
/// 到了上新日(或首次开放时,错过的上新日在下一晚补上):按声望开放的档次抽 StockCount 种货,每种 1–3 件(高档与显卡、高科技 1 件)。
public bool Refresh(int day, int rep, Rng rng, Func value)
{
if (!Open(rep) || RefreshedDay >= LastRefreshNight(day)) return false;
RefreshedDay = day;
Offers.Clear();
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, Tuning.BmSpotQtyMax) });
return true;
}
/// 买一件:付 asset 币。付出的币按它的脏比例带走脏枚数,成本按价值比例留在卖家那边。成功返回 true,库存减一。
public bool Buy(BmOffer o, Wallet w, string asset, Func price)
{
if (o == null || o.Qty <= 0 || asset == Assets.CRC) return false;
double p = price(asset);
if (p <= 0) return false;
double qty = o.Price / p;
if (w.Avail(asset) + 1e-9 < qty) return false;
double total = w.Value(price);
double share = total > 0 ? Math.Min(1, o.Price / total) : 0;
w.Basis -= w.Basis * share;
w.Add(asset, -qty);
o.Qty--;
if (o.Qty <= 0) Offers.Remove(o);
return true;
}
/// 夜间 NPC 买家:现货和商家货架上每件货各按 被买走,卖完下架。返回卖出的件数。
public int NpcBuys(Rng rng)
{
int total = 0;
foreach (var o in Offers) { int n = NpcSold(o.Qty, rng); o.Qty -= n; total += n; }
foreach (var l in Catalog) { int n = NpcSold(l.Qty, rng); l.Qty -= n; total += n; }
Offers.RemoveAll(o => o.Qty <= 0);
Catalog.RemoveAll(l => l.Qty <= 0);
return total;
}
private static int NpcSold(int qty, Rng rng)
{
int n = 0;
for (int i = 0; i < qty; i++) if (rng.Chance(Tuning.BmNpcBuyChance)) n++;
return n;
}
/// Ponero 对信用点的价格:跟随 PSC。
public static double PoneroPrice(double pscPrice) => pscPrice * Tuning.PoneroPscRatio;
public double DeskLeft(int day) => Math.Max(0, Tuning.PoneroDailyCap - (DeskDay == day ? DeskToday : 0));
/// 兑入 Ponero:from 币 amount 个,扣手续费后按 Ponero 价到账;兑掉的币连同其中的脏枚数一起消失,换来的 Ponero 是干净的。返回到账数量,额度或余额不足返回 -1。
public double ToPonero(Wallet w, string from, double amount, int day, Func price)
{
double value = amount * price(from);
if (from == Assets.Ponero || amount <= 0 || value <= 0 || w.Avail(from) + 1e-9 < amount || value > DeskLeft(day) + 1e-9) return -1;
w.Add(from, -amount);
double got = value * (1 - Tuning.PoneroFee) / price(Assets.Ponero);
w.Add(Assets.Ponero, got);
w.RecordBuy(Assets.Ponero, got, value);
Spend(day, value);
return got;
}
/// Ponero 兑出成 to 币:手续费同上,计入当天额度。返回到账数量,不足返回 -1。
public double FromPonero(Wallet w, string to, double amount, int day, Func price)
{
double value = amount * price(Assets.Ponero);
if (to == Assets.Ponero || amount <= 0 || value <= 0 || w.Avail(Assets.Ponero) + 1e-9 < amount || value > DeskLeft(day) + 1e-9 || price(to) <= 0) return -1;
w.Add(Assets.Ponero, -amount);
double got = value * (1 - Tuning.PoneroFee) / price(to);
w.Add(to, got);
w.RecordBuy(to, got, value);
Spend(day, value);
return got;
}
private void Spend(int day, double value)
{
if (DeskDay != day) { DeskDay = day; DeskToday = 0; }
DeskToday += value;
}
}
}