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(); }