Files
probably-decentralized/src/Decentralized/TerminalWindow.OwnMix.cs
T
ZHU MINCHAOandClaude Opus 5.5 71002cbced 第十一阶段 stage 3:自营混币器
- Domain/OwnMixer:注资开张、NPC 客户存入、按匿名集洗污点出款(和普通混币器同一套抽象,匿名集主要是本金和收入)、手续费收入取回全额带污点、本金带自己的污点;客流按"费率 + 出款剩下的污点"和 Tumbler 比,乘黑市声望;风声累积按概率查封(全部没收、记 FENCING、停业)、便衣存入(记 FENCING)、卷款跑路(全额污点、记 FRAUD、黑市声望下降、永久关闭)
- 灰网页"自营混币器"标签(买下场外换币台就有;TerminalWindow 拆成 partial,页面在 TerminalWindow.OwnMix.cs):状态、费率、口碑、风声、匿名集、各币种本金/收入/客户在途;费率、开张/暂停、注资(账单转账)、取回、卷款跑路(合约签名)
- 夜间结算、日志、报纸新闻;浏览器显示"自营混币器"地址名
- Tuning.OwnMix*(research/mixsim 标定),单元测试,手册与 8 种语言
- 实测:注资 20 PSC 开张,三晚各一笔存入、一次便衣

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:14:28 +08:00

137 lines
7.8 KiB
C#

using System;
using System.Globalization;
using System.Linq;
using UnityEngine;
using Decentralized.Domain;
namespace Decentralized
{
/// <summary>灰网页的"自营混币器"(买下场外换币台就有):状态、口碑、风声、池子,费率、开张或暂停、注资、取回、卷款跑路。规则见 <see cref="OwnMixer"/>。</summary>
public static partial class TerminalWindow
{
private static string _ownAsset = Assets.CRT;
private static bool _ownRugConfirm;
private static readonly float[] OwnWBase = { 70f, 110f, 110f, 110f };
private static float[] OwnW => Sc(OwnWBase);
private static readonly TextAnchor[] OwnAligns = { TextAnchor.MiddleLeft, TextAnchor.MiddleRight, TextAnchor.MiddleRight, TextAnchor.MiddleRight };
private static void OwnMixPage(CustomUIBuilder b)
{
var d = Session.Data;
var m = d.OwnMix;
int day = StoreStation.GetDayCounter();
double enforce = StoryMods.Current.Sanction(day);
Columns(b, l =>
{
l.AddHeader(L.T("ownmix.h"));
string status = m.Closed ? L.T("ownmix.status.closed") : m.Seized(day) ? L.T("ownmix.status.seized", m.SeizedUntil + 1) : m.Open ? L.T("ownmix.status.open") : L.T("ownmix.status.paused");
l.AddLabel(status, m.Closed || m.Seized(day) ? "ownmix_status_note" : "ownmix_status_dim");
var w = new[] { 220f, 200f };
TableRow(l, w, L.T("ownmix.k.fee"), (m.Fee * 100).ToString("0", CultureInfo.InvariantCulture) + "%");
TableRow(l, w, L.T("ownmix.k.rating"), (m.Rating * 100).ToString("0", CultureInfo.InvariantCulture) + "%");
TableRow(l, w, L.T("ownmix.k.heat"), L.T("ownmix.heat." + m.HeatLevel(enforce)));
TableRow(l, w, L.T("ownmix.k.anon"), Fmt.Credits(m.Anonymity(Session.Price)));
TableRow(l, w, L.T("ownmix.k.fees"), Fmt.Credits(m.FeesTotal));
l.AddSeparator();
TableHeader(l, OwnW.Sum() + 8f, OwnW, OwnAligns, L.T("col.asset"), L.T("ownmix.col.capital"), L.T("ownmix.col.earned"), L.T("ownmix.col.pending"));
foreach (var a in OwnMixer.AssetsList)
{
var p = m.Pool(a);
double pend = m.Total(a) - m.Free(a);
TableRow(l, OwnW.Sum() + 8f, OwnW, OwnAligns, new[] { a, Fmt.Num(p.Capital), Fmt.Num(p.Earned), Fmt.Num(pend) });
}
}, r =>
{
if (m.Closed || m.Seized(day)) { r.AddLabel(m.Closed ? L.T("ownmix.status.closed") : L.T("ownmix.status.seized", m.SeizedUntil + 1), "ownmix_closed_note"); return; }
var w = d.Hot;
r.AddHeader(L.T("ownmix.k.fee"));
r.BeginRow(4f);
foreach (var f in Tuning.OwnMixFees)
{
double fee = f;
PledgerSkin.Pick(r, (fee * 100).ToString("0", CultureInfo.InvariantCulture) + "%", Math.Abs(fee - m.Fee) < 1e-9, () => { m.Fee = fee; Session.Save(); Open(); }, _picked);
}
r.End();
r.AddButton(m.Open ? L.T("ownmix.pause") : L.T("ownmix.open"), () =>
{
if (m.Open) m.Open = false; else m.Start(Session.Price);
Log.Info($"自营混币器:{(m.Open ? "开张" : "暂停")},费率 {m.Fee:P0}");
Session.Save(); Open();
});
r.AddSeparator();
r.BeginRow(4f);
foreach (var a in OwnMixer.AssetsList)
{
var asset = a;
PledgerSkin.Pick(r, asset, asset == _ownAsset, () => { _ownAsset = asset; Open(); }, _picked);
}
r.End();
r.AddLabel(L.T("mixer.balance", _ownAsset, Fmt.Num(w.Avail(_ownAsset))));
r.AddLabel(L.T("ownmix.free", Fmt.Num(m.Free(_ownAsset)), _ownAsset));
AmountRow(r, true, _ownAsset);
r.BeginRow(6f);
r.AddButton(L.T("ownmix.inject"), () =>
{
if (!ReadAmount(out double v)) return;
string asset = _ownAsset;
BillFor(d.Address("ownmix"), L.T("pay.ownmix", Fmt.Num(v), asset), asset, v, pw =>
{
if (!m.Inject(pw, asset, v, Session.Price)) { _message = L.T("msg.balance"); Open(); return; }
_message = L.T("ownmix.injected", Fmt.Num(v), asset);
Log.Info($"自营混币器注资 {v} {asset},匿名集 {m.Anonymity(Session.Price):0}");
Session.Save(); Open();
});
});
r.AddButton(L.T("ownmix.withdraw"), () =>
{
if (!ReadAmount(out double v)) return;
string asset = _ownAsset;
if (v > m.Free(asset) + 1e-9) { _message = L.T("ownmix.err"); Open(); return; }
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
Contract = true, Party = d.Address("ownmix"), Asset = asset, Amount = 0, Spend = 0, FeeAsset = BestFeeAsset(w),
Summary = L.T("ownmix.withdraw.sum", Fmt.Num(v), asset),
Execute = pw =>
{
if (!m.Withdraw(pw, asset, v, Session.Price, out double over)) { _message = L.T("ownmix.err"); Open(); return; }
_message = L.T("ownmix.withdrawn", Fmt.Num(v), asset) + TaintCrime.Commit(over);
Log.Info($"自营混币器取回 {v} {asset},钱包污点 {pw.Taint:0},超出 {over:0}");
Session.Save(); Open();
},
});
});
r.End();
r.AddSeparator();
double total = OwnMixer.AssetsList.Sum(a => m.Total(a) * Session.Price(a));
if (total <= 1e-9) return;
r.AddButton(_ownRugConfirm ? L.T("ownmix.rug.confirm", Fmt.Credits(total)) : L.T("ownmix.rug", Fmt.Credits(total)), () =>
{
if (!_ownRugConfirm) { _ownRugConfirm = true; Open(); return; }
_ownRugConfirm = false;
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
Contract = true, Party = d.Address("ownmix"), Asset = Assets.CRT, Amount = 0, Spend = 0, FeeAsset = BestFeeAsset(w),
Summary = L.T("ownmix.rug.sum"),
Execute = pw =>
{
if (m.Closed) return;
double value = OwnMixer.AssetsList.Sum(a => m.Total(a) * Session.Price(a));
m.Rug(pw, Session.Price, out double over);
PlayerStore.Instance.secData.CommitCrime("FRAUD", (int)Tuning.OwnMixRugCrime);
StoreReputation.ModReputation(StoreReputation.FACTION_BLACK_MARKET, -Tuning.OwnMixRugRep);
News.Show("pd_news_ownmix_rug_" + StoreStation.GetDayCounter(), L.T("news.ownmix.rug.title"), L.T("news.ownmix.rug.desc"), 4);
_message = L.T("ownmix.rugged", Fmt.Credits(value)) + TaintCrime.Commit(over);
Log.Info($"自营混币器卷款跑路:{value:0},钱包 {pw.Address}");
Session.Save(); Open();
},
});
});
});
}
}
}