using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Burst2Flame;
using Burst2Flame.Observable;
using HarmonyLib;
using RoguelikeUnlimitedLevelProgression.Core;
using RoguelikeUnlimitedLevelProgression.Patches;
using RoguelikeUnlimitedLevelProgression.Runtime;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("RoguelikeUnlimitedLevelProgression")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Remove o teto de nivel 20 do modo Roguelike (singleplayer): um nivel por vitoria, XP estendido apos o 20 e recompensas Skills/Itens/Atributos com recibo duravel quando o pool de skills esgota.")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1")]
[assembly: AssemblyProduct("RoguelikeUnlimitedLevelProgression")]
[assembly: AssemblyTitle("RoguelikeUnlimitedLevelProgression")]
[assembly: AssemblyVersion("0.1.1.0")]
namespace RoguelikeUnlimitedLevelProgression
{
[BepInPlugin("com.gumatos.roguelikeunlimitedlevelprogression", "Roguelike Unlimited Level Progression", "0.1.1")]
public class Plugin : BaseUnityPlugin
{
public const string PluginGuid = "com.gumatos.roguelikeunlimitedlevelprogression";
public const string PluginName = "Roguelike Unlimited Level Progression";
public const string PluginVersion = "0.1.1";
private Harmony _harmony;
internal static Plugin Instance { get; private set; }
private void Awake()
{
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Expected O, but got Unknown
Instance = this;
Log.Attach(((BaseUnityPlugin)this).Logger);
Log.Info("Roguelike Unlimited Level Progression 0.1.1 (com.gumatos.roguelikeunlimitedlevelprogression) carregado.");
PluginState.Reset();
BindConfiguration();
try
{
LedgerService.ConfigureDefault(Paths.ConfigPath);
}
catch (Exception ex)
{
Log.Error("falha ao configurar o ledger: " + ex.GetType().Name + ": " + ex.Message);
}
if (!PluginState.Enabled)
{
Log.Warn("General.Enabled=false: nenhum patch aplicado (comportamento vanilla)");
return;
}
_harmony = new Harmony("com.gumatos.roguelikeunlimitedlevelprogression");
if (!PatchRegistry.ApplyAll(_harmony, out var error))
{
PluginState.GameplayGroupApplied = false;
Log.Error("mod INATIVO: " + error);
try
{
_harmony.UnpatchSelf();
return;
}
catch (Exception)
{
return;
}
}
PluginState.GameplayGroupApplied = true;
PatchRegistry.LogOwnership("Awake");
Log.Info("politica efetiva: MaxLevel(config)=" + ((PluginState.MaxLevelConfig == 0) ? "0 (sem teto voluntario)" : PluginState.MaxLevelConfig.ToString()) + " RewardQueryLevel=" + PluginState.RewardQueryLevel + " Verbose=" + PluginState.Verbose + " diagnostico=RLP-2 (recusas sempre visiveis)");
Log.Warn("v0.1 singleplayer: em PlayingMultiplayer a progressao ampliada permanece desligada e saves/ledgers estendidos NAO sao suportados em sessao multiplayer");
}
private void BindConfiguration()
{
ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Liga a progressao ampliada do modo Roguelike. Desligado = comportamento vanilla integral.");
ConfigEntry<int> obj = ((BaseUnityPlugin)this).Config.Bind<int>("Progression", "MaxLevel", 0, "Teto voluntario de nivel. 0 = sem teto de gameplay configurado. Valores positivos precisam ser >= 21. Reduzir NAO rebaixa personagens ja salvos.");
ConfigEntry<int> val2 = ((BaseUnityPlugin)this).Config.Bind<int>("Rewards", "RewardQueryLevel", 20, "Nivel de consulta (1..30) das chances de skill e do pool de equipamento nos level-ups acima de 20. Mantem o patamar configurado em vez de extrapolar as curvas.");
ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Verbose", false, "Log detalhado de carga, alvos e primeira chamada da politica. Sem spam por frame.");
PluginState.Enabled = val.Value;
int num = obj.Value;
if (num != 0 && num < 21)
{
Log.Warn("Progression.MaxLevel=" + num + " invalido (use 0 ou >= 21); adotando 0 (sem teto voluntario)");
num = 0;
}
PluginState.MaxLevelConfig = num;
int num2 = val2.Value;
if (num2 < 1)
{
num2 = 1;
}
if (num2 > 30)
{
num2 = 30;
}
if (num2 != val2.Value)
{
Log.Warn("Rewards.RewardQueryLevel=" + val2.Value + " fora de 1..30; adotando " + num2);
}
PluginState.RewardQueryLevel = num2;
PluginState.Verbose = val3.Value;
}
private void OnDestroy()
{
Log.Info("ciclo de vida: OnDestroy do host; patches de sessao PRESERVADOS; grupo=" + PluginState.GameplayGroupApplied);
PatchRegistry.LogOwnership("OnDestroy");
}
}
}
namespace RoguelikeUnlimitedLevelProgression.Runtime
{
internal static class GameState
{
private static bool UnknownNetwork(string reason)
{
Log.Refuse("rede", reason + "; multiplayer efetivo=True (fail-closed)");
return true;
}
public static bool IsMultiplayer()
{
try
{
NetworkingManager instance = NetworkingManager.Instance;
if ((Object)(object)instance == (Object)null)
{
return UnknownNetwork("NetworkingManager.Instance=null");
}
NetworkManager networkManager = instance.NetworkManager;
if (networkManager == null)
{
return UnknownNetwork("NetworkManager=null");
}
Root root = ((Observer<Root>)(object)networkManager).Root;
if (root == null)
{
return UnknownNetwork("Root=null");
}
bool playingMultiplayer = root.PlayingMultiplayer;
Log.InfoOnce("estado de rede: Root.PlayingMultiplayer=" + playingMultiplayer);
if (playingMultiplayer)
{
Log.Refuse("rede", "Root.PlayingMultiplayer=True");
}
return playingMultiplayer;
}
catch (Exception ex)
{
return UnknownNetwork("leitura indisponivel: " + ex.GetType().Name + ": " + ex.Message);
}
}
public static bool IsRoguelikeMode()
{
try
{
Game instance = Game.Instance;
if ((Object)(object)instance == (Object)null)
{
return Log.Refuse("modo", "Game.Instance=null");
}
bool roguelikeModeActive = instance.RoguelikeModeActive;
Log.InfoOnce("estado de modo: Game.RoguelikeModeActive=" + roguelikeModeActive);
if (!roguelikeModeActive)
{
Log.Refuse("modo", "Game.RoguelikeModeActive=False");
}
return roguelikeModeActive;
}
catch (Exception ex)
{
return Log.Refuse("modo", "leitura indisponivel: " + ex.GetType().Name + ": " + ex.Message);
}
}
public static RoguelikeSettings Settings()
{
try
{
GlobalSettingsManager instance = GlobalSettingsManager.instance;
if ((Object)(object)instance == (Object)null)
{
Log.Refuse("settings", "GlobalSettingsManager.instance=null; usando fallback vanilla");
return null;
}
RoguelikeSettings roguelikeManager = instance.roguelikeManager;
if ((Object)(object)roguelikeManager == (Object)null)
{
Log.Refuse("settings", "roguelikeManager=null; usando fallback vanilla");
}
return roguelikeManager;
}
catch (Exception ex)
{
Log.Refuse("settings", "leitura indisponivel: " + ex.GetType().Name + ": " + ex.Message);
return null;
}
}
}
internal static class Log
{
private static readonly HashSet<string> _seen = new HashSet<string>();
private static ManualLogSource _source;
public static void Attach(ManualLogSource source)
{
_source = source;
}
public static void Info(string msg)
{
if (_source != null)
{
_source.LogInfo((object)msg);
}
}
public static void Warn(string msg)
{
if (_source != null)
{
_source.LogWarning((object)msg);
}
}
public static void Error(string msg)
{
if (_source != null)
{
_source.LogError((object)msg);
}
}
public static void WarnOnce(string msg)
{
if (_seen.Add("W:" + msg))
{
Warn(msg);
}
}
public static bool Refuse(string origin, string reason, bool once = true)
{
PluginState.LastDenial = origin + ": " + reason;
string msg = "ampliacao recusada [" + origin + "]: " + reason;
if (once)
{
WarnOnce(msg);
}
else
{
Warn(msg);
}
return false;
}
public static bool Vanilla(string origin, string reason)
{
Refuse(origin, reason);
return true;
}
internal static void ResetSeenForTests()
{
_seen.Clear();
}
public static void InfoOnce(string msg)
{
if (_seen.Add("I:" + msg))
{
Info(msg);
}
}
}
internal static class ExtensionScope
{
[ThreadStatic]
private static int _depth;
[ThreadStatic]
private static Character _owner;
public static bool Active => _depth > 0;
public static Character Owner => _owner;
public static int Depth => _depth;
public static bool AppliesTo(Character c)
{
if (c == null)
{
return Log.Refuse("escopo", "character=null");
}
try
{
if (!PluginState.Enabled)
{
return Log.Refuse("escopo", "General.Enabled=False");
}
if (c.IsAI)
{
return Log.Refuse("escopo", "IsAI=True");
}
if (!c.IsRoguelikeCharacter)
{
return Log.Refuse("escopo", "IsRoguelikeCharacter=False");
}
}
catch (Exception ex)
{
return Log.Refuse("escopo", "leitura do personagem falhou: " + ex.GetType().Name + ": " + ex.Message);
}
if (!GameState.IsRoguelikeMode())
{
return Log.Refuse("escopo", "modo roguelike=False");
}
if (GameState.IsMultiplayer())
{
return Log.Refuse("escopo", "multiplayer efetivo=True");
}
return true;
}
public static bool Push(Character c)
{
if (c == null)
{
return Log.Refuse("escopo.Push", "character=null");
}
if (_depth == 0)
{
_owner = c;
}
_depth++;
return true;
}
public static void Pop()
{
if (_depth > 0)
{
_depth--;
if (_depth == 0)
{
_owner = null;
}
}
}
public static Character CurrentExtended()
{
if (_depth <= 0)
{
Log.Refuse("escopo.CurrentExtended", "escopo inativo");
return null;
}
Character owner = _owner;
if (owner == null)
{
Log.Refuse("escopo.CurrentExtended", "owner=null");
return null;
}
if (!AppliesTo(owner))
{
return null;
}
return owner;
}
public static void ResetForTests()
{
_depth = 0;
_owner = null;
}
}
internal static class LedgerService
{
internal const string ModFolderName = "com.gumatos.roguelikeunlimitedlevelprogression";
private static readonly object _gate = new object();
private static readonly Dictionary<string, LedgerDocument> _cache = new Dictionary<string, LedgerDocument>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, LedgerLoadStatus> _status = new Dictionary<string, LedgerLoadStatus>(StringComparer.OrdinalIgnoreCase);
private static ReceiptLedgerStore _store;
public static ReceiptLedgerStore Store => _store;
public static void ConfigureDefault(string configPath)
{
_store = new ReceiptLedgerStore(Path.Combine(configPath, "com.gumatos.roguelikeunlimitedlevelprogression", "receipts"));
Log.Info("Ledger em " + _store.Directory);
}
public static void ConfigureStoreForTests(ReceiptLedgerStore store)
{
_store = store;
ResetAll();
}
public static void ResetAll()
{
lock (_gate)
{
_cache.Clear();
_status.Clear();
}
}
public static string IdentityOf(Character c)
{
if (c == null)
{
return null;
}
try
{
CharacterSaveFile characterSaveFile = c.CharacterSaveFile;
if (characterSaveFile != null && !string.IsNullOrEmpty(characterSaveFile.Guid))
{
return characterSaveFile.Guid;
}
}
catch (Exception)
{
}
try
{
if (!string.IsNullOrEmpty(c.CharacterPresetFileGuid))
{
return "preset-" + c.CharacterPresetFileGuid;
}
}
catch (Exception)
{
}
try
{
return "owner" + c.OwnerID + "-" + (c.CharacterName ?? "unknown");
}
catch (Exception)
{
}
return null;
}
public static string PresetGuidOf(Character c)
{
if (c == null)
{
return "";
}
try
{
return c.CharacterPresetFileGuid ?? "";
}
catch (Exception)
{
return "";
}
}
private static LedgerDocument EnsureLoaded(string guid, string presetGuid)
{
if (_cache.TryGetValue(guid, out var value))
{
return value;
}
if (_store == null)
{
_status[guid] = LedgerLoadStatus.Corrupted;
return null;
}
string error;
LedgerLoadStatus ledgerLoadStatus = _store.Load(guid, presetGuid, out value, out error);
switch (ledgerLoadStatus)
{
case LedgerLoadStatus.Missing:
value = ReceiptLedgerStore.NewRun(guid, presetGuid);
_cache[guid] = value;
_status[guid] = LedgerLoadStatus.Missing;
if (PluginState.Verbose)
{
Log.Info("Ledger novo para " + guid + " (run " + value.RunId + ")");
}
return value;
case LedgerLoadStatus.Ok:
_cache[guid] = value;
_status[guid] = LedgerLoadStatus.Ok;
return value;
default:
_status[guid] = ledgerLoadStatus;
Log.Error("Ledger de " + guid + " nao utilizavel (" + ledgerLoadStatus.ToString() + "): " + error + " - a recompensa NAO sera consumida nem convertida em recibo");
return null;
}
}
public static LedgerLoadStatus StatusOf(string guid)
{
if (guid != null && _status.TryGetValue(guid, out var value))
{
return value;
}
return LedgerLoadStatus.Missing;
}
public static int ReceiptCount(Character c)
{
return ReceiptCountByGuid(IdentityOf(c), PresetGuidOf(c));
}
public static int ReceiptCountByGuid(string guid, string presetGuid)
{
if (guid == null)
{
return 0;
}
lock (_gate)
{
return EnsureLoaded(guid, presetGuid)?.SkippedSkillCount ?? 0;
}
}
public static bool TryRecordSkippedSkill(Character c, int earnedLevel, out string error)
{
return TryRecordSkippedSkillByGuid(IdentityOf(c), PresetGuidOf(c), earnedLevel, out error);
}
public static bool TryRecordSkippedSkillByGuid(string guid, string presetGuid, int earnedLevel, out string error)
{
error = null;
if (guid == null)
{
error = "identidade do personagem indisponivel";
return false;
}
lock (_gate)
{
LedgerDocument ledgerDocument = EnsureLoaded(guid, presetGuid);
if (ledgerDocument == null)
{
error = "ledger nao utilizavel (" + StatusOf(guid).ToString() + ")";
return false;
}
int ordinal = ledgerDocument.NextOrdinal();
if (!ledgerDocument.AddReceipt(ordinal, earnedLevel, "empty_skill_pool"))
{
return true;
}
try
{
_store.Save(ledgerDocument);
_status[guid] = LedgerLoadStatus.Ok;
}
catch (LedgerWriteException ex)
{
ledgerDocument.SkippedSkillReceipts.RemoveAll((SkillReceipt r) => r.Ordinal == ordinal);
error = "falha ao gravar o recibo: " + ex.Message;
Log.Error("Recibo de skill nao gravado para " + guid + ": " + error);
return false;
}
Log.Info("Recibo de skill registrado: guid=" + guid + " ordinal=" + ordinal + " nivel=" + earnedLevel + " motivo=empty_skill_pool (S=" + ledgerDocument.SkippedSkillCount + ")");
return true;
}
}
public static void OnPresetSyncSuccess(Character c, string presetGuid)
{
if (c != null)
{
OnPresetSyncSuccessByGuid(IdentityOf(c), presetGuid);
}
}
public static void OnPresetSyncSuccessByGuid(string guid, string presetGuid)
{
if (string.IsNullOrEmpty(presetGuid))
{
Log.Warn("SyncCharacterToPreset sem preset valido: ledger NAO foi resetado");
}
else
{
if (guid == null)
{
return;
}
lock (_gate)
{
LedgerDocument ledgerDocument = EnsureLoaded(guid, presetGuid);
if (ledgerDocument == null)
{
ledgerDocument = ReceiptLedgerStore.NewRun(guid, presetGuid);
_cache[guid] = ledgerDocument;
Log.Warn("Ledger de " + guid + " estava " + StatusOf(guid).ToString() + "; criando documento novo para a run");
}
else
{
ReceiptLedgerStore.StartNewRun(ledgerDocument, presetGuid);
}
try
{
_store.Save(ledgerDocument);
_status[guid] = LedgerLoadStatus.Ok;
Log.Info("Run resetada para " + guid + " (run " + ledgerDocument.RunId + ")");
}
catch (LedgerWriteException ex)
{
Log.Error("Falha ao gravar a run resetada de " + guid + ": " + ex.Message);
}
}
}
}
}
internal static class PatchRegistryEvidence
{
private static bool _seen;
public static void FirstHook(string origin)
{
if (!_seen)
{
_seen = true;
Log.Info("hook vivo (antes das guardas): " + origin + "; grupo=" + PluginState.GameplayGroupApplied);
PatchRegistry.LogOwnership("primeiro-hook");
}
}
}
internal static class PluginState
{
public static bool Enabled = true;
public static int MaxLevelConfig;
public static int RewardQueryLevel = 20;
public static bool Verbose;
public static bool GameplayGroupApplied;
public static string LastDenial;
public static void Reset()
{
Enabled = true;
MaxLevelConfig = 0;
RewardQueryLevel = 20;
Verbose = false;
GameplayGroupApplied = false;
LastDenial = null;
}
}
public static class Policy
{
public static bool CanAdvance(Character character, RoguelikeSettings settings)
{
PatchRegistryEvidence.FirstHook("Policy.CanAdvance");
int vanillaCap = 20;
try
{
if ((Object)(object)settings != (Object)null)
{
vanillaCap = settings.RoguelikeMaxLevel;
}
}
catch (Exception ex)
{
Log.WarnOnce("Policy.CanAdvance: settings indisponivel (" + ex.GetType().Name + "); teto vanilla conhecido usado");
}
if (character == null)
{
DiagnoseOnce("character=null");
return false;
}
AdvanceInputs i;
try
{
i = BuildInputs(character, vanillaCap);
}
catch (Exception ex2)
{
Log.Error("Policy.CanAdvance: falha ao montar entradas (" + ex2.GetType().Name + ": " + ex2.Message + "); usando comparacao vanilla");
DiagnoseOnce("falha ao montar entradas: " + ex2.GetType().Name);
return Fallback(character, vanillaCap);
}
bool flag;
AdvanceDenial denial;
try
{
flag = PolicyCore.CanAdvance(in i, out denial);
}
catch (Exception ex3)
{
Log.Error("Policy.CanAdvance: falha na decisao (" + ex3.GetType().Name + ": " + ex3.Message + "); usando comparacao vanilla");
DiagnoseOnce("falha na decisao: " + ex3.GetType().Name);
return Fallback(character, vanillaCap);
}
Diagnose(in i, denial, flag);
return flag;
}
private static bool Fallback(Character character, int vanillaCap)
{
try
{
return character.Level < vanillaCap;
}
catch (Exception ex)
{
return Log.Refuse("politica.Fallback", "leitura do nivel falhou: " + ex.GetType().Name, once: false);
}
}
private static AdvanceInputs BuildInputs(Character character, int vanillaCap)
{
AdvanceInputs result = new AdvanceInputs
{
ModEnabled = PluginState.Enabled,
IsAi = character.IsAI,
IsRoguelikeCharacter = character.IsRoguelikeCharacter,
IsMultiplayer = GameState.IsMultiplayer(),
IsRoguelikeMode = GameState.IsRoguelikeMode(),
Level = character.Level,
VanillaCap = vanillaCap,
VoluntaryCap = PluginState.MaxLevelConfig,
TargetsPresent = PluginState.GameplayGroupApplied
};
bool flag = (result.CurveUsable = XpBackend.EnsureReady());
result.SafetyBound = (flag ? XpBackend.SafetyBound : 0);
if (flag)
{
result.NextLevelRepresentable = XpBackend.NextLevelRepresentable(result.Level, out var detail);
int pointsPerLevel = XpBackend.PointsPerLevelFor(character);
result.StatBudgetOk = XpBackend.StatBudgetOk(result.Level + 1, pointsPerLevel, out detail);
}
return result;
}
private static void Diagnose(in AdvanceInputs i, AdvanceDenial denial, bool allow)
{
PluginState.LastDenial = denial.ToString();
if (!allow || denial != AdvanceDenial.None || PluginState.Verbose)
{
Log.Info("politica: allow=" + allow + " motivo=" + denial.ToString() + " enabled=" + i.ModEnabled + " nivel=" + i.Level + " capVanilla=" + i.VanillaCap + " personagemRoguelike=" + i.IsRoguelikeCharacter + " modoRoguelike=" + i.IsRoguelikeMode + " ia=" + i.IsAi + " multiplayer=" + i.IsMultiplayer + " alvos=" + i.TargetsPresent + " curva=" + i.CurveUsable + " proximoRepresentavel=" + i.NextLevelRepresentable + " orcamentoPontos=" + i.StatBudgetOk + " tetoVoluntario=" + i.VoluntaryCap + " tetoRepresentacao=" + i.SafetyBound);
}
}
private static void DiagnoseOnce(string motivo)
{
PluginState.LastDenial = motivo;
Log.Refuse("politica", "chamada sem decisao: " + motivo, once: false);
}
public static double EffectiveMaxLevel(Character character, double vanillaValue)
{
if (character == null)
{
Log.Refuse("MaxLevel", "character=null");
return vanillaValue;
}
try
{
if (character.IsAI)
{
Log.Refuse("MaxLevel", "IsAI=True");
return vanillaValue;
}
if (!character.IsRoguelikeCharacter)
{
Log.Refuse("MaxLevel", "IsRoguelikeCharacter=False");
return vanillaValue;
}
if (!PluginState.Enabled)
{
Log.Refuse("MaxLevel", "General.Enabled=False");
return vanillaValue;
}
bool flag = !character.IsLoaded;
double savedLoadLevel = 0.0;
if (flag)
{
CharacterSaveFile characterSaveFile = character.CharacterSaveFile;
if (characterSaveFile != null)
{
savedLoadLevel = characterSaveFile.ExperienceLevel;
}
}
if (!PluginState.GameplayGroupApplied)
{
Log.Refuse("MaxLevel", "GameplayGroupApplied=False");
}
bool flag2 = PluginState.GameplayGroupApplied && !GameState.IsMultiplayer() && XpBackend.EnsureReady();
if (!flag2)
{
Log.Refuse("MaxLevel", "extensao indisponivel; usando teto vanilla com piso de carga");
}
double num = PolicyCore.EffectiveMaxLevel(new AdvanceInputs
{
ModEnabled = PluginState.Enabled,
IsAi = false,
IsRoguelikeCharacter = true,
IsMultiplayer = false,
IsRoguelikeMode = true,
Level = character.Level,
VanillaCap = 20,
VoluntaryCap = (flag2 ? PluginState.MaxLevelConfig : 0),
SafetyBound = (flag2 ? XpBackend.SafetyBound : 0),
VanillaMaxLevel = vanillaValue,
IsLoading = flag,
SavedLoadLevel = savedLoadLevel,
CurveUsable = true,
NextLevelRepresentable = true,
StatBudgetOk = true,
TargetsPresent = true
});
if (!XpCurve.IsUsableNumber(num))
{
Log.Refuse("MaxLevel", "teto calculado nao finito; mantendo vanilla");
return vanillaValue;
}
return num;
}
catch (Exception ex)
{
Log.WarnOnce("EffectiveMaxLevel: falha (" + ex.GetType().Name + ": " + ex.Message + "); mantendo valor vanilla");
return vanillaValue;
}
}
}
public enum LedgerLoadStatus
{
Missing,
Ok,
Corrupted,
UnsupportedSchema,
IdentityMismatch,
WriteFailure
}
public sealed class LedgerWriteException : Exception
{
public LedgerWriteException(string message, Exception inner)
: base(message, inner)
{
}
}
public sealed class ReceiptLedgerStore
{
private readonly string _dir;
public string Directory => _dir;
public ReceiptLedgerStore(string directory)
{
if (string.IsNullOrEmpty(directory))
{
throw new ArgumentNullException("directory");
}
_dir = directory;
}
public string PathFor(string characterGuid)
{
return Path.Combine(_dir, SafeName(characterGuid) + ".json");
}
public string BackupPathFor(string characterGuid)
{
return Path.Combine(_dir, SafeName(characterGuid) + ".json.bak");
}
public static string SafeName(string guid)
{
if (string.IsNullOrEmpty(guid))
{
return "unknown";
}
StringBuilder stringBuilder = new StringBuilder(guid.Length);
foreach (char c in guid)
{
bool flag = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_';
stringBuilder.Append(flag ? c : '_');
}
return stringBuilder.ToString();
}
public LedgerLoadStatus Load(string characterGuid, string expectedPresetGuid, out LedgerDocument doc, out string error)
{
doc = null;
error = null;
string path = PathFor(characterGuid);
if (!File.Exists(path))
{
return LedgerLoadStatus.Missing;
}
string text;
try
{
text = File.ReadAllText(path, Encoding.UTF8);
}
catch (Exception ex)
{
error = "leitura falhou: " + ex.Message;
return LedgerLoadStatus.Corrupted;
}
switch (LedgerDocument.Parse(text, out doc, out error))
{
case LedgerParseStatus.UnsupportedSchema:
return LedgerLoadStatus.UnsupportedSchema;
default:
if (string.IsNullOrEmpty(error))
{
error = "ledger corrompido";
}
doc = null;
return LedgerLoadStatus.Corrupted;
case LedgerParseStatus.Ok:
if (!string.IsNullOrEmpty(expectedPresetGuid) && !string.IsNullOrEmpty(doc.PresetGuid) && !string.Equals(doc.PresetGuid, expectedPresetGuid, StringComparison.OrdinalIgnoreCase))
{
error = "preset_guid divergente: ledger=" + doc.PresetGuid + " esperado=" + expectedPresetGuid;
doc = null;
return LedgerLoadStatus.IdentityMismatch;
}
if (!string.IsNullOrEmpty(characterGuid) && !string.IsNullOrEmpty(doc.CharacterGuid) && !string.Equals(doc.CharacterGuid, characterGuid, StringComparison.OrdinalIgnoreCase))
{
error = "character_guid divergente";
doc = null;
return LedgerLoadStatus.IdentityMismatch;
}
return LedgerLoadStatus.Ok;
}
}
public void Save(LedgerDocument doc)
{
if (doc == null)
{
throw new ArgumentNullException("doc");
}
string text = PathFor(doc.CharacterGuid);
string text2 = text + ".tmp";
string contents = doc.Serialize();
try
{
System.IO.Directory.CreateDirectory(_dir);
File.WriteAllText(text2, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
if (File.Exists(text))
{
File.Replace(text2, text, BackupPathFor(doc.CharacterGuid), ignoreMetadataErrors: true);
}
else
{
File.Move(text2, text);
}
}
catch (Exception inner)
{
try
{
if (File.Exists(text2))
{
File.Delete(text2);
}
}
catch
{
}
throw new LedgerWriteException("falha ao gravar o sidecar em " + text, inner);
}
}
public static LedgerDocument NewRun(string characterGuid, string presetGuid)
{
return new LedgerDocument
{
SchemaVersion = 1,
CharacterGuid = (characterGuid ?? ""),
RunId = Guid.NewGuid().ToString(),
PresetGuid = (presetGuid ?? "")
};
}
public static void StartNewRun(LedgerDocument doc, string presetGuid)
{
if (doc == null)
{
throw new ArgumentNullException("doc");
}
doc.RunId = Guid.NewGuid().ToString();
if (!string.IsNullOrEmpty(presetGuid))
{
doc.PresetGuid = presetGuid;
}
doc.SkippedSkillReceipts.Clear();
}
}
internal static class XpBackend
{
[ThreadStatic]
private static bool _suppressed;
public static bool Ready { get; private set; }
public static double Floor20 { get; private set; }
public static double Transition20 { get; private set; }
public static int SafetyBound { get; private set; }
public static int PointsPerLevel { get; private set; }
public static string LastError { get; private set; }
public static string BoundFormula { get; private set; }
public static bool Suppressed => _suppressed;
public static int CapLevelForQueries()
{
return PluginState.RewardQueryLevel;
}
public static bool EnsureReady()
{
if (Ready)
{
return true;
}
try
{
_suppressed = true;
float experienceFloorForLevel;
float experienceFloorForLevel2;
try
{
experienceFloorForLevel = Character.GetExperienceFloorForLevel(20);
experienceFloorForLevel2 = Character.GetExperienceFloorForLevel(21);
}
finally
{
_suppressed = false;
}
double floor = experienceFloorForLevel;
double num = (double)experienceFloorForLevel2 - (double)experienceFloorForLevel;
if (!XpCurve.CurveUsable(floor, num))
{
LastError = "parametros vanilla invalidos: F20=" + experienceFloorForLevel.ToString("R") + " F21=" + experienceFloorForLevel2.ToString("R") + " D20=" + num.ToString("R") + " (exigidos: finitos, D20>0, F21>F20)";
Log.WarnOnce("XP estendido indisponivel: " + LastError);
return false;
}
Floor20 = floor;
Transition20 = num;
PointsPerLevel = DefaultPointsPerLevel();
SafetyBound = XpCurve.SafetyBoundLevel(floor, num, PointsPerLevel);
BoundFormula = "min(20+floor((1e30-F20)/D20), 1+2^24/pontosPorNivel, 1+(int.MaxValue-2)/pontosPorNivel) com F20=" + floor.ToString("R") + " D20=" + num.ToString("R") + " pontosPorNivel=" + PointsPerLevel + " => " + SafetyBound;
Ready = true;
Log.InfoOnce("XP estendido pronto: F20=" + floor.ToString("R") + " D20=" + num.ToString("R") + " teto de representacao=" + SafetyBound);
Log.Info("Formula do teto de representacao: " + BoundFormula);
return true;
}
catch (Exception ex)
{
_suppressed = false;
LastError = ex.GetType().Name + ": " + ex.Message;
Log.WarnOnce("XP estendido indisponivel: " + LastError);
return false;
}
}
private static int DefaultPointsPerLevel()
{
int num = 0;
try
{
GameState.Settings();
GlobalSettingsManager instance = GlobalSettingsManager.instance;
if ((Object)(object)instance != (Object)null && (Object)(object)instance.globalSettings != (Object)null)
{
num = instance.globalSettings.NumStatsPerNewLevel;
}
}
catch (Exception)
{
num = 0;
}
if (num <= 0)
{
num = 1;
}
return num;
}
public static int PointsPerLevelFor(Character c)
{
int num = ((PointsPerLevel <= 0) ? 1 : PointsPerLevel);
try
{
if (c != null)
{
num += (int)c["AdditionalRoguelikeAttributesPointsPerLevel"];
}
}
catch (Exception)
{
}
if (num <= 0)
{
return 1;
}
return num;
}
public static bool TryFloor(int level, out double value)
{
value = 0.0;
if (!Ready || level <= 20)
{
return false;
}
value = XpCurve.Floor(Floor20, Transition20, level);
return XpCurve.IsUsableNumber(value);
}
public static bool TryTransition(int level, out double value)
{
value = 0.0;
if (!Ready || level <= 20)
{
return false;
}
value = XpCurve.Transition(Transition20, level);
if (XpCurve.IsUsableNumber(value))
{
return value > 0.0;
}
return false;
}
public static bool TryLevelFromXp(float xp, out double level)
{
level = 0.0;
if (!Ready)
{
return false;
}
double num = xp;
if (!XpCurve.IsUsableNumber(num) || num < Floor20)
{
return false;
}
level = XpCurve.LevelFromXp(Floor20, Transition20, num);
return XpCurve.IsUsableNumber(level);
}
public static bool TryXpFromLevel(float level)
{
if (!Ready)
{
return false;
}
if (!XpCurve.IsUsableNumber(level) || level <= 20f)
{
return false;
}
int num = (int)Math.Floor(level);
double num2 = level - (float)num;
return XpCurve.IsUsableNumber(XpCurve.Floor(Floor20, Transition20, num) + num2 * Transition20);
}
public static double XpFromLevel(float level)
{
int num = (int)Math.Floor(level);
double num2 = level - (float)num;
return XpCurve.Floor(Floor20, Transition20, num) + num2 * Transition20;
}
public static bool NextLevelRepresentable(int currentLevel, out string detail)
{
detail = null;
if (!Ready)
{
detail = "backend nao pronto";
return false;
}
if (currentLevel < 0 || currentLevel >= 2147483645)
{
detail = "nivel atual fora de faixa: " + currentLevel;
return false;
}
int num = currentLevel + 1;
if (!TryFloor(num, out var value) && num > 20)
{
detail = "F(" + num + ") nao finito";
return false;
}
if (!TryFloor(num + 1, out var value2) && num + 1 > 20)
{
detail = "F(" + (num + 1) + ") nao finito";
return false;
}
if (num > 20 && !(value2 > value))
{
detail = "F(" + num + ")=" + value.ToString("R") + " nao e estritamente menor que F(" + (num + 1) + ")=" + value2.ToString("R");
return false;
}
if (num > 20)
{
double num2 = XpCurve.LevelFromXp(Floor20, Transition20, value2);
int num3 = XpCurve.LevelIndexFromXp(Floor20, Transition20, value2);
if (num3 != num + 1)
{
detail = "inversa de F(" + (num + 1) + ") devolveu " + num3;
return false;
}
if (Math.Abs(num2 - (double)(num + 1)) > 1E-06)
{
detail = "inversa analitica imprecisa em L=" + (num + 1) + ": " + num2.ToString("R");
return false;
}
}
return true;
}
public static bool StatBudgetOk(int nextLevel, int pointsPerLevel, out string detail)
{
detail = null;
if (nextLevel < 1)
{
detail = "nivel invalido";
return false;
}
long num = (long)(nextLevel - 1) * (long)pointsPerLevel;
if (num > 2147483645)
{
detail = "soma de pontos excede int32: " + num;
return false;
}
if (num > 16777216)
{
detail = "soma de pontos excede o inteiro exato de float (2^24): " + num;
return false;
}
return true;
}
public static void ResetForTests()
{
Ready = false;
Floor20 = 0.0;
Transition20 = 0.0;
SafetyBound = 0;
PointsPerLevel = 0;
LastError = null;
BoundFormula = null;
_suppressed = false;
}
public static bool PrimeForTests(double floor20, double transition, int pointsPerLevel)
{
ResetForTests();
if (!XpCurve.CurveUsable(floor20, transition))
{
return false;
}
Floor20 = floor20;
Transition20 = transition;
PointsPerLevel = ((pointsPerLevel <= 0) ? 1 : pointsPerLevel);
SafetyBound = XpCurve.SafetyBoundLevel(floor20, transition, PointsPerLevel);
Ready = true;
return true;
}
}
}
namespace RoguelikeUnlimitedLevelProgression.Patches
{
public sealed class BattleResultTarget
{
public MethodInfo GetLevel;
public MethodInfo GetRoguelikeSettings;
public FieldInfo RoguelikeManagerField;
public FieldInfo RoguelikeMaxLevelField;
public MethodInfo GiveExperience;
public MethodInfo ListAddCharacter;
public MethodInfo CanAdvance;
}
public static class BattleResultTranspiler
{
public static int LastMatchCount = -1;
public static string LastFailure = "transpiler ainda nao executado";
public static bool LastApplied;
public static bool TryTransform(IList<CodeInstruction> input, BattleResultTarget t, out List<CodeInstruction> result, out string failure)
{
//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
//IL_00de: Expected O, but got Unknown
result = null;
failure = null;
List<CodeInstruction> list = new List<CodeInstruction>(input);
int num = -1;
int num2 = 0;
for (int i = 0; i + 5 < list.Count; i++)
{
if (WindowMatches(list, i, t, out var _))
{
num2++;
if (num < 0)
{
num = i;
}
}
}
LastMatchCount = num2;
if (num2 == 0)
{
failure = "nenhuma correspondencia estrutural completa no bloco de resultado";
LastFailure = failure;
LastApplied = false;
return false;
}
if (num2 > 1)
{
failure = "correspondencia ambigua: " + num2 + " janelas casaram";
LastFailure = failure;
LastApplied = false;
return false;
}
if (!HasPrizeTail(list, num, t, out failure))
{
LastFailure = failure;
LastApplied = false;
return false;
}
if (!CanRewrite(list, num, t, out failure))
{
LastFailure = failure;
LastApplied = false;
return false;
}
int index = num + 1;
int index2 = num + 4;
int index3 = num + 5;
CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)t.CanAdvance);
CopyList(list[index2], val, "labels");
CopyList(list[index2], val, "blocks");
list[index2] = val;
CodeInstruction obj = list[index3];
obj.opcode = ((obj.opcode == OpCodes.Bge_S) ? OpCodes.Brfalse_S : OpCodes.Brfalse);
list.RemoveAt(index);
result = list;
LastFailure = null;
LastApplied = true;
return true;
}
private static bool IsAnyLocalLoad(OpCode op)
{
if (op == OpCodes.Ldloc_0 || op == OpCodes.Ldloc_1 || op == OpCodes.Ldloc_2 || op == OpCodes.Ldloc_3)
{
return true;
}
if (!(op == OpCodes.Ldloc))
{
return op == OpCodes.Ldloc_S;
}
return true;
}
private static bool SameMethod(object operand, MethodInfo expected)
{
if (expected == null || operand == null)
{
return false;
}
MethodInfo methodInfo = operand as MethodInfo;
if (methodInfo == null)
{
return false;
}
if (methodInfo == expected)
{
return true;
}
if (methodInfo.MetadataToken == expected.MetadataToken && methodInfo.DeclaringType == expected.DeclaringType)
{
return string.Equals(methodInfo.Name, expected.Name, StringComparison.Ordinal);
}
return false;
}
private static bool SameField(object operand, FieldInfo expected)
{
if (expected == null || operand == null)
{
return false;
}
FieldInfo fieldInfo = operand as FieldInfo;
if (fieldInfo == null)
{
return false;
}
if (fieldInfo == expected)
{
return true;
}
if (fieldInfo.MetadataToken == expected.MetadataToken && fieldInfo.DeclaringType == expected.DeclaringType)
{
return string.Equals(fieldInfo.Name, expected.Name, StringComparison.Ordinal);
}
return false;
}
private static bool WindowMatches(IList<CodeInstruction> ins, int i, BattleResultTarget t, out string reason)
{
reason = null;
if (!IsAnyLocalLoad(ins[i].opcode))
{
return false;
}
if (ins[i + 1].opcode != OpCodes.Callvirt || !SameMethod(ins[i + 1].operand, t.GetLevel))
{
return false;
}
if (ins[i + 2].opcode != OpCodes.Call || !SameMethod(ins[i + 2].operand, t.GetRoguelikeSettings))
{
return false;
}
if (ins[i + 3].opcode != OpCodes.Ldfld || !SameField(ins[i + 3].operand, t.RoguelikeManagerField))
{
return false;
}
if (ins[i + 4].opcode != OpCodes.Ldfld || !SameField(ins[i + 4].operand, t.RoguelikeMaxLevelField))
{
return false;
}
OpCode opcode = ins[i + 5].opcode;
if (opcode != OpCodes.Bge_S && opcode != OpCodes.Bge)
{
return false;
}
if (!IsBranchTarget(ins[i + 5].operand))
{
return false;
}
reason = "ok";
return true;
}
private static bool IsBranchTarget(object operand)
{
if (operand == null)
{
return false;
}
return operand.GetType().FullName == "System.Reflection.Emit.Label";
}
private static IList ListOf(CodeInstruction ci, string name)
{
FieldInfo fieldInfo = Reflect.Field(typeof(CodeInstruction), name);
if (fieldInfo != null)
{
return fieldInfo.GetValue(ci) as IList;
}
PropertyInfo propertyInfo = Reflect.Property(typeof(CodeInstruction), name);
if (propertyInfo != null)
{
return propertyInfo.GetValue(ci, null) as IList;
}
return null;
}
private static void CopyList(CodeInstruction from, CodeInstruction to, string name)
{
IList list = ListOf(from, name);
IList list2 = ListOf(to, name);
if (list != null && list2 != null)
{
for (int i = 0; i < list.Count; i++)
{
list2.Add(list[i]);
}
}
}
private static int CountOf(CodeInstruction ci, string name)
{
return ListOf(ci, name)?.Count ?? 0;
}
private static bool LabelsContain(CodeInstruction ci, object target)
{
IList list = ListOf(ci, "labels");
if (list == null)
{
return false;
}
for (int i = 0; i < list.Count; i++)
{
if (object.Equals(list[i], target))
{
return true;
}
}
return false;
}
private static bool HasPrizeTail(IList<CodeInstruction> ins, int match, BattleResultTarget t, out string failure)
{
failure = null;
int num = match + 5;
object operand = ins[num].operand;
bool flag = false;
bool flag2 = false;
int num2 = Math.Min(ins.Count, num + 24);
for (int i = num + 1; i < num2 && !LabelsContain(ins[i], operand); i++)
{
if ((ins[i].opcode == OpCodes.Callvirt || ins[i].opcode == OpCodes.Call) && SameMethod(ins[i].operand, t.GiveExperience))
{
flag = true;
}
else if (ins[i].opcode == OpCodes.Callvirt && SameMethod(ins[i].operand, t.ListAddCharacter))
{
flag2 = true;
}
}
if (!flag)
{
failure = "cauda de premio ausente: GiveExperience nao encontrado entre o branch e seu destino";
return false;
}
if (!flag2)
{
failure = "cauda de premio ausente: List<Character>::Add nao encontrado entre o branch e seu destino";
return false;
}
return true;
}
private static bool CanRewrite(IList<CodeInstruction> ins, int match, BattleResultTarget t, out string failure)
{
failure = null;
CodeInstruction ci = ins[match + 1];
if (CountOf(ci, "labels") > 0 || CountOf(ci, "blocks") > 0)
{
failure = "a instrucao get_Level removida possui labels/exception blocks: reescrita insegura";
return false;
}
if (t.CanAdvance == null)
{
failure = "Policy.CanAdvance nao resolvido";
return false;
}
return true;
}
}
public static class BattleResultPatches
{
internal static BattleResultTarget ResolveTarget(out string missing)
{
missing = null;
BattleResultTarget battleResultTarget = new BattleResultTarget();
battleResultTarget.GetLevel = Reflect.PropertyGetter(typeof(Character), "Level");
battleResultTarget.GetRoguelikeSettings = Reflect.Method(typeof(GlobalSettingsManager), "get_instance");
battleResultTarget.RoguelikeManagerField = Reflect.Field(typeof(GlobalSettingsManager), "roguelikeManager");
battleResultTarget.RoguelikeMaxLevelField = Reflect.Field(typeof(RoguelikeSettings), "RoguelikeMaxLevel");
battleResultTarget.GiveExperience = Reflect.Method(typeof(Character), "GiveExperience", typeof(float), typeof(bool));
battleResultTarget.ListAddCharacter = Reflect.Method(typeof(List<Character>), "Add", typeof(Character));
battleResultTarget.CanAdvance = Reflect.Method(typeof(Policy), "CanAdvance", typeof(Character), typeof(RoguelikeSettings));
List<string> list = new List<string>();
if (battleResultTarget.GetLevel == null)
{
list.Add("Character::get_Level");
}
if (battleResultTarget.GetRoguelikeSettings == null)
{
list.Add("GlobalSettingsManager::get_instance");
}
if (battleResultTarget.RoguelikeManagerField == null)
{
list.Add("GlobalSettingsManager::roguelikeManager");
}
if (battleResultTarget.RoguelikeMaxLevelField == null)
{
list.Add("RoguelikeSettings::RoguelikeMaxLevel");
}
if (battleResultTarget.GiveExperience == null)
{
list.Add("Character::GiveExperience");
}
if (battleResultTarget.ListAddCharacter == null)
{
list.Add("List<Character>::Add");
}
if (battleResultTarget.CanAdvance == null)
{
list.Add("Policy::CanAdvance");
}
if (list.Count > 0)
{
missing = string.Join(", ", list.ToArray());
}
return battleResultTarget;
}
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
string missing;
BattleResultTarget t = ResolveTarget(out missing);
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
if (missing != null)
{
BattleResultTranspiler.LastApplied = false;
BattleResultTranspiler.LastFailure = "membros ausentes na DLL: " + missing;
Log.Error("Transpiler do resultado: " + BattleResultTranspiler.LastFailure);
return list;
}
if (!BattleResultTranspiler.TryTransform(list, t, out var result, out var failure))
{
Log.Error("Transpiler do resultado recusou a janela (" + failure + "); IL original mantido");
return list;
}
return result;
}
}
public static class MaxLevelPatches
{
public static void Postfix(Character __instance, ref float __result)
{
PatchRegistryEvidence.FirstHook("Character.MaxLevel");
__result = (float)Policy.EffectiveMaxLevel(__instance, __result);
}
}
public static class LegacyMirrorPatches
{
public static bool Prefix(Character __instance, float val)
{
if (__instance == null)
{
return Log.Vanilla("CharacterOwner", "character=null");
}
if (val <= 20f)
{
return Log.Vanilla("Mirror", "nivel <= 20 (mirror vanilla)");
}
if (!ExtensionScope.AppliesTo(__instance))
{
return true;
}
try
{
if (__instance.IsLoaded)
{
__instance.OldExperience = OldExperienceConverter.GetOldExperienceFromLevel(20f);
}
__instance.QueueCharacterSave(false);
if (PluginState.Verbose)
{
Log.Info("Mirror legado limitado ao nivel 20 para " + __instance.CharacterName + " (ExperienceLevel=" + val.ToString("R") + ")");
}
return false;
}
catch (Exception ex)
{
Log.WarnOnce("Mirror legado falhou para " + __instance.CharacterName + ": " + ex.GetType().Name + ": " + ex.Message + " (executando o corpo vanilla)");
return true;
}
}
}
public static class XpStaticPatches
{
public static bool PrefixFloor(int level, ref float __result)
{
if (XpBackend.Suppressed)
{
return Log.Vanilla("XP", "consulta vanilla suprimida pelo backend");
}
if (!ExtensionScope.Active)
{
return Log.Vanilla("XP", "escopo de personagem inativo");
}
if (!XpBackend.TryFloor(level, out var value))
{
return Log.Vanilla("XP.Floor", (!XpBackend.Ready) ? "backend nao pronto" : ((level <= 20) ? "nivel <= 20 (curva vanilla)" : "piso calculado nao finito"));
}
__result = (float)value;
return false;
}
public static bool PrefixNext(int level, ref float __result)
{
if (XpBackend.Suppressed)
{
return Log.Vanilla("XP", "consulta vanilla suprimida pelo backend");
}
if (!ExtensionScope.Active)
{
return Log.Vanilla("XP", "escopo de personagem inativo");
}
if (!XpBackend.TryTransition(level, out var value))
{
return Log.Vanilla("XP.Next", (!XpBackend.Ready) ? "backend nao pronto" : ((level <= 20) ? "nivel <= 20 (curva vanilla)" : "transicao calculada invalida"));
}
__result = (float)value;
return false;
}
public static bool PrefixLevelFromXp(float experience, ref float __result)
{
if (XpBackend.Suppressed)
{
return Log.Vanilla("XP", "consulta vanilla suprimida pelo backend");
}
if (!ExtensionScope.Active)
{
return Log.Vanilla("XP", "escopo de personagem inativo");
}
if (!XpBackend.TryLevelFromXp(experience, out var level))
{
return Log.Vanilla("XP.LevelFromXp", (!XpBackend.Ready) ? "backend nao pronto" : ((!XpCurve.IsUsableNumber(experience)) ? "XP nao finito" : (((double)experience < XpBackend.Floor20) ? "XP abaixo de F20 (curva vanilla)" : "inversa calculada nao finita")));
}
__result = (float)level;
return false;
}
public static bool PrefixXpFromLevel(float level, ref float __result)
{
if (XpBackend.Suppressed)
{
return Log.Vanilla("XP", "consulta vanilla suprimida pelo backend");
}
if (!ExtensionScope.Active)
{
return Log.Vanilla("XP", "escopo de personagem inativo");
}
if (!XpCurve.IsUsableNumber(level))
{
return Log.Vanilla("XP.XpFromLevel", "nivel nao finito");
}
if (level <= 20f)
{
return Log.Vanilla("XP.XpFromLevel", "nivel <= 20 (curva vanilla)");
}
if (!XpBackend.Ready)
{
return Log.Vanilla("XP", "backend nao pronto");
}
double num = XpBackend.XpFromLevel(level);
if (!XpCurve.IsUsableNumber(num))
{
return Log.Vanilla("XP.XpFromLevel", "XP calculado nao finito");
}
__result = (float)num;
return false;
}
public static bool PrefixLevelIndex(float experience, ref int __result)
{
if (XpBackend.Suppressed)
{
return Log.Vanilla("XP", "consulta vanilla suprimida pelo backend");
}
if (!ExtensionScope.Active)
{
return Log.Vanilla("XP", "escopo de personagem inativo");
}
if (!XpBackend.Ready)
{
return Log.Vanilla("XP", "backend nao pronto");
}
double num = experience;
if (!XpCurve.IsUsableNumber(num))
{
return Log.Vanilla("XP.LevelIndex", "XP nao finito");
}
if (num < XpBackend.Floor20)
{
return Log.Vanilla("XP.LevelIndex", "XP abaixo de F20 (curva vanilla)");
}
__result = XpCurve.LevelIndexFromXp(XpBackend.Floor20, XpBackend.Transition20, num);
return false;
}
}
public static class ScopePatches
{
public static bool PrefixCharacterOwner(Character __instance, out bool __state)
{
__state = false;
if (__instance == null)
{
return Log.Vanilla("CharacterOwner", "character=null");
}
if (!ExtensionScope.AppliesTo(__instance))
{
return true;
}
__state = ExtensionScope.Push(__instance);
return true;
}
public static void FinalizerCharacterOwner(bool __state)
{
if (__state)
{
ExtensionScope.Pop();
}
}
}
internal static class PatchRegistry
{
internal sealed class Plan
{
public string Id;
public MethodBase Target;
public MethodInfo Prefix;
public MethodInfo Postfix;
public MethodInfo Transpiler;
public MethodInfo Finalizer;
}
internal static bool TryBuildPlans(out List<Plan> outPlans, out List<string> outMissing)
{
List<Plan> plans = new List<Plan>();
List<string> missing = new List<string>();
Type typeFromHandle = typeof(Character);
Type typeFromHandle2 = typeof(InventoryManager);
Type typeFromHandle3 = typeof(Skillbar);
Type typeFromHandle4 = typeof(RoguelikeManager);
Add("Root.SendGameCompleteFromServer", Reflect.Method(typeof(Root), "SendGameCompleteFromServer"), null, null, Reflect.Method(typeof(BattleResultPatches), "Transpiler"));
Add("Character.get_MaxLevel", Reflect.PropertyGetter(typeFromHandle, "MaxLevel"), null, Reflect.Method(typeof(MaxLevelPatches), "Postfix"));
Add("Character.ExperienceLevelDidSet", Reflect.Method(typeFromHandle, "ExperienceLevelDidSet", typeof(float)), Reflect.Method(typeof(LegacyMirrorPatches), "Prefix"));
Add("Character.GetExperienceFloorForLevel", Reflect.Method(typeFromHandle, "GetExperienceFloorForLevel", typeof(int)), Reflect.Method(typeof(XpStaticPatches), "PrefixFloor"));
Add("Character.GetExperienceForNextLevel", Reflect.Method(typeFromHandle, "GetExperienceForNextLevel", typeof(int)), Reflect.Method(typeof(XpStaticPatches), "PrefixNext"));
Add("Character.GetExperienceLevelFromExperience", Reflect.Method(typeFromHandle, "GetExperienceLevelFromExperience", typeof(float)), Reflect.Method(typeof(XpStaticPatches), "PrefixLevelFromXp"));
Add("Character.GetExperienceFromExperienceLevel", Reflect.Method(typeFromHandle, "GetExperienceFromExperienceLevel", typeof(float)), Reflect.Method(typeof(XpStaticPatches), "PrefixXpFromLevel"));
Add("Character.GetLevelFromExperience", Reflect.Method(typeFromHandle, "GetLevelFromExperience", typeof(float)), Reflect.Method(typeof(XpStaticPatches), "PrefixLevelIndex"));
MethodInfo prefix = Reflect.Method(typeof(ScopePatches), "PrefixCharacterOwner");
MethodInfo finalizer = Reflect.Method(typeof(ScopePatches), "FinalizerCharacterOwner");
Add("Character.get_Experience", Reflect.PropertyGetter(typeFromHandle, "Experience"), prefix, null, null, finalizer);
Add("Character.get_ExpForNextLevel", Reflect.PropertyGetter(typeFromHandle, "ExpForNextLevel"), prefix, null, null, finalizer);
Add("Character.get_MaxExperience", Reflect.PropertyGetter(typeFromHandle, "MaxExperience"), prefix, null, null, finalizer);
Add("Character.GiveExperience", Reflect.Method(typeFromHandle, "GiveExperience", typeof(float), typeof(bool)), prefix, null, null, finalizer);
Add("InventoryManager.RefreshHealthbars", Reflect.Method(typeFromHandle2, "RefreshHealthbars"), Reflect.Method(typeof(InventoryManagerPatches), "Prefix"), Reflect.Method(typeof(InventoryManagerPatches), "Postfix"), null, Reflect.Method(typeof(InventoryManagerPatches), "Finalizer"));
Add("Skillbar.UpdateExpBar", Reflect.Method(typeFromHandle3, "UpdateExpBar"), Reflect.Method(typeof(SkillbarPatches), "Prefix"), null, null, Reflect.Method(typeof(SkillbarPatches), "Finalizer"));
Add("RoguelikeManager.GetSkillChoices", Reflect.Method(typeFromHandle4, "GetSkillChoices"), Reflect.Method(typeof(RewardQueryPatches), "PrefixSkillChoices"));
Add("RoguelikeManager.GetItemChoices", Reflect.Method(typeFromHandle4, "GetItemChoices"), Reflect.Method(typeof(RewardQueryPatches), "PrefixItemChoices"), Reflect.Method(typeof(RewardQueryPatches), "PostfixItemChoices"));
Add("RoguelikeManager.GetRoguelikeLevelsNeededCount", Reflect.Method(typeFromHandle4, "GetRoguelikeLevelsNeededCount"), null, Reflect.Method(typeof(RewardQueryPatches), "PostfixNeededCount"));
Add("RoguelikeManager.OpenSkillSelectWindow", Reflect.Method(typeFromHandle4, "OpenSkillSelectWindow"), null, Reflect.Method(typeof(LevelUpStagePatches), "PostfixOpenSkillSelectWindow"));
Add("RoguelikeManager.RerollSkill", Reflect.Method(typeFromHandle4, "RerollSkill"), Reflect.Method(typeof(RerollPatches), "PrefixRerollSkill"));
Add("Character.SyncCharacterToPreset", Reflect.Method(typeFromHandle, "SyncCharacterToPreset"), null, Reflect.Method(typeof(PresetSyncPatches), "Postfix"));
if (missing.Count > 0)
{
outPlans = null;
outMissing = missing;
return false;
}
outPlans = plans;
outMissing = missing;
return true;
void Add(string id, MethodBase target, MethodInfo methodInfo = null, MethodInfo postfix = null, MethodInfo transpiler = null, MethodInfo methodInfo2 = null)
{
if (target == null)
{
missing.Add(id);
}
else if (methodInfo == null && postfix == null && transpiler == null && methodInfo2 == null)
{
missing.Add(id + "#handler");
}
else
{
plans.Add(new Plan
{
Id = id,
Target = target,
Prefix = methodInfo,
Postfix = postfix,
Transpiler = transpiler,
Finalizer = methodInfo2
});
}
}
}
private static string MissingError(List<string> missing)
{
return "alvos Harmony ausentes nesta DLL: " + string.Join(", ", missing.ToArray()) + " - NENHUM patch do mod foi aplicado (fail-closed)";
}
public static bool ApplyAll(Harmony harmony, out string error)
{
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Expected O, but got Unknown
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Expected O, but got Unknown
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Expected O, but got Unknown
//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Expected O, but got Unknown
//IL_0210: Unknown result type (might be due to invalid IL or missing references)
//IL_021a: Expected O, but got Unknown
error = null;
if (!TryBuildPlans(out var outPlans, out var outMissing))
{
error = MissingError(outMissing);
return false;
}
List<Plan> list = new List<Plan>();
foreach (Plan item in outPlans)
{
try
{
PatchProcessor val = harmony.CreateProcessor(item.Target);
if (item.Prefix != null)
{
val.AddPrefix(new HarmonyMethod(item.Prefix));
}
if (item.Postfix != null)
{
val.AddPostfix(new HarmonyMethod(item.Postfix));
}
if (item.Transpiler != null)
{
val.AddTranspiler(new HarmonyMethod(item.Transpiler));
}
if (item.Finalizer != null)
{
val.AddFinalizer(new HarmonyMethod(item.Finalizer));
}
val.Patch();
list.Add(item);
}
catch (Exception ex)
{
error = "falha ao aplicar " + item.Id + ": " + ex.GetType().Name + ": " + ex.Message;
Rollback(harmony, list.Count);
return false;
}
}
if (!BattleResultTranspiler.LastApplied)
{
error = "transpiler critico do resultado recusou o alvo (" + (BattleResultTranspiler.LastFailure ?? "motivo desconhecido") + "); grupo de gameplay revertido inteiro";
Rollback(harmony, list.Count);
return false;
}
try
{
MethodInfo methodInfo = Reflect.Method(typeof(PostBattleManager), "OpenPostBattleMenu");
MethodInfo methodInfo2 = Reflect.Method(typeof(PostBattleFailsafePatches), "Prefix");
if (methodInfo == null || methodInfo2 == null)
{
Log.Warn("failsafe de pos-batalha NAO aplicado: " + ((methodInfo == null) ? "PostBattleManager.OpenPostBattleMenu" : "PostBattleFailsafePatches.Prefix") + " nao resolvido (sem efeito; grupo critico intacto)");
}
else
{
PatchProcessor obj = harmony.CreateProcessor((MethodBase)methodInfo);
obj.AddPrefix(new HarmonyMethod(methodInfo2));
obj.Patch();
Log.Info("failsafe de pos-batalha aplicado em PostBattleManager.OpenPostBattleMenu");
}
}
catch (Exception ex2)
{
Log.Warn("failsafe de pos-batalha NAO aplicado (" + ex2.GetType().Name + ": " + ex2.Message + "); grupo critico intacto");
}
Log.Info("Grupo de gameplay aplicado: " + list.Count + " alvos; janela estrutural encontrada " + BattleResultTranspiler.LastMatchCount + "x");
return true;
}
internal static void LogOwnership(string origin)
{
try
{
if (!TryBuildPlans(out var outPlans, out var outMissing))
{
Log.Refuse("ownership/" + origin, MissingError(outMissing));
return;
}
int num = 0;
foreach (Plan item in outPlans)
{
Patches patchInfo = Harmony.GetPatchInfo(item.Target);
if (patchInfo != null && patchInfo.Owners.Contains("com.gumatos.roguelikeunlimitedlevelprogression"))
{
num++;
}
else
{
Log.Refuse("ownership/" + origin, "owner ausente em " + item.Id);
}
}
MethodInfo methodInfo = Reflect.Method(typeof(PostBattleManager), "OpenPostBattleMenu");
bool flag = ((methodInfo == null) ? null : Harmony.GetPatchInfo((MethodBase)methodInfo))?.Prefixes.Any((Patch p) => p.owner == "com.gumatos.roguelikeunlimitedlevelprogression" && p.PatchMethod.DeclaringType == typeof(PostBattleFailsafePatches)) ?? false;
MethodInfo methodInfo2 = Reflect.Method(typeof(GameLogic), "GetCharacterLootDropModifier");
Patches val = ((methodInfo2 == null) ? null : Harmony.GetPatchInfo((MethodBase)methodInfo2));
bool flag2 = val != null && val.Owners.Contains("com.gumatos.roguelikedebugger");
Log.Info("ownership [" + origin + "]: gameplay=" + num + "/" + outPlans.Count + " failsafePrefix=" + flag + " debuggerLootModifier=" + flag2 + " assembly=" + typeof(Root).Assembly.Location + " mvid=" + typeof(Root).Module.ModuleVersionId);
if (!flag)
{
Log.Refuse("ownership/" + origin, "prefix failsafe ausente AGORA");
}
}
catch (Exception ex)
{
Log.Warn("ownership [" + origin + "] NAO CONFERIDO: " + ex.GetType().Name + ": " + ex.Message);
}
}
private static void Rollback(Harmony harmony, int count)
{
try
{
Harmony.UnpatchID("com.gumatos.roguelikeunlimitedlevelprogression");
}
catch (Exception ex)
{
Log.Error("rollback falhou: " + ex.GetType().Name + ": " + ex.Message);
}
}
}
public static class PostBattleFailsafePatches
{
private static void Deny(string reason)
{
Log.Refuse("pos-batalha", reason, once: false);
}
public static void Prefix(bool victory, List<Character> roguelikeCharactersToLevel)
{
try
{
Log.Info("pos-batalha: hook entrou; victory=" + victory + " lista=" + ((roguelikeCharactersToLevel == null) ? "null" : roguelikeCharactersToLevel.Count.ToString()) + " enabled=" + PluginState.Enabled + " grupo=" + PluginState.GameplayGroupApplied);
PatchRegistryEvidence.FirstHook("PostBattleManager.OpenPostBattleMenu");
PatchRegistry.LogOwnership("pos-batalha");
if (!victory)
{
Deny("victory=False");
return;
}
if (roguelikeCharactersToLevel == null)
{
Deny("roguelikeCharactersToLevel=null");
return;
}
if (!PluginState.Enabled)
{
Deny("General.Enabled=False");
return;
}
if (!PluginState.GameplayGroupApplied)
{
Deny("GameplayGroupApplied=False");
return;
}
if (!GameState.IsRoguelikeMode())
{
Deny("IsRoguelikeMode=False");
return;
}
if (GameState.IsMultiplayer())
{
Deny("IsMultiplayer=True (inclui rede incerta)");
return;
}
GameLogic instance = GameLogic.instance;
if ((Object)(object)instance == (Object)null)
{
Deny("GameLogic.instance=null");
return;
}
IEnumerable<Character> controlledCharacters = instance.ControlledCharacters;
if (controlledCharacters == null)
{
Deny("ControlledCharacters=null");
return;
}
List<Character> list = new List<Character>(controlledCharacters);
if (list.Count == 0)
{
Deny("ControlledCharacters vazio");
return;
}
RoguelikeSettings settings = GameState.Settings();
foreach (Character item in list)
{
if (item == null)
{
Deny("character=null na lista controlada");
continue;
}
Log.Info("pos-batalha: personagem=" + item.CharacterName + " nivel=" + item.Level + " IsAI=" + item.IsAI + " IsRoguelikeCharacter=" + item.IsRoguelikeCharacter);
if (item.IsAI)
{
Deny("IsAI=True: " + item.CharacterName);
continue;
}
if (!item.IsRoguelikeCharacter)
{
Deny("IsRoguelikeCharacter=False: " + item.CharacterName);
continue;
}
if (roguelikeCharactersToLevel.Contains(item))
{
Deny("nivel adicional nao concedido: ja na lista vanilla: " + item.CharacterName);
continue;
}
if (!Policy.CanAdvance(item, settings))
{
Deny("Policy.CanAdvance=False: " + item.CharacterName + "; " + PluginState.LastDenial);
continue;
}
int level = item.Level;
item.GiveExperience(item.ExpForNextLevel, false);
roguelikeCharactersToLevel.Add(item);
Log.Info("pos-batalha (failsafe): nivel estendido concedido a " + item.CharacterName + " (" + level + " -> " + item.Level + "); janela vanilla nao havia promovido");
}
}
catch (Exception ex)
{
Deny("falha " + ex.GetType().Name + ": " + ex.Message + "; fluxo vanilla mantido");
}
}
}
internal static class Reflect
{
internal const BindingFlags Any = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
internal static MethodInfo Method(Type type, string name, params Type[] parameters)
{
if (type == null || string.IsNullOrEmpty(name))
{
return null;
}
Type type2 = type;
while (type2 != null)
{
MethodInfo methodInfo = null;
try
{
if (parameters == null || parameters.Length == 0)
{
MethodInfo[] methods = type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo2 in methods)
{
if (!(methodInfo2.Name != name) && methodInfo == null)
{
methodInfo = methodInfo2;
}
}
}
else
{
methodInfo = type2.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null);
}
}
catch (AmbiguousMatchException)
{
}
if (methodInfo != null)
{
return methodInfo;
}
type2 = type2.BaseType;
}
return null;
}
internal static PropertyInfo Property(Type type, string name)
{
if (type == null || string.IsNullOrEmpty(name))
{
return null;
}
Type type2 = type;
while (type2 != null)
{
PropertyInfo property = type2.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
return property;
}
type2 = type2.BaseType;
}
return null;
}
internal static MethodInfo PropertyGetter(Type type, string name)
{
PropertyInfo propertyInfo = Property(type, name);
if (propertyInfo == null)
{
return null;
}
return propertyInfo.GetGetMethod(nonPublic: true);
}
internal static FieldInfo Field(Type type, string name)
{
if (type == null || string.IsNullOrEmpty(name))
{
return null;
}
Type type2 = type;
while (type2 != null)
{
FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field;
}
type2 = type2.BaseType;
}
return null;
}
}
public static class RewardQueryPatches
{
public static void PrefixSkillChoices(ref int level, Character character)
{
if (PluginState.Enabled && level > 20 && character != null && ExtensionScope.AppliesTo(character))
{
int rewardQueryLevel = PluginState.RewardQueryLevel;
if (rewardQueryLevel > 20 && rewardQueryLevel != level)
{
level = rewardQueryLevel;
}
}
}
public static void PrefixItemChoices(ref int level, Character recipient)
{
if (PluginState.Enabled && level > 20 && recipient != null && ExtensionScope.AppliesTo(recipient))
{
int rewardQueryLevel = PluginState.RewardQueryLevel;
if (rewardQueryLevel > 20 && rewardQueryLevel != level)
{
level = rewardQueryLevel;
}
}
}
public static void PostfixItemChoices(ref List<Item> __result)
{
if (__result != null)
{
int num = __result.RemoveAll((Item item) => item == null);
if (num > 0)
{
Log.Warn("GetItemChoices devolveu " + num + " item(ns) nulo(s); removidos antes de popular a janela");
}
if (__result.Count == 0)
{
Log.WarnOnce("GetItemChoices devolveu lista vazia: estagio de itens sera pulado, seguindo para atributos");
}
}
}
public static void PostfixNeededCount(Character character, ref int __result)
{
if (!PluginState.Enabled || character == null || !ExtensionScope.AppliesTo(character))
{
return;
}
int num = LedgerService.ReceiptCount(character);
if (num > 0)
{
__result -= num;
if (__result < 0)
{
__result = 0;
}
}
}
}
public static class LevelUpStagePatches
{
private const int MaxStageHops = 8;
public static void PostfixOpenSkillSelectWindow(RoguelikeManager __instance)
{
try
{
EnsurePlayableStage(__instance);
}
catch (Exception ex)
{
Log.WarnOnce("Adaptador de estagio falhou (" + ex.GetType().Name + ": " + ex.Message + "); janela vanilla mantida");
}
}
internal static void EnsurePlayableStage(RoguelikeManager mgr)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Invalid comparison between Unknown and I4
if ((Object)(object)mgr == (Object)null)
{
return;
}
for (int i = 0; i < 8; i++)
{
LevelUpStage curLevelUpStage;
try
{
curLevelUpStage = mgr.CurLevelUpStage;
}
catch (Exception)
{
return;
}
Character currentRoguelikeSkillSelectingCharacter = mgr.CurrentRoguelikeSkillSelectingCharacter;
if ((int)curLevelUpStage == 0)
{
if (HasSkillOption(mgr) || currentRoguelikeSkillSelectingCharacter == null || !ExtensionScope.AppliesTo(currentRoguelikeSkillSelectingCharacter))
{
return;
}
if (!LedgerService.TryRecordSkippedSkill(currentRoguelikeSkillSelectingCharacter, SafeLevel(currentRoguelikeSkillSelectingCharacter), out var error))
{
Log.Error("pool de skills esgotado e recibo NAO gravado (" + error + "): estagio NAO sera pulado");
return;
}
Log.Warn("pool de skills esgotado no nivel " + SafeLevel(currentRoguelikeSkillSelectingCharacter) + ": recibo S gravado, seguindo para o proximo estagio");
if (!AdvanceStage(mgr, currentRoguelikeSkillSelectingCharacter))
{
return;
}
}
else
{
if ((int)curLevelUpStage != 1 || HasItemOption(mgr))
{
return;
}
Log.Warn("pool de itens vazio: seguindo para o proximo estagio");
if (!AdvanceStage(mgr, currentRoguelikeSkillSelectingCharacter))
{
return;
}
}
}
Log.WarnOnce("adaptador de estagio atingiu o limite de saltos; janela mantida como esta");
}
private static int SafeLevel(Character c)
{
try
{
return c.Level;
}
catch (Exception)
{
return 0;
}
}
private static bool HasSkillOption(RoguelikeManager mgr)
{
try
{
RoguelikeSkillOption[] skillOptionBtns = mgr.SkillOptionBtns;
if (skillOptionBtns == null)
{
return false;
}
foreach (RoguelikeSkillOption val in skillOptionBtns)
{
if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)null && ((Component)val).gameObject.activeSelf && (Object)(object)val.SkillInfo != (Object)null)
{
return true;
}
}
return false;
}
catch (Exception)
{
return true;
}
}
private static bool HasItemOption(RoguelikeManager mgr)
{
try
{
Inventory itemInventory = mgr.ItemInventory;
if ((Object)(object)itemInventory == (Object)null)
{
return true;
}
List<Item> allItems = itemInventory.AllItems;
if (allItems == null || allItems.Count == 0)
{
return false;
}
for (int i = 0; i < allItems.Count; i++)
{
if (allItems[i] != null)
{
return true;
}
}
return false;
}
catch (Exception)
{
return true;
}
}
private static bool AdvanceStage(RoguelikeManager mgr, Character character)
{
//IL_0012: 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)
//IL_002e: 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_0036: Invalid comparison between Unknown and I4
LevelUpStage[] levelUpSequenceOrder = mgr.LevelUpSequenceOrder;
if (levelUpSequenceOrder == null || levelUpSequenceOrder.Length == 0)
{
return false;
}
int num = Array.IndexOf(levelUpSequenceOrder, mgr.CurLevelUpStage);
if (num < 0)
{
return false;
}
int num2 = num + 1;
while (num2 < levelUpSequenceOrder.Length)
{
LevelUpStage val = (mgr.CurLevelUpStage = levelUpSequenceOrder[num2]);
if ((int)val == 2)
{
int num3 = 0;
try
{
num3 = ((character != null) ? character.UnspentStatPoints : 0);
}
catch (Exception)
{
num3 = 0;
}
if (num3 <= 0)
{
num2++;
continue;
}
}
return true;
}
mgr.CharactersWaitingForLevelUp.RemoveAt(0);
if (mgr.CharactersWaitingForLevelUp.Count > 0)
{
mgr.OpenSkillSelectWindow(mgr.CharactersWaitingForLevelUp[0], false);
}
else
{
mgr.Close();
}
return false;
}
}
public static class RerollPatches
{
public static bool PrefixRerollSkill(RoguelikeManager __instance)
{
try
{
Character val = (((Object)(object)__instance != (Object)null) ? __instance.CurrentRoguelikeSkillSelectingCharacter : null);
RoguelikeSkillOption val2 = (((Object)(object)__instance != (Object)null) ? __instance.SelectedSkillOption : null);
if (val == null || (Object)(object)val2 == (Object)null || (Object)(object)val2.SkillInfo == (Object)null)
{
return true;
}
if (!ExtensionScope.AppliesTo(val))
{
return true;
}
RoguelikeSettings val3 = GameState.Settings();
int num = (((Object)(object)val3 != (Object)null) ? val3.NumSkillOptions : 4);
int num2 = 0;
try
{
num2 = (int)val["AdditionalRoguelikeSkillOptions"];
}
catch (Exception)
{
num2 = 0;
}
int num3 = num + num2;
if (num3 <= 0)
{
num3 = 1;
}
List<SkillInfo> list = new List<SkillInfo>(val.SkillsFromPoints);
list.Remove(val2.SkillInfo);
List<SkillInfo> skillChoices = __instance.GetSkillChoices(num3, val.Level, list, val2.SkillInfo.Tier, val);
if (skillChoices == null || skillChoices.Count == 0)
{
Log.Warn("reroll sem opcoes no tier " + val2.SkillInfo.Tier + ": reroll cancelado, skill original preservada");
return false;
}
return true;
}
catch (Exception ex2)
{
Log.WarnOnce("guarda de reroll falhou (" + ex2.GetType().Name + ": " + ex2.Message + "); seguindo o fluxo vanilla");
return true;
}
}
}
public static class PresetSyncPatches
{
public static void Postfix(Character __instance, CharacterPresetFile preset)
{
if (__instance == null)
{
return;
}
if ((Object)(object)preset == (Object)null)
{
Log.Warn("SyncCharacterToPreset com preset nulo: ledger NAO resetado");
}
else
{
if (!ExtensionScope.AppliesTo(__instance))
{
return;
}
int num = 1;
try
{
num = __instance.Level;
}
catch (Exception)
{
return;
}
if (num != 1)
{
Log.Warn("SyncCharacterToPreset terminou com Level=" + num + " (esperado 1): ledger NAO resetado");
return;
}
string text = "";
try
{
text = preset.Guid.ToString();
}
catch (Exception)
{
text = "";
}
if (string.IsNullOrEmpty(text))
{
try
{
text = __instance.CharacterPresetFileGuid;
}
catch (Exception)
{
}
}
LedgerService.OnPresetSyncSuccess(__instance, text);
}
}
}
public static class InventoryManagerPatches
{
public static bool Prefix(InventoryManager __instance, out Character __state)
{
__state = null;
try
{
Character val = (((Object)(object)GameLogic.instance != (Object)null) ? GameLogic.instance.CurrentlySelectedCharacter : null);
if (val == null)
{
return true;
}
if (!ExtensionScope.AppliesTo(val))
{
return true;
}
if (ExtensionScope.Push(val))
{
__state = val;
}
}
catch (Exception)
{
__state = null;
}
return true;
}
public static void Finalizer(Character __state)
{
if (__state != null)
{
ExtensionScope.Pop();
}
}
public static void Postfix(InventoryManager __instance, Character __state)
{
if (__state == null || (Object)(object)__instance == (Object)null || !ExtensionScope.AppliesTo(__state))
{
return;
}
int level;
try
{
level = __state.Level;
}
catch (Exception)
{
return;
}
if (level < 30)
{
return;
}
try
{
float experienceFloorForLevel = Character.GetExperienceFloorForLevel(level);
float experienceFloorForLevel2 = Character.GetExperienceFloorForLevel(level + 1);
float experience = __state.Experience;
float num = (float)Math.Ceiling(experienceFloorForLevel2) - (float)Math.Ceiling(experienceFloorForLevel);
float num2 = (float)Math.Ceiling(experience) - (float)Math.Ceiling(experienceFloorForLevel);
if (!XpCurve.IsUsableNumber(num) || !XpCurve.IsUsableNumber(num2) || !(num > 0f))
{
Log.WarnOnce("RefreshHealthbars: barra de XP sem divisor valido em nivel " + level + "; exibindo 0");
if ((Object)(object)__instance.ToNextLevelImage != (Object)null)
{
__instance.ToNextLevelImage.fillAmount = 0f;
}
if ((Object)(object)__instance.ToNextLevelText != (Object)null)
{
((TMP_Text)__instance.ToNextLevelText).text = "0";
}
return;
}
float num3 = num2 / num;
if (num3 < 0f)
{
num3 = 0f;
}
if (num3 > 1f)
{
num3 = 1f;
}
if ((Object)(object)__instance.ToNextLevelImage != (Object)null)
{
__instance.ToNextLevelImage.fillAmount = num3;
}
if ((Object)(object)__instance.ToNextLevelText != (Object)null)
{
float num4 = num - num2;
if (num4 < 0f)
{
num4 = 0f;
}
((TMP_Text)__instance.ToNextLevelText).text = GUIManager.instance.floatToText((float)Math.Ceiling(num4));
}
}
catch (Exception ex2)
{
Log.WarnOnce("RefreshHealthbars: recálculo da barra falhou (" + ex2.GetType().Name + ": " + ex2.Message + ")");
}
}
}
public static class SkillbarPatches
{
public static bool Prefix(Skillbar __instance, out bool __state)
{
__state = false;
Character val = null;
try
{
val = (((Object)(object)__instance != (Object)null) ? __instance.Character : null);
}
catch (Exception)
{
return true;
}
if (val == null)
{
return true;
}
if (!ExtensionScope.AppliesTo(val))
{
return true;
}
__state = ExtensionScope.Push(val);
try
{
int level = val.Level;
float num = Character.GetExperienceFloorForLevel(level + 1) - Character.GetExperienceFloorForLevel(level);
if (!XpCurve.IsUsableNumber(num) || num <= 0f)
{
Log.WarnOnce("UpdateExpBar: divisor invalido (" + num.ToString("R") + "); barra zerada");
if ((Object)(object)__instance.expBar != (Object)null)
{
__instance.expBar.fillAmount = 0f;
}
if (__state)
{
ExtensionScope.Pop();
__state = false;
}
return false;
}
}
catch (Exception ex2)
{
Log.WarnOnce("UpdateExpBar: guarda falhou (" + ex2.GetType().Name + ": " + ex2.Message + ")");
if (__state)
{
ExtensionScope.Pop();
__state = false;
}
return false;
}
return true;
}
public static void Finalizer(bool __state)
{
if (__state)
{
ExtensionScope.Pop();
}
}
}
}
namespace RoguelikeUnlimitedLevelProgression.Core
{
public sealed class SkillReceipt
{
public int Ordinal;
public int EarnedLevel;
public string Reason = "empty_skill_pool";
public const string ReasonEmptySkillPool = "empty_skill_pool";
}
public static class ReceiptReasons
{
public const string EmptySkillPool = "empty_skill_pool";
}
public enum LedgerParseStatus
{
Ok,
Corrupted,
UnsupportedSchema,
IdentityMismatch
}
public sealed class LedgerDocument
{
public const int CurrentSchemaVersion = 1;
public int SchemaVersion = 1;
public string CharacterGuid = "";
public string RunId = "";
public string PresetGuid = "";
public readonly List<SkillReceipt> SkippedSkillReceipts = new List<SkillReceipt>();
public int SkippedSkillCount => SkippedSkillReceipts.Count;
public int NextOrdinal()
{
int num = 0;
for (int i = 0; i < SkippedSkillReceipts.Count; i++)
{
if (SkippedSkillReceipts[i].Ordinal > num)
{
num = SkippedSkillReceipts[i].Ordinal;
}
}
return num + 1;
}
public bool AddReceipt(int ordinal, int earnedLevel, string reason)
{
for (int i = 0; i < SkippedSkillReceipts.Count; i++)
{
if (SkippedSkillReceipts[i].Ordinal == ordinal)
{
return false;
}
}
SkippedSkillReceipts.Add(new SkillReceipt
{
Ordinal = ordinal,
EarnedLevel = earnedLevel,
Reason = reason
});
SkippedSkillReceipts.Sort((SkillReceipt a, SkillReceipt b) => a.Ordinal.CompareTo(b.Ordinal));
return true;
}
public string Serialize()
{
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.Append("{\"schema_version\":").Append(MiniJson.Number(SchemaVersion));
stringBuilder.Append(",\"character_guid\":").Append(MiniJson.Escape(CharacterGuid));
stringBuilder.Append(",\"run_id\":").Append(MiniJson.Escape(RunId));
stringBuilder.Append(",\"preset_guid\":").Append(MiniJson.Escape(PresetGuid));
stringBuilder.Append(",\"skipped_skill_receipts\":[");
for (int i = 0; i < SkippedSkillReceipts.Count; i++)
{
if (i > 0)
{
stringBuilder.Append(',');
}
SkillReceipt skillReceipt = SkippedSkillReceipts[i];
stringBuilder.Append("{\"ordinal\":").Append(MiniJson.Number(skillReceipt.Ordinal));
stringBuilder.Append(",\"earned_level\":").Append(MiniJson.Number(skillReceipt.EarnedLevel));
stringBuilder.Append(",\"reason\":").Append(MiniJson.Escape(skillReceipt.Reason));
stringBuilder.Append('}');
}
stringBuilder.Append("]}");
return stringBuilder.ToString();
}
public static LedgerParseStatus Parse(string text, out LedgerDocument doc, out string error)
{
doc = null;
error = null;
Dictionary<string, object> dictionary = MiniJson.TryParseObject(text, out error);
if (dictionary == null)
{
return LedgerParseStatus.Corrupted;
}
if (!dictionary.TryGetValue("schema_version", out var value))
{
error = "schema_version ausente";
return LedgerParseStatus.Corrupted;
}
int num = AsInt(value, out error);
if (error != null)
{
return LedgerParseStatus.Corrupted;
}
if (num != 1)
{
error = "schema_version " + num + " nao suportado";
return LedgerParseStatus.UnsupportedSchema;
}
LedgerDocument ledgerDocument = new LedgerDocument
{
SchemaVersion = num
};
if (!TryString(dictionary, "character_guid", out ledgerDocument.CharacterGuid, out error))
{
return Fail(error, LedgerParseStatus.Corrupted);
}
if (!TryString(dictionary, "run_id", out ledgerDocument.RunId, out error))
{
return Fail(error, LedgerParseStatus.Corrupted);
}
if (!TryString(dictionary, "preset_guid", out ledgerDocument.PresetGuid, out error))
{
return Fail(error, LedgerParseStatus.Corrupted);
}
if (!dictionary.TryGetValue("skipped_skill_receipts", out var value2))
{
error = "skipped_skill_receipts ausente";
return LedgerParseStatus.Corrupted;
}
if (!(value2 is List<object> list))
{
error = "skipped_skill_receipts nao e array";
return LedgerParseStatus.Corrupted;
}
HashSet<int> hashSet = new HashSet<int>();
foreach (object item in list)
{
if (!(item is Dictionary<string, object> o))
{
error = "recibo nao e objeto";
return LedgerParseStatus.Corrupted;
}
int num2 = AsInt(Require(o, "ordinal", ref error), out error);
if (error != null)
{
return LedgerParseStatus.Corrupted;
}
int earnedLevel = AsInt(Require(o, "earned_level", ref error), out error);
if (error != null)
{
return LedgerParseStatus.Corrupted;
}
if (!TryString(o, "reason", out var value3, out error))
{
return LedgerParseStatus.Corrupted;
}
if (!hashSet.Add(num2))
{
error = "ordinal duplicado: " + num2;
return LedgerParseStatus.Corrupted;
}
ledgerDocument.SkippedSkillReceipts.Add(new SkillReceipt
{
Ordinal = num2,
EarnedLevel = earnedLevel,
Reason = value3
});
}
ledgerDocument.SkippedSkillReceipts.Sort((SkillReceipt a, SkillReceipt b) => a.Ordinal.CompareTo(b.Ordinal));
doc = ledgerDocument;
return LedgerParseStatus.Ok;
}
private static LedgerParseStatus Fail(string error, LedgerParseStatus status)
{
return status;
}
private static object Require(Dictionary<string, object> o, string key, ref string error)
{
if (!o.TryGetValue(key, out var value))
{
error = key + " ausente";
return null;
}
return value;
}
private static bool TryString(Dictionary<string, object> o, string key, out string value, out string error)
{
value = null;
error = null;
if (!o.TryGetValue(key, out var value2))
{
error = key + " ausente";
return false;
}
if (value2 == null)
{
value = "";
return true;
}
if (!(value2 is string text))
{
error = key + " nao e string";
return false;
}
value = text;
return true;
}
private static int AsInt(object v, out string error)
{
error = null;
if (v is long)
{
return (int)(long)v;
}
if (v is double num)
{
if (Math.Floor(num) != num)
{
error = "inteiro esperado";
return 0;
}
return (int)num;
}
error = "inteiro esperado";
return 0;
}
}
public static class MiniJson
{
public sealed class ParseException : Exception
{
public ParseException(string message)
: base(message)
{
}
}
private sealed class Parser
{
private readonly string _s;
private int _i;
public Parser(string s)
{
_s = s;
_i = 0;
}
public object ParseDocument()
{
SkipWs();
object result = ParseValue();
SkipWs();
if (_i != _s.Length)
{
throw new ParseException("conteudo extra apos o documento na posicao " + _i);
}
return result;
}
private void SkipWs()
{
while (_i < _s.Length)
{
char c = _s[_i];
if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
{
_i++;
continue;
}
break;
}
}
private object ParseValue()
{
if (_i >= _s.Length)
{
throw new ParseException("fim inesperado");
}
switch (_s[_i])
{
case '{':
return ParseObject();
case '[':
return ParseArray();
case '"':
return ParseString();
case 't':
Expect("true");
return true;
case 'f':
Expect("false");
return false;
case 'n':
Expect("null");
return null;
default:
return ParseNumber();
}
}
private void Expect(string literal)
{
if (_i + literal.Length > _s.Length || string.CompareOrdinal(_s, _i, literal, 0, literal.Length) != 0)
{
throw new ParseException("literal invalido na posicao " + _i);
}
_i += literal.Length;
}
private Dictionary<string, object> ParseObject()
{
Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
_i++;
SkipWs();
if (_i < _s.Length && _s[_i] == '}')
{
_i++;
return dictionary;
}
while (true)
{
SkipWs();
if (_i >= _s.Length || _s[_i] != '"')
{
throw new ParseException("chave esperada na posicao " + _i);
}
string text = ParseString();
if (dictionary.ContainsKey(text))
{
throw new ParseException("chave duplicada: " + text);
}
SkipWs();
if (_i >= _s.Length || _s[_i] != ':')
{
throw new ParseException("':' esperado na posicao " + _i);
}
_i++;
SkipWs();
dictionary[text] = ParseValue();
SkipWs();
if (_i >= _s.Length)
{
throw new ParseException("fim inesperado no objeto");
}
if (_s[_i] != ',')
{
break;
}
_i++;
}
if (_s[_i] == '}')
{
_i++;
return dictionary;
}
throw new ParseException("',' ou '}' esperado na posicao " + _i);
}
private List<object> ParseArray()
{
List<object> list = new List<object>();
_i++;
SkipWs();
if (_i < _s.Length && _s[_i] == ']')
{
_i++;
return list;
}
while (true)
{
SkipWs();
list.Add(ParseValue());
SkipWs();
if (_i >= _s.Length)
{
throw new ParseException("fim inesperado no array");
}
if (_s[_i] != ',')
{
break;
}
_i++;
}
if (_s[_i] == ']')
{
_i++;
return list;
}
throw new ParseException("',' ou ']' esperado na posicao " + _i);
}
private string ParseString()
{
_i++;
StringBuilder stringBuilder = new StringBuilder();
while (_i < _s.Length)
{
char c = _s[_i++];
switch (c)
{
case '"':
return stringBuilder.ToString();
default:
stringBuilder.Append(c);
break;
case '\\':
{
if (_i >= _s.Length)
{
throw new ParseException("escape incompleto");
}
char c2 = _s[_i++];
switch (c2)
{
case '"':
stringBuilder.Append('"');
break;
case '\\':
stringBuilder.Append('\\');
break;
case '/':
stringBuilder.Append('/');
break;
case 'n':
stringBuilder.Append('\n');
break;
case 'r':
stringBuilder.Append('\r');
break;
case 't':
stringBuilder.Append('\t');
break;
case 'b':
stringBuilder.Append('\b');
break;
case 'f':
stringBuilder.Append('\f');
break;
case 'u':
if (_i + 4 > _s.Length)
{
throw new ParseException("escape unicode incompleto");
}
stringBuilder.Append((char)int.Parse(_s.Substring(_i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
_i += 4;
break;
default:
throw new ParseException("escape invalido: \\" + c2);
}
break;
}
}
}
throw new ParseException("string sem fechamento");
}
private object ParseNumber()
{
int i = _i;
if (_i < _s.Length && (_s[_i] == '-' || _s[_i] == '+'))
{
_i++;
}
bool flag = false;
while (_i < _s.Length)
{
char c = _s[_i];
if (c >= '0' && c <= '9')
{
_i++;
continue;
}
if (c != '.' && c != 'e' && c != 'E' && c != '+' && c != '-')
{
break;
}
flag = true;
_i++;
}
if (_i == i)
{
throw new ParseException("numero esperado na posicao " + i);
}
string text = _s.Substring(i, _i - i);
if (!flag && long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
return result;
}
if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
{
throw new ParseException("numero invalido: " + text);
}
if (double.IsNaN(result2) || double.IsInfinity(result2))
{
throw new ParseException("numero nao finito: " + text);
}
return result2;
}
}
public static string Escape(string s)
{
if (s == null)
{
return "null";
}
StringBuilder stringBuilder = new StringBuilder(s.Length + 2);
stringBuilder.Append('"');
foreach (char c in s)
{
switch (c)
{
case '"':
stringBuilder.Append("\\\"");
continue;
case '\\':
stringBuilder.Append("\\\\");
continue;
case '\n':
stringBuilder.Append("\\n");
continue;
case '\r':
stringBuilder.Append("\\r");
continue;
case '\t':
stringBuilder.Append("\\t");
continue;
case '\b':
stringBuilder.Append("\\b");
continue;
case '\f':
stringBuilder.Append("\\f");
continue;
}
if (c < ' ')
{
StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
int num = c;
stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
}
else
{
stringBuilder.Append(c);
}
}
stringBuilder.Append('"');
return stringBuilder.ToString();
}
public static string Number(long v)
{
return v.ToString(CultureInfo.InvariantCulture);
}
public static string Number(double v)
{
if (double.IsNaN(v) || double.IsInfinity(v))
{
throw new ArgumentException("numero nao finito");
}
return v.ToString("R", CultureInfo.InvariantCulture);
}
public static Dictionary<string, object> TryParseObject(string text, out string error)
{
error = null;
if (text == null)
{
error = "texto nulo";
return null;
}
try
{
if (!(new Parser(text).ParseDocument() is Dictionary<string, object> result))
{
error = "raiz nao e objeto";
return null;
}
return result;
}
catch (Exception ex)
{
error = ex.GetType().Name + ": " + ex.Message;
return null;
}
}
}
public enum AdvanceDenial
{
None,
VanillaScope,
ModDisabled,
WrongCharacterScope,
Multiplayer,
VoluntaryCap,
NumericGuard,
SafetyBound,
TargetMissing
}
public struct AdvanceInputs
{
public bool ModEnabled;
public bool IsAi;
public bool IsRoguelikeCharacter;
public bool IsMultiplayer;
public bool IsRoguelikeMode;
public int Level;
public int VanillaCap;
public int VoluntaryCap;
public int SafetyBound;
public double VanillaMaxLevel;
public bool IsLoading;
public double SavedLoadLevel;
public bool CurveUsable;
public bool NextLevelRepresentable;
public bool StatBudgetOk;
public bool TargetsPresent;
}
public static class PolicyCore
{
public static bool InExtendedScope(in AdvanceInputs i)
{
if (i.ModEnabled && !i.IsAi && i.IsRoguelikeCharacter && i.IsRoguelikeMode)
{
return !i.IsMultiplayer;
}
return false;
}
public static bool CanAdvance(in AdvanceInputs i, out AdvanceDenial denial)
{
if (!InExtendedScope(in i))
{
denial = AdvanceDenial.VanillaScope;
return i.Level < i.VanillaCap;
}
if (!i.TargetsPresent)
{
denial = AdvanceDenial.TargetMissing;
return false;
}
if (!i.CurveUsable)
{
denial = AdvanceDenial.NumericGuard;
return false;
}
if (!i.NextLevelRepresentable || !i.StatBudgetOk)
{
denial = AdvanceDenial.NumericGuard;
return false;
}
if (i.VoluntaryCap > 0 && i.Level >= i.VoluntaryCap)
{
denial = AdvanceDenial.VoluntaryCap;
return false;
}
if (i.SafetyBound > 0 && i.Level >= i.SafetyBound)
{
denial = AdvanceDenial.SafetyBound;
return false;
}
denial = AdvanceDenial.None;
return true;
}
public static double EffectiveMaxLevel(in AdvanceInputs i)
{
double num = ((i.SafetyBound > 0) ? ((double)i.SafetyBound) : ((i.VanillaMaxLevel > 0.0) ? i.VanillaMaxLevel : 20.0));
if (i.VoluntaryCap > 0 && (double)i.VoluntaryCap < num)
{
num = i.VoluntaryCap;
}
double num2 = i.Level;
if (i.IsLoading && XpCurve.IsUsableNumber(i.SavedLoadLevel) && i.SavedLoadLevel > num2)
{
num2 = i.SavedLoadLevel;
}
if (!(num > num2))
{
return num2;
}
return num;
}
}
public static class XpCurve
{
public const int VanillaCapLevel = 20;
public const double FloatSafeXpCeiling = 1E+30;
public const int FloatExactIntegerLimit = 16777216;
public static bool CurveUsable(double floor20, double transition)
{
if (!IsUsableNumber(floor20) || !IsUsableNumber(transition))
{
return false;
}
if (floor20 < 0.0)
{
return false;
}
if (transition <= 0.0)
{
return false;
}
double num = floor20 + transition;
if (!IsUsableNumber(num))
{
return false;
}
if (!(num > floor20))
{
return false;
}
return true;
}
public static bool IsUsableNumber(double v)
{
if (!double.IsNaN(v))
{
return !double.IsInfinity(v);
}
return false;
}
public static double Floor(double floor20, double transition, int level)
{
return floor20 + transition * (double)(level - 20);
}
public static double Transition(double transition, int level)
{
return transition;
}
public static double LevelFromXp(double floor20, double transition, double xp)
{
return 20.0 + (xp - floor20) / transition;
}
public static int LevelIndexFromXp(double floor20, double transition, double xp)
{
int num = (int)Math.Floor(LevelFromXp(floor20, transition, xp));
if (Floor(floor20, transition, num + 1) <= xp)
{
num++;
}
while (num > 20 && Floor(floor20, transition, num) > xp)
{
num--;
}
return num;
}
public static int SafetyBoundLevel(double floor20, double transition, int pointsPerLevel)
{
int val = ClampToInt(20.0 + (1E+30 - floor20) / transition);
int val2 = ((pointsPerLevel > 0) ? ClampToInt(1.0 + 16777216.0 / (double)pointsPerLevel) : 2147483645);
int val3 = ((pointsPerLevel > 0) ? ClampToInt(1.0 + 2147483645.0 / (double)pointsPerLevel) : 2147483645);
int num = Math.Min(val, Math.Min(val2, val3));
if (num < 21)
{
num = 21;
}
return num;
}
private static int ClampToInt(double v)
{
if (double.IsNaN(v))
{
return 21;
}
if (v >= 2147483645.0)
{
return 2147483645;
}
if (v <= 0.0)
{
return 1;
}
return (int)Math.Floor(v);
}
}
}