using System; using System.Collections.Generic; using System.Linq; using Decentralized.Domain; namespace Decentralized { /// /// 预测市场的接入层:按游戏当前状态给出候选盘口,夜间读游戏事件、派系行动、水纯度和本 mod 价格来结算,判断下注时玩家是否已经知道答案。 /// public static class PredictGame { public static readonly string[] Factions = { "FACTION_SECURITY", "FACTION_REVOLUTION", "FACTION_LOWER_LEVEL", "FACTION_UPPER_LEVEL", "FACTION_BLACK_MARKET" }; private static readonly double[] GutterCuts = { 8200, 8400, 8600, 8800 }, RustCuts = { 9030, 9060, 9090, 9120 }; private static readonly double[] PriceMult = { 0.9, 0.95, 1.0, 1.05, 1.1 }; public static bool Available(int day) => Session.Data != null && Unlock.IsUnlocked && day >= Tuning.PredictUnlockDay; /// 夜间观测用的标识:生效中的事件,以及派系行动的 "F:派系"。 public static List ActiveIds() { var list = new List(); var mgr = StoreStation.Instance?.storeEventManager; if (mgr == null) return list; foreach (var e in mgr.GetActiveEvents()) { list.Add(e.identifier); if (e.isFactionOperation && !string.IsNullOrEmpty(e.sourceFaction)) list.Add("F:" + e.sourceFaction); } return list; } public static List Candidates(int day) { var d = Session.Data; var list = new List(); var mgr = StoreStation.Instance?.storeEventManager; if (mgr == null) return list; // 常规事件:开门时先过"今天有没有事件"(5 天后才开始,有 0/1/2 件在身上时 50/35/25%),再按权重抽 int counter = mgr.GetEventCounter(); double dayChance = counter == 0 ? 0.50 : counter == 1 ? 0.35 : counter == 2 ? 0.25 : 0; var pool = StoreEventManager.normalEventBlueprints.Where(e => !mgr.IsEventOnCooldown(e.identifier)).ToList(); double total = pool.Sum(e => (double)e.odd); foreach (var bp in pool) { if (mgr.IsEventActive(bp.identifier)) continue; string name; try { name = bp.storeEventFunc().GetDisplayName(); } catch (Exception) { continue; } double odd = bp.odd; list.Add(new PredictCandidate { Kind = PredictKind.Event, Key = bp.identifier, Title = name, Prob = (from, to) => PredictMath.EventProb(dayChance, odd, total, to - from + 1) }); } foreach (var f in Factions) { if (mgr.GetActiveEvents().Any(e => e.isFactionOperation && e.sourceFaction == f)) continue; list.Add(new PredictCandidate { Kind = PredictKind.Faction, Key = f, Title = L.T("predict.faction." + f), Prob = (from, to) => PredictMath.FactionProb(to - from + 1) }); } foreach (var t in GutterCuts) { double thr = t; list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "gutter", Title = L.T("predict.purity.gutter"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 8000, 8999) }); } foreach (var t in RustCuts) { double thr = t; list.Add(new PredictCandidate { Kind = PredictKind.Purity, Key = "rust", Title = L.T("predict.purity.rust"), Param = thr, Tomorrow = true, Prob = (a, b) => PredictMath.PurityAtLeast(thr, 9000, 9150) }); } double price = d.Market.Price, anchor = d.Market.Anchor(d.ActiveEvents); foreach (var mult in PriceMult) { double thr = Math.Round(price * mult, price >= 100 ? 0 : 1); list.Add(new PredictCandidate { Kind = PredictKind.Price, Key = "PSC", Title = "PSC", Param = thr, Prob = (from, to) => PredictMath.PriceAtLeast(price, anchor, Tuning.PscVol, Tuning.Revert, to - day, thr) }); } // 价格盘一次只留一个(阈值不同的盘口会互相对冲) return list; } /// 开门后补盘。 public static void Morning(SaveData d, int day) { if (!Available(day)) return; var made = d.Predict.Refill(day, d.Rng, Candidates(day).Where(c => c.Kind != PredictKind.Price || !d.Predict.Markets.Any(m => m.Outcome < 0 && m.Kind == PredictKind.Price)).ToList()); foreach (var m in made) Log.Info($"预测盘口 #{m.Id}:{m.Kind} {m.Key} {m.Title} 阈值 {m.Param} 窗口 {m.FromDay}-{m.ToDay} 先验 {m.P0:0.000}"); } /// 夜间结算的结果:事件、派系读观测;纯度读当天的数值;行情读夜间更新之前的价格。 public static bool? Outcome(PredictMarket m, SaveData d) { switch (m.Kind) { case PredictKind.Event: case PredictKind.Faction: return d.Predict.Happened(m); case PredictKind.Purity: var st = StoreStation.Instance; return st == null ? (bool?)null : (m.Key == "gutter" ? st.todayGutterflowPurity : st.todayRustwaterPurity) >= m.Param; case PredictKind.Price: return d.Market.Price >= m.Param; } return null; } /// 下注时玩家是否已经知道答案:这个事件(或派系行动)已经排进日程、玩家看得到(传闻纸条、派系特权),而且落在窗口里。只有押"会发生"才算内幕单。 public static bool Known(PredictMarket m, bool yes) { if (!yes || (m.Kind != PredictKind.Event && m.Kind != PredictKind.Faction)) return false; var mgr = StoreStation.Instance?.storeEventManager; if (mgr == null) return false; int today = StoreStation.GetDayCounter(); for (int i = 0; i < mgr.futurEvents.Count; i++) { var e = mgr.futurEvents[i]; if (!e.IsKnown) continue; bool match = m.Kind == PredictKind.Event ? e.identifier == m.Key : e.isFactionOperation && e.sourceFaction == m.Key; int start = today + mgr.futurEventsDay[i]; if (match && start >= m.FromDay && start <= m.ToDay) return true; } return false; } public static string Question(PredictMarket m) { string win = m.FromDay == m.ToDay ? L.T("predict.win.one", m.FromDay) : L.T("predict.win.range", m.FromDay, m.ToDay); switch (m.Kind) { case PredictKind.Event: return L.T("predict.q.event", win, m.Title); case PredictKind.Faction: return L.T("predict.q.faction", win, m.Title); case PredictKind.Purity: return L.T("predict.q.purity", win, m.Title, Fmt.Num(m.Param)); default: return L.T("predict.q.price", win, m.Title, Fmt.Num(m.Param)); } } } }