218 lines
11 KiB
C#
218 lines
11 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Decentralized.Domain;
|
||
|
||
namespace Decentralized
|
||
{
|
||
/// <summary>
|
||
/// 每天的推进:开门时日内波动和 NPC 大户;睡觉时(游戏 EndNight 的最后)挖矿、损耗与暴毙、全网、稳定币、行情结算,写晨间日志,存档。
|
||
/// 矿架自己在夜间早段扣电并登记显卡(Items.NightDraw),这里只结算。
|
||
/// </summary>
|
||
public static class Night
|
||
{
|
||
public static void Subscribe()
|
||
{
|
||
ModHook.OnShutterOpenedLate += OnOpen;
|
||
ModHook.OnGoingSleepEarly += OnSleepEarly;
|
||
ModHook.OnGoingSleepLate += OnSleepLate;
|
||
}
|
||
|
||
public static void Unsubscribe()
|
||
{
|
||
ModHook.OnShutterOpenedLate -= OnOpen;
|
||
ModHook.OnGoingSleepEarly -= OnSleepEarly;
|
||
ModHook.OnGoingSleepLate -= OnSleepLate;
|
||
}
|
||
|
||
private static bool Active => Session.Data != null && Unlock.IsUnlocked;
|
||
|
||
/// <summary>游戏新闻事件与派系行动 → 行情事件。</summary>
|
||
public static List<MarketEvent> ActiveEvents()
|
||
{
|
||
var list = new List<MarketEvent>();
|
||
var mgr = StoreStation.Instance?.storeEventManager;
|
||
if (mgr == null) return list;
|
||
foreach (var e in mgr.GetActiveEvents())
|
||
{
|
||
switch (e.identifier)
|
||
{
|
||
case "stationWideBlackout": case "powerShortage": case "powerPlantMaintenance": list.Add(MarketEvent.Blackout); break;
|
||
case "crackdown": case "sec_martial_law": list.Add(MarketEvent.Crackdown); break;
|
||
case "bm_gang_war": list.Add(MarketEvent.GangWar); break;
|
||
case "rev_raid_upper_level": list.Add(MarketEvent.RevRaidUpper); break;
|
||
case "ll_riot": case "ll_worker_strike": list.Add(MarketEvent.LowerUnrest); break;
|
||
}
|
||
}
|
||
return list.Distinct().ToList();
|
||
}
|
||
|
||
private static void OnOpen()
|
||
{
|
||
if (!Active) return;
|
||
Log.Guard("开门行情", () =>
|
||
{
|
||
var d = Session.Data;
|
||
int day = StoreStation.GetDayCounter();
|
||
d.Market.Open(ActiveEvents(), d.Rng, day);
|
||
d.Market.Whale(d.Rng);
|
||
if (!d.OpeningNewsShown)
|
||
{
|
||
News.Show("pd_news_open", L.T("news.open.title"), L.T("news.open.desc"), 2);
|
||
d.OpeningNewsShown = true;
|
||
}
|
||
Session.Save();
|
||
});
|
||
}
|
||
|
||
private static void OnSleepEarly()
|
||
{
|
||
Session.TonightCards.Clear();
|
||
Session.TonightRigs = Session.TonightPoweredRigs = 0;
|
||
Session.TonightKj = 0;
|
||
}
|
||
|
||
private static void OnSleepLate()
|
||
{
|
||
if (!Active) return;
|
||
Log.Guard("夜间结算", Settle);
|
||
}
|
||
|
||
/// <summary>链上的夜间事件:网络拥堵、地址投毒的 0 币转入、钓鱼公告。</summary>
|
||
private static void ChainNight(SaveData d, int day)
|
||
{
|
||
if (d.GasPhase < 0) d.GasPhase = d.Rng.Range(0, Tuning.TxGasPeriod - 1);
|
||
d.Gas = TxRules.Gas(day + 1, d.GasPhase, d.Rng);
|
||
Log.Info($"明天 gas ×{d.Gas:0.00}");
|
||
if (day >= d.CongestedUntil && d.Rng.Chance(Tuning.TxCongestionChance))
|
||
{
|
||
d.CongestedFrom = day + 1;
|
||
d.CongestedUntil = day + d.Rng.Range(1, Tuning.TxCongestionDaysMax);
|
||
News.Show("pd_news_congest_" + day, L.T("news.congest.title"), L.T("news.congest.desc"), d.CongestedUntil - day);
|
||
}
|
||
foreach (var p in d.PoisonPlans.Where(x => x.Day <= day + 1).ToList())
|
||
{
|
||
d.PoisonPlans.Remove(p);
|
||
var w = d.Own(p.Wallet);
|
||
if (w == null) continue;
|
||
string fake = ChainAddress.Lookalike(p.Target, d.Rng);
|
||
TxRules.Record(w.History, new TxRecord { Addr = fake, Asset = Assets.PSC, Amount = 0, Day = day + 1, In = true });
|
||
Log.Info($"地址投毒:{p.Target} 的相似地址 {fake} 向 {w.Address} 转入 0 币");
|
||
}
|
||
if (d.Rng.Chance(Tuning.TxPhishChance))
|
||
{
|
||
string fake = ChainAddress.Make(d.Rng.NextUInt());
|
||
if (Unlock.BlackUnlocked && d.Rng.Chance(0.5)) d.Black.Forum.Add("phish:" + fake);
|
||
else News.Show("pd_news_phish_" + day, L.T("news.phish.title"), L.T("news.phish.desc", fake), 2);
|
||
Log.Info("钓鱼公告:" + fake);
|
||
}
|
||
}
|
||
|
||
private static void Settle()
|
||
{
|
||
var d = Session.Data;
|
||
int day = StoreStation.GetDayCounter();
|
||
var events = ActiveEvents();
|
||
Log.Info($"夜间事件:{string.Join(",", events)}(新增 {string.Join(",", events.Where(e => !d.ActiveEvents.Contains(e)))}),黑市声望 {StoreReputation.GetBMReputation()}");
|
||
bool wasSanctioned = d.Mixer.Sanctioned(day);
|
||
foreach (var ev in events.Where(e => !d.ActiveEvents.Contains(e)))
|
||
{
|
||
d.Stable.OnEventStart(ev, day, d.Rng); d.Mixer.OnEvent(ev, day, d.Rng);
|
||
// 镇压开始:黑市为换取放行,按声望出卖一批 CRT 地址
|
||
if (ev == MarketEvent.Crackdown && Stablecoins.CrtFrozenByCrackdown(StoreReputation.GetBMReputation(), d.Rng))
|
||
{
|
||
double frozen = d.Wallets.Sum(w => w.Freeze(Assets.CRT, Tuning.CrtFreezeShare, day + Tuning.CrtUnfreezeAfter));
|
||
if (frozen > 0)
|
||
{
|
||
PlayerStore.Instance.AddNightLog(L.T("nightlog.crt_freeze", Fmt.Num(frozen), day + Tuning.CrtUnfreezeAfter), "cc7755");
|
||
News.Show("pd_news_crtfreeze_" + day, L.T("news.crtfreeze.title"), L.T("news.crtfreeze.desc"), 3);
|
||
}
|
||
}
|
||
}
|
||
foreach (var w in d.Wallets)
|
||
foreach (var t in w.Thaw(day)) PlayerStore.Instance.AddNightLog(L.T("nightlog.thaw", Fmt.Num(t.Value), t.Key));
|
||
if (!wasSanctioned && d.Mixer.Sanctioned(day) && Unlock.MixerUnlocked) News.Show("pd_news_sanction_" + day, L.T("news.sanction.title"), L.T("news.sanction.desc"), 3);
|
||
d.ActiveEvents = events;
|
||
if (Unlock.BlackUnlocked) BlackDelivery.Night(d, day, events.Contains(MarketEvent.Crackdown));
|
||
d.Network.Availability = (events.Contains(MarketEvent.Blackout) ? 0.8 : 1) * (events.Contains(MarketEvent.LowerUnrest) ? 0.9 : 1);
|
||
|
||
var units = Session.TonightCards.Select(c => (item: c.Key, unit: Items.ToUnit(c.Key), bonus: c.Value)).ToList();
|
||
double hash = units.Sum(x => x.unit.Hash(x.bonus));
|
||
double mined = d.Network.Mine(hash);
|
||
if (mined > 0) d.Hot.Add(Assets.PSC, mined);
|
||
|
||
var report = new NightReport
|
||
{
|
||
Day = day, Rigs = Session.TonightRigs, PoweredRigs = Session.TonightPoweredRigs, Kj = Session.TonightKj,
|
||
Hash = hash, NetworkHash = d.Network.NpcHash * d.Network.Availability + hash, Mined = mined,
|
||
};
|
||
foreach (var (item, unit, bonus) in units)
|
||
{
|
||
bool died = unit.Night(bonus, d.Rng);
|
||
Items.Store(item, unit);
|
||
if (died) report.Burnt.Add(item.name);
|
||
}
|
||
|
||
double rewardBefore = d.Network.Reward;
|
||
d.Network.Night(day, d.Market.Price, hash);
|
||
if (d.Network.Reward < rewardBefore) d.Market.OnHalving(d.Rng);
|
||
if (d.Network.Reward < rewardBefore) News.Show("pd_news_halving_" + day, L.T("news.halving.title"), L.T("news.halving.desc"), 2);
|
||
double crcFactor = d.Stable.Night(day, d.Rng, 0);
|
||
if (crcFactor < 1)
|
||
foreach (var w in d.Wallets.Append(d.Poinbase))
|
||
if (w.Get(Assets.CRC) > 0) w.Balances[Assets.CRC] = w.Get(Assets.CRC) * crcFactor;
|
||
d.Market.UpdateSupply(day);
|
||
if (Tuning.HalvingDays - day % Tuning.HalvingDays == 3) News.Show("pd_news_prehalving_" + day, L.T("news.prehalving.title"), L.T("news.prehalving.desc"), 3);
|
||
d.Market.Night(events, d.Rng, day, Session.Price);
|
||
report.Price = d.Market.Price;
|
||
d.LastNight = report;
|
||
d.Day = day;
|
||
|
||
var ps = PlayerStore.Instance;
|
||
if (mined > 0) ps.AddNightLog(L.T("nightlog.mined", Fmt.Num(mined)));
|
||
foreach (var m in d.Mixer.Night(d.WalletAt, day, Session.Price, d.Rng))
|
||
{
|
||
Log.Info($"混币器:{(m.Rugged ? "跑路" : "提回")} {m.Amount:0.##} {m.Asset},污点 {m.TaintBefore:0} → {m.TaintAfter:0},标记 {m.Flagged}");
|
||
if (m.Rugged) ps.AddNightLog(L.T("nightlog.mixer_rug", Fmt.Num(m.Amount), m.Asset), "cc7755");
|
||
else
|
||
{
|
||
string line = L.T("nightlog.mixer", Fmt.Num(m.Amount), m.Asset, Fmt.Credits(m.TaintBefore), Fmt.Credits(m.TaintAfter)) + (m.Flagged ? L.T("nightlog.mixer_flag") : "");
|
||
if (m.Flagged) ps.AddNightLog(line, "cc7755"); else ps.AddNightLog(line);
|
||
}
|
||
}
|
||
// 被投毒的硬件钱包:钱够多以后,别人用手里的助记词把它转空
|
||
foreach (var w in d.Wallets.Where(x => x.Poisoned))
|
||
{
|
||
double v = w.Value(Session.Price);
|
||
if (!d.Rng.Chance(Wallet.DrainChance(v))) continue;
|
||
w.Drain(Session.Price);
|
||
Log.Info($"投毒钱包 {w.Address} 被转空:{v:0}");
|
||
ps.AddNightLog(L.T("nightlog.drained", WalletWindow.NameOf(w), Fmt.Credits(v)), "cc7755");
|
||
}
|
||
ChainNight(d, day);
|
||
foreach (var name in report.Burnt) ps.AddNightLog(L.T("nightlog.burnt", name), "cc7755");
|
||
int unpowered = report.Rigs - report.PoweredRigs;
|
||
if (unpowered > 0 && units.Count + unpowered > 0) ps.AddNightLog(L.T("nightlog.unpowered", unpowered), "cc7755");
|
||
if (d.CoinSalesToday > 0) ps.AddNightLog(L.T("nightlog.coinpay", Fmt.Credits(d.CoinSalesToday)));
|
||
d.CoinSalesToday = 0;
|
||
Log.Info($"夜间结算第 {day} 天:钱包污点 {d.Hot.Taint:0},CRT {d.Hot.Get(Assets.CRT):0}(冻结 {(d.Hot.Frozen.TryGetValue(Assets.CRT, out var fz) ? fz : 0):0}),混币器在途 {d.Mixer.Pending.Count},制裁至 {d.Mixer.SanctionedUntil}");
|
||
Session.Save();
|
||
}
|
||
}
|
||
|
||
/// <summary>mod 新闻:不带价格修正的新闻事件,和原版新闻一样出现在报纸上。</summary>
|
||
public static class News
|
||
{
|
||
public static void Show(string id, string title, string text, int days)
|
||
{
|
||
var mgr = StoreStation.Instance?.storeEventManager;
|
||
if (mgr == null || mgr.IsEventActive(id)) return;
|
||
mgr.ActivateEvent(new StoreEvent
|
||
{
|
||
identifier = id, newsName = title, displayName = title, newsDescription = text,
|
||
duration = days, eventType = StoreEvent.EventType.COSMETIC, eventArea = StoreEvent.EventArea.ALL,
|
||
importance = 2, cooldown = 0, negociationDatas = new List<NegociationData>(),
|
||
});
|
||
}
|
||
}
|
||
}
|