Stage 4: Web3 black market spot counter and Ponero desk

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-03 19:35:48 +08:00
1 parent 74f0dac57e
commit f43cd487e5
11 files changed
+366 -1

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Decentralized.Domain
{
/// <summary>黑市现货柜台上的一件货:游戏物品 id、剩余件数、含溢价的单价(信用点)。</summary>
public sealed class BmOffer
{
public string Item;
public int Qty;
public int Price;
public int Level;
}
/// <summary>
/// Web3 黑市:黑市派系在链上的实体。现货柜台每周刷新一批违禁品(档次随黑市声望放开),只收币,脏币也收,
/// 付出去的币连同按比例的污点一起留在卖家那边;Ponero 兑换处把任意币兑成无法追踪的 Ponero(污点留在原地),有每日额度和手续费。
/// 镇压期间整个平台下线。
/// </summary>
public sealed class BlackMarket
{
public List<BmOffer> Offers = new List<BmOffer>();
public int RefreshedDay = -999;
/// <summary>Ponero 兑换处当天已兑换的价值(信用点)及其日期。</summary>
public double DeskToday;
public int DeskDay = -1;
/// <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),
P("dream_cap_seed", 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("small_pistol_ammo_p", 2), P("heavy_pistol_ammo_p", 2),
P("flashbang_grenade", 2), P("smoke_grenade", 2), P("dream_dust", 2), P("restricted_chemical", 2),
P("sci_keycard", 2), P("sec_keycard", 2), P("crypto_module_eng", 2), P("crypto_module_med", 2), P("unlicensed_phagimycin_pill", 2),
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),
};
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;
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);
/// <summary>溢价后的单价(信用点)。</summary>
public static int PriceOf(long baseValue) => (int)Math.Max(1, Math.Round(baseValue * (1 + Tuning.BmPremium)));
/// <summary>每周刷新一次(或首次开放时):按声望开放的档次抽 StockCount 种货,每种 1–3 件。value 给出物品的游戏基础价。</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) });
}
return true;
}
/// <summary>买一件:付 asset 币。钱包里按价值比例带走的污点和成本留在卖家那边。成功返回 true,库存减一。</summary>
public bool Buy(BmOffer o, Wallet w, string asset, Func<string, double> 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.Taint -= w.Taint * share;
w.Basis -= w.Basis * share;
w.Add(asset, -qty);
o.Qty--;
if (o.Qty <= 0) Offers.Remove(o);
return true;
}
/// <summary>Ponero 对信用点的价格:跟随 PSC。</summary>
public static double PoneroPrice(double pscPrice) => pscPrice * Tuning.PoneroPscRatio;
public double DeskLeft(int day) => Math.Max(0, Tuning.PoneroDailyCap - (DeskDay == day ? DeskToday : 0));
/// <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);
if (from == Assets.Ponero || amount <= 0 || value <= 0 || w.Avail(from) + 1e-9 < amount || value > DeskLeft(day) + 1e-9) return -1;
double total = w.Value(price);
double share = total > 0 ? Math.Min(1, value / total) : 0;
w.Taint -= w.Taint * share;
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;
}
/// <summary>Ponero 兑出成 to 币:手续费同上,计入当天额度。返回到账数量,不足返回 -1。</summary>
public double FromPonero(Wallet w, string to, double amount, int day, Func<string, double> 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;
}
}
}