Fix wallet destroy fund loss, Tumbler progress timing, settle idempotence, bounds guards (from external review)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 10:02:02 +08:00
1 parent d0054443ec
commit 4501807cd2
5 files changed
+38 -10

No files matched your search

+2 -1
View File
@@ -142,6 +142,7 @@ namespace Decentralized
{
var d = Session.Data;
int day = StoreStation.GetDayCounter();
if (d.Day == day) { Log.Info($"第 {day} 天夜间结算已执行过,跳过重复调用"); return; }
StoryLinks.Night(d, day);
TumblerStory.Night(d, day);
var events = ActiveEvents();
@@ -256,7 +257,7 @@ namespace Decentralized
News.Show("pd_news_ponzi_" + day, L.T("news.ponzi.title"), L.T("news.ponzi.desc"), 4);
Log.Info($"理财崩盘:{ponzi.Kind},池子 {ponzi.Amount:0} 被没收");
}
foreach (var e in d.Scams.Night(day, d.Rng, d.Own, new[] { Assets.CRT, Assets.PSC, Assets.CRC, Assets.CRA }, Session.Price))
foreach (var e in d.Scams.Night(day, d.Rng, d.WalletAt, new[] { Assets.CRT, Assets.PSC, Assets.CRC, Assets.CRA }, Session.Price))
{
var sw = d.Own(e.Wallet);
string wn = sw != null ? WalletWindow.NameOf(sw) : e.Wallet;
+3
View File
@@ -65,6 +65,7 @@ namespace Decentralized
var e = mgr.futurEvents[i];
if (knownOnly && !e.IsKnown) continue;
bool match = m.Kind == PredictKind.Event ? e.identifier == m.Key : e.isFactionOperation && e.sourceFaction == m.Key;
if (i >= mgr.futurEventsDay.Count) break;
int start = today + mgr.futurEventsDay[i];
if (match && start >= m.FromDay && start <= m.ToDay) return start;
}
@@ -154,6 +155,7 @@ namespace Decentralized
for (int i = 0; i < mgr.futurEvents.Count; i++)
{
var e = mgr.futurEvents[i];
if (i >= mgr.futurEventsDay.Count) break;
int start = day + mgr.futurEventsDay[i];
if (!e.IsKnown || start < day + 2 || e.eventType == StoreEvent.EventType.COSMETIC) continue;
bool op = e.isFactionOperation;
@@ -294,6 +296,7 @@ namespace Decentralized
if (mgr != null)
for (int i = 0; i < mgr.futurEvents.Count; i++)
{
if (i >= mgr.futurEventsDay.Count) break;
var e = mgr.futurEvents[i];
if (e.IsKnown && (e.identifier == "crackdown" || e.identifier == "stationWideBlackout" || e.identifier == "powerShortage"))
list.Add(L.T("predict.clue.queued2", today + mgr.futurEventsDay[i], e.GetDisplayName()));
+5 -1
View File
@@ -972,7 +972,11 @@ namespace Decentralized
var d = Session.Data;
int idx = d.Wallets.IndexOf(w);
if (idx <= 0) return;
if (w.Balances.Values.Any(v => v > 1e-9) || w.Frozen.Values.Any(v => v > 1e-9)) { _destroyPick = null; _message = L.T("wallets.destroy.notempty", WalletWindow.NameOf(w)); Open(); return; }
bool busy = d.Predict.Positions.Any(p => ChainAddress.Same(p.Wallet, w.Address))
|| d.Scams.Pools.Any(p => p.Of(w.Address) > 0 || p.Pending.Any(q => ChainAddress.Same(q.Wallet, w.Address)))
|| d.Staking.Lots.Any(l => ChainAddress.Same(l.Wallet, w.Address) && (l.Amount > 1e-9 || l.Unlocking.Count > 0))
|| d.Mixer.Pending.Any(m => ChainAddress.Same(m.Wallet, w.Address));
if (w.Balances.Values.Any(v => v > 1e-9) || w.Frozen.Values.Any(v => v > 1e-9) || busy) { _destroyPick = null; _message = L.T("wallets.destroy.notempty", WalletWindow.NameOf(w)); Open(); return; }
if (_destroyPick != w.Address) { _destroyPick = w.Address; _message = null; Open(); return; }
_destroyPick = null;
string name = WalletWindow.NameOf(w);
+22 -8
View File
@@ -26,8 +26,8 @@ namespace Decentralized
if (t.TryStart(day, Unlock.MixerUnlocked)) Log.Info($"Tumbler 剧情开始:第 {t.StartDay} 天第一次上门");
int due = t.Due(day);
if (due == 0) return;
t.Visited(day);
if (due == 4 && t.Choice == Tumbler.Report) { Log.Info("Tumbler 已被举报,不再上门"); Session.Save(); return; }
// 举报之后第 4 次不上门;其余的上门要等对话真正走完才算数(当天没聊完、被清场或读档重来,明天照样来)
if (due == 4 && t.Choice == Tumbler.Report) { t.Visited(day); Log.Info("Tumbler 已被举报,不再上门"); Session.Save(); return; }
var c = Visit(due, t.Choice);
manager.AddNextClient(c);
Log.Info($"Tumbler 第 {due} 次上门(选择 {t.Choice})");
@@ -71,8 +71,8 @@ namespace Decentralized
var d = Session.Data;
switch (n)
{
case 1: Lines(c, c.mainDialogue, "tumbler.v1.", 4).SetEndAction(Leave); break;
case 2: Lines(c, c.mainDialogue, "tumbler.v2.", 3).SetEndAction(Leave); break;
case 1: Lines(c, c.mainDialogue, "tumbler.v1.", 4).SetEndAction(() => Finish(1)); break;
case 2: Lines(c, c.mainDialogue, "tumbler.v2.", 3).SetEndAction(() => Finish(2)); break;
case 3:
{
var ask = new Dialogue();
@@ -91,11 +91,11 @@ namespace Decentralized
if (choice == Tumbler.Hide)
{
bool has = EmporiumEntry.Instance != null && EmporiumEntry.Instance.GetAllOwnedItems().Any(i => i != null && i.identifier == Items.KeyDrive);
if (has) Lines(c, c.mainDialogue, "tumbler.v4.back.", 3).SetEndAction(() => { Returned(); Leave(); });
else Lines(c, c.mainDialogue, "tumbler.v4.lost.", 3).SetEndAction(Leave);
if (has) Lines(c, c.mainDialogue, "tumbler.v4.back.", 3).SetEndAction(() => { Returned(); Finish(4); });
else Lines(c, c.mainDialogue, "tumbler.v4.lost.", 3).SetEndAction(() => Finish(4));
}
else if (choice == Tumbler.Sell) Lines(c, c.mainDialogue, "tumbler.v4.sold.", 3).SetEndAction(Leave);
else Lines(c, c.mainDialogue, "tumbler.v4.none.", 1).SetEndAction(Leave); // 第 3 次没做选择(被遣散)
else if (choice == Tumbler.Sell) Lines(c, c.mainDialogue, "tumbler.v4.sold.", 3).SetEndAction(() => Finish(4));
else Lines(c, c.mainDialogue, "tumbler.v4.none.", 1).SetEndAction(() => Finish(4));
break;
}
c.AddBasicDialog();
@@ -105,6 +105,19 @@ namespace Decentralized
private static void Leave() => DialogUIManager.Instance.CurrentClientLeave();
/// <summary>第 n 次上门的对话走完:进度推进一步(只在这一次还没记过时),存档,顾客离开。</summary>
private static void Finish(int n)
{
Log.Guard("Tumbler 上门结束", () =>
{
var t = Session.Data.Story.Tumbler;
if (t.Stage == n - 1) t.Visited(StoreStation.GetDayCounter());
Log.Info($"Tumbler 第 {n} 次上门结束,进度 {t.Stage}");
Session.Save();
});
Leave();
}
/// <summary>第 3 次上门做出的选择。</summary>
private static void Choose(int choice)
{
@@ -112,6 +125,7 @@ namespace Decentralized
{
var d = Session.Data;
d.Story.Tumbler.Choice = choice;
if (d.Story.Tumbler.Stage == 2) d.Story.Tumbler.Visited(StoreStation.GetDayCounter());
int day = StoreStation.GetDayCounter();
switch (choice)
{