Black market: useful goods only, player selling with price-dependent speed and taint
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b57f3eedc
commit
502366e7c4
10 files changed
+363
-21
No files matched your search
@@ -20,6 +20,11 @@
|
|||||||
- 挖矿报告并进 Web3 页的"挖矿"子页,终端顶栏不再有"报告"。
|
- 挖矿报告并进 Web3 页的"挖矿"子页,终端顶栏不再有"报告"。
|
||||||
- 游戏内验证:受控交付的对话与两个选项显示,拒签后快递员离开、下一位顾客进来。没在游戏里验证:签收的罪证、蜜罐与落网的夜间追查(只有单元测试)。
|
- 游戏内验证:受控交付的对话与两个选项显示,拒签后快递员离开、下一位顾客进来。没在游戏里验证:签收的罪证、蜜罐与落网的夜间追查(只有单元测试)。
|
||||||
|
|
||||||
|
- 货架重做:违禁品池只留能转手的货(枪械、钥匙卡、密码解序器芯片、爆炸物),弹药、毒品、药品、化学品、种子全部移除;高科技物品池换成矿机能用的东西(7 种系统模组、能量电池、能量阵列、高级模组提取器、矿架);显卡与高科技各占 30%,高科技不再需要中档声望。
|
||||||
|
- 玩家出售:黑市页新增"出售"子页。储藏区里的违禁品、高科技物品和显卡做成货架,选标价倍率 0.6–1.8(相对参考价;显卡按标称型号与成色)上架,收 5% 上架费。每晚按期望天数成交:≤0.9 倍 1 天,1.0 倍 2 天,1.8 倍 14 天,之间插值。成交扣 8% 抽成,货款托管 3 天后以 CRT 进钱包。货款带污点的概率和比例随倍率从 20%/20% 涨到 90%/80%。
|
||||||
|
- 出售的风险:假卡有 50% 被买家看穿退货,好评 −10(好评影响成交速度,每笔成交 +1);平台跑路时上架的货和未放款的货款都没了;蜜罐期间成交的货款和近期买家落网会被追查记罪证;镇压期间平台不成交。"我的订单与情报"页多出"我的上架",可以撤下。
|
||||||
|
- 游戏内验证:货架显示显卡、模组、能量电池;买一张卡后出售页列出储藏区里的货,上架(标价 400,上架费 20),过夜成交(货款 368),3 天后到账(CRT 2804 → 3172,这次随机没带污点)。没有在游戏里验证:假卡退货、跑路时的出售损失(只有单元测试)。
|
||||||
|
|
||||||
### 第三阶段:数值调优(进行中)
|
### 第三阶段:数值调优(进行中)
|
||||||
|
|
||||||
- 混币器每轮手续费 6% → 4%;用币付款的顾客概率提到上层 30%、黑市 50%、革命派 50%、下层 25%,并多付 12%(溢价只加在币数上,污点仍按货值算)。
|
- 混币器每轮手续费 6% → 4%;用币付款的顾客概率提到上层 30%、黑市 50%、革命派 50%、下层 25%,并多付 12%(溢价只加在币数上,污点仍按货值算)。
|
||||||
|
|||||||
@@ -37,23 +37,22 @@ namespace Decentralized.Domain
|
|||||||
public double DeskToday;
|
public double DeskToday;
|
||||||
public int DeskDay = -1;
|
public int DeskDay = -1;
|
||||||
|
|
||||||
/// <summary>违禁品池:游戏物品 id 与违禁等级(1 低、2 中、3 高)。全部来自游戏已有的违禁品。</summary>
|
/// <summary>违禁品池:游戏物品 id 与违禁等级(1 低、2 中、3 高)。都是能转手的货(枪械、钥匙卡、密码解序器芯片、爆炸物);弹药、毒品、药品玩家用不上,不上架。</summary>
|
||||||
public static readonly KeyValuePair<string, int>[] Pool =
|
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("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("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("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("revolver", 2), P("heavy_handmade_pistol", 2), P("small_pistol_ammo_p", 2), P("heavy_pistol_ammo_p", 2),
|
P("sci_keycard", 2), P("sec_keycard", 2), P("crypto_module_eng", 2), P("crypto_module_med", 2),
|
||||||
P("flashbang_grenade", 2), P("smoke_grenade", 2), P("dream_dust", 2), P("restricted_chemical", 2),
|
P("shotgun", 3), P("smg", 3), P("c4", 3), P("cmd_keycard", 3), P("crypto_module_sec", 3),
|
||||||
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),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>高科技物品池(不是违禁品,黑市上要加价)。</summary>
|
/// <summary>高科技物品池(不是违禁品,黑市上要加价):矿机能用上的系统模组、能量电池、能量阵列,和矿架本身。</summary>
|
||||||
public static readonly string[] TechPool =
|
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", "system_module_eco", "system_module_efficiancy",
|
||||||
"system_module_overclock", "system_module_performance", "system_module_quality", "module_extractor_advanced", "powerblock",
|
"system_module_fineness", "system_module_streamlining", "energy_credit_ext", "energy_credit_hyp", "energy_credit_breeder",
|
||||||
|
"powerblock", "module_extractor_advanced", "pd_rig",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static KeyValuePair<string, int> P(string id, int level) => new KeyValuePair<string, int>(id, level);
|
private static KeyValuePair<string, int> P(string id, int level) => new KeyValuePair<string, int>(id, level);
|
||||||
@@ -84,7 +83,7 @@ namespace Decentralized.Domain
|
|||||||
var c = UsedDealers.Card(gpus, Tuning.BmGpuFake, 0, rng);
|
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) });
|
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)
|
else if (r < Tuning.BmGpuChance + Tuning.BmTechChance && tech.Count > 0)
|
||||||
{
|
{
|
||||||
int k = rng.Range(0, tech.Count - 1);
|
int k = rng.Range(0, tech.Count - 1);
|
||||||
string id = tech[k];
|
string id = tech[k];
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ namespace Decentralized.Domain
|
|||||||
{
|
{
|
||||||
public string Kind; // ready / scam_refund / scam_loss / rug / intercept / reopen
|
public string Kind; // ready / scam_refund / scam_loss / rug / intercept / reopen
|
||||||
public BmOrder Order;
|
public BmOrder Order;
|
||||||
|
public BmSale Sale;
|
||||||
public double Amount;
|
public double Amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ namespace Decentralized.Domain
|
|||||||
public bool Closed(int day) => day <= ClosedUntil;
|
public bool Closed(int day) => day <= ClosedUntil;
|
||||||
|
|
||||||
/// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary>
|
/// <summary>托管中和已提前放款但尚未交付的资金(信用点),平台跑路的概率随它增加。</summary>
|
||||||
public double Held => Orders.Where(o => o.State == OrderState.Pending).Sum(o => (double)o.Price);
|
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)));
|
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);
|
public static int EscrowFee(int price) => (int)Math.Ceiling(price * Tuning.BmEscrowFee);
|
||||||
@@ -151,18 +152,23 @@ namespace Decentralized.Domain
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>每晚推进订单:平台跑路、到期的订单(卖家跑路、被截获、死投被挖走)、转入可取状态。镇压期间到期顺延一天。</summary>
|
/// <summary>每晚推进订单:平台跑路、到期的订单(卖家跑路、被截获、死投被挖走)、转入可取状态。镇压期间到期顺延一天。</summary>
|
||||||
public List<BmEvent> Night(int day, bool crackdown, Wallet w, Rng rng, int secRep = 0)
|
public List<BmEvent> Night(int day, bool crackdown, Wallet w, Rng rng, int secRep = 0, Func<string, double> price = null)
|
||||||
{
|
{
|
||||||
var ev = new List<BmEvent>();
|
var ev = new List<BmEvent>();
|
||||||
if (!Honeypot(day) && rng.Chance(HoneypotChance(secRep))) { HoneypotUntil = day + Tuning.BmHoneypotDays; if (!Forum.Contains("easy")) Forum.Add("easy"); }
|
if (!Honeypot(day) && rng.Chance(HoneypotChance(secRep))) { HoneypotUntil = day + Tuning.BmHoneypotDays; if (!Forum.Contains("easy")) Forum.Add("easy"); }
|
||||||
if (ClosedUntil == day) ev.Add(new BmEvent { Kind = "reopen" });
|
if (ClosedUntil == day) ev.Add(new BmEvent { Kind = "reopen" });
|
||||||
if (!Closed(day) && Orders.Any(o => o.State == OrderState.Pending) && rng.Chance(RugChance))
|
if (!Closed(day) && (Orders.Any(o => o.State == OrderState.Pending) || Sales.Any(x => x.State == SaleState.Listed || x.State == SaleState.Sold)) && rng.Chance(RugChance))
|
||||||
{
|
{
|
||||||
foreach (var o in Orders.Where(o => o.State == OrderState.Pending))
|
foreach (var o in Orders.Where(o => o.State == OrderState.Pending))
|
||||||
{
|
{
|
||||||
o.State = OrderState.Lost; o.Why = "rug";
|
o.State = OrderState.Lost; o.Why = "rug";
|
||||||
ev.Add(new BmEvent { Kind = "rug", Order = o, Amount = o.Price });
|
ev.Add(new BmEvent { Kind = "rug", Order = o, Amount = o.Price });
|
||||||
}
|
}
|
||||||
|
foreach (var sl in Sales.Where(x => x.State == SaleState.Listed || x.State == SaleState.Sold))
|
||||||
|
{
|
||||||
|
ev.Add(new BmEvent { Kind = "sale_rug", Sale = sl, Amount = sl.State == SaleState.Sold ? sl.Proceeds : sl.ListPrice });
|
||||||
|
sl.State = SaleState.Lost; sl.Why = "rug";
|
||||||
|
}
|
||||||
ClosedUntil = day + Tuning.BmRugClosedDays;
|
ClosedUntil = day + Tuning.BmRugClosedDays;
|
||||||
Offers.Clear(); Catalog.Clear(); Sellers.Clear();
|
Offers.Clear(); Catalog.Clear(); Sellers.Clear();
|
||||||
return ev;
|
return ev;
|
||||||
@@ -186,6 +192,7 @@ namespace Decentralized.Domain
|
|||||||
o.State = OrderState.Ready;
|
o.State = OrderState.Ready;
|
||||||
ev.Add(new BmEvent { Kind = "ready", Order = o });
|
ev.Add(new BmEvent { Kind = "ready", Order = o });
|
||||||
}
|
}
|
||||||
|
if (price != null) SalesNight(day, crackdown, w, price, rng, ev);
|
||||||
// 卖家落网:近期成交过的卖家各有小概率被抓,他们留存的订单记录之后会连累买家
|
// 卖家落网:近期成交过的卖家各有小概率被抓,他们留存的订单记录之后会连累买家
|
||||||
foreach (var name in Orders.Where(x => x.State == OrderState.Done && day - x.DueDay <= 14).Select(x => x.Seller).Distinct().ToList())
|
foreach (var name in Orders.Where(x => x.State == OrderState.Done && day - x.DueDay <= 14).Select(x => x.Seller).Distinct().ToList())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Decentralized.Domain
|
||||||
|
{
|
||||||
|
public enum SaleState { Listed, Sold, Paid, Lost, Delisted }
|
||||||
|
|
||||||
|
/// <summary>玩家在黑市上架的一件货。货交给平台(从储藏区拿走),成交后货款托管几天再到钱包,可能带污点。</summary>
|
||||||
|
public sealed class BmSale
|
||||||
|
{
|
||||||
|
public string Item;
|
||||||
|
public string Model, FakeOf;
|
||||||
|
public double Health;
|
||||||
|
/// <summary>标价倍率与标价(信用点)。</summary>
|
||||||
|
public double Mult;
|
||||||
|
public int ListPrice;
|
||||||
|
public int ListedDay, PayDay;
|
||||||
|
public SaleState State = SaleState.Listed;
|
||||||
|
/// <summary>成交后扣掉抽成的货款(信用点)与其中带的污点。</summary>
|
||||||
|
public int Proceeds;
|
||||||
|
public double Taint;
|
||||||
|
/// <summary>终态原因:fake(买家看穿假卡退货)/ rug / delist。</summary>
|
||||||
|
public string Why;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed partial class BlackMarket
|
||||||
|
{
|
||||||
|
public List<BmSale> Sales = new List<BmSale>();
|
||||||
|
/// <summary>玩家作为卖家的好评(%):影响成交速度;卖假卡被看穿会下降,每笔成交回升一点。</summary>
|
||||||
|
public int Rating = Tuning.BmRatingStart;
|
||||||
|
|
||||||
|
/// <summary>期望成交天数:倍率 ≤0.9 一天,1.0 两天,1.8 十四天,之间插值(0.9–1.0 线性,1.0 以上指数)。</summary>
|
||||||
|
public static double ExpectedDays(double m)
|
||||||
|
{
|
||||||
|
if (m <= 0.9) return Tuning.BmSellDaysFast;
|
||||||
|
if (m < 1.0) return Tuning.BmSellDaysFast + (m - 0.9) / 0.1 * (Tuning.BmSellDays1 - Tuning.BmSellDaysFast);
|
||||||
|
return Tuning.BmSellDays1 * Math.Pow(Tuning.BmSellDaysMax / Tuning.BmSellDays1, (m - 1.0) / (Tuning.BmSellMax - 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double T(double m) => Math.Max(0, Math.Min(1, (m - Tuning.BmSellMin) / (Tuning.BmSellMax - Tuning.BmSellMin)));
|
||||||
|
/// <summary>货款带污点的概率与(带的时候)污点占货款的比例:都随标价倍率上升。</summary>
|
||||||
|
public static double TaintChance(double m) => Tuning.BmSaleTaintChanceMin + T(m) * (Tuning.BmSaleTaintChanceMax - Tuning.BmSaleTaintChanceMin);
|
||||||
|
public static double TaintRatio(double m) => Tuning.BmSaleTaintRatioMin + T(m) * (Tuning.BmSaleTaintRatioMax - Tuning.BmSaleTaintRatioMin);
|
||||||
|
|
||||||
|
/// <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));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// <summary>上架:交付一件货,收上架费(付给 feeAsset 币,污点与成本按比例带走)。倍率超出范围、平台关闭或余额不足返回 null。</summary>
|
||||||
|
public BmSale List(string item, string model, string fakeOf, double health, long refValue, double m, Wallet w, string feeAsset, int day, Func<string, double> price)
|
||||||
|
{
|
||||||
|
if (m < Tuning.BmSellMin - 1e-9 || m > Tuning.BmSellMax + 1e-9 || refValue <= 0 || Closed(day) || feeAsset == Assets.CRC) return null;
|
||||||
|
int listPrice = ListPriceOf(refValue, m);
|
||||||
|
double p = price(feeAsset);
|
||||||
|
if (p <= 0) return null;
|
||||||
|
double qty = ListFee(listPrice) / p;
|
||||||
|
if (w.Avail(feeAsset) + 1e-9 < qty) return null;
|
||||||
|
double total = w.Value(price);
|
||||||
|
double share = total > 0 ? Math.Min(1, ListFee(listPrice) / total) : 0;
|
||||||
|
w.Taint -= w.Taint * share; w.Basis -= w.Basis * share;
|
||||||
|
w.Add(feeAsset, -qty);
|
||||||
|
var s = new BmSale { Item = item, Model = model, FakeOf = fakeOf, Health = health, Mult = m, ListPrice = listPrice, ListedDay = day };
|
||||||
|
Sales.Add(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delist(BmSale s) { s.State = SaleState.Delisted; s.Why = "delist"; }
|
||||||
|
|
||||||
|
/// <summary>未到账的货款(信用点):跑路时一并归零,也推高跑路概率。</summary>
|
||||||
|
public double HeldSales => Sales.Where(x => x.State == SaleState.Sold).Sum(x => (double)x.Proceeds);
|
||||||
|
|
||||||
|
/// <summary>每晚推进出售:上架的货按概率成交(假卡可能被看穿退货),成交的货款托管到期后进钱包。镇压期间平台下线,不推进。</summary>
|
||||||
|
private void SalesNight(int day, bool crackdown, Wallet w, Func<string, double> price, Rng rng, List<BmEvent> ev)
|
||||||
|
{
|
||||||
|
if (crackdown) return;
|
||||||
|
foreach (var s in Sales.Where(x => x.State == SaleState.Listed && x.ListedDay < day).ToList())
|
||||||
|
{
|
||||||
|
if (!rng.Chance(SaleChance(s.Mult))) continue;
|
||||||
|
if (s.FakeOf != null && rng.Chance(Tuning.BmFakeDetect))
|
||||||
|
{
|
||||||
|
s.State = SaleState.Delisted; s.Why = "fake";
|
||||||
|
Rating = Math.Max(0, Rating - Tuning.BmRatingFakeLoss);
|
||||||
|
ev.Add(new BmEvent { Kind = "sale_fake", Sale = s });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
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 });
|
||||||
|
ev.Add(new BmEvent { Kind = "sale_sold", Sale = s, Amount = s.Proceeds });
|
||||||
|
}
|
||||||
|
foreach (var s in Sales.Where(x => x.State == SaleState.Sold && day >= x.PayDay).ToList())
|
||||||
|
{
|
||||||
|
double p = price(Assets.CRT);
|
||||||
|
if (p <= 0) continue;
|
||||||
|
w.Add(Assets.CRT, s.Proceeds / p);
|
||||||
|
w.Taint += s.Taint;
|
||||||
|
w.Basis += s.Proceeds;
|
||||||
|
s.State = SaleState.Paid;
|
||||||
|
ev.Add(new BmEvent { Kind = "sale_paid", Sale = s, Amount = s.Proceeds });
|
||||||
|
}
|
||||||
|
// 买家落网:近期到账的出售记录会连累卖家(玩家)
|
||||||
|
var recent = Sales.Where(x => x.State == SaleState.Paid && day - x.PayDay <= 14).ToList();
|
||||||
|
if (recent.Count > 0 && rng.Chance(Tuning.BmSellerBust))
|
||||||
|
{
|
||||||
|
foreach (var s in recent) Traps.Add(new BmTrap { Day = day + Tuning.BmTrapDelay, Amount = s.Proceeds, Why = "seller", Item = s.Item });
|
||||||
|
Forum.Add("busted_buyer");
|
||||||
|
}
|
||||||
|
Sales.RemoveAll(x => (x.State == SaleState.Paid || x.State == SaleState.Delisted || x.State == SaleState.Lost) && day - Math.Max(x.ListedDay, x.PayDay) > 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,13 @@ namespace Decentralized.Domain
|
|||||||
public const double BmHoneypotBase = 0.01, BmHoneypotPerSecRep = 0.0004, BmHoneypotMax = 0.06, BmControlled = 0.35, BmSellerBust = 0.008;
|
public const double BmHoneypotBase = 0.01, BmHoneypotPerSecRep = 0.0004, BmHoneypotMax = 0.06, BmControlled = 0.35, BmSellerBust = 0.008;
|
||||||
public const int BmHoneypotDays = 6, BmTrapDelay = 4;
|
public const int BmHoneypotDays = 6, BmTrapDelay = 4;
|
||||||
/// <summary>货架上除违禁品外的两类货:高科技物品(需中档声望)和显卡的出现概率、加价(在现货溢价 / 订单价之外再加),显卡里夹假卡的比例。</summary>
|
/// <summary>货架上除违禁品外的两类货:高科技物品(需中档声望)和显卡的出现概率、加价(在现货溢价 / 订单价之外再加),显卡里夹假卡的比例。</summary>
|
||||||
public const double BmTechChance = 0.15, BmGpuChance = 0.20, BmTechMarkup = 0.5, BmGpuMarkup = 0.2, BmGpuFake = 0.20;
|
public const double BmTechChance = 0.30, BmGpuChance = 0.30, BmTechMarkup = 0.5, BmGpuMarkup = 0.2, BmGpuFake = 0.20;
|
||||||
|
/// <summary>玩家上架出售:标价倍率范围(相对参考价)、上架费、成交抽成、成交后货款托管天数、假卡被买家看穿的概率;
|
||||||
|
/// 倍率 ≤0.9 / 1.0 / 1.8 时的期望成交天数(之间插值);货款带污点的概率与比例随倍率从下限涨到上限;卖家好评的初始值、看穿一次扣的点数。</summary>
|
||||||
|
public const double BmListFee = 0.05, BmCommission = 0.08, BmSellMin = 0.6, BmSellMax = 1.8, BmFakeDetect = 0.5;
|
||||||
|
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>黑市人脉升级的价格。</summary>
|
/// <summary>黑市人脉升级的价格。</summary>
|
||||||
public const int BmUnlockPrice = 1500;
|
public const int BmUnlockPrice = 1500;
|
||||||
/// <summary>单个金融违规事件记入的罪证上限。</summary>
|
/// <summary>单个金融违规事件记入的罪证上限。</summary>
|
||||||
|
|||||||
@@ -48,6 +48,31 @@ namespace Decentralized
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 玩家出售:从储藏区挑能上架的货 ----
|
||||||
|
|
||||||
|
/// <summary>这件物品在黑市上的货 id(显卡是 "gpu:型号",其余是物品 id);不能上架返回 null。</summary>
|
||||||
|
public static string SellId(GameItem it)
|
||||||
|
{
|
||||||
|
if (it == null || it.identifier == null) return null;
|
||||||
|
if (Items.IsGpu(it)) return "gpu:" + Items.ToUnit(it).Model;
|
||||||
|
string id = it.identifier;
|
||||||
|
return BlackMarket.Pool.Any(x => x.Key == id) || BlackMarket.TechPool.Contains(id) ? id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static System.Collections.Generic.List<GameItem> Sellable() =>
|
||||||
|
GraphUtils.FindAllChildrenType<GameItem>(EmporiumEntry.Instance.invElement, i => GeneralHelper.IsItemOwned(i) && SellId(i) != null, i => i.identifier != Items.Rig);
|
||||||
|
|
||||||
|
/// <summary>参考价:显卡按标称型号与成色,其余按游戏基础价。</summary>
|
||||||
|
public static long RefValue(GameItem it)
|
||||||
|
{
|
||||||
|
string id = SellId(it);
|
||||||
|
if (id == null) return 0;
|
||||||
|
if (Items.IsGpu(it)) { var u = Items.ToUnit(it); return CardValue.Of(u.Model, u.Health); }
|
||||||
|
return Value(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GoodKind KindOf(string id) => id.StartsWith("gpu:") ? GoodKind.Gpu : BlackMarket.TechPool.Contains(id) ? GoodKind.Tech : GoodKind.Contraband;
|
||||||
|
|
||||||
/// <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)
|
||||||
{
|
{
|
||||||
@@ -62,8 +87,25 @@ namespace Decentralized
|
|||||||
public static void Night(SaveData d, int day, bool crackdown)
|
public static void Night(SaveData d, int day, bool crackdown)
|
||||||
{
|
{
|
||||||
var ps = PlayerStore.Instance;
|
var ps = PlayerStore.Instance;
|
||||||
foreach (var e in d.Black.Night(day, crackdown, d.Hot, d.Rng, StoreReputation.GetSecReputation()))
|
foreach (var e in d.Black.Night(day, crackdown, d.Hot, d.Rng, StoreReputation.GetSecReputation(), Session.Price))
|
||||||
{
|
{
|
||||||
|
if (e.Sale != null)
|
||||||
|
{
|
||||||
|
string sname = ItemName(e.Sale.Item);
|
||||||
|
Log.Info($"黑市出售事件:{e.Kind} {e.Sale.Item} ×{e.Sale.Mult} 货款 {e.Amount:0} 污点 {e.Sale.Taint:0}");
|
||||||
|
switch (e.Kind)
|
||||||
|
{
|
||||||
|
case "sale_sold": ps.AddNightLog(L.T("nightlog.bm.sale_sold", sname, Fmt.Num(e.Amount), e.Sale.PayDay), "99bb66"); break;
|
||||||
|
case "sale_paid": ps.AddNightLog(L.T("nightlog.bm.sale_paid", sname, Fmt.Num(e.Amount), Fmt.Num(e.Sale.Taint)), "99bb66"); break;
|
||||||
|
case "sale_fake":
|
||||||
|
Items.Give(Make(e.Sale.Item, e.Sale.Model, e.Sale.FakeOf, e.Sale.Health));
|
||||||
|
ps.AddNightLog(L.T("nightlog.bm.sale_fake", sname), "cc7755");
|
||||||
|
break;
|
||||||
|
case "sale_rug": ps.AddNightLog(L.T("nightlog.bm.sale_rug", sname, Fmt.Num(e.Amount)), "cc7755"); break;
|
||||||
|
}
|
||||||
|
if (e.Kind == "sale_rug") News.Show("pd_news_bmrug_" + day, L.T("news.bmrug.title"), L.T("news.bmrug.desc"), 4);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
string name = e.Order != null && e.Kind != "trap" ? ItemName(e.Order.Item) : null;
|
string name = e.Order != null && e.Kind != "trap" ? ItemName(e.Order.Item) : null;
|
||||||
Log.Info($"黑市订单事件:{e.Kind} {e.Order?.Item} {e.Order?.Seller} {e.Order?.Mode} 受控 {e.Order?.Controlled} 金额 {e.Amount:0}");
|
Log.Info($"黑市订单事件:{e.Kind} {e.Order?.Item} {e.Order?.Seller} {e.Order?.Mode} 受控 {e.Order?.Controlled} 金额 {e.Amount:0}");
|
||||||
switch (e.Kind)
|
switch (e.Kind)
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ black.kind.Contraband.3,Contraband (high)
|
|||||||
black.kind.Tech,High-tech (markup)
|
black.kind.Tech,High-tech (markup)
|
||||||
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
|
nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open."
|
||||||
item.web3guide.name,Web3 Handbook
|
item.web3guide.name,Web3 Handbook
|
||||||
item.web3guide.desc,"Bank: depositing turns credits into CRC; withdrawing turns CRC back into credits and goes through compliance screening. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Ponero desk: Ponero is a privacy coin and swapping in wipes the taint on that value; 8% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter."
|
item.web3guide.desc,"Bank: depositing turns credits into CRC; withdrawing turns CRC back into credits and goes through compliance screening. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell page, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout; 5% listing fee, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Ponero desk: Ponero is a privacy coin and swapping in wipes the taint on that value; 8% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter."
|
||||||
courier.odd1,...Package is here.
|
courier.odd1,...Package is here.
|
||||||
courier.odd2,"(His hand stays in his pocket.) Sign here."
|
courier.odd2,"(His hand stays in his pocket.) Sign here."
|
||||||
courier.sign,Sign
|
courier.sign,Sign
|
||||||
@@ -286,3 +286,26 @@ nightlog.bm.trap.seller,"Black market: a seller was arrested and the order recor
|
|||||||
web3.sub.mining,Mining
|
web3.sub.mining,Mining
|
||||||
courier.sign.say,I'll sign.
|
courier.sign.say,I'll sign.
|
||||||
courier.refuse.say,Not taking it. Send it back.
|
courier.refuse.say,Not taking it. Send it back.
|
||||||
|
black.forum.busted_buyer,"Word is a batch of buyers got arrested, and the sale records are still on the platform."
|
||||||
|
nightlog.bm.sale_sold,"Black market: your {0} sold for {1}; payout on day {2}."
|
||||||
|
nightlog.bm.sale_paid,"Black market: payout of {1} for {0} arrived (CRT), taint {2}."
|
||||||
|
nightlog.bm.sale_fake,"Black market: the buyer saw that {0} is a fake and returned it; your rating dropped."
|
||||||
|
nightlog.bm.sale_rug,"Black market: the platform rugged; your {0} and the payout ({1}) are gone."
|
||||||
|
black.sub.sell,Sell
|
||||||
|
black.sell.h,Sell
|
||||||
|
black.sell.mult,"Price x{0}"
|
||||||
|
black.sell.info,"Price {0} ~{1} days to sell payout tainted {2}% of the time ({3}% of it)"
|
||||||
|
black.sell.list,List
|
||||||
|
black.sell.none,Nothing sellable in storage.
|
||||||
|
black.sell.ref,"Ref {0}"
|
||||||
|
black.sell.listed,"Listed: {0} at {1}."
|
||||||
|
black.sell.delist,Take down
|
||||||
|
black.sell.rating,"Rating {0}%"
|
||||||
|
black.mine.sales,My listings
|
||||||
|
black.mine.sale,"{0} x{1}: {2}"
|
||||||
|
black.sale.Listed,listed
|
||||||
|
black.sale.Sold,"sold, payout day {0}"
|
||||||
|
black.sale.Paid,paid
|
||||||
|
black.sale.Lost,platform rugged, gone
|
||||||
|
black.sale.Delisted,taken down
|
||||||
|
black.sale.Delisted.fake,buyer spotted the fake and returned it
|
||||||
|
@@ -269,7 +269,7 @@ black.kind.Contraband.3,违禁品(高)
|
|||||||
black.kind.Tech,高科技物品(加价)
|
black.kind.Tech,高科技物品(加价)
|
||||||
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
|
nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。"
|
||||||
item.web3guide.name,Web3 手册
|
item.web3guide.name,Web3 手册
|
||||||
item.web3guide.desc,"银行:存入把信用点换成 CRC;提取把 CRC 换回信用点,提取要过合规审查。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。Ponero 兑换处:Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 8% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。"
|
item.web3guide.desc,"银行:存入把信用点换成 CRC;提取把 CRC 换回信用点,提取要过合规审查。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的出售页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高;上架收 5% 上架费,成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。Ponero 兑换处:Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 8% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。"
|
||||||
courier.odd1,……东西到了。
|
courier.odd1,……东西到了。
|
||||||
courier.odd2,"(他的手一直放在口袋里)在这儿签个字。"
|
courier.odd2,"(他的手一直放在口袋里)在这儿签个字。"
|
||||||
courier.sign,签收
|
courier.sign,签收
|
||||||
@@ -285,3 +285,26 @@ nightlog.bm.trap.seller,"黑市:卖家落网,他留存的订单记录连累
|
|||||||
web3.sub.mining,挖矿
|
web3.sub.mining,挖矿
|
||||||
courier.sign.say,签收。
|
courier.sign.say,签收。
|
||||||
courier.refuse.say,不收,退回去。
|
courier.refuse.say,不收,退回去。
|
||||||
|
black.forum.busted_buyer,"有人说最近一批买家被抓了,成交记录还在平台上。"
|
||||||
|
nightlog.bm.sale_sold,"黑市:你上架的 {0} 卖出了,货款 {1},第 {2} 天放款。"
|
||||||
|
nightlog.bm.sale_paid,"黑市:{0} 的货款 {1} 到账(CRT),其中污点 {2}。"
|
||||||
|
nightlog.bm.sale_fake,"黑市:买家看穿了 {0} 是假卡,退了货,你的好评下降。"
|
||||||
|
nightlog.bm.sale_rug,"黑市:平台跑路,你的 {0} 和货款({1})没了。"
|
||||||
|
black.sub.sell,出售
|
||||||
|
black.sell.h,出售
|
||||||
|
black.sell.mult,"标价 ×{0}"
|
||||||
|
black.sell.info,"标价 {0} 约 {1} 天成交 货款带污点 {2}%(比例 {3}%)"
|
||||||
|
black.sell.list,上架
|
||||||
|
black.sell.none,储藏区里没有能卖的货。
|
||||||
|
black.sell.ref,"参考价 {0}"
|
||||||
|
black.sell.listed,"已上架:{0},标价 {1}。"
|
||||||
|
black.sell.delist,撤下
|
||||||
|
black.sell.rating,"好评 {0}%"
|
||||||
|
black.mine.sales,我的上架
|
||||||
|
black.mine.sale,"{0} ×{1}:{2}"
|
||||||
|
black.sale.Listed,上架中
|
||||||
|
black.sale.Sold,"已成交,第 {0} 天放款"
|
||||||
|
black.sale.Paid,已到账
|
||||||
|
black.sale.Lost,平台跑路,没了
|
||||||
|
black.sale.Delisted,已撤下
|
||||||
|
black.sale.Delisted.fake,买家看穿假卡退货
|
||||||
|
@@ -267,7 +267,7 @@ namespace Decentralized
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum BlackTab { Shop, Orders, Mine, Desk }
|
private enum BlackTab { Shop, Orders, Sell, Mine, Desk }
|
||||||
private static BlackTab _blackTab = BlackTab.Shop;
|
private static BlackTab _blackTab = BlackTab.Shop;
|
||||||
private static PayMode _mode = PayMode.Escrow;
|
private static PayMode _mode = PayMode.Escrow;
|
||||||
private static string _deskAsset = Assets.PSC;
|
private static string _deskAsset = Assets.PSC;
|
||||||
@@ -312,6 +312,7 @@ namespace Decentralized
|
|||||||
if (d.Black.Closed(day)) { b.AddLabel(L.T("black.rugged", d.Black.ClosedUntil - day + 1)); return; }
|
if (d.Black.Closed(day)) { b.AddLabel(L.T("black.rugged", d.Black.ClosedUntil - day + 1)); return; }
|
||||||
if (_blackTab == BlackTab.Desk && desk) { DeskPage(b, rep, day); return; }
|
if (_blackTab == BlackTab.Desk && desk) { DeskPage(b, rep, day); return; }
|
||||||
if (_blackTab == BlackTab.Mine) { MinePage(b, day); return; }
|
if (_blackTab == BlackTab.Mine) { MinePage(b, day); return; }
|
||||||
|
if (_blackTab == BlackTab.Sell) { SellPage(b, day); return; }
|
||||||
bool orders = _blackTab == BlackTab.Orders;
|
bool orders = _blackTab == BlackTab.Orders;
|
||||||
var w = d.Hot;
|
var w = d.Hot;
|
||||||
b.AddLabel(L.T("black.rep", rep, BlackMarket.MaxLevel(rep)) + " " + L.T("mixer.balance", _payAsset, Fmt.Num(w.Avail(_payAsset))) + (w.Taint > 0.5 ? " " + L.T("wallet.taint", Fmt.Credits(w.Taint)) : ""));
|
b.AddLabel(L.T("black.rep", rep, BlackMarket.MaxLevel(rep)) + " " + L.T("mixer.balance", _payAsset, Fmt.Num(w.Avail(_payAsset))) + (w.Taint > 0.5 ? " " + L.T("wallet.taint", Fmt.Credits(w.Taint)) : ""));
|
||||||
@@ -358,6 +359,60 @@ namespace Decentralized
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double _sellMult = 1.0;
|
||||||
|
private static readonly double[] SellMults = { 0.6, 0.8, 0.9, 1.0, 1.2, 1.5, 1.8 };
|
||||||
|
|
||||||
|
/// <summary>出售:储藏区里能上架的货做成货架,选标价倍率后上架。</summary>
|
||||||
|
private static void SellPage(CustomUIBuilder b, int day)
|
||||||
|
{
|
||||||
|
var d = Session.Data;
|
||||||
|
b.BeginRow();
|
||||||
|
foreach (var m in SellMults)
|
||||||
|
{
|
||||||
|
double mult = m;
|
||||||
|
string n = m.ToString("0.0", CultureInfo.InvariantCulture);
|
||||||
|
b.AddButton(Math.Abs(mult - _sellMult) < 1e-9 ? $"[×{n}]" : "×" + n, () => { _sellMult = mult; Open(); });
|
||||||
|
}
|
||||||
|
b.End();
|
||||||
|
b.AddLabel(L.T("black.sell.info", "×" + _sellMult.ToString("0.0", CultureInfo.InvariantCulture), (int)Math.Round(BlackMarket.ExpectedDays(_sellMult)), (int)Math.Round(BlackMarket.TaintChance(_sellMult) * 100), (int)Math.Round(BlackMarket.TaintRatio(_sellMult) * 100)) + " " + L.T("black.sell.rating", d.Black.Rating));
|
||||||
|
b.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(d.Hot.Avail(_payAsset))));
|
||||||
|
b.AddSeparator();
|
||||||
|
var items = BlackDelivery.Sellable();
|
||||||
|
if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; }
|
||||||
|
Shelf(b, items.Select(it =>
|
||||||
|
{
|
||||||
|
string id = BlackDelivery.SellId(it);
|
||||||
|
var kind = BlackDelivery.KindOf(id);
|
||||||
|
var g = new BmGood { Item = id, Kind = kind, Level = kind == GoodKind.Contraband ? BlackMarket.Pool.First(x => x.Key == id).Value : 3 };
|
||||||
|
if (kind == GoodKind.Gpu) g.Health = Items.ToUnit(it).Health;
|
||||||
|
g.Price = BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult);
|
||||||
|
return new { g, it };
|
||||||
|
}).Select(x => { SellCards[x.g] = x.it; return x.g; }).ToList(), (c, g) =>
|
||||||
|
{
|
||||||
|
var it = SellCards[g];
|
||||||
|
GoodHead(c, g, L.T("black.sell.ref", BlackDelivery.RefValue(it)));
|
||||||
|
c.AddLabel(L.T("black.price", g.Price));
|
||||||
|
c.AddFlexibleSpace();
|
||||||
|
c.AddButton(L.T("black.sell.list"), () => ListItem(it));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly System.Collections.Generic.Dictionary<BmGood, GameItem> SellCards = new System.Collections.Generic.Dictionary<BmGood, GameItem>();
|
||||||
|
|
||||||
|
private static void ListItem(GameItem it)
|
||||||
|
{
|
||||||
|
var d = Session.Data;
|
||||||
|
string id = BlackDelivery.SellId(it);
|
||||||
|
if (id == null) { Open(); return; }
|
||||||
|
var u = Items.IsGpu(it) ? Items.ToUnit(it) : null;
|
||||||
|
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), _sellMult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
|
||||||
|
if (s == null) { _message = L.T("msg.balance"); Open(); return; }
|
||||||
|
it.Destroy();
|
||||||
|
_message = L.T("black.sell.listed", ItemName(id), s.ListPrice);
|
||||||
|
Session.Save();
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
|
||||||
private const int ShelfCols = 4;
|
private const int ShelfCols = 4;
|
||||||
private const float CardW = 172f, CardH = 268f;
|
private const float CardW = 172f, CardH = 268f;
|
||||||
|
|
||||||
@@ -411,6 +466,15 @@ namespace Decentralized
|
|||||||
Open();
|
Open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void Delist(BmSale s)
|
||||||
|
{
|
||||||
|
var d = Session.Data;
|
||||||
|
d.Black.Delist(s);
|
||||||
|
Items.Give(BlackDelivery.Make(s.Item, s.Model, s.FakeOf, s.Health));
|
||||||
|
Session.Save();
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>我的订单与论坛情报。</summary>
|
/// <summary>我的订单与论坛情报。</summary>
|
||||||
private static void MinePage(CustomUIBuilder b, int day)
|
private static void MinePage(CustomUIBuilder b, int day)
|
||||||
{
|
{
|
||||||
@@ -418,7 +482,14 @@ namespace Decentralized
|
|||||||
Columns(b, l =>
|
Columns(b, l =>
|
||||||
{
|
{
|
||||||
l.AddHeader(L.T("black.mine.h"));
|
l.AddHeader(L.T("black.mine.h"));
|
||||||
if (d.Black.Orders.Count == 0) l.AddLabel(L.T("black.mine.none"));
|
if (d.Black.Orders.Count == 0 && d.Black.Sales.Count == 0) l.AddLabel(L.T("black.mine.none"));
|
||||||
|
foreach (var sl in d.Black.Sales.Where(x => x.State != SaleState.Delisted || x.Why == "fake").OrderByDescending(x => x.ListedDay))
|
||||||
|
{
|
||||||
|
var sale = sl;
|
||||||
|
string st = sale.State == SaleState.Sold ? L.T("black.sale.Sold", sale.PayDay) : L.T("black.sale." + sale.State + (sale.State == SaleState.Delisted && sale.Why != null ? "." + sale.Why : ""));
|
||||||
|
l.AddLabel(L.T("black.mine.sale", ItemName(sale.Item), sale.Mult.ToString("0.0", CultureInfo.InvariantCulture), st));
|
||||||
|
if (sale.State == SaleState.Listed) l.AddButton(L.T("black.sell.delist"), () => Delist(sale));
|
||||||
|
}
|
||||||
foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay))
|
foreach (var o in d.Black.Orders.OrderByDescending(x => x.PlacedDay))
|
||||||
l.AddLabel(L.T("black.mine.line", ItemName(o.Item), o.Seller, L.T("black.mode." + o.Mode), L.T("black.state." + o.State + (o.Why != null && o.State != OrderState.Done ? "." + o.Why : "")), o.DueDay));
|
l.AddLabel(L.T("black.mine.line", ItemName(o.Item), o.Seller, L.T("black.mode." + o.Mode), L.T("black.state." + o.State + (o.Why != null && o.State != OrderState.Done ? "." + o.Why : "")), o.DueDay));
|
||||||
}, r =>
|
}, r =>
|
||||||
|
|||||||
@@ -654,6 +654,54 @@ internal static class Program
|
|||||||
Check(r.Closed(40) && !r.Closed(41) && r.Place(new BmListing { Seller = 0, Item = "x", Price = 1 }, PayMode.Escrow, rw, Assets.CRT, 30, price) == null, "关闭期间不能下单");
|
Check(r.Closed(40) && !r.Closed(41) && r.Place(new BmListing { Seller = 0, Item = "x", Price = 1 }, PayMode.Escrow, rw, Assets.CRT, 30, price) == null, "关闭期间不能下单");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Test("黑市出售", () =>
|
||||||
|
{
|
||||||
|
Func<string, double> price = a => a == Assets.CRT ? 1 : 100;
|
||||||
|
Check(Near(BlackMarket.ExpectedDays(0.6), 1, 1e-9) && Near(BlackMarket.ExpectedDays(0.9), 1, 1e-9) && Near(BlackMarket.ExpectedDays(1.0), 2, 1e-9) && Near(BlackMarket.ExpectedDays(1.8), 14, 1e-6), "期望成交天数:0.9 以下 1 天,1.0 两天,1.8 十四天");
|
||||||
|
Check(BlackMarket.ExpectedDays(1.2) > BlackMarket.ExpectedDays(1.0) && BlackMarket.ExpectedDays(1.5) > BlackMarket.ExpectedDays(1.2), "倍率越高越慢");
|
||||||
|
Check(BlackMarket.TaintChance(1.8) > BlackMarket.TaintChance(0.6) && BlackMarket.TaintRatio(1.8) > BlackMarket.TaintRatio(0.6) && Near(BlackMarket.TaintChance(0.6), Tuning.BmSaleTaintChanceMin, 1e-9), "倍率越高,带污点的概率和比例越高");
|
||||||
|
var bm = new BlackMarket();
|
||||||
|
var w = new Wallet(); w.Add(Assets.CRT, 1000);
|
||||||
|
Check(bm.List("c4", null, null, 0, 500, 2.0, w, Assets.CRT, 1, price) == null && bm.List("c4", null, null, 0, 500, 0.5, w, Assets.CRT, 1, price) == null, "倍率超出 0.6–1.8 不能上架");
|
||||||
|
var s = bm.List("c4", null, null, 0, 500, 1.0, w, Assets.CRT, 1, price);
|
||||||
|
Check(s != null && s.ListPrice == 500 && Near(w.Get(Assets.CRT), 1000 - 25, 1e-9), "上架:标价 500,上架费 5%");
|
||||||
|
int sold1 = 0, sold18 = 0;
|
||||||
|
for (int i = 0; i < 2000; i++) { if (new Rng((ulong)i + 1).Chance(bm.SaleChance(0.8))) sold1++; if (new Rng((ulong)i + 1).Chance(bm.SaleChance(1.8))) sold18++; }
|
||||||
|
Check(sold1 == 2000 && sold18 > 80 && sold18 < 220, $"每晚成交概率:0.8 倍必成交,1.8 倍约 1/14:{sold18}/2000");
|
||||||
|
// 成交与到账:夜晚 1 成交(0.8 倍必成交),托管 3 天后到钱包
|
||||||
|
var b2 = new BlackMarket();
|
||||||
|
var w2 = new Wallet(); w2.Add(Assets.CRT, 1000);
|
||||||
|
var s2 = b2.List("c4", null, null, 0, 500, 0.8, w2, Assets.CRT, 1, price);
|
||||||
|
double crt0 = w2.Get(Assets.CRT);
|
||||||
|
var e1 = b2.Night(2, false, w2, new Rng(7), 0, price);
|
||||||
|
Check(s2.State == SaleState.Sold && s2.Proceeds == (int)Math.Round(400 * (1 - Tuning.BmCommission)) && s2.PayDay == 2 + Tuning.BmSaleHold && Near(w2.Get(Assets.CRT), crt0, 1e-9), "成交:扣抽成,货款先托管");
|
||||||
|
b2.Night(4, false, w2, new Rng(8), 0, price);
|
||||||
|
Check(s2.State == SaleState.Sold, "没到放款日不动");
|
||||||
|
b2.Night(5, false, w2, new Rng(9), 0, price);
|
||||||
|
Check(s2.State == SaleState.Paid && Near(w2.Get(Assets.CRT), crt0 + s2.Proceeds, 1e-9) && Near(w2.Taint, s2.Taint, 1e-9) && (s2.Taint == 0 || Near(s2.Taint, s2.Proceeds * BlackMarket.TaintRatio(0.8), 1e-6)), "放款:货款进钱包,污点按比例");
|
||||||
|
// 假卡被看穿
|
||||||
|
int fake = 0;
|
||||||
|
for (int i = 0; i < 400; i++)
|
||||||
|
{
|
||||||
|
var b3 = new BlackMarket(); var w3 = new Wallet(); w3.Add(Assets.CRT, 1000);
|
||||||
|
var s3 = b3.List("gpu:RTX70", "RTX70", "RTX60", 60, 500, 0.8, w3, Assets.CRT, 1, price);
|
||||||
|
b3.Night(2, false, w3, new Rng((ulong)i + 3), 0, price);
|
||||||
|
if (s3.State == SaleState.Delisted && s3.Why == "fake") { fake++; if (b3.Rating >= Tuning.BmRatingStart) throw new Exception("好评没降"); }
|
||||||
|
}
|
||||||
|
Check(fake > 150 && fake < 250, $"假卡约一半被看穿退货:{fake}/400");
|
||||||
|
// 平台跑路:上架的货与未放款货款都没了
|
||||||
|
var b4 = new BlackMarket(); var w4 = new Wallet(); w4.Add(Assets.CRT, 100000);
|
||||||
|
for (int i = 0; i < 30; i++) b4.List("c4", null, null, 0, 5000, 1.8, w4, Assets.CRT, 1, price);
|
||||||
|
bool rugged = false;
|
||||||
|
for (int d = 2; d < 400 && !rugged; d++) { var ev = b4.Night(d, false, w4, new Rng((ulong)d * 31), 0, price); rugged = ev.Any(e => e.Kind == "sale_rug"); }
|
||||||
|
Check(rugged && b4.Sales.All(x => x.State != SaleState.Listed && x.State != SaleState.Sold), "跑路:上架与未放款全部归零");
|
||||||
|
// 镇压期间不推进
|
||||||
|
var b5 = new BlackMarket(); var w5 = new Wallet(); w5.Add(Assets.CRT, 1000);
|
||||||
|
var s5 = b5.List("c4", null, null, 0, 500, 0.8, w5, Assets.CRT, 1, price);
|
||||||
|
b5.Night(2, true, w5, new Rng(1), 0, price);
|
||||||
|
Check(s5.State == SaleState.Listed, "镇压期间平台下线,不成交");
|
||||||
|
});
|
||||||
|
|
||||||
Console.WriteLine($"\n{_passed} 通过,{_failed} 失败");
|
Console.WriteLine($"\n{_passed} 通过,{_failed} 失败");
|
||||||
return _failed == 0 ? 0 : 1;
|
return _failed == 0 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user