diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcb29b3..0e916dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,8 @@
- 订单改成商家货架(用户意见):黑市页"订单"改名"商家",每周从 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 万)。药物改成并入药物商家,现货柜台的货不变。所有货都能转手出售。
- 尖端店铺与个人卖家(用户意见):贵货(单件基础价 ≥ 2000:超科技涡轮增压器、超科技储藏区、心智重编程器、顶级显卡等)不再被一刀切掉,改由"尖端店铺"卖:每周只有 15% 的概率出现,2 种货、库存 1–2 件、价格系数 0.85–1.00、送货 5 天;顶级全新显卡也在这里。个人卖家:每周固定 2 位,什么都卖(可能是药、模组、显卡、仍有 10% 混进贵货),每件价格系数各自随机 0.50–1.20(捡漏或被宰),库存 1–2 件,是骗子的概率 25%,都是新号所以只接受托管。常规商家仍只卖基础价 ≤ 4000 的货。
+- 定价改成直接输价格(用户意见):点货架上的"上架"或"我的订单"里的"改价",出现"设定标价"页,显示参考价(真实价)和输入框;×0.6–×1.8 档位只是帮忙把价格填进输入框,也可以手输整数,输入后显示"标价 83(×1.38)";价格要在参考价的 0.6–1.8 倍之间(显示可输范围),确定后走付款确认(上架费 5% / 改价费 2% 按新标价算)。成交速度和货款污点都按 标价 ÷ 参考价 的实际比例连续计算(不再只有档位)。"我的订单"里每件上架的货显示"参考 60 标价 83(×1.38)",下面是"改价"和"撤下"。出售页顶上的档位按钮保留,作为默认倍率(预填价格、"全部上架"用)。游戏内验证:档位填价(×1.5 → 90)、手输 83(×1.38,上架费 5)、范围外(200)被拒并提示 36–108。
+- 自己的储藏区和背包不再出现在可上架的货里,有内容的容器也不行(之前储藏区(扩建)会被列出来,上架会把里面的东西一起带走)。
- 上架后可以改价(用户意见):"我的订单与情报"页每件上架的货下面一排倍率按钮(0.6–1.8,当前的带方括号)和"撤下",点倍率先走付款确认(改价费 2%,`BmRepriceFee`,手输平台地址),标价按参考价重算,成交概率按新倍率走,上架日不变。游戏内验证:0.6 倍的 RTX 70 改成 ×1.2,改价费 12,标价 600。蜜罐基础概率维持 1%(模拟里进取策略罪证 ≥ 300 约 48 天,用户认为可接受)。
- 追查调整(经济模拟后):同一晚到期的追查按来源合成一次事件,记入货款的 40%(`BmTrapShare`),同一次蜜罐 / 落网累计最多 400(`BmTrapWindowCap`);没收到货的订单(骗子退款、被截获、跑路、拒签、被查封)取消对应追查。原来每单各记一次、每晚上限 400,一个蜜罐窗口能连续记几千,模拟里一局 120 天几乎一直在罪证线以上。
- 手输收款地址(用户意见):Web3 里的付款与转账——黑市现货、商家下单、上架费、混币器存入、Ponero 兑换——点下去先出"确认付款"页,显示摘要和收款地址(商家是名字里的 0x… ,平台、混币器、Ponero 兑换处各有自己的随机地址,存档保存),要手输地址才放款,不分大小写,输错提示"地址不符,没有付款",可取消。DEX 兑换和银行存取不加(没有收款方);交易所的转账等阶段 5 多钱包时再接同一个确认页。游戏内验证:现货买超频模组,输错一位被拒、输对成交;Ponero 兑换处进入确认页。
diff --git a/src/Decentralized/BlackDelivery.cs b/src/Decentralized/BlackDelivery.cs
index b7ccbfa..ee3b37c 100644
--- a/src/Decentralized/BlackDelivery.cs
+++ b/src/Decentralized/BlackDelivery.cs
@@ -65,8 +65,11 @@ namespace Decentralized
return Merchants.IsGood(id) ? id : null;
}
+ /// 自己的储藏区、背包不能上架:卖掉会带走里面的东西,也会丢掉容量。
+ private static readonly System.Collections.Generic.HashSet StorageIds = new System.Collections.Generic.HashSet(Merchants.Table[MerchantType.Storage].Select(e => e.Id));
+
public static System.Collections.Generic.List Sellable() =>
- GraphUtils.FindAllChildrenType(EmporiumEntry.Instance.invElement, i => GeneralHelper.IsItemOwned(i) && SellId(i) != null, i => i.identifier != Items.Rig);
+ GraphUtils.FindAllChildrenType(EmporiumEntry.Instance.invElement, i => GeneralHelper.IsItemOwned(i) && SellId(i) != null && !StorageIds.Contains(i.identifier) && i.FindAllChildItems().Count == 0, i => i.identifier != Items.Rig);
/// 参考价:显卡按标称型号与成色,其余按游戏基础价。
public static long RefValue(GameItem it)
diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv
index 24c5ac1..9fe53d3 100644
--- a/src/Decentralized/ModFiles/Localization/en.csv
+++ b/src/Decentralized/ModFiles/Localization/en.csv
@@ -300,7 +300,7 @@ 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.mine.sale,"{0} ref {1} price {2} (x{3}): {4}"
black.sale.Listed,listed
black.sale.Sold,"sold, payout day {0}"
black.sale.Paid,paid
@@ -343,6 +343,13 @@ black.pay.cancel,Cancel
black.pay.bad,Address does not match; nothing paid.
pay.mix,"Mixer {0} {1}"
pay.swap,"Ponero desk {0} {1}"
-pay.listfee, (listing fee)
+pay.listfee," (listing fee x{0})"
pay.repricefee," (repricing fee x{0})"
black.sell.repriced,"Repriced: {0}, now {1}."
+black.edit.ref,"Reference price {0}"
+black.edit.input,Enter a price
+black.edit.price,"Price {0} (x{1})"
+black.edit.ok,OK
+black.edit.cancel,Cancel
+black.edit.range,"The price must be 0.6 to 1.8 times the reference ({0} to {1})."
+black.sell.reprice,Reprice
diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv
index 7ae74cd..3dc1ca5 100644
--- a/src/Decentralized/ModFiles/Localization/zh.csv
+++ b/src/Decentralized/ModFiles/Localization/zh.csv
@@ -299,7 +299,7 @@ black.sell.listed,"已上架:{0},标价 {1}。"
black.sell.delist,撤下
black.sell.rating,"好评 {0}%"
black.mine.sales,我的上架
-black.mine.sale,"{0} ×{1}:{2}"
+black.mine.sale,"{0} 参考 {1} 标价 {2}(×{3}):{4}"
black.sale.Listed,上架中
black.sale.Sold,"已成交,第 {0} 天放款"
black.sale.Paid,已到账
@@ -342,6 +342,13 @@ black.pay.cancel,取消
black.pay.bad,地址不符,没有付款。
pay.mix,"混币器 {0} {1}"
pay.swap,"Ponero 兑换处 {0} {1}"
-pay.listfee,(上架费)
+pay.listfee,"(上架费 ×{0})"
pay.repricefee,"(改价费 ×{0})"
black.sell.repriced,"已改价:{0},标价 {1}。"
+black.edit.ref,"参考价 {0}"
+black.edit.input,输入标价
+black.edit.price,"标价 {0}(×{1})"
+black.edit.ok,确定
+black.edit.cancel,取消
+black.edit.range,"标价要在参考价的 0.6 到 1.8 倍之间({0} 到 {1})。"
+black.sell.reprice,改价
diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs
index fa5bed6..697339d 100644
--- a/src/Decentralized/TerminalWindow.cs
+++ b/src/Decentralized/TerminalWindow.cs
@@ -18,6 +18,9 @@ namespace Decentralized
private static Page _page = Page.Home;
private sealed class PendingPay { public string Address, Summary; public int Total; public Action Execute; }
private static PendingPay _pay;
+ private sealed class EditPrice { public GameItem Item; public BmSale Sale; public string Id; public long Ref; }
+ private static EditPrice _edit;
+ private static string _editPrice = "";
private static string _addr = "";
private static string _amount = "";
private static string _message;
@@ -33,7 +36,7 @@ namespace Decentralized
{
var page = p;
string name = L.T("tab." + p.ToString().ToLowerInvariant());
- b.AddButton(page == _page ? $"[{name}]" : name, () => { _page = page; _message = null; _pay = null; Open(); });
+ b.AddButton(page == _page ? $"[{name}]" : name, () => { _page = page; _message = null; _pay = null; _edit = null; Open(); });
}
b.End();
b.AddSeparator();
@@ -51,7 +54,7 @@ namespace Decentralized
b.Show();
}
- private static void Go(Page p) { _page = p; _message = null; _pay = null; Open(); }
+ private static void Go(Page p) { _page = p; _message = null; _pay = null; _edit = null; Open(); }
private static void Entry(CustomUIBuilder b, string icon, string name, string line1, string line2, Page target)
{
@@ -248,7 +251,7 @@ namespace Decentralized
if ((t == Web3Tab.Mixer && !Unlock.MixerUnlocked) || (t == Web3Tab.Black && !Unlock.BlackUnlocked)) continue;
var tab = t;
string name = L.T("web3.sub." + t.ToString().ToLowerInvariant());
- b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _pay = null; Open(); });
+ b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _pay = null; _edit = null; Open(); });
}
b.End();
if (_web3Tab == Web3Tab.Mining) { ReportPage(b); return; }
@@ -310,10 +313,11 @@ namespace Decentralized
if (t == BlackTab.Desk && !desk) continue;
var tab = t;
string name = L.T("black.sub." + t.ToString().ToLowerInvariant());
- b.AddButton(tab == _blackTab ? $"[{name}]" : name, () => { _blackTab = tab; _message = null; _pay = null; Open(); });
+ b.AddButton(tab == _blackTab ? $"[{name}]" : name, () => { _blackTab = tab; _message = null; _pay = null; _edit = null; Open(); });
}
b.End();
if (d.Black.Closed(day)) { b.AddLabel(L.T("black.rugged", d.Black.ClosedUntil - day + 1)); return; }
+ if (_edit != null) { EditPage(b); return; }
if (_blackTab == BlackTab.Desk && desk) { DeskPage(b, rep, day); return; }
if (_blackTab == BlackTab.Mine) { MinePage(b, day); return; }
if (_blackTab == BlackTab.Sell) { SellPage(b, day); return; }
@@ -366,7 +370,7 @@ namespace Decentralized
}
b.End();
b.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(d.Hot.Avail(_payAsset))) + " " + L.T("black.sell.rating", d.Black.Rating) + " " + L.T("black.sell.slots", d.Black.ActiveListings, Tuning.BmMaxListings));
- b.AddButton(L.T("black.sell.all"), () => BeginPay(d.Address("platform"), L.T("black.sell.all") + L.T("pay.listfee"), BlackDelivery.Sellable().Sum(it => BlackMarket.ListFee(BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult))), ListAll));
+ b.AddButton(L.T("black.sell.all"), () => BeginPay(d.Address("platform"), L.T("black.sell.all") + L.T("pay.listfee", _sellMult.ToString("0.0", CultureInfo.InvariantCulture)), BlackDelivery.Sellable().Sum(it => BlackMarket.ListFee(BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult))), ListAll));
b.AddSeparator();
var items = BlackDelivery.Sellable();
if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; }
@@ -384,7 +388,7 @@ namespace Decentralized
GoodHead(c, g, "");
c.AddLabel(L.T("black.price", g.Price));
c.AddFlexibleSpace();
- c.AddButton(L.T("black.sell.list"), () => BeginPay(d.Address("platform"), ItemName(g.Item) + L.T("pay.listfee"), BlackMarket.ListFee(g.Price), () => ListItem(it)));
+ c.AddButton(L.T("black.sell.list"), () => BeginEdit(new EditPrice { Item = it, Id = g.Item, Ref = BlackDelivery.RefValue(it) }, g.Price));
});
}
@@ -409,13 +413,13 @@ namespace Decentralized
Open();
}
- private static void ListItem(GameItem it)
+ private static void ListItem(GameItem it, double mult)
{
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);
+ var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), mult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
if (s == null) { _message = d.Black.ActiveListings >= Tuning.BmMaxListings ? L.T("black.sell.full", Tuning.BmMaxListings) : L.T("msg.balance"); Open(); return; }
it.Destroy();
_message = L.T("black.sell.listed", ItemName(id), s.ListPrice);
@@ -551,6 +555,51 @@ namespace Decentralized
Open();
}
+ private static void BeginEdit(EditPrice e, int initial)
+ {
+ _edit = e;
+ _editPrice = initial.ToString(CultureInfo.InvariantCulture);
+ _message = null;
+ Open();
+ }
+
+ private static int MinPrice(long refValue) => (int)Math.Ceiling(Tuning.BmSellMin * refValue - 1e-9);
+ private static int MaxPrice(long refValue) => (int)Math.Floor(Tuning.BmSellMax * refValue + 1e-9);
+
+ /// 设定标价:显示参考价,档位按钮只是帮忙填价格,价格也可以手输;按 标价 / 参考价 的比例算成交和污点。
+ private static void EditPage(CustomUIBuilder b)
+ {
+ var e = _edit;
+ b.AddHeader(ItemName(e.Id));
+ b.AddLabel(L.T("black.edit.ref", e.Ref));
+ b.BeginPanel(RightW, -1f, TextAnchor.MiddleCenter, 0f, 0f);
+ b.AddInput(L.T("black.edit.input"), _editPrice, s => _editPrice = s, "price", s => _editPrice = s);
+ b.End();
+ b.BeginRow();
+ foreach (var m in SellMults)
+ {
+ double mult = m;
+ b.AddButton("×" + m.ToString("0.0", CultureInfo.InvariantCulture), () => { _editPrice = BlackMarket.ListPriceOf(e.Ref, mult).ToString(CultureInfo.InvariantCulture); Open(); });
+ }
+ b.End();
+ if (int.TryParse((_editPrice ?? "").Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int p) && p > 0)
+ b.AddLabel(L.T("black.edit.price", p, (p / (double)e.Ref).ToString("0.00", CultureInfo.InvariantCulture)));
+ b.BeginRow();
+ b.AddButton(L.T("black.edit.ok"), () =>
+ {
+ if (!int.TryParse((_editPrice ?? "").Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int price) || price < MinPrice(e.Ref) || price > MaxPrice(e.Ref))
+ { _message = L.T("black.edit.range", MinPrice(e.Ref), MaxPrice(e.Ref)); Open(); return; }
+ double mult = price / (double)e.Ref;
+ var d = Session.Data;
+ int fee = e.Sale != null ? BlackMarket.RepriceFee(price) : BlackMarket.ListFee(price);
+ _edit = null;
+ BeginPay(d.Address("platform"), ItemName(e.Id) + L.T(e.Sale != null ? "pay.repricefee" : "pay.listfee", mult.ToString("0.00", CultureInfo.InvariantCulture)), fee,
+ () => { if (e.Sale != null) RepriceSale(e.Sale, mult); else ListItem(e.Item, mult); });
+ });
+ b.AddButton(L.T("black.edit.cancel"), () => { _edit = null; _message = null; Open(); });
+ b.End();
+ }
+
private static void RepriceSale(BmSale s, double mult)
{
var d = Session.Data;
@@ -581,20 +630,11 @@ namespace Decentralized
{
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));
+ l.AddLabel(L.T("black.mine.sale", ItemName(sale.Item), sale.Ref, sale.ListPrice, sale.Mult.ToString("0.00", CultureInfo.InvariantCulture), st));
if (sale.State == SaleState.Listed)
{
l.BeginRow();
- foreach (var m in SellMults)
- {
- double mult = m;
- string n = m.ToString("0.0", CultureInfo.InvariantCulture);
- l.AddButton(Math.Abs(mult - sale.Mult) < 1e-9 ? $"[{n}]" : n, () =>
- {
- if (Math.Abs(mult - sale.Mult) < 1e-9) return;
- BeginPay(d.Address("platform"), ItemName(sale.Item) + L.T("pay.repricefee", n), BlackMarket.RepriceFee(BlackMarket.ListPriceOf(sale.Ref, mult)), () => RepriceSale(sale, mult));
- });
- }
+ l.AddButton(L.T("black.sell.reprice"), () => BeginEdit(new EditPrice { Sale = sale, Id = sale.Item, Ref = sale.Ref }, sale.ListPrice));
l.AddButton(L.T("black.sell.delist"), () => Delist(sale));
l.End();
}