Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ForgeOracleClient v1.0.0
plugins/ForgeOracle.Core.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using ForgeOracle.Core.Abstractions; using ForgeOracle.Core.Items; using ForgeOracle.Core.Prediction; using ForgeOracle.Core.Random; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Rodrigo Pistón")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Rodrigo Pistón. Portions adapted from Forge Oracle, Copyright (c) 2026 papptimus (MIT).")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b339d12d4618f268b5a0cd2061fd0e95979a5439")] [assembly: AssemblyProduct("ForgeOracle.Core")] [assembly: AssemblyTitle("ForgeOracle.Core")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ForgeOracle.Core.Random { public static class UnityFloat { public const uint MantissaMask = 8388607u; public const float Divisor = 8388607f; private const float Reciprocal = 1.192093E-07f; public static float FromRaw(uint raw) { return (float)(raw & 0x7FFFFF) * 1.192093E-07f; } public static double FromRawDouble(uint raw) { return (double)(raw & 0x7FFFFF) / 8388607.0; } } public struct UnityXorShift128 { private const uint InitMultiplier = 1812433253u; private uint _x; private uint _y; private uint _z; private uint _w; public static UnityXorShift128 FromSeed(int seed) { UnityXorShift128 result = new UnityXorShift128 { _x = (uint)seed }; result._y = result._x * 1812433253 + 1; result._z = result._y * 1812433253 + 1; result._w = result._z * 1812433253 + 1; return result; } public uint NextUInt() { uint num = _x ^ (_x << 11); _x = _y; _y = _z; _z = _w; _w = _w ^ (_w >> 19) ^ num ^ (num >> 8); return _w; } public float NextValue() { return UnityFloat.FromRaw(NextUInt()); } public int NextRange(int min, int max) { if (min > max) { throw new ArgumentOutOfRangeException("min", "Only ascending ranges are modelled."); } if (min == max) { return min; } uint num = (uint)((ulong)max - (ulong)min); return (int)(min + NextUInt() % num); } } public static class ValheimHash { private const int Seed = 5381; private const int Mix = 1566083941; public static int GetStableHashCode(string text) { if (text == null) { throw new ArgumentNullException("text"); } int num = 5381; int num2 = 5381; for (int i = 0; i < text.Length && text[i] != 0; i += 2) { num = ((num << 5) + num) ^ text[i]; if (i == text.Length - 1 || text[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ text[i + 1]; } return num + num2 * 1566083941; } } } namespace ForgeOracle.Core.Prediction { public sealed class AttemptForecast { public int Index { get; } public int TargetQuality { get; } public int Key { get; } public SeedOrigin SeedOrigin { get; } public UpgradeRoll Roll { get; } public PredictionStatus Status { get; } public int ResultingQuality { get; } public bool Conditional { get; } public bool Hypothetical => Index > 0; public int LevelsLost { get { if (Status != PredictionStatus.LevelLoss) { return 0; } return TargetQuality - 1 - ResultingQuality; } } public AttemptForecast(int index, int targetQuality, int key, SeedOrigin origin, UpgradeRoll roll, OutcomeDecision decision, bool conditional) { Index = index; TargetQuality = targetQuality; Key = key; SeedOrigin = origin; Roll = roll; Status = decision.Status; ResultingQuality = decision.ResultingQuality; Conditional = conditional; } } public static class ForecastBuilder { public static bool TryResolveSeed(ForgeContext context, int targetQuality, out int key, out int seed, out SeedOrigin origin) { key = UpgradeKeys.ComputeKey(context.Item, targetQuality); origin = SeedOrigin.Stored; seed = 0; if (!context.Seeds.WasCaptured(key)) { return false; } if (context.Seeds.TryGetStored(key, out seed)) { return true; } origin = SeedOrigin.DerivedFromName; if (context.PlayerName.Length == 0) { return false; } seed = UpgradeKeys.InitialSeed(context.PlayerName, key); return true; } public static IReadOnlyList<AttemptForecast> Repeated(ForgeContext context, UpgradeRules rules) { List<AttemptForecast> list = new List<AttemptForecast>(); int targetQuality = context.TargetQuality; if (!TryResolveSeed(context, targetQuality, out var key, out var seed, out var origin)) { return list; } for (int i = 0; i < context.ForecastCount; i++) { UpgradeRoll roll = UpgradeRoll.FromSeed(seed); OutcomeDecision decision = OutcomeRules.Decide(roll, rules, targetQuality); list.Add(new AttemptForecast(i, targetQuality, key, origin, roll, decision, conditional: false)); seed = roll.NextSeed; origin = SeedOrigin.Simulated; } return list; } public static IReadOnlyList<AttemptForecast> Progression(ForgeContext context) { List<AttemptForecast> list = new List<AttemptForecast>(); bool flag = false; for (int i = 0; i < context.ForecastCount; i++) { int num = context.TargetQuality + i; if (!context.RulesByTarget.TryGetValue(num, out UpgradeRules value) || !TryResolveSeed(context, num, out var key, out var seed, out var origin)) { break; } UpgradeRoll roll = UpgradeRoll.FromSeed(seed); OutcomeDecision decision = OutcomeRules.Decide(roll, value, num); list.Add(new AttemptForecast(i, num, key, origin, roll, decision, flag)); flag |= decision.Status != PredictionStatus.Success; } return list; } } public sealed class ForgePredictionService { private readonly IReadOnlyList<IForgePredictionProvider> _providers; public IReadOnlyList<IForgePredictionProvider> Providers => _providers; public ForgePredictionService(IReadOnlyList<IForgePredictionProvider> providers) { _providers = providers ?? throw new ArgumentNullException("providers"); } public IForgePredictionProvider? Select(ForgeContext context) { foreach (IForgePredictionProvider provider in _providers) { if (provider.CanPredict(context)) { return provider; } } return null; } public PredictionResult Predict(ForgeContext context) { IForgePredictionProvider forgePredictionProvider = Select(context); if (forgePredictionProvider == null) { return PredictionResult.Unknown(UnknownReason.MissingThresholds, "none", "None", context.Issues); } try { return forgePredictionProvider.Predict(context); } catch (Exception ex) { return PredictionResult.Unknown(UnknownReason.ProviderError, forgePredictionProvider.Id, forgePredictionProvider.DisplayName, context.Issues, ex.ToString()); } } } public static class OutcomeRules { public static OutcomeDecision Decide(UpgradeRoll roll, UpgradeRules rules, int targetQuality) { return ToDecision(Classify(roll.Roll, rules), rules.Failure, targetQuality); } private static FailureKind? Classify(float roll, UpgradeRules rules) { if (rules.SuccessChance >= roll) { return null; } if (rules.Failure.Classifier != null) { return rules.Failure.Classifier(roll); } return (!(rules.BreakChance >= 1f - roll)) ? FailureKind.LevelLoss : FailureKind.Destroyed; } private static OutcomeDecision ToDecision(FailureKind? kind, FailureModel failure, int target) { int num = target - 1; if (!kind.HasValue) { return new OutcomeDecision(PredictionStatus.Success, target); } if (kind == FailureKind.Destroyed) { return new OutcomeDecision(PredictionStatus.Destruction, 0); } int num2 = ((kind == FailureKind.NoChange) ? num : failure.QualityAfterLoss(target)); return new OutcomeDecision((num2 == num) ? PredictionStatus.NoChange : PredictionStatus.LevelLoss, num2); } } public readonly struct OutcomeDecision { public PredictionStatus Status { get; } public int ResultingQuality { get; } public OutcomeDecision(PredictionStatus status, int resultingQuality) { Status = status; ResultingQuality = resultingQuality; } } public sealed class PredictionResult { private static readonly IReadOnlyList<AttemptForecast> NoRows = new AttemptForecast[0]; private static readonly IReadOnlyList<CompatibilityIssue> NoIssues = new CompatibilityIssue[0]; public PredictionStatus Status { get; } public PredictionConfidence Confidence { get; } public UnknownReason Reason { get; } public string ProviderId { get; } public string ProviderName { get; } public AttemptForecast? Next { get; private set; } public UpgradeRules? Rules { get; private set; } public IReadOnlyList<AttemptForecast> RepeatedAttempts { get; private set; } = NoRows; public IReadOnlyList<AttemptForecast> Progression { get; private set; } = NoRows; public IReadOnlyList<CompatibilityIssue> Issues { get; private set; } = NoIssues; public string Error { get; private set; } = string.Empty; public bool IsKnown => Status != PredictionStatus.Unknown; private PredictionResult(PredictionStatus status, PredictionConfidence confidence, UnknownReason reason, string providerId, string providerName) { Status = status; Confidence = confidence; Reason = reason; ProviderId = providerId ?? string.Empty; ProviderName = providerName ?? string.Empty; } public static PredictionResult Unknown(UnknownReason reason, string providerId, string providerName, IReadOnlyList<CompatibilityIssue>? issues = null, string error = "") { return new PredictionResult(PredictionStatus.Unknown, PredictionConfidence.Unsupported, reason, providerId, providerName) { Issues = (issues ?? NoIssues), Error = (error ?? string.Empty) }; } public static PredictionResult Known(AttemptForecast next, PredictionConfidence confidence, string providerId, string providerName, UpgradeRules rules, PredictionRows rows, IReadOnlyList<CompatibilityIssue> issues) { return new PredictionResult(next.Status, confidence, UnknownReason.None, providerId, providerName) { Next = next, Rules = rules, RepeatedAttempts = rows.Repeated, Progression = rows.Progression, Issues = (issues ?? NoIssues) }; } } public sealed class PredictionRows { public IReadOnlyList<AttemptForecast> Repeated { get; } public IReadOnlyList<AttemptForecast> Progression { get; } public PredictionRows(IReadOnlyList<AttemptForecast> repeated, IReadOnlyList<AttemptForecast> progression) { Repeated = repeated; Progression = progression; } } public class SeededForgePredictionProvider : IForgePredictionProvider { public static SeededForgePredictionProvider Vanilla { get; } = new SeededForgePredictionProvider("vanilla", "Vanilla"); public string Id { get; } public string DisplayName { get; } public SeededForgePredictionProvider(string id, string displayName) { Id = id; DisplayName = displayName; } public virtual bool CanPredict(ForgeContext context) { if (!HasBlockingIssue(context.Issues)) { return context.RulesByTarget.ContainsKey(context.TargetQuality); } return false; } public PredictionResult Predict(ForgeContext context) { if (HasBlockingIssue(context.Issues)) { return PredictionResult.Unknown(UnknownReason.ModifiedAlgorithm, Id, DisplayName, context.Issues); } if (!context.RulesByTarget.TryGetValue(context.TargetQuality, out UpgradeRules value)) { return PredictionResult.Unknown(UnknownReason.MissingThresholds, Id, DisplayName, context.Issues); } IReadOnlyList<AttemptForecast> readOnlyList = ForecastBuilder.Repeated(context, value); if (readOnlyList.Count == 0) { return PredictionResult.Unknown(UnknownReason.MissingPlayerName, Id, DisplayName, context.Issues); } PredictionRows rows = new PredictionRows(readOnlyList, ForecastBuilder.Progression(context)); AttemptForecast next = readOnlyList[0]; return PredictionResult.Known(next, Assess(context, next), Id, DisplayName, value, rows, context.Issues); } public static PredictionConfidence Assess(ForgeContext context, AttemptForecast next) { if (!context.Rng.Verified) { return PredictionConfidence.Unverified; } foreach (CompatibilityIssue issue in context.Issues) { if (issue.Severity != IssueSeverity.Info) { return PredictionConfidence.Unverified; } } return PredictionConfidence.Verified; } private static bool HasBlockingIssue(IReadOnlyList<CompatibilityIssue> issues) { foreach (CompatibilityIssue issue in issues) { if (issue.Severity == IssueSeverity.Blocking) { return true; } } return false; } } public sealed class SeedSnapshot { private readonly Dictionary<int, int> _stored; private readonly HashSet<int> _captured; public static SeedSnapshot Empty { get; } = new SeedSnapshot(new Dictionary<int, int>(), new HashSet<int>()); public int StoredCount => _stored.Count; private SeedSnapshot(Dictionary<int, int> stored, HashSet<int> captured) { _stored = stored; _captured = captured; } public static SeedSnapshot Capture(IEnumerable<int> keys, TryGetSeed lookup) { if (keys == null || lookup == null) { throw new ArgumentNullException((keys == null) ? "keys" : "lookup"); } Dictionary<int, int> dictionary = new Dictionary<int, int>(); HashSet<int> hashSet = new HashSet<int>(); foreach (int key in keys) { hashSet.Add(key); if (lookup(key, out var seed)) { dictionary[key] = seed; } } return new SeedSnapshot(dictionary, hashSet); } public static SeedSnapshot FromTable(IReadOnlyDictionary<int, int> table, IEnumerable<int> keys) { return Capture(keys, delegate(int key, out int seed) { return table.TryGetValue(key, out seed); }); } public bool WasCaptured(int key) { return _captured.Contains(key); } public bool TryGetStored(int key, out int seed) { return _stored.TryGetValue(key, out seed); } } public delegate bool TryGetSeed(int key, out int seed); public sealed class UnsupportedForgePredictionProvider : IForgePredictionProvider { private readonly UnknownReason _reason; public string Id { get; } public string DisplayName { get; } public UnsupportedForgePredictionProvider(string id, string displayName, UnknownReason reason) { Id = id; DisplayName = displayName; _reason = reason; } public bool CanPredict(ForgeContext context) { return true; } public PredictionResult Predict(ForgeContext context) { return PredictionResult.Unknown(_reason, Id, DisplayName, context.Issues); } } public readonly struct UpgradeRoll { public const int NextSeedBound = 99999999; public int Seed { get; } public uint RawDraw { get; } public float Roll { get; } public int NextSeed { get; } private UpgradeRoll(int seed, uint raw, int nextSeed) { Seed = seed; RawDraw = raw; Roll = UnityFloat.FromRaw(raw); NextSeed = nextSeed; } public static UpgradeRoll FromSeed(int seed) { UnityXorShift128 unityXorShift = UnityXorShift128.FromSeed(seed); uint raw = unityXorShift.NextUInt(); int nextSeed = unityXorShift.NextRange(0, 99999999); return new UpgradeRoll(seed, raw, nextSeed); } public static UpgradeRoll FromDraws(int seed, uint rawDraw, int nextSeed) { return new UpgradeRoll(seed, rawDraw, nextSeed); } } public sealed class UpgradeRules { public float SuccessChance { get; } public float BreakChance { get; } public float BreakRefund { get; } public string Source { get; } public FailureModel Failure { get; } public UpgradeRules(float successChance, float breakChance, float breakRefund, string source) : this(successChance, breakChance, breakRefund, source, FailureModel.Vanilla) { } public UpgradeRules(float successChance, float breakChance, float breakRefund, string source, FailureModel failure) { SuccessChance = successChance; BreakChance = breakChance; BreakRefund = breakRefund; Source = source ?? string.Empty; Failure = failure ?? FailureModel.Vanilla; } } public enum FailureKind { Destroyed, LevelLoss, NoChange } public delegate FailureKind FailureClassifier(float roll); public sealed class FailureModel { public static FailureModel Vanilla { get; } = new FailureModel(1, int.MinValue, null, "vanilla"); public int LevelsLost { get; } public int MinimumQuality { get; } public FailureClassifier? Classifier { get; } public string Description { get; } private FailureModel(int levelsLost, int minimumQuality, FailureClassifier? classifier, string description) { LevelsLost = levelsLost; MinimumQuality = minimumQuality; Classifier = classifier; Description = description; } public static FailureModel WithLevelLoss(int levelsLost, int minimumQuality, string description) { return new FailureModel(levelsLost, minimumQuality, null, description); } public static FailureModel Custom(FailureClassifier classifier, int levelsLost, int minimumQuality, string description) { return new FailureModel(levelsLost, minimumQuality, classifier, description); } public int QualityAfterLoss(int targetQuality) { int num = targetQuality - 1 - LevelsLost; if (num >= MinimumQuality) { return num; } return MinimumQuality; } } } namespace ForgeOracle.Core.Items { public sealed class ItemIdentity : IEquatable<ItemIdentity> { public string PrefabName { get; } public string NameToken { get; } public int MaxQuality { get; } public int PrefabHash { get; } public ItemIdentity(string prefabName, string nameToken, int maxQuality) { if (string.IsNullOrEmpty(prefabName)) { throw new ArgumentException("A prefab name is required.", "prefabName"); } PrefabName = prefabName; NameToken = nameToken ?? string.Empty; MaxQuality = maxQuality; PrefabHash = ValheimHash.GetStableHashCode(prefabName); } public bool Equals(ItemIdentity? other) { if (other != null) { return string.Equals(PrefabName, other.PrefabName, StringComparison.Ordinal); } return false; } public override bool Equals(object? obj) { return Equals(obj as ItemIdentity); } public override int GetHashCode() { return PrefabHash; } public override string ToString() { return PrefabName; } } public static class UpgradeKeys { public static int ComputeKey(ItemIdentity item, int targetQuality) { return item.PrefabHash + targetQuality; } public static int InitialSeed(string playerName, int key) { return ValheimHash.GetStableHashCode(playerName) + key; } } } namespace ForgeOracle.Core.Abstractions { public sealed class CompatibilityIssue { public string Code { get; } public IssueSeverity Severity { get; } public string Source { get; } public string Detail { get; } public CompatibilityIssue(string code, IssueSeverity severity, string source, string detail) { Code = code ?? string.Empty; Severity = severity; Source = source ?? string.Empty; Detail = detail ?? string.Empty; } public override string ToString() { return $"[{Severity}] {Code} ({Source}): {Detail}"; } } public enum PredictionStatus { Success, Destruction, LevelLoss, NoChange, Unknown } public enum PredictionConfidence { Verified, Unverified, Unsupported } public enum SeedOrigin { Stored, DerivedFromName, Simulated } public enum IssueSeverity { Info, Unverified, Blocking } public enum UnknownReason { None, Disabled, NotAtUpgrader, NoSelection, NoUpgraderResource, MissingPlayerName, MissingThresholds, ModifiedAlgorithm, RngMismatch, ProviderError } public sealed class ForgeContext { private static readonly IReadOnlyList<CompatibilityIssue> NoIssues = new CompatibilityIssue[0]; public ItemIdentity Item { get; } public int CurrentQuality { get; } public int TargetQuality => CurrentQuality + 1; public string PlayerName { get; } public SeedSnapshot Seeds { get; } public IReadOnlyDictionary<int, UpgradeRules> RulesByTarget { get; } public RngProfile Rng { get; } public int ForecastCount { get; } public IReadOnlyList<CompatibilityIssue> Issues { get; } public ForgeContext(ItemIdentity item, int currentQuality, string playerName, SeedSnapshot seeds, IReadOnlyDictionary<int, UpgradeRules> rulesByTarget, RngProfile rng, int forecastCount, IReadOnlyList<CompatibilityIssue>? issues = null) { Item = item ?? throw new ArgumentNullException("item"); CurrentQuality = currentQuality; PlayerName = playerName ?? string.Empty; Seeds = seeds ?? throw new ArgumentNullException("seeds"); RulesByTarget = rulesByTarget ?? throw new ArgumentNullException("rulesByTarget"); Rng = rng ?? throw new ArgumentNullException("rng"); ForecastCount = Math.Max(1, forecastCount); Issues = issues ?? NoIssues; } public static IEnumerable<int> KeysFor(ItemIdentity item, int currentQuality, int forecastCount) { for (int i = 0; i < Math.Max(1, forecastCount); i++) { yield return UpgradeKeys.ComputeKey(item, currentQuality + 1 + i); } } } public sealed class RngProfile { public static RngProfile Unchecked { get; } = new RngProfile(verified: false, "not self-tested"); public bool Verified { get; } public string Description { get; } public RngProfile(bool verified, string description) { Verified = verified; Description = description ?? string.Empty; } } public interface IForgePredictionProvider { string Id { get; } string DisplayName { get; } bool CanPredict(ForgeContext context); PredictionResult Predict(ForgeContext context); } }
plugins/ForgeOracleClient.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ForgeOracle.Core.Abstractions; using ForgeOracle.Core.Items; using ForgeOracle.Core.Prediction; using ForgeOracle.Core.Random; using ForgeOracle.Plugin.Compatibility; using ForgeOracle.Plugin.Configuration; using ForgeOracle.Plugin.Diagnostics; using ForgeOracle.Plugin.Game; using ForgeOracle.Plugin.Localization; using ForgeOracle.Plugin.Patches; using ForgeOracle.Plugin.Prediction; using ForgeOracle.Plugin.UI; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Rodrigo Pistón")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Rodrigo Pistón. Portions adapted from Forge Oracle, Copyright (c) 2026 papptimus (MIT).")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b339d12d4618f268b5a0cd2061fd0e95979a5439")] [assembly: AssemblyProduct("ForgeOracleClient")] [assembly: AssemblyTitle("ForgeOracleClient")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ForgeOracle.Plugin { [BepInPlugin("heido.valheim.forgeoracleclient", "ForgeOracleClient", "1.0.0")] public sealed class ForgeOracleClientPlugin : BaseUnityPlugin { public const string Guid = "heido.valheim.forgeoracleclient"; public const string Name = "ForgeOracleClient"; public const string Version = "1.0.0"; private static readonly HashSet<string> Reported = new HashSet<string>(); private static ManualLogSource? _log; private Harmony? _harmony; internal static PluginRuntime? Runtime { get; private set; } internal static void ReportError(string site, Exception ex) { if (Reported.Add(site + ex.GetType().FullName)) { ManualLogSource? log = _log; if (log != null) { log.LogError((object)$"{site}: {ex}"); } } } private void Awake() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) _log = ((BaseUnityPlugin)this).Logger; if (Application.isBatchMode) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Headless (server) process: Forge Oracle is client-only and stays idle."); return; } PluginConfig pluginConfig = new PluginConfig(((BaseUnityPlugin)this).Config); RngSelfTestResult rng = (pluginConfig.RngSelfTest.Value ? RngSelfTest.Run() : RngSelfTestResult.Skipped); LogSelfTest(rng); Runtime = new PluginRuntime(pluginConfig, rng, "heido.valheim.forgeoracleclient", ((BaseUnityPlugin)this).Logger); _harmony = new Harmony("heido.valheim.forgeoracleclient"); _harmony.PatchAll(typeof(InventoryGuiPatches)); _harmony.PatchAll(typeof(DoCraftingObserver)); _harmony.PatchAll(typeof(RollObserver)); ConsoleCommands.Register(Runtime, ((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded (game {2}).", "ForgeOracleClient", "1.0.0", Version.CurrentVersion)); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Runtime?.Controller.Unload(); Runtime = null; } private void LogSelfTest(RngSelfTestResult rng) { if (rng.Passed || !rng.Ran) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("RNG self-test: " + rng)); } else { ((BaseUnityPlugin)this).Logger.LogError((object)("RNG self-test FAILED, predictions are disabled: " + rng)); } } } internal sealed class PluginRuntime { public PluginConfig Config { get; } public RngSelfTestResult Rng { get; set; } public Localizer Localizer { get; } public PredictionPipeline Pipeline { get; } public PredictionController Controller { get; } public ConfirmationGuard Confirmation { get; } public AttemptRecorder Recorder { get; } public PluginRuntime(PluginConfig config, RngSelfTestResult rng, string harmonyId, ManualLogSource log) { Config = config; Rng = rng; Localizer = new Localizer(config); Pipeline = new PredictionPipeline(harmonyId, () => Rng); Controller = new PredictionController(config, Localizer, Pipeline, () => Rng, log); Confirmation = new ConfirmationGuard(config, Localizer, Controller); Recorder = new AttemptRecorder(log); } public void BeginAttempt(InventoryGui gui, Player player) { if (Config.ValidationLog.Value) { Recorder.Begin(gui, player, Pipeline); } } public void ObserveRoll(Player player, int hash, float roll) { Recorder.OnRoll(player, hash, roll); Controller.Invalidate(afterAttempt: true); } public void EndAttempt(Player player) { Recorder.End(player); Controller.Invalidate(afterAttempt: true); } } } namespace ForgeOracle.Plugin.UI { internal sealed class CompactLine { private string? _prefix; private GlyphSet? _glyphs; public void Apply(ViewModel model, InventoryGui gui, bool visible, float scale) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!visible) { _prefix = null; return; } GlyphSet valueOrDefault = _glyphs.GetValueOrDefault(); GlyphSet num; if (!_glyphs.HasValue) { valueOrDefault = Glyphs.Detect(gui.m_recipeDecription.font); _glyphs = valueOrDefault; num = valueOrDefault; } else { num = valueOrDefault; } GlyphSet set = num; string text = Glyphs.For(model.Status, set); int num2 = Mathf.RoundToInt(112f * scale); int num3 = Mathf.RoundToInt(80f * scale); string text2 = ((model.CompactNote.Length == 0) ? string.Empty : string.Format(" <size={0}%><color={1}>· {2}</color></size>", num3, "#A8A8A8", model.CompactNote)); _prefix = $"<size={num2}%><b><color={model.CompactColor}>{text} {model.CompactText}</color></b></size>" + text2 + "\n"; } public void Clear() { _prefix = null; } public void Decorate(InventoryGui gui) { TMP_Text recipeDecription = gui.m_recipeDecription; if (_prefix != null && ((Behaviour)recipeDecription).enabled) { recipeDecription.text = _prefix + recipeDecription.text; } } } internal sealed class DetailPanel { public const string ObjectName = "ForgeOracle_Panel"; private const float Width = 340f; private const float Gap = 12f; private readonly RectTransform _root; private readonly Image _background; private readonly Color _backgroundColor; private readonly Image _icon; private readonly TextMeshProUGUI _title; private readonly TextMeshProUGUI _body; private readonly TextMeshProUGUI _forecast; private readonly TextMeshProUGUI _expand; private readonly TextMeshProUGUI _mode; private readonly UITooltip? _modeTooltip; private static readonly Vector3[] Corners = (Vector3[])(object)new Vector3[4]; private PanelSide _side = PanelSide.Right; public bool Alive => (Object)(object)_root != (Object)null; private DetailPanel(RectTransform root, PanelParts parts) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) _root = root; _background = parts.Background; _backgroundColor = ((Graphic)parts.Background).color; _icon = parts.Icon; _title = parts.Title; _body = parts.Body; _forecast = parts.Forecast; _expand = parts.Expand; _mode = parts.Mode; _modeTooltip = parts.ModeTooltip; } public static DetailPanel Create(InventoryGui gui, PluginConfig cfg, Action toggleExpand, Action toggleMode) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) Transform val = ((Transform)gui.m_crafting).Find("ForgeOracle_Panel"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } RectTransform val2 = UiFactory.Rect("ForgeOracle_Panel", (Transform)(object)gui.m_crafting); val2.sizeDelta = new Vector2(340f, 0f); Layout(val2); PanelParts panelParts = PanelParts.Build(val2, gui, toggleExpand, toggleMode); DetailPanel panel = new DetailPanel(val2, panelParts); PanelDrag panelDrag = ((Component)panelParts.Title).gameObject.AddComponent<PanelDrag>(); panelDrag.Target = val2; panelDrag.Enabled = () => cfg.Draggable.Value; panelDrag.Dropped = delegate(Vector2 pos) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) cfg.PanelOffset.Value = pos - BasePosition(panel._side); }; return panel; } public void Apply(ViewModel model, PluginConfig cfg, string title, string expandLabel, string modeLabel, string modeTooltip) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) ((Component)_root).gameObject.SetActive(cfg.ShowDetailPanel.Value); if (cfg.ShowDetailPanel.Value) { Place(cfg); UiFactory.SetOpacity(_background, _backgroundColor, cfg.PanelOpacity.Value); ((TMP_Text)_title).text = "<b>" + title + "</b>"; _icon.sprite = model.Icon; ((Behaviour)_icon).enabled = (Object)(object)model.Icon != (Object)null; ((TMP_Text)_body).text = model.PanelBody; ((TMP_Text)_forecast).text = model.Forecast; ((Component)_forecast).gameObject.SetActive(model.Forecast.Length > 0); ((TMP_Text)_expand).text = expandLabel; ((TMP_Text)_mode).text = modeLabel; ((Component)_mode).gameObject.SetActive(cfg.ExpandedLayout.Value); UITooltip? modeTooltip2 = _modeTooltip; if (modeTooltip2 != null) { modeTooltip2.Set(title, cfg.EnableTooltips.Value ? modeTooltip : string.Empty, (RectTransform)null, default(Vector2)); } } } public void SetVisible(bool visible) { if (Alive) { ((Component)_root).gameObject.SetActive(visible); } } public void Destroy() { if (Alive) { Object.Destroy((Object)(object)((Component)_root).gameObject); } } private void Place(PluginConfig cfg) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if (cfg.ResetPanelPosition.Value) { cfg.ResetPanel(); } _side = ((cfg.Side.Value == PanelSide.Auto) ? FreeSide(cfg.UiScale.Value) : cfg.Side.Value); bool flag = _side == PanelSide.Right; RectTransform root = _root; RectTransform root2 = _root; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(flag ? 1f : 0f, 1f); root2.anchorMax = val; root.anchorMin = val; _root.pivot = new Vector2(flag ? 0f : 1f, 1f); _root.anchoredPosition = BasePosition(_side) + cfg.PanelOffset.Value; ((Transform)_root).localScale = Vector3.one * cfg.UiScale.Value; } private PanelSide FreeSide(float scale) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) RectTransform val = (RectTransform)((Transform)_root).parent; Canvas componentInParent = ((Component)val).GetComponentInParent<Canvas>(); if ((Object)(object)componentInParent == (Object)null) { return PanelSide.Right; } Canvas rootCanvas = componentInParent.rootCanvas; Camera val2 = (((int)rootCanvas.renderMode == 0) ? null : rootCanvas.worldCamera); val.GetWorldCorners(Corners); float x = RectTransformUtility.WorldToScreenPoint(val2, Corners[0]).x; float num = (float)Screen.width - RectTransformUtility.WorldToScreenPoint(val2, Corners[2]).x; float num2 = 352f * scale * rootCanvas.scaleFactor; if (num >= num2) { return PanelSide.Right; } if (!(x >= num2) && !(x > num)) { return PanelSide.Right; } return PanelSide.Left; } private static Vector2 BasePosition(PanelSide side) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector2((side == PanelSide.Right) ? 12f : (-12f), 0f); } private static void Layout(RectTransform root) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown VerticalLayoutGroup obj = ((Component)root).gameObject.AddComponent<VerticalLayoutGroup>(); ((LayoutGroup)obj).padding = new RectOffset(14, 14, 12, 12); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 6f; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ContentSizeFitter obj2 = ((Component)root).gameObject.AddComponent<ContentSizeFitter>(); obj2.verticalFit = (FitMode)2; obj2.horizontalFit = (FitMode)0; } } internal sealed class ForgeView { private readonly PluginConfig _cfg; private readonly Localizer _loc; private InventoryGui? _owner; private readonly CompactLine _compact = new CompactLine(); private DetailPanel? _panel; private ViewModel? _model; public ForgeView(PluginConfig cfg, Localizer loc) { _cfg = cfg; _loc = loc; } public void Render(InventoryGui gui, ViewModel model) { EnsureBuilt(gui); PlayWarningSound(gui, model); _model = model; _compact.Apply(model, gui, _cfg.ShowCompact.Value, _cfg.UiScale.Value); string text = _cfg.Forecast.Value.ToString(); _panel.Apply(model, _cfg, _loc.Get("panel.title"), _loc.Get(_cfg.ExpandedLayout.Value ? "panel.collapse" : "panel.expand"), _loc.Get("panel.mode"), _loc.Get("tooltip." + text)); } public void Hide() { _compact.Clear(); _panel?.SetVisible(visible: false); _model = null; } public void Decorate(InventoryGui gui) { _compact.Decorate(gui); } public void Forget(InventoryGui gui) { if (gui == _owner) { _owner = null; _compact.Clear(); _panel = null; _model = null; } } public void Destroy() { _compact.Clear(); _panel?.Destroy(); _owner = null; _panel = null; } private void EnsureBuilt(InventoryGui gui) { if (gui != _owner || _panel == null || !_panel.Alive) { Destroy(); _owner = gui; _panel = DetailPanel.Create(gui, _cfg, delegate { _cfg.ExpandedLayout.Value = !_cfg.ExpandedLayout.Value; }, delegate { _cfg.Forecast.Value = ((_cfg.Forecast.Value != ForecastMode.Progression) ? ForecastMode.Progression : ForecastMode.RepeatedAttempts); }); } } private void PlayWarningSound(InventoryGui gui, ViewModel model) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 bool flag = (int)model.Status == 1 && (_model == null || (int)_model.Status != 1); if (_cfg.EnableSounds.Value && flag) { GameObject val = ((Component)gui.m_craftButton).GetComponent<ButtonSfx>()?.m_sfxPrefab; if ((Object)(object)val != (Object)null) { Object.Instantiate<GameObject>(val); } } } } internal sealed class PanelDrag : MonoBehaviour, IBeginDragHandler, IEventSystemHandler, IDragHandler, IEndDragHandler { public RectTransform? Target { get; set; } public Func<bool>? Enabled { get; set; } public Action<Vector2>? Dropped { get; set; } public void OnBeginDrag(PointerEventData eventData) { } public void OnDrag(PointerEventData eventData) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Target == (Object)null) && (Enabled == null || Enabled())) { Canvas componentInParent = ((Component)Target).GetComponentInParent<Canvas>(); float num = (((Object)(object)componentInParent != (Object)null) ? componentInParent.rootCanvas.scaleFactor : 1f); RectTransform? target = Target; target.anchoredPosition += eventData.delta / Mathf.Max(0.01f, num); } } public void OnEndDrag(PointerEventData eventData) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Target != (Object)null && (Enabled == null || Enabled())) { Dropped?.Invoke(Target.anchoredPosition); } } } internal sealed class PanelParts { public Image Background { get; } public Image Icon { get; } public TextMeshProUGUI Title { get; } public TextMeshProUGUI Body { get; } public TextMeshProUGUI Forecast { get; } public TextMeshProUGUI Expand { get; } public TextMeshProUGUI Mode { get; } public UITooltip? ModeTooltip { get; } private PanelParts(Image background, Image icon, TextMeshProUGUI title, TextMeshProUGUI body, TextMeshProUGUI forecast, TextMeshProUGUI expand, TextMeshProUGUI mode, UITooltip? modeTooltip) { Background = background; Icon = icon; Title = title; Body = body; Forecast = forecast; Expand = expand; Mode = mode; ModeTooltip = modeTooltip; } public static PanelParts Build(RectTransform root, InventoryGui gui, Action toggleExpand, Action toggleMode) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown TMP_Text recipeDecription = gui.m_recipeDecription; Image background = UiFactory.Background(root, gui, 0.9f); RectTransform parent = Row("Header", (Transform)(object)root, 32f); Image val = ((Component)UiFactory.Rect("Icon", (Transform)(object)parent)).gameObject.AddComponent<Image>(); val.preserveAspect = true; Fixed(((Component)val).gameObject, 32f, 32f); TextMeshProUGUI val2 = UiFactory.Text("Title", (Transform)(object)parent, recipeDecription, 20f, (TextAlignmentOptions)513); ((Graphic)val2).raycastTarget = true; Flexible(((Component)val2).gameObject); TextMeshProUGUI body = Paragraph("Body", (Transform)(object)root, recipeDecription, 16f); TextMeshProUGUI forecast = Paragraph("Forecast", (Transform)(object)root, recipeDecription, 15f); RectTransform parent2 = Row("Footer", (Transform)(object)root, 22f); TextMeshProUGUI val3 = UiFactory.TextButton("Expand", (Transform)(object)parent2, recipeDecription, 15f, (UnityAction)delegate { toggleExpand(); }); TextMeshProUGUI val4 = UiFactory.TextButton("Mode", (Transform)(object)parent2, recipeDecription, 15f, (UnityAction)delegate { toggleMode(); }); Flexible(((Component)val3).gameObject); Flexible(((Component)val4).gameObject); UITooltip modeTooltip = UiFactory.Tooltip(((Component)val4).gameObject, gui, "Forge Oracle", string.Empty); return new PanelParts(background, val, val2, body, forecast, val3, val4, modeTooltip); } private static TextMeshProUGUI Paragraph(string name, Transform parent, TMP_Text style, float size) { TextMeshProUGUI obj = UiFactory.Text(name, parent, style, size, (TextAlignmentOptions)257); ((TMP_Text)obj).textWrappingMode = (TextWrappingModes)1; return obj; } private static RectTransform Row(string name, Transform parent, float height) { RectTransform obj = UiFactory.Rect(name, parent); HorizontalLayoutGroup obj2 = ((Component)obj).gameObject.AddComponent<HorizontalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)obj2).spacing = 8f; ((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false; ((LayoutGroup)obj2).childAlignment = (TextAnchor)3; Fixed(((Component)obj).gameObject, -1f, height); return obj; } private static void Fixed(GameObject go, float width, float height) { LayoutElement obj = go.AddComponent<LayoutElement>(); obj.preferredWidth = width; obj.preferredHeight = height; obj.minHeight = height; } private static void Flexible(GameObject go) { go.AddComponent<LayoutElement>().flexibleWidth = 1f; } } internal static class UiFactory { public static RectTransform Rect(string name, Transform parent) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown RectTransform val = (RectTransform)new GameObject(name, new Type[1] { typeof(RectTransform) }) { layer = ((Component)parent).gameObject.layer }.transform; ((Transform)val).SetParent(parent, false); return val; } public static TextMeshProUGUI Text(string name, Transform parent, TMP_Text style, float size, TextAlignmentOptions alignment) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI obj = ((Component)Rect(name, parent)).gameObject.AddComponent<TextMeshProUGUI>(); ((TMP_Text)obj).font = style.font; ((TMP_Text)obj).fontSharedMaterial = style.fontSharedMaterial; ((Graphic)obj).color = Color.white; ((TMP_Text)obj).fontSize = size; ((TMP_Text)obj).alignment = alignment; ((TMP_Text)obj).richText = true; ((Graphic)obj).raycastTarget = false; return obj; } public static TextMeshProUGUI TextButton(string name, Transform parent, TMP_Text style, float size, UnityAction onClick) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI val = Text(name, parent, style, size, (TextAlignmentOptions)513); ((Graphic)val).raycastTarget = true; Button obj = ((Component)val).gameObject.AddComponent<Button>(); ((Selectable)obj).targetGraphic = (Graphic)(object)val; ((UnityEvent)obj.onClick).AddListener(onClick); ColorBlock colors = ((Selectable)obj).colors; ((ColorBlock)(ref colors)).highlightedColor = new Color(1f, 0.85f, 0.5f); ((Selectable)obj).colors = colors; return val; } public static Image Background(RectTransform target, InventoryGui gui, float opacity) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) Image val = ((Component)target).gameObject.AddComponent<Image>(); Image val2 = FindPanelImage(gui); if ((Object)(object)val2 != (Object)null) { val.sprite = val2.sprite; val.type = val2.type; ((Graphic)val).material = ((Graphic)val2).material; val.pixelsPerUnitMultiplier = val2.pixelsPerUnitMultiplier; } SetOpacity(val, (Color)(((Object)(object)val2 != (Object)null) ? ((Graphic)val2).color : new Color(0.1f, 0.09f, 0.08f)), opacity); return val; } public static void SetOpacity(Image image, Color baseColor, float opacity) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) ((Graphic)image).color = new Color(baseColor.r, baseColor.g, baseColor.b, Mathf.Clamp01(opacity)); } public static UITooltip? Tooltip(GameObject target, InventoryGui gui, string topic, string text) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) UITooltip component = ((Component)gui.m_craftButton).GetComponent<UITooltip>(); if ((Object)(object)component == (Object)null || (Object)(object)component.m_tooltipPrefab == (Object)null) { return null; } UITooltip obj = target.AddComponent<UITooltip>(); obj.m_tooltipPrefab = component.m_tooltipPrefab; obj.Set(topic, text, (RectTransform)null, default(Vector2)); return obj; } private static Image? FindPanelImage(InventoryGui gui) { Image component = ((Component)gui.m_crafting).GetComponent<Image>(); if ((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null) { return component; } Image[] componentsInChildren = ((Component)gui.m_crafting).GetComponentsInChildren<Image>(true); foreach (Image val in componentsInChildren) { string text = ((Object)((Component)val).gameObject).name.ToLowerInvariant(); if ((Object)(object)val.sprite != (Object)null && (text.Contains("bkg") || text.Contains("background"))) { return val; } } return null; } } internal sealed class ViewModel { public string CompactNote { get; set; } = string.Empty; public PredictionStatus Status { get; } public string CompactText { get; } public string CompactColor { get; } public string PanelBody { get; } public string Forecast { get; } public Sprite? Icon { get; set; } public ViewModel(PredictionStatus status, string compactText, string compactColor, string panelBody, string forecast) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) Status = status; CompactText = compactText; CompactColor = compactColor; PanelBody = panelBody; Forecast = forecast; } } internal static class Palette { public const string Muted = "#A8A8A8"; public const string Warning = "#FFCC66"; public static string For(PredictionStatus status, bool colorblind) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected I4, but got Unknown switch ((int)status) { case 0: if (!colorblind) { return "#6BD66B"; } return "#4DA6FF"; case 1: if (!colorblind) { return "#FF5C5C"; } return "#FF9900"; case 2: if (!colorblind) { return "#FFB34D"; } return "#FFE066"; case 3: return "#D0D0D0"; default: return "#9A9A9A"; } } } internal enum GlyphSet { Symbols, Latin } internal static class Glyphs { private const string Symbols = "▲✖▼■"; public static GlyphSet Detect(TMP_FontAsset? font) { uint[] array = default(uint[]); if (!((Object)(object)font != (Object)null) || !font.HasCharacters("▲✖▼■", ref array, true, false)) { return GlyphSet.Latin; } return GlyphSet.Symbols; } public static string For(PredictionStatus status, GlyphSet set) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected I4, but got Unknown bool flag = set == GlyphSet.Symbols; switch ((int)status) { case 0: if (!flag) { return "»"; } return "▲"; case 1: if (!flag) { return "×"; } return "✖"; case 2: if (!flag) { return "«"; } return "▼"; case 3: if (!flag) { return "="; } return "■"; default: return "?"; } } } internal sealed class ViewModelBuilder { private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture; private readonly PluginConfig _cfg; private readonly Localizer _loc; public ViewModelBuilder(PluginConfig cfg, Localizer loc) { _cfg = cfg; _loc = loc; } public ViewModel Build(PredictionResult result, ForgeSelection? selection, string? forecastNoteKey, RngSelfTestResult rng) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) bool flag = _cfg.HideWhenUnverified.Value && (int)result.Confidence > 0; if (!result.IsKnown || flag || result.Next == null) { string reason = (flag ? _loc.Format("warn.unverified", ConfidenceText(result.Confidence)) : _loc.Get("reason." + ((object)result.Reason/*cast due to .constrained prefix*/).ToString())); return Unavailable(result, selection, reason); } AttemptForecast next = result.Next; string compactText = CompactKey(next); string compactColor = Palette.For(next.Status, _cfg.ColorblindPalette.Value); string panelBody = string.Join("\n", KnownLines(result, next, selection, rng).ToArray()); return new ViewModel(next.Status, compactText, compactColor, panelBody, Forecast(result, forecastNoteKey)) { Icon = ((selection != null) ? selection.Item.GetIcon() : null), CompactNote = (_cfg.ShowConfidence.Value ? ConfidenceText(result.Confidence) : string.Empty) }; } public ViewModel Stale(ForgeSelection? selection) { string compactText = _loc.Get("compact.unavailable"); string panelBody = Header(selection) + "\n\n" + _loc.Get("reason.Stale"); string compactColor = Palette.For((PredictionStatus)4, colorblind: false); return new ViewModel((PredictionStatus)4, compactText, compactColor, panelBody, string.Empty); } private ViewModel Unavailable(PredictionResult result, ForgeSelection? selection, string reason) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 string key = (((int)result.Reason == 7) ? "compact.modified" : "compact.unavailable"); List<string> list = new List<string> { Header(selection), string.Empty, reason }; list.Add(_loc.Format("panel.provider", result.ProviderName)); AddIssues(list, result.Issues, (IssueSeverity)1); string compactColor = Palette.For((PredictionStatus)4, colorblind: false); return new ViewModel((PredictionStatus)4, _loc.Get(key), compactColor, string.Join("\n", list.ToArray()), "") { Icon = ((selection != null) ? selection.Item.GetIcon() : null) }; } private List<string> KnownLines(PredictionResult result, AttemptForecast next, ForgeSelection? selection, RngSelfTestResult rng) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string> { Header(selection), string.Empty, "<b>" + _loc.Get("panel.next") + "</b>" }; string text = Palette.For(next.Status, _cfg.ColorblindPalette.Value); list.Add("<color=" + text + "><b>" + Outcome(next) + "</b></color>"); if (_cfg.ShowExactRoll.Value && result.Rules != null) { Localizer loc = _loc; object[] array = new object[1]; UpgradeRoll roll = next.Roll; array[0] = ((UpgradeRoll)(ref roll)).Roll.ToString("0.000000", Invariant); list.Add(loc.Format("panel.roll", array)); list.Add(_loc.Format("panel.threshold", result.Rules.SuccessChance.ToString("0.000000", Invariant))); list.Add(BreakLine(result.Rules)); } if (_cfg.ShowConfidence.Value) { list.Add(_loc.Format("panel.confidence", ConfidenceText(result.Confidence))); } list.Add(_loc.Format("panel.provider", result.ProviderName)); list.Add("<color=#A8A8A8>" + _loc.Get("panel.seed." + ((object)next.SeedOrigin/*cast due to .constrained prefix*/).ToString()) + "</color>"); if (!rng.Passed) { list.Add("<color=#FFCC66>! " + _loc.Format("warn.unverified", _loc.Get("warn.rng")) + "</color>"); } AddIssues(list, result.Issues, (IssueSeverity)1); return list; } private string Forecast(PredictionResult result, string? noteKey) { if (!_cfg.ExpandedLayout.Value || result.Next == null) { return string.Empty; } ForecastMode value = _cfg.Forecast.Value; IReadOnlyList<AttemptForecast> readOnlyList = ((value == ForecastMode.Progression) ? result.Progression : result.RepeatedAttempts); string text = _loc.Format("panel.forecast." + value, result.Next.TargetQuality); List<string> list = new List<string> { "<b>" + text + "</b>" }; foreach (AttemptForecast item in readOnlyList) { list.Add(Row(item)); } if (noteKey != null) { list.Add("<color=#A8A8A8><size=85%>" + _loc.Get(noteKey) + "</size></color>"); } return string.Join("\n", list.ToArray()); } private string Row(AttemptForecast row) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) string text = Palette.For(row.Status, _cfg.ColorblindPalette.Value); string text2 = $"{row.Index + 1,2}. +{row.TargetQuality} <color={text}>{Outcome(row)}</color>"; UpgradeRoll roll; if (_cfg.ShowExactRoll.Value) { string text3 = text2; roll = row.Roll; text2 = text3 + " " + ((UpgradeRoll)(ref roll)).Roll.ToString("0.0000", Invariant); } if (_cfg.ShowSeedTransitions.Value) { string text4 = text2; roll = row.Roll; object arg = ((UpgradeRoll)(ref roll)).Seed; roll = row.Roll; text2 = text4 + string.Format(" <color={0}>{1} → {2}</color>", "#A8A8A8", arg, ((UpgradeRoll)(ref roll)).NextSeed); } if (!row.Conditional) { return text2; } return "<color=#A8A8A8><i>" + text2 + " (" + _loc.Get("panel.forecast.conditional") + ")</i></color>"; } private string Header(ForgeSelection? selection) { if (selection == null) { return "<b>" + _loc.Get("panel.title") + "</b>"; } string arg = Localization.instance.Localize(selection.Item.m_shared.m_name); string arg2 = _loc.Format("panel.target", selection.TargetQuality); string text = $"<b>{arg} +{selection.CurrentQuality}</b>\n{arg2}"; if (_cfg.ShowPrefabId.Value) { string name = ((Object)((Component)selection.Recipe.m_item).gameObject).name; text = text + "\n<color=#A8A8A8><size=85%>" + name + "</size></color>"; } return text; } private string BreakLine(UpgradeRules rules) { if (rules.Failure.Classifier != null) { return "<color=#A8A8A8>" + rules.Failure.Description + "</color>"; } return _loc.Format("panel.break", rules.BreakChance.ToString("0.000000", Invariant)); } private void AddIssues(List<string> lines, IReadOnlyList<CompatibilityIssue> issues, IssueSeverity minimum) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) foreach (CompatibilityIssue issue in issues) { if (issue.Severity >= minimum) { lines.Add("<color=#FFCC66><size=85%>! " + issue.Detail + "</size></color>"); } } } private string CompactKey(AttemptForecast next) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected I4, but got Unknown PredictionStatus status = next.Status; return (int)status switch { 0 => _loc.Get("compact.success"), 1 => _loc.Get("compact.destroyed"), 2 => _loc.Format("compact.levelloss", next.LevelsLost), _ => _loc.Get("compact.nochange"), }; } private string Outcome(AttemptForecast row) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown PredictionStatus status = row.Status; return (int)status switch { 0 => _loc.Get("outcome.success"), 1 => _loc.Get("outcome.destroyed"), 2 => _loc.Format("outcome.levelloss", row.LevelsLost), 3 => _loc.Get("outcome.nochange"), _ => _loc.Get("outcome.unknown"), }; } private unsafe string ConfidenceText(PredictionConfidence confidence) { return _loc.Get("confidence." + ((object)(*(PredictionConfidence*)(&confidence))/*cast due to .constrained prefix*/).ToString()); } } } namespace ForgeOracle.Plugin.Prediction { internal sealed class ConfirmationGuard { private const float WindowSeconds = 5f; private readonly PluginConfig _cfg; private readonly Localizer _loc; private readonly PredictionController _controller; private object? _armedItem; private float _armedAt = float.NegativeInfinity; public ConfirmationGuard(PluginConfig cfg, Localizer loc, PredictionController controller) { _cfg = cfg; _loc = loc; _controller = controller; } public bool ShouldBlock(InventoryGui gui) { ItemData itemData = ((RecipeDataPair)(ref gui.m_selectedRecipe)).ItemData; if (!_cfg.ConfirmDestruction.Value || itemData == null || !PredictsDestruction(itemData)) { return false; } bool flag = itemData == _armedItem && Time.unscaledTime - _armedAt <= 5f; _armedItem = (flag ? null : itemData); _armedAt = (flag ? float.NegativeInfinity : Time.unscaledTime); if (!flag) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, _loc.Get("confirm.destroy"), 0, (Sprite)null, false); } } return !flag; } private bool PredictsDestruction(ItemData item) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 if (_controller.Current != null && (int)_controller.Current.Status == 1) { return _controller.CurrentSelection?.Item == item; } return false; } } internal sealed class PredictionController { private readonly PluginConfig _cfg; private readonly Localizer _loc; private readonly PredictionPipeline _pipeline; private readonly ForgeView _view; private readonly ViewModelBuilder _builder; private readonly Func<RngSelfTestResult> _rng; private readonly ManualLogSource _log; private SelectionKey _key; private int _generation; private bool _stale; public PredictionResult? Current { get; private set; } public ForgeSelection? CurrentSelection { get; private set; } public PredictionPipeline Pipeline => _pipeline; public PredictionController(PluginConfig cfg, Localizer loc, PredictionPipeline pipeline, Func<RngSelfTestResult> rng, ManualLogSource log) { _cfg = cfg; _loc = loc; _pipeline = pipeline; _rng = rng; _log = log; _view = new ForgeView(cfg, loc); _builder = new ViewModelBuilder(cfg, loc); } public void Invalidate(bool afterAttempt) { _generation++; _stale |= afterAttempt && !_cfg.RefreshOnStateChanges.Value; } public void Refresh() { _stale = false; _generation++; } public void OnUpdateRecipe(InventoryGui gui, Player player) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) _loc.Refresh(); if (!_cfg.Enabled.Value || (Object)(object)ForgeSelection.CurrentUpgrader(player) == (Object)null) { Hide(); return; } KeyboardShortcut value = _cfg.ToggleHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { _cfg.ShowDetailPanel.Value = !_cfg.ShowDetailPanel.Value; } SelectionKey key = SelectionKey.Read(gui, player, _generation, _cfg.Version, _loc.Version); if (!key.Equals(_key)) { _stale &= key.SameRecipe(_key); _key = key; Recompute(gui, player); } _view.Decorate(gui); } public void Hide() { _view.Hide(); _key = default(SelectionKey); } public void OnGuiDestroyed(InventoryGui gui) { _view.Forget(gui); } public void Unload() { _view.Destroy(); } private void Recompute(InventoryGui gui, Player player) { ForgeSelection selection; SelectionState selectionState = ForgeSelection.FromGui(gui, player, out selection); CurrentSelection = selection; if (selectionState == SelectionState.NoSelection) { Current = null; _view.Hide(); } else { ViewModel model = (_stale ? _builder.Stale(selection) : BuildModel(selectionState, selection)); _view.Render(gui, model); } } private ViewModel BuildModel(SelectionState state, ForgeSelection? selection) { PredictionResult result = (Current = ((state == SelectionState.Ready && selection != null) ? Predict(selection) : _pipeline.Unknown((UnknownReason)4))); string forecastNoteKey = _pipeline.LastEnvironment?.Resolver.ForecastNoteKey; return _builder.Build(result, selection, forecastNoteKey, _rng()); } private PredictionResult Predict(ForgeSelection selection) { try { PredictionResult result = _pipeline.Predict(selection, _cfg.ClampedCount); LogResult(selection, result); return result; } catch (Exception ex) { ManualLogSource log = _log; ItemDrop item = selection.Recipe.m_item; log.LogError((object)$"Prediction failed for {((item != null) ? ((Object)item).name : null)}: {ex}"); return _pipeline.Unknown((UnknownReason)9, ex.Message); } } private void LogResult(ForgeSelection selection, PredictionResult result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) if ((int)result.Reason == 9) { _log.LogError((object)("Provider " + result.ProviderId + " failed: " + result.Error)); } else if (_cfg.DebugLogging.Value) { ManualLogSource log = _log; string text = $"Predicted {((Object)selection.Recipe.m_item).name} +{selection.TargetQuality}: {result.Status} "; object arg = result.Confidence; string providerId = result.ProviderId; AttemptForecast next = result.Next; float? obj; if (next == null) { obj = null; } else { UpgradeRoll roll = next.Roll; obj = ((UpgradeRoll)(ref roll)).Roll; } log.LogInfo((object)(text + $"({arg}, {providerId}, roll {obj:R})")); } } } internal sealed class PredictionPipeline { private readonly string _harmonyId; private readonly Func<RngSelfTestResult> _rng; public ForgeEnvironment? LastEnvironment { get; private set; } public PredictionPipeline(string harmonyId, Func<RngSelfTestResult> rng) { _harmonyId = harmonyId; _rng = rng; } public PredictionResult Unknown(UnknownReason reason, string error = "") { //IL_0016: Unknown result type (might be due to invalid IL or missing references) ForgeEnvironment forgeEnvironment = LastEnvironment ?? ForgeEnvironment.Detect(_harmonyId); return PredictionResult.Unknown(reason, forgeEnvironment.Provider.Id, forgeEnvironment.Provider.DisplayName, forgeEnvironment.Issues, error); } public PredictionResult Predict(ForgeSelection selection, int count) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) ForgeEnvironment forgeEnvironment = (LastEnvironment = ForgeEnvironment.Detect(_harmonyId)); RngSelfTestResult rngSelfTestResult = _rng(); if (rngSelfTestResult.Ran && !rngSelfTestResult.Passed) { return PredictionResult.Unknown((UnknownReason)8, forgeEnvironment.Provider.Id, forgeEnvironment.Provider.DisplayName, forgeEnvironment.Issues, rngSelfTestResult.ToString()); } string problem; Dictionary<int, UpgradeRules> dictionary = ResolveRules(forgeEnvironment.Resolver, selection, count, out problem); if (!dictionary.ContainsKey(selection.TargetQuality)) { return PredictionResult.Unknown((UnknownReason)6, forgeEnvironment.Provider.Id, forgeEnvironment.Provider.DisplayName, forgeEnvironment.Issues, problem); } ForgeContext val = BuildContext(selection, dictionary, rngSelfTestResult, count, Issues(forgeEnvironment, selection)); return new ForgePredictionService((IReadOnlyList<IForgePredictionProvider>)(object)new IForgePredictionProvider[1] { forgeEnvironment.Provider }).Predict(val); } private static ForgeContext BuildContext(ForgeSelection selection, Dictionary<int, UpgradeRules> rules, RngSelfTestResult rng, int count, IReadOnlyList<CompatibilityIssue> issues) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown ItemIdentity val = selection.Identity(); SeedSnapshot val2 = LiveSeeds.Snapshot(selection.Player, val, selection.CurrentQuality, count); return new ForgeContext(val, selection.CurrentQuality, selection.Player.GetPlayerName(), val2, (IReadOnlyDictionary<int, UpgradeRules>)rules, rng.ToProfile(), count, issues); } private static Dictionary<int, UpgradeRules> ResolveRules(IRulesResolver resolver, ForgeSelection selection, int count, out string problem) { Dictionary<int, UpgradeRules> dictionary = new Dictionary<int, UpgradeRules>(); problem = string.Empty; UpgradeRules rules; for (int i = selection.TargetQuality; i < selection.TargetQuality + count && resolver.TryResolve(selection, i, out rules, out problem); i++) { if (rules == null) { break; } dictionary[i] = rules; } return dictionary; } private static IReadOnlyList<CompatibilityIssue> Issues(ForgeEnvironment env, ForgeSelection selection) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown List<CompatibilityIssue> list = new List<CompatibilityIssue>(env.Issues); if ((Object)(object)selection.Idol != (Object)null && selection.Idol.m_itemData.m_shared.m_maxQuality > 1 && env.Resolver.PluginGuid == "MidnightsFX.ImpactfulSkills") { list.Add(new CompatibilityIssue("idol-quality-scaling", (IssueSeverity)1, "MidnightsFX.ImpactfulSkills", "This idol has quality levels; ImpactfulSkills may raise the result.")); } return list; } } internal readonly struct SelectionKey : IEquatable<SelectionKey> { private readonly int _recipe; private readonly int _item; private readonly int _quality; private readonly int _player; private readonly int _generation; private readonly int _config; private readonly int _language; private SelectionKey(int recipe, int item, int quality, int player, int generation, int config, int language) { _recipe = recipe; _item = item; _quality = quality; _player = player; _generation = generation; _config = config; _language = language; } public static SelectionKey Read(InventoryGui gui, Player player, int generation, int config, int language) { Recipe recipe = ((RecipeDataPair)(ref gui.m_selectedRecipe)).Recipe; ItemData itemData = ((RecipeDataPair)(ref gui.m_selectedRecipe)).ItemData; return new SelectionKey(((Object)(object)recipe != (Object)null) ? ((Object)recipe).GetInstanceID() : 0, (itemData != null) ? RuntimeHelpers.GetHashCode(itemData) : 0, itemData?.m_quality ?? (-1), ((Object)player).GetInstanceID(), generation, config, language); } public bool SameRecipe(SelectionKey other) { if (_recipe == other._recipe) { return _player == other._player; } return false; } public bool Equals(SelectionKey other) { if (SameRecipe(other) && _item == other._item && _quality == other._quality && _generation == other._generation && _config == other._config) { return _language == other._language; } return false; } public override bool Equals(object? obj) { if (obj is SelectionKey other) { return Equals(other); } return false; } public override int GetHashCode() { return _recipe ^ (_item * 397) ^ _generation; } } } namespace ForgeOracle.Plugin.Patches { [HarmonyPatch(typeof(InventoryGui))] internal static class InventoryGuiPatches { [HarmonyPostfix] [HarmonyPatch("UpdateRecipe")] [HarmonyPriority(0)] private static void UpdateRecipePostfix(InventoryGui __instance, Player player) { try { ForgeOracleClientPlugin.Runtime?.Controller.OnUpdateRecipe(__instance, player); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("UpdateRecipe", ex); } } [HarmonyPostfix] [HarmonyPatch("Hide")] private static void HidePostfix() { try { ForgeOracleClientPlugin.Runtime?.Controller.Hide(); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("Hide", ex); } } [HarmonyPostfix] [HarmonyPatch("OnDestroy")] private static void OnDestroyPostfix(InventoryGui __instance) { try { ForgeOracleClientPlugin.Runtime?.Controller.OnGuiDestroyed(__instance); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("OnDestroy", ex); } } [HarmonyPrefix] [HarmonyPatch("OnCraftPressed")] private static bool OnCraftPressedPrefix(InventoryGui __instance) { try { PluginRuntime? runtime = ForgeOracleClientPlugin.Runtime; return runtime == null || !runtime.Confirmation.ShouldBlock(__instance); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("OnCraftPressed", ex); return true; } } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class DoCraftingObserver { [HarmonyPrefix] [HarmonyPriority(1000)] private static void Prefix(InventoryGui __instance, Player player) { try { ForgeOracleClientPlugin.Runtime?.BeginAttempt(__instance, player); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("DoCrafting prefix", ex); } } [HarmonyPostfix] private static void Postfix(Player player) { try { ForgeOracleClientPlugin.Runtime?.EndAttempt(player); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("DoCrafting postfix", ex); } } } [HarmonyPatch(typeof(Player), "GetItemUpgradeChance")] internal static class RollObserver { [HarmonyPostfix] private static void Postfix(Player __instance, int hash, float __result) { try { ForgeOracleClientPlugin.Runtime?.ObserveRoll(__instance, hash, __result); } catch (Exception ex) { ForgeOracleClientPlugin.ReportError("GetItemUpgradeChance postfix", ex); } } } } namespace ForgeOracle.Plugin.Localization { internal sealed class Localizer { private readonly PluginConfig _config; private Dictionary<string, string> _active = Texts.English; public int Version { get; private set; } public Localizer(PluginConfig config) { _config = config; } public void Refresh() { Localization instance = Localization.instance; string gameLanguage = ((instance != null) ? instance.GetSelectedLanguage() : null) ?? string.Empty; Dictionary<string, string> dictionary = Resolve(_config.Language.Value, gameLanguage); if (dictionary != _active) { _active = dictionary; Version++; } } public string Get(string key) { if (_active.TryGetValue(key, out string value) || Texts.English.TryGetValue(key, out value)) { return value; } return key; } public string Format(string key, params object[] args) { return string.Format(CultureInfo.InvariantCulture, Get(key), args); } private static Dictionary<string, string> Resolve(LanguageOption option, string gameLanguage) { switch (option) { case LanguageOption.English: return Texts.English; case LanguageOption.Spanish: return Texts.Spanish; default: if (!(gameLanguage == "Spanish")) { return Texts.English; } return Texts.Spanish; } } } internal static class Texts { public static readonly Dictionary<string, string> English = new Dictionary<string, string> { ["compact.success"] = "NEXT UPGRADE: SUCCESS", ["compact.destroyed"] = "NEXT UPGRADE: ITEM DESTROYED", ["compact.levelloss"] = "NEXT UPGRADE: LEVEL -{0}", ["compact.nochange"] = "NEXT UPGRADE: NO CHANGE", ["compact.unavailable"] = "PREDICTION UNAVAILABLE", ["compact.modified"] = "PREDICTION UNAVAILABLE — MODIFIED FORGE ALGORITHM", ["outcome.success"] = "SUCCESS", ["outcome.destroyed"] = "ITEM DESTROYED", ["outcome.levelloss"] = "LEVEL -{0}", ["outcome.nochange"] = "NO CHANGE", ["outcome.unknown"] = "UNKNOWN", ["confidence.Verified"] = "VERIFIED", ["confidence.Unverified"] = "UNVERIFIED", ["confidence.Unsupported"] = "UNSUPPORTED", ["panel.title"] = "FORGE ORACLE", ["panel.target"] = "Target level: +{0}", ["panel.next"] = "NEXT ATTEMPT", ["panel.roll"] = "Roll: {0}", ["panel.threshold"] = "Threshold: {0}", ["panel.break"] = "Break threshold: {0}", ["panel.confidence"] = "Confidence: {0}", ["panel.provider"] = "Provider: {0}", ["panel.seed.Stored"] = "Seed: stored on character", ["panel.seed.DerivedFromName"] = "Seed: first attempt (from character name)", ["panel.forecast.RepeatedAttempts"] = "NEXT ATTEMPTS AT +{0}", ["panel.forecast.Progression"] = "LEVEL BY LEVEL FROM +{0}", ["panel.forecast.conditional"] = "only if the earlier step succeeds", ["panel.forecast.skilldrift"] = "Later rows assume your current forging skill.", ["panel.mode"] = "[switch view]", ["panel.expand"] = "[+] forecast", ["panel.collapse"] = "[−] forecast", ["tooltip.RepeatedAttempts"] = "What the next attempts at this exact level would give, one after another, with any copy of this item. Rows after the first only happen if you try again at this level.", ["tooltip.Progression"] = "The first attempt at each higher level, assuming every earlier step succeeds. Rows after a predicted failure are hypothetical: the item would not reach them.", ["reason.NoUpgraderResource"] = "This recipe has no Forge of Potential idol.", ["reason.MissingPlayerName"] = "The character name is not available yet.", ["reason.MissingThresholds"] = "The forge chances could not be read.", ["reason.ModifiedAlgorithm"] = "Another mod replaces the forge roll.", ["reason.RngMismatch"] = "The RNG self-test failed on this game version.", ["reason.ProviderError"] = "Prediction failed (see the BepInEx log).", ["reason.Stale"] = "Attempt made; select the item again or run 'forgeoracle refresh'.", ["warn.unverified"] = "Not verified: {0}", ["warn.rng"] = "RNG self-test did not pass", ["confirm.destroy"] = "Forge Oracle: the next attempt is predicted to DESTROY this item. Click again to proceed." }; public static readonly Dictionary<string, string> Spanish = new Dictionary<string, string> { ["compact.success"] = "PRÓXIMA MEJORA: ÉXITO", ["compact.destroyed"] = "PRÓXIMA MEJORA: OBJETO DESTRUIDO", ["compact.levelloss"] = "PRÓXIMA MEJORA: NIVEL -{0}", ["compact.nochange"] = "PRÓXIMA MEJORA: SIN CAMBIOS", ["compact.unavailable"] = "PREDICCIÓN NO DISPONIBLE", ["compact.modified"] = "PREDICCIÓN NO DISPONIBLE — ALGORITMO DE FORJA MODIFICADO", ["outcome.success"] = "ÉXITO", ["outcome.destroyed"] = "OBJETO DESTRUIDO", ["outcome.levelloss"] = "NIVEL -{0}", ["outcome.nochange"] = "SIN CAMBIOS", ["outcome.unknown"] = "DESCONOCIDO", ["confidence.Verified"] = "VERIFICADA", ["confidence.Unverified"] = "SIN VERIFICAR", ["confidence.Unsupported"] = "NO COMPATIBLE", ["panel.title"] = "ORÁCULO DE LA FORJA", ["panel.target"] = "Nivel objetivo: +{0}", ["panel.next"] = "PRÓXIMO INTENTO", ["panel.roll"] = "Tirada: {0}", ["panel.threshold"] = "Umbral: {0}", ["panel.break"] = "Umbral de rotura: {0}", ["panel.confidence"] = "Confianza: {0}", ["panel.provider"] = "Proveedor: {0}", ["panel.seed.Stored"] = "Semilla: guardada en el personaje", ["panel.seed.DerivedFromName"] = "Semilla: primer intento (del nombre del personaje)", ["panel.forecast.RepeatedAttempts"] = "PRÓXIMOS INTENTOS A +{0}", ["panel.forecast.Progression"] = "NIVEL POR NIVEL DESDE +{0}", ["panel.forecast.conditional"] = "solo si el paso anterior tiene éxito", ["panel.forecast.skilldrift"] = "Las filas siguientes suponen tu habilidad de forja actual.", ["panel.mode"] = "[cambiar vista]", ["panel.expand"] = "[+] pronóstico", ["panel.collapse"] = "[−] pronóstico", ["tooltip.RepeatedAttempts"] = "Lo que darían los próximos intentos a este mismo nivel, uno tras otro, con cualquier copia de este objeto. Las filas siguientes solo ocurren si vuelves a intentarlo.", ["tooltip.Progression"] = "El primer intento en cada nivel superior, suponiendo que cada paso anterior tiene éxito. Tras un fallo previsto las filas son hipotéticas: el objeto no llegaría a ellas.", ["reason.NoUpgraderResource"] = "Esta receta no tiene ídolo de la Forja del Potencial.", ["reason.MissingPlayerName"] = "El nombre del personaje aún no está disponible.", ["reason.MissingThresholds"] = "No se pudieron leer las probabilidades de la forja.", ["reason.ModifiedAlgorithm"] = "Otro mod reemplaza la tirada de la forja.", ["reason.RngMismatch"] = "La autoprueba del RNG falló en esta versión del juego.", ["reason.ProviderError"] = "La predicción falló (ver el registro de BepInEx).", ["reason.Stale"] = "Intento realizado; vuelve a elegir el objeto o usa 'forgeoracle refresh'.", ["warn.unverified"] = "Sin verificar: {0}", ["warn.rng"] = "la autoprueba del RNG no pasó", ["confirm.destroy"] = "Oráculo de la Forja: el próximo intento DESTRUIRÁ este objeto. Haz clic otra vez para continuar." }; } } namespace ForgeOracle.Plugin.Game { internal enum SelectionState { NotAtUpgrader, NoSelection, NoUpgraderResource, Ready } internal sealed class ForgeSelection { public Player Player { get; } public CraftingStation Station { get; } public Recipe Recipe { get; } public ItemData Item { get; } public ItemDrop? Idol { get; } public int CurrentQuality => Item.m_quality; public int TargetQuality => Item.m_quality + 1; private ForgeSelection(Player player, CraftingStation station, Recipe recipe, ItemData item, ItemDrop? idol) { Player = player; Station = station; Recipe = recipe; Item = item; Idol = idol; } public static CraftingStation? CurrentUpgrader(Player player) { CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if (!((Object)(object)currentCraftingStation != (Object)null) || !currentCraftingStation.m_upgrader) { return null; } return currentCraftingStation; } public static SelectionState FromGui(InventoryGui gui, Player player, out ForgeSelection? selection) { return From(player, ((RecipeDataPair)(ref gui.m_selectedRecipe)).Recipe, ((RecipeDataPair)(ref gui.m_selectedRecipe)).ItemData, out selection); } public static SelectionState FromCraft(InventoryGui gui, Player player, out ForgeSelection? selection) { return From(player, gui.m_craftRecipe, gui.m_craftUpgradeItem, out selection); } public ItemIdentity Identity() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown SharedData shared = Recipe.m_item.m_itemData.m_shared; return new ItemIdentity(((Object)((Component)Recipe.m_item).gameObject).name, shared.m_name, shared.m_maxQuality); } private static SelectionState From(Player player, Recipe? recipe, ItemData? item, out ForgeSelection? selection) { selection = null; CraftingStation val = CurrentUpgrader(player); if ((Object)(object)val == (Object)null) { return SelectionState.NotAtUpgrader; } if ((Object)(object)recipe == (Object)null || item == null || (Object)(object)recipe.m_item == (Object)null) { return SelectionState.NoSelection; } ItemDrop val2 = FindIdol(recipe); selection = new ForgeSelection(player, val, recipe, item, val2); if (!((Object)(object)val2 == (Object)null)) { return SelectionState.Ready; } return SelectionState.NoUpgraderResource; } private static ItemDrop? FindIdol(Recipe recipe) { if (recipe.m_resources == null) { return null; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val.m_upgraderResource) { return val.m_resItem; } } return null; } } internal static class LiveSeeds { public static bool TryGet(Player player, int key, out int seed) { Dictionary<int, int> itemUpgradeSeeds = player.m_itemUpgradeSeeds; if (itemUpgradeSeeds == null) { seed = 0; return false; } return itemUpgradeSeeds.TryGetValue(key, out seed); } public static int Count(Player player) { return player.m_itemUpgradeSeeds?.Count ?? 0; } public static SeedSnapshot Snapshot(Player player, ItemIdentity item, int currentQuality, int count) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown return SeedSnapshot.Capture(ForgeContext.KeysFor(item, currentQuality, count), (TryGetSeed)delegate(int key, out int seed) { return TryGet(player, key, out seed); }); } public static int? EffectiveSeed(Player player, int key, out bool stored) { stored = TryGet(player, key, out var seed); if (stored) { return seed; } string playerName = player.GetPlayerName(); if (playerName.Length != 0) { return UpgradeKeys.InitialSeed(playerName, key); } return null; } } } namespace ForgeOracle.Plugin.Diagnostics { internal sealed class AttemptObservation { public DateTime Time { get; } public string Prefab { get; } public int CurrentQuality { get; } public PredictionResult Predicted { get; } public float? ActualRoll { get; set; } public int? ActualNextSeed { get; set; } public float SuccessAtRoll { get; set; } public float BreakAtRoll { get; set; } public PredictionStatus Observed { get; set; } = (PredictionStatus)4; public int ObservedQuality { get; set; } public bool RollMatches { get { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (ActualRoll.HasValue && Predicted.Next != null) { UpgradeRoll roll = Predicted.Next.Roll; return ((UpgradeRoll)(ref roll)).Roll == ActualRoll; } return false; } } public bool SeedMatches { get { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (ActualNextSeed.HasValue && Predicted.Next != null) { UpgradeRoll roll = Predicted.Next.Roll; return ((UpgradeRoll)(ref roll)).NextSeed == ActualNextSeed; } return false; } } public bool OutcomeMatches { get { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (Predicted.IsKnown) { return Predicted.Status == Observed; } return false; } } public bool Matches { get { if (RollMatches && SeedMatches) { return OutcomeMatches; } return false; } } public AttemptObservation(string prefab, int currentQuality, PredictionResult predicted) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) Time = DateTime.Now; Prefab = prefab; CurrentQuality = currentQuality; Predicted = predicted; } public override string ToString() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) CultureInfo invariantCulture = CultureInfo.InvariantCulture; AttemptForecast next = Predicted.Next; object obj; UpgradeRoll roll; if (next == null) { obj = null; } else { roll = next.Roll; obj = ((UpgradeRoll)(ref roll)).Roll.ToString("0.000000", invariantCulture); } if (obj == null) { obj = "-"; } string arg = (string)obj; AttemptForecast next2 = Predicted.Next; object obj2; if (next2 == null) { obj2 = null; } else { roll = next2.Roll; obj2 = ((UpgradeRoll)(ref roll)).NextSeed.ToString(invariantCulture); } if (obj2 == null) { obj2 = "-"; } string arg2 = (string)obj2; string text = ((!Predicted.IsKnown) ? "NOT PREDICTED" : (Matches ? "MATCH" : "MISMATCH")); return $"{Time:HH:mm:ss} {Prefab} +{CurrentQuality}->+{CurrentQuality + 1}: " + $"predicted {Predicted.Status} (roll {arg}, next seed {arg2}, " + $"{Predicted.Confidence}, {Predicted.ProviderId}); observed {Observed} -> q{ObservedQuality} " + "(roll " + (ActualRoll?.ToString("0.000000", invariantCulture) ?? "-") + ", next seed " + (ActualNextSeed?.ToString(invariantCulture) ?? "-") + ", odds " + SuccessAtRoll.ToString("0.0000", invariantCulture) + "/" + BreakAtRoll.ToString("0.0000", invariantCulture) + ") => " + text; } } internal sealed class AttemptRecorder { private sealed class PendingAttempt { public ForgeSelection Selection { get; } public AttemptObservation Observation { get; } public int[] CountsBefore { get; } public PendingAttempt(ForgeSelection selection, AttemptObservation observation, int[] countsBefore) { Selection = selection; Observation = observation; CountsBefore = countsBefore; } } private const int Capacity = 50; private readonly LinkedList<AttemptObservation> _history = new LinkedList<AttemptObservation>(); private readonly ManualLogSource _log; private PendingAttempt? _pending; public IEnumerable<AttemptObservation> History => _history; public int Total { get; private set; } public int Matched { get; private set; } public AttemptRecorder(ManualLogSource log) { _log = log; } public void Begin(InventoryGui gui, Player player, PredictionPipeline pipeline) { _pending = null; if (ForgeSelection.FromCraft(gui, player, out ForgeSelection selection) == SelectionState.Ready && selection != null) { PredictionResult predicted = pipeline.Predict(selection, 1); AttemptObservation observation = new AttemptObservation(((Object)((Component)selection.Recipe.m_item).gameObject).name, selection.CurrentQuality, predicted); _pending = new PendingAttempt(selection, observation, CountByQuality(player, selection)); } } public void OnRoll(Player player, int hash, float roll) { if (_pending != null && player == _pending.Selection.Player) { AttemptObservation observation = _pending.Observation; observation.ActualRoll = roll; observation.ActualNextSeed = (LiveSeeds.TryGet(player, hash, out var seed) ? new int?(seed) : ((int?)null)); if ((Object)(object)_pending.Selection.Idol != (Object)null) { observation.SuccessAtRoll = _pending.Selection.Idol.m_itemData.m_shared.m_upgradeChance; observation.BreakAtRoll = _pending.Selection.Idol.m_itemData.m_shared.m_breakChance; } } } public void End(Player player) { PendingAttempt pending = _pending; _pending = null; if (pending != null && pending.Observation.ActualRoll.HasValue) { Classify(pending, CountByQuality(player, pending.Selection)); Store(pending.Observation); } } private static void Classify(PendingAttempt pending, int[] after) { int currentQuality = pending.Selection.CurrentQuality; for (int num = after.Length - 1; num >= 0; num--) { if (after[num] - pending.CountsBefore[num] + ((num == currentQuality) ? 1 : 0) > 0) { pending.Observation.ObservedQuality = num; pending.Observation.Observed = (PredictionStatus)((num <= currentQuality) ? ((num == currentQuality) ? 3 : 2) : 0); return; } } pending.Observation.Observed = (PredictionStatus)1; } private void Store(AttemptObservation observation) { Total++; Matched += (observation.Matches ? 1 : 0); _history.AddFirst(observation); if (_history.Count > 50) { _history.RemoveLast(); } string text = "[Validation] " + observation; if (observation.Predicted.IsKnown && !observation.Matches) { _log.LogWarning((object)text); } else { _log.LogInfo((object)text); } } private static int[] CountByQuality(Player player, ForgeSelection selection) { int[] array = new int[selection.TargetQuality + 1]; string name = selection.Item.m_shared.m_name; for (int i = 0; i < array.Length; i++) { array[i] = ((Humanoid)player).GetInventory().CountItems(name, i, false); } return array; } } internal static class ConsoleCommands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleOptionsFetcher <>9__1_1; internal List<string> <Register>b__1_1() { return new List<string>(Subcommands); } } private static readonly string[] Subcommands = new string[10] { "status", "item", "predict", "providers", "history", "selftest", "refresh", "debug", "reload", "resetpanel" }; public static void Register(PluginRuntime runtime, ManualLogSource log) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown string text = "[" + string.Join("|", Subcommands) + "] Forge Oracle diagnostics"; ConsoleEvent val = delegate(ConsoleEventArgs args) { Run(runtime, args, log); }; object obj = <>c.<>9__1_1; if (obj == null) { ConsoleOptionsFetcher val2 = () => new List<string>(Subcommands); <>c.<>9__1_1 = val2; obj = (object)val2; } new ConsoleCommand("forgeoracle", text, val, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj, false, false, false); } private static void Run(PluginRuntime runtime, ConsoleEventArgs args, ManualLogSource log) { string text = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status"); try { foreach (string item in Execute(runtime, text, args.Args)) { args.Context.AddString(item); } } catch (Exception ex) { log.LogError((object)$"forgeoracle {text}: {ex}"); args.Context.AddString("forgeoracle: " + ex.Message); } } private static IEnumerable<string> Execute(PluginRuntime runtime, string sub, string[] args) { StatusReport statusReport = new StatusReport(runtime); switch (sub) { case "status": return statusReport.Status(); case "item": return statusReport.Item(); case "predict": { int result; return statusReport.Predict((args.Length > 2 && int.TryParse(args[2], out result)) ? result : 5); } case "providers": return statusReport.Providers(); case "history": return statusReport.History(); case "selftest": return SelfTest(runtime); case "refresh": runtime.Controller.Refresh(); return new string[1] { "forgeoracle: prediction refreshed" }; case "debug": return Toggle(runtime); case "reload": runtime.Config.Reload(); return new string[1] { "forgeoracle: configuration reloaded" }; case "resetpanel": runtime.Config.ResetPanel(); return new string[1] { "forgeoracle: panel position reset" }; default: return new string[1] { "forgeoracle: unknown subcommand. Try: " + string.Join(", ", Subcommands) }; } } private static IEnumerable<string> SelfTest(PluginRuntime runtime) { runtime.Rng = RngSelfTest.Run(); runtime.Controller.Refresh(); return new string[1] { "forgeoracle: RNG self-test " + runtime.Rng }; } private static IEnumerable<string> Toggle(PluginRuntime runtime) { runtime.Config.DebugLogging.Value = !runtime.Config.DebugLogging.Value; return new string[1] { "forgeoracle: debug logging " + (runtime.Config.DebugLogging.Value ? "on" : "off") }; } } internal sealed class RngSelfTestResult { public static RngSelfTestResult Skipped { get; } = new RngSelfTestResult(ran: false, 0, 0, "disabled in config"); public bool Ran { get; } public int Samples { get; } public int Mismatches { get; } public string Detail { get; } public bool Passed { get { if (Ran && Samples > 0) { return Mismatches == 0; } return false; } } public RngSelfTestResult(bool ran, int samples, int mismatches, string detail) { Ran = ran; Samples = samples; Mismatches = mismatches; Detail = detail; } public RngProfile ToProfile() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new RngProfile(Passed, ToString()); } public override string ToString() { if (!Ran) { return "not run (" + Detail + ")"; } return $"{Samples - Mismatches}/{Samples} seeds match Unity ({Detail})"; } } internal static class RngSelfTest { private static readonly int[] EdgeSeeds = new int[7] { 0, 1, -1, -2147483648, 2147483647, 99999998, 12345678 }; public static RngSelfTestResult Run() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) State state = Random.state; int mismatches = 0; string detail = "all equal"; List<int> list = Seeds(); try { foreach (int item in list) { if (!Matches(item, out string detail2) && mismatches++ == 0) { detail = detail2; } } } finally { Random.state = state; } return new RngSelfTestResult(ran: true, list.Count, mismatches, detail); } private static bool Matches(int seed, out string detail) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Random.InitState(seed); float value = Random.value; int num = Random.Range(0, 99999999); UpgradeRoll val = UpgradeRoll.FromSeed(seed); detail = $"seed {seed}: unity {value:R}/{num}, managed {((UpgradeRoll)(ref val)).Roll:R}/{((UpgradeRoll)(ref val)).NextSeed}"; if (value == ((UpgradeRoll)(ref val)).Roll) { return num == ((UpgradeRoll)(ref val)).NextSeed; } return false; } private static List<int> Seeds() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) List<int> list = new List<int>(EdgeSeeds); UnityXorShift128 val = UnityXorShift128.FromSeed(20261028); while (list.Count < 256) { list.Add((int)((UnityXorShift128)(ref val)).NextUInt()); } return list; } } internal sealed class StatusReport { private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture; private readonly PluginRuntime _runtime; public StatusReport(PluginRuntime runtime) { _runtime = runtime; } public IEnumerable<string> Status() { ForgeEnvironment env = ForgeEnvironment.Detect("heido.valheim.forgeoracleclient"); yield return string.Format("Forge Oracle {0}, game {1}", "1.0.0", Version.CurrentVersion); yield return "Enabled: " + _runtime.Config.Enabled.Value; yield return "RNG self-test: " + _runtime.Rng; yield return "Forge implementation: " + env.Provider.DisplayName + " (" + env.Provider.Id + ")"; foreach (string item in IssueLines(env.Issues)) { yield return item; } foreach (string item2 in SelectionSummary()) { yield return item2; } AttemptRecorder recorder = _runtime.Recorder; yield return $"Validated attempts this session: {recorder.Matched}/{recorder.Total} matched"; } public IEnumerable<string> Item() { string why; ForgeSelection sel = Selection(out why); if (sel == null) { yield return why; yield break; } ItemIdentity id = sel.Identity(); yield return $"Prefab: {id.PrefabName} (hash {id.PrefabHash}), token {id.NameToken}, " + "name " + Localization.instance.Localize(id.NameToken); yield return $"Quality {sel.CurrentQuality} -> {sel.TargetQuality} (declared max {id.MaxQuality})"; int num = UpgradeKeys.ComputeKey(id, sel.TargetQuality); bool stored; int? num2 = LiveSeeds.EffectiveSeed(sel.Player, num, out stored); yield return string.Format("Key {0}: seed {1} ", num, num2?.ToString(Invariant) ?? "unavailable") + string.Format("({0}); table has {1} entries", stored ? "stored" : "derived from name", LiveSeeds.Count(sel.Player)); if ((Object)(object)sel.Idol != (Object)null) { SharedData shared = sel.Idol.m_itemData.m_shared; yield return $"Idol {((Object)sel.Idol).name}: upgrade {shared.m_upgradeChance:R}, break {shared.m_breakChance:R}, " + $"refund {shared.m_breakReturnIngreientsAmount:R}"; } } public IEnumerable<string> Predict(int count) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) string why; ForgeSelection forgeSelection = Selection(out why); if (forgeSelection != null) { PredictionResult val = _runtime.Pipeline.Predict(forgeSelection, Math.Max(1, Math.Min(20, count))); List<string> list = new List<string>(); list.Add($"{val.Status} ({val.Confidence}, {val.ProviderName}) {val.Reason}"); list.Add("Repeated attempts at +" + forgeSelection.TargetQuality + ":"); list.AddRange(Rows(val.RepeatedAttempts)); list.Add("Level by level (hypothetical after a predicted failure):"); list.AddRange(Rows(val.Progression)); return list; } return new string[1] { why }; } public IEnumerable<string> Providers() { ForgeEnvironment env = ForgeEnvironment.Detect("heido.valheim.forgeoracleclient"); foreach (IRulesResolver item in ForgeEnvironment.AllResolvers()) { KnownMod knownMod = KnownMods.Find(item.PluginGuid); string text = ((item.PluginGuid.Length == 0) ? "built in" : ModReflection.Version(item.PluginGuid)); yield return string.Format("{0}: active={1}, loaded={2}, ", item.Id, item.IsActive(env.Scan), (text.Length > 0) ? text : "no") + string.Format("audited={0}, live-validated={1}", knownMod?.AuditedVersion ?? "-", LiveValidation.IsValidated(item.PluginGuid)); } foreach (ForeignPatch rollPatch in env.Scan.RollPatches) { yield return "Foreign patch: " + rollPatch; } foreach (ForeignPatch craftPatch in env.Scan.CraftPatches) { yield return "Foreign patch: " + craftPatch; } yield return "Selected: " + env.Provider.DisplayName; } public IEnumerable<string> History() { yield return $"{_runtime.Recorder.Matched}/{_runtime.Recorder.Total} attempts matched this session"; foreach (AttemptObservation item in _runtime.Recorder.History) { yield return item.ToString(); } } private IEnumerable<string> SelectionSummary() { string why; ForgeSelection forgeSelection = Selection(out why); if (forgeSelection == null) { yield return why; yield break; } PredictionResult result = _runtime.Pipeline.Predict(forgeSelection, 1); yield return $"Selected: {((Object)((Component)forgeSelection.Recipe.m_item).gameObject).name} +{forgeSelection.CurrentQuality} -> +{forgeSelection.TargetQuality}"; AttemptForecast next = result.Next; yield return "Seed: " + (((next != null) ? ((object)next.SeedOrigin/*cast due to .constrained prefix*/).ToString() : null) ?? "unavailable"); yield return $"Next attempt: {result.Status}, confidence {result.Confidence} {result.Reason}"; } private static IEnumerable<string> IssueLines(IReadOnlyList<CompatibilityIssue> issues) { foreach (CompatibilityIssue issue in issues) { yield return " " + (object)issue; } } private static IEnumerable<string> Rows(IReadOnlyList<AttemptForecast> rows) { foreach (AttemptForecast row in rows) { UpgradeRoll roll = row.Roll; string text = ((UpgradeRoll)(ref roll)).Roll.ToString("0.000000", Invariant); string text2 = $" {row.Index + 1}. +{row.TargetQuality} {row.Status} roll {text} "; roll = row.Roll; object arg = ((UpgradeRoll)(ref roll)).Seed; roll = row.Roll; yield return text2 + string.Format("seed {0} -> {1} {2}", arg, ((UpgradeRoll)(ref roll)).NextSeed, row.Conditional ? "(conditional)" : string.Empty); } } private static ForgeSelection? Selection(out string why) { Player localPlayer = Player.m_localPlayer; InventoryGui instance = InventoryGui.instance; why = "No forge selection: open a Forge of Potential and select an item."; if ((Object)(object)localPlayer == (Object)null || (Object)(object)instance == (Object)null) { return null; } if (ForgeSelection.FromGui(instance, localPlayer, out ForgeSelection selection) != SelectionState.Ready) { return null; } return selection; } } } namespace ForgeOracle.Plugin.Configuration { internal enum LanguageOption { Auto, English, Spanish } internal enum ForecastMode { RepeatedAttempts, Progression } internal enum PanelSide { Auto, Right, Left } internal sealed class PluginConfig { private readonly ConfigFile _file; public int Version { get; private set; } public ConfigEntry<bool> Enabled { get; } public ConfigEntry<LanguageOption> Language { get; } public ConfigEntry<bool> RefreshOnStateChanges { get; } public ConfigEntry<bool> DebugLogging { get; } public ConfigEntry<KeyboardShortcut> ToggleHotkey { get; } public ConfigEntry<bool> ShowCompact { get; private set; } public ConfigEntry<bool> ShowConfidence { get; private set; } public ConfigEntry<bool> ShowExactRoll { get; private set; } public ConfigEntry<bool> ShowPrefabId { get; private set; } public ConfigEntry<bool> ShowSeedTransitions { get; private set; } public ConfigEntry<bool> EnableTooltips { get; private set; } public ConfigEntry<bool> ColorblindPalette { get; private set; } public ConfigEntry<bool> HideWhenUnverified { get; private set; } public ConfigEntry<bool> ShowDetailPanel { get; private set; } public ConfigEntry<bool> ExpandedLayout { get; private set; } public ConfigEntry<int> PredictionCount { get; private set; } public ConfigEntry<ForecastMode> Forecast { get; private set; } public ConfigEntry<float> PanelOpacity { get; private set; } public ConfigEntry<float> UiScale { get; private set; } public ConfigEntry<PanelSide> Side { get; private set; } public ConfigEntry<bool> Draggable { get; private set; } public ConfigEntry<Vector2> PanelOffset { get; private set; } public ConfigEntry<bool> ResetPanelPosition { get; private set; } public ConfigEntry<bool> ConfirmDestruction { get; private set; } public ConfigEntry<bool> EnableSounds { get; private set; } public ConfigEntry<bool> RngSelfTest { get; private set; } public ConfigEntry<bool> ValidationLog { get; private set; } public int ClampedCount => Math.Max(1, Math.Min(20, PredictionCount.Value)); public PluginConfig(ConfigFile file) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) _file = file; Enabled = file.Bind<bool>("1 - General", "Enabled", true, "Show Forge of Potential predictions."); Language = file.Bind<LanguageOption>("1 - General", "Language", LanguageOption.Auto, "Interface language. Auto follows the game language (English or Spanish, else English)."); RefreshOnStateChanges = file.Bind<bool>("1 - General", "RefreshOnStateChanges", true, "Recompute automatically after an upgrade attempt. When off, the prediction is cleared after each attempt until the selection changes or 'forgeoracle refresh' is run."); DebugLogging = file.Bind<bool>("1 - General", "DebugLogging", false, "Log every recomputation."); ToggleHotkey = file.Bind<KeyboardShortcut>("1 - General", "ToggleHotkey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Shows or hides the detailed panel while the forge is open."); BindDisplay(file); BindPanel(file); BindSafety(file); file.SettingChanged += delegate { Version++; }; } public void Reload() { _file.Reload(); } public void ResetPanel() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) PanelOffset.Value = Vector2.zero; ResetPanelPosition.Value = false; } private void BindDisplay(ConfigFile file) { ShowCompact = file.Bind<bool>("2 - Display", "ShowCompactPrediction", true, "Show the one-line prediction next to the upgrade button."); ShowConfidence = file.Bind<bool>("2 - Display", "ShowConfidence", true, "Show VERIFIED / UNVERIFIED next to the prediction."); ShowExactRoll = file.Bind<bool>("2 - Display", "ShowExactRoll", false, "Show the exact roll and threshold values."); ShowPrefabId = file.Bind<bool>("2 - Display", "ShowInternalPrefabId", false, "Show the internal prefab name used by the algorithm."); ShowSeedTransitions = file.Bind<bool>("2 - Display", "ShowSeedTransitions", false, "Advanced: show seed -> next seed for each row."); EnableTooltips = file.Bind<bool>("2 - Display", "EnableTooltips", true, "Explain the forecast modes in tooltips."); ColorblindPalette = file.Bind<bool>("2 - Display", "ColorblindPalette", false, "Use a blue / orange palette instead of green / red."); HideWhenUnverified = file.Bind<bool>("2 - Display", "HideWhenUnverified", false, "Show PREDICTION UNAVAILABLE instead of an unverified prediction."); } private void BindPanel(ConfigFile file) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) ShowDetailPanel = file.Bind<bool>("3 - Detailed panel", "ShowDetailedPanel", false, "Show the detailed Forge Oracle panel."); ExpandedLayout = file.Bind<bool>("3 - Detailed panel", "ExpandedLayout", true, "Show the forecast table inside the panel."); PredictionCount = file.Bind<int>("3 - Detailed panel", "PredictionCount", 5, new ConfigDescription("Forecast rows (3, 5, 10 or 20 are typical).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>())); Forecast = file.Bind<ForecastMode>("3 - Detailed panel", "ForecastMode", ForecastMode.RepeatedAttempts, "Which forecast table to show."); PanelOpacity = file.Bind<float>("3 - Detailed panel", "PanelOpacity", 0.9f, new ConfigDescription("Background opacity of the panel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1f), Array.Empty<object>())); UiScale = file.Bind<float>("3 - Detailed panel", "UiScale", 1f, new ConfigDescription("Scale of the compact line and the panel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>())); Side = file.Bind<PanelSide>("3 - Detailed panel", "Anchor", PanelSide.Auto, "Which side of the crafting panel the panel attaches to."); Draggable = file.Bind<bool>("3 - Detailed panel", "Draggable", true, "Allow dragging the panel by its title."); PanelOffset = file.Bind<Vector2>("3 - Detailed panel", "PanelOffset", Vector2.zero, "Panel offset from its anchor, saved after dragging."); ResetPanelPosition = file.Bind<bool>("3 - Detailed panel", "ResetPanelPosition", false, "Set to true to move the panel back to its anchor."); } private void BindSafety(ConfigFile file) { ConfirmDestruction = file.Bind<bool>("4 - Safety and diagnostics", "ConfirmPredictedDestruction", false, "Ask for a second click when the next attempt is predicted to destroy the item. This only delays the click locally; it never changes the outcome."); EnableSounds = file.Bind<bool>("4 - Safety and diagnostics", "EnableSounds", false, "Play the UI click sound when a destruction is predicted."); RngSelfTest = file.Bind<bool>("4 - Safety and diagnostics", "RngSelfTest", true, "At startup, compare the private RNG copy with Unity's (the Unity RNG state is saved and restored). Predictions are only VERIFIED when this passes."); ValidationLog = file.Bind<bool>("4 - Safety and diagnostics", "ValidationLog", true, "After each real attempt, log the predicted and the observed outcome side by side."); } } } namespace ForgeOracle.Plugin.Compatibility { internal sealed class ForesightForgeRulesResolver : IRulesResolver { private Type? _plugin; private Type? _simulator; public string Id => "foresightforge"; public string DisplayName => "Vanilla roll + ForesightForge TierScaled"; public string PluginGuid => "com.drakosdj.foresightforge"; public string? ForecastNoteKey => null; private Type? PluginType => _plugin ?? (_plugin = AccessTools.TypeByName("ForesightForge.ForesightForgePlugin")); private Type? Simulator => _simulator ?? (_simulator = AccessTools.TypeByName("ForesightForge.UpgradeSimulator")); public bool IsActive(ForgeScan scan) { if (scan.IsLoaded(PluginGuid)) { return Mode()?.ToString() == "TierScaled"; } return false; } public bool TryResolve(ForgeSelection selection, int targetQuality, out UpgradeRules? rules, out string problem) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown rules = null; MethodInfo methodInfo = ModReflection.StaticMethod(Simulator, "GetIdolTier"); MethodInfo evaluate = ModReflection.StaticMethod(Simulator, "EvaluateFailure"); object mode = Mode(); if (methodInfo == null || evaluate == null || mode == null || (Object)(object)selection.Idol == (Object)null) { problem = "ForesightForge simulator methods not found"; return false; } SharedData shared = selection.Idol.m_itemData.m_shared; int tier = (int)ModReflection.Invoke(methodInfo, selection.Idol); float up = shared.m_upgradeChance; float brk = shared.m_breakChance; FailureModel val = FailureModel.Custom((FailureClassifier)((float roll) => Map(ModReflection.Invoke(evaluate, roll, up, brk, tier, mode))), 1, 1, "ForesightForge TierScaled, idol tier " + tier); rules = new UpgradeRules(up, brk, shared.m_breakReturnIngreientsAmount, ((Object)selection.Idol).name + " (ForesightForge tier " + tier + ")", val); problem = string.Empty; return true; } private object? Mode() { return ((PluginType == null) ? null : AccessTools.Property(PluginType, "EffectiveOutcomeMode"))?.GetValue(null, null); } private static FailureKind Map(object failureType) { return (FailureKind)(failureType.ToString() switch { "NoChange" => 2, "Downgrade" => 1, "Break" => 0, _ => throw new InvalidOperationException("Unexpected ForesightForge failure type " + failureType), }); } } internal sealed class ForgeEnvironment { private static readonly IRulesResolver Vanilla = new VanillaRulesResolver(); private static readonly IRulesResolver[] Overhauls = new IRulesResolver[3] { new ImpactfulSkillsRulesResolver(), new PotentialReforgedRulesResolver(), new ForesightForgeRulesResolver() }; public IRulesResolver Resolver { get; } public IForgePredictionProvider Provider { get; } public IReadOnlyList<CompatibilityIssue> Issues { get; } public ForgeScan Scan { get; } private ForgeEnvironment(IRulesResolver resolver, IReadOnlyList<CompatibilityIssue> issues, ForgeScan scan) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown Resolver = resolver; Issues = issues; Scan = scan; IForgePredictionProvider obj; if (!HasBlocking(issues)) { IForgePredictionProvider val = (IForgePredictionProvider)new SeededForgePredictionProvider(resolver.Id, resolver.DisplayName); obj = val; } else { IForgePredictionProvider val = (IForgePredictionProvider)new UnsupportedForgePredictionProvider("unsupported", "Modified forge", (UnknownReason)7); obj = val; } Provider = obj; } public static IReadOnlyList<IRulesResolver> AllResolvers() { List<IRulesResolver> list = new List<IRulesResolver>(); list.Add(Vanilla); list.AddRange(Overhauls); return list; } public static ForgeEnvironment Detect(string ownHarmonyId) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown ForgeScan scan = ForgeScan.Capture(ownHarmonyId); List<CompatibilityIssue> list = new List<CompatibilityIssue>(); ClassifyRollPatches(scan, list); ClassifyCraftPatches(scan, list); if (ModReflection.IsLoaded("akuichi.ReforgedPotential")) { list.Add(new CompatibilityIssue("random-roll", (IssueSeverity)2, "akuichi.ReforgedPotential", "ReforgedPotential rolls with the unseeded global Unity RNG; the outcome cannot be known.")); } return new ForgeEnvironment(SelectResolver(scan, list), list, scan); } private static IRulesResolver SelectResolver(ForgeScan scan, List<CompatibilityIssue> issues) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown List<IRulesResolver> list = new List<IRulesResolver>(); IRulesResolver[] overhauls = Overhauls; foreach (IRulesResolver rulesResolver in overhauls) { if (rulesResolver.IsActive(scan)) { list.Add(rulesResolver); } } if (list.Count > 1) { issues.Add(new CompatibilityIssue("multiple-forge-overhauls", (IssueSeverity)2, string.Join(", ", list.ConvertAll((IRulesResolver r) => r.PluginGuid).ToArray()), "Several mods change the forge decision; their combined effect is not modelled.")); return Vanilla; } if (list.Count == 0) { return Vanilla; } AddVersionIssues(list[0], issues); return list[0]; } private static void AddVersionIssues(IRulesResolver resolver, List<CompatibilityIssue> issues) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown KnownMod knownMod = KnownMods.Find(resolver.PluginGuid); string text = ModReflection.Version(resolver.PluginGuid); if (knownMod != null && text != knownMod.AuditedVersion) { issues.Add(new CompatibilityIssue("untested-version", (IssueSeverity)1, resolver.PluginGuid, knownMod.Name + " " + text + " is loaded; the provider was audited against " + knownMod.AuditedVersion + ".")); } if (!LiveValidation.IsValidated(resolver.PluginGuid)) { issues.Add(new CompatibilityIssue("provider-not-live-tested", (IssueSeverity)1, resolver.PluginGuid, "The " + resolver.DisplayName + " provider has not been validated in game yet.")); } } private static void ClassifyRollPatches(ForgeScan scan, List<CompatibilityIssue> issues) { //IL_0039: Unknown result type (