using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("CarlosMMOUnderground")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CarlosMMOUnderground")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("2d4c38b1-694e-417f-b093-13494f2d9fe5")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace CarlosMMO; [BepInPlugin("carlos.mmo.underground", "CarlosMMOUnderground", "1.1.1")] public class CarlosMMOUnderground : BaseUnityPlugin { public const string ModGuid = "carlos.mmo.underground"; public const string ModName = "CarlosMMOUnderground"; public const string ModVersion = "1.1.1"; internal static ManualLogSource Log; internal static Harmony Harmony; internal static ConfigEntry ModEnabled; internal static ConfigEntry ForceAllowPiecesInDungeons; internal static ConfigEntry IgnoreNoBuildChecks; internal static ConfigEntry VerboseLogging; internal static ConfigEntry LogDungeonEntry; internal static ConfigEntry LogBuildUnlock; internal static ConfigEntry LogPieceAllowance; internal static ConfigEntry EnableDungeonClaim; internal static ConfigEntry LogDungeonClaim; internal static ConfigEntry ClaimedDungeonsData; private static bool _lastDungeonState = false; private static float _nextDungeonCheckTime = 0f; private static string _lastDungeonType = "Unknown"; private static readonly HashSet _loggedPieces = new HashSet(); private static readonly HashSet _claimedDungeons = new HashSet(); private static readonly HashSet _loggedKnownClaims = new HashSet(); private static float _nextClaimEntryLogTime = 0f; private static bool _pendingClaim = false; private static float _pendingClaimExpireTime = 0f; private static string _pendingPieceName = "UnknownPiece"; private void Awake() { //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ModEnabled = ((BaseUnityPlugin)this).Config.Bind("1 - General", "Enabled", true, "Habilita o mod."); ForceAllowPiecesInDungeons = ((BaseUnityPlugin)this).Config.Bind("1 - General", "ForceAllowPiecesInDungeons", true, "Força peças a funcionarem em dungeon/interior."); IgnoreNoBuildChecks = ((BaseUnityPlugin)this).Config.Bind("1 - General", "IgnoreNoBuildChecks", true, "Ignora bloqueios de construção em dungeon/interior."); EnableDungeonClaim = ((BaseUnityPlugin)this).Config.Bind("1 - Claim", "EnableDungeonClaim", true, "Ativa o sistema simples de claim de dungeon."); ClaimedDungeonsData = ((BaseUnityPlugin)this).Config.Bind("1 - Claim", "ClaimedDungeonsData", "", "Dados serializados das dungeons claimadas. Não editar manualmente."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("1 - Debug", "VerboseLogging", true, "Logs detalhados gerais."); LogDungeonEntry = ((BaseUnityPlugin)this).Config.Bind("1 - Debug", "LogDungeonEntry", true, "Mostra log ao entrar/sair de dungeon ou interior."); LogBuildUnlock = ((BaseUnityPlugin)this).Config.Bind("1 - Debug", "LogBuildUnlock", true, "Mostra log quando o bloqueio de construção é ignorado."); LogPieceAllowance = ((BaseUnityPlugin)this).Config.Bind("1 - Debug", "LogPieceAllowance", false, "Mostra log quando uma peça é liberada para dungeon. Pode gerar muito log."); LogDungeonClaim = ((BaseUnityPlugin)this).Config.Bind("1 - Debug", "LogDungeonClaim", true, "Mostra log quando uma dungeon é claimada ou reencontrada."); LoadClaims(); Harmony = new Harmony("carlos.mmo.underground"); try { Harmony.PatchAll(typeof(CarlosMMOUnderground).Assembly); PatchOptionalTargets(Harmony); Log.LogInfo((object)"CarlosMMOUnderground 1.1.1 carregado."); Log.LogInfo((object)"Modo atual: Dungeon Livre + Claim simples com confirmação atrasada."); Log.LogInfo((object)("Claims carregados: " + _claimedDungeons.Count)); } catch (Exception ex) { Log.LogError((object)"Erro ao aplicar patches:"); Log.LogError((object)ex); } } private void Update() { if (IsEnabled()) { TryTrackDungeonState(); TryResolvePendingClaim(); } } private static void PatchOptionalTargets(Harmony harmony) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown TryPatchAllMethodsNamed(harmony, typeof(Player), "PlacePiece", null, new HarmonyMethod(typeof(PlayerPlacePieceClaimPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.Public))); } private static void TryPatchAllMethodsNamed(Harmony harmony, Type type, string methodName, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { try { List declaredMethods = AccessTools.GetDeclaredMethods(type); int num = 0; for (int i = 0; i < declaredMethods.Count; i++) { MethodInfo methodInfo = declaredMethods[i]; if (methodInfo == null || methodInfo.Name != methodName) { continue; } try { harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; if (VerboseLogging.Value) { Log.LogInfo((object)$"Patch: {type.Name}.{methodInfo.Name}"); } } catch (Exception ex) { Log.LogWarning((object)$"Falha patch {type.Name}.{methodInfo.Name}: {ex.Message}"); } } if (VerboseLogging.Value) { Log.LogInfo((object)$"Total patch {type.Name}.{methodName}: {num}"); } } catch (Exception ex2) { Log.LogWarning((object)$"Erro ao buscar métodos {type.Name}: {ex2.Message}"); } } internal static bool IsEnabled() { return ModEnabled != null && ModEnabled.Value; } internal static bool ShouldIgnoreNoBuild() { return IsEnabled() && IgnoreNoBuildChecks != null && IgnoreNoBuildChecks.Value; } internal static bool ShouldForcePieces() { return IsEnabled() && ForceAllowPiecesInDungeons != null && ForceAllowPiecesInDungeons.Value; } internal static bool ShouldUseClaim() { return IsEnabled() && EnableDungeonClaim != null && EnableDungeonClaim.Value; } internal static bool IsInDungeon() { string dungeonType; return DetectDungeonState(out dungeonType); } internal static bool DetectDungeonState(out string dungeonType) { dungeonType = "Unknown"; try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } MethodInfo method = ((object)localPlayer).GetType().GetMethod("InInterior", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null && method.Invoke(localPlayer, null) is int num && num != 0) { dungeonType = DetectCurrentDungeonType(localPlayer); return true; } MethodInfo method2 = ((object)localPlayer).GetType().GetMethod("IsInsideDungeon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method2 != null) { object obj = method2.Invoke(localPlayer, null); if (obj is bool && (bool)obj) { dungeonType = DetectCurrentDungeonType(localPlayer); return true; } } } catch (Exception ex) { if (VerboseLogging.Value) { Log.LogWarning((object)("Falha ao detectar dungeon/interior: " + ex.Message)); } } return false; } private static string DetectCurrentDungeonType(Player player) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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) try { if ((Object)(object)player == (Object)null) { return "Unknown"; } string text = string.Empty; if ((Object)(object)((Component)player).gameObject != (Object)null) { Scene scene = ((Component)player).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { scene = ((Component)player).gameObject.scene; text = ((Scene)(ref scene)).name; } } string text2 = (string.IsNullOrEmpty(text) ? string.Empty : text.ToLowerInvariant()); if (text2.Contains("troll")) { return "TrollCave"; } if (text2.Contains("crypt")) { return "Crypt"; } if (text2.Contains("sunken")) { return "SunkenCrypt"; } if (text2.Contains("burial")) { return "BurialChamber"; } if (text2.Contains("cave")) { return "Cave"; } if (!string.IsNullOrEmpty(text)) { return text; } } catch { } return "InteriorOrDungeon"; } private void TryTrackDungeonState() { if (Time.time < _nextDungeonCheckTime) { return; } _nextDungeonCheckTime = Time.time + 1f; string dungeonType; bool flag = DetectDungeonState(out dungeonType); if (flag != _lastDungeonState || dungeonType != _lastDungeonType) { if (LogDungeonEntry.Value) { if (flag) { Log.LogInfo((object)("[DungeonLivre] Jogador entrou em dungeon/interior: " + dungeonType)); } else { Log.LogInfo((object)"[DungeonLivre] Jogador saiu de dungeon/interior."); } } _lastDungeonState = flag; _lastDungeonType = dungeonType; } if (!flag || !ShouldUseClaim() || !(Time.time >= _nextClaimEntryLogTime)) { return; } _nextClaimEntryLogTime = Time.time + 5f; string text = BuildCurrentDungeonClaimKey(); if (!string.IsNullOrEmpty(text) && _claimedDungeons.Contains(text) && !_loggedKnownClaims.Contains(text)) { _loggedKnownClaims.Add(text); if (LogDungeonClaim.Value) { Log.LogInfo((object)("[DungeonLivre] Dungeon já claimada detectada: " + text)); } } } private void TryResolvePendingClaim() { if (!_pendingClaim) { return; } if (!ShouldUseClaim()) { _pendingClaim = false; } else if (Time.time > _pendingClaimExpireTime) { if (VerboseLogging.Value) { Log.LogInfo((object)"[DungeonLivre] Claim pendente expirou sem confirmação."); } _pendingClaim = false; } else if (IsInDungeon()) { TryClaimCurrentDungeon(_pendingPieceName); _pendingClaim = false; } } internal static void QueuePendingClaim(string pieceName) { if (ShouldUseClaim()) { _pendingClaim = true; _pendingClaimExpireTime = Time.time + 3f; _pendingPieceName = (string.IsNullOrEmpty(pieceName) ? "UnknownPiece" : pieceName); if (VerboseLogging.Value) { Log.LogInfo((object)("[DungeonLivre] Claim pendente armado para peça: " + _pendingPieceName)); } } } internal static void ForcePiece(Piece piece) { if ((Object)(object)piece == (Object)null || !ShouldForcePieces()) { return; } try { FieldInfo fieldInfo = AccessTools.Field(((object)piece).GetType(), "m_allowedInDungeons"); if (fieldInfo == null) { return; } if (!(fieldInfo.GetValue(piece) is int num) || num == 0) { fieldInfo.SetValue(piece, true); } if (LogPieceAllowance.Value) { string name = ((Object)piece).name; if (!string.IsNullOrEmpty(name) && !_loggedPieces.Contains(name)) { _loggedPieces.Add(name); Log.LogInfo((object)("[DungeonLivre] Peça liberada para dungeon: " + name)); } } } catch (Exception ex) { if (VerboseLogging.Value) { Log.LogWarning((object)("Falha ao liberar peça em dungeon: " + ex.Message)); } } } internal static void FixGhost(Player player) { if ((Object)(object)player == (Object)null || !ShouldForcePieces()) { return; } try { FieldInfo fieldInfo = AccessTools.Field(((object)player).GetType(), "m_placementGhost"); if (fieldInfo == null) { return; } object? value = fieldInfo.GetValue(player); GameObject val = (GameObject)((value is GameObject) ? value : null); if (!((Object)(object)val == (Object)null)) { Piece component = val.GetComponent(); if ((Object)(object)component != (Object)null) { ForcePiece(component); } } } catch (Exception ex) { if (VerboseLogging.Value) { Log.LogWarning((object)("Falha ao ajustar placement ghost: " + ex.Message)); } } } internal static void LogBuildUnlockOnce(string sourceName) { if (IsEnabled() && LogBuildUnlock != null && LogBuildUnlock.Value && IsInDungeon()) { Log.LogInfo((object)("[DungeonLivre] Bloqueio de construção ignorado em dungeon/interior via " + sourceName + ".")); } } internal static void TryClaimCurrentDungeon(string pieceName) { if (!ShouldUseClaim() || !IsInDungeon()) { return; } string text = BuildCurrentDungeonClaimKey(); if (!string.IsNullOrEmpty(text) && !_claimedDungeons.Contains(text)) { _claimedDungeons.Add(text); SaveClaims(); if (LogDungeonClaim.Value) { Log.LogInfo((object)("[DungeonLivre] Dungeon claimada com sucesso: " + text + " | peça inicial: " + pieceName)); } } } private static string BuildCurrentDungeonClaimKey() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0070: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return string.Empty; } if (!DetectDungeonState(out var dungeonType)) { return string.Empty; } Vector3 position = ((Component)localPlayer).transform.position; int num = Mathf.RoundToInt(position.x / 10f); int num2 = Mathf.RoundToInt(position.y / 10f); int num3 = Mathf.RoundToInt(position.z / 10f); string worldNameSafe = GetWorldNameSafe(); return $"{worldNameSafe}|{dungeonType}|{num.ToString(CultureInfo.InvariantCulture)}|{num2.ToString(CultureInfo.InvariantCulture)}|{num3.ToString(CultureInfo.InvariantCulture)}"; } catch (Exception ex) { if (VerboseLogging.Value) { Log.LogWarning((object)("Falha ao montar claim key: " + ex.Message)); } } return string.Empty; } private static string GetWorldNameSafe() { try { if ((Object)(object)ZNet.instance != (Object)null) { MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "GetWorldName", (Type[])null, (Type[])null); if (methodInfo != null) { object obj = methodInfo.Invoke(ZNet.instance, null); if (obj is string && !string.IsNullOrEmpty((string)obj)) { return (string)obj; } } } } catch { } return "UnknownWorld"; } private void LoadClaims() { _claimedDungeons.Clear(); try { string value = ClaimedDungeonsData.Value; if (string.IsNullOrEmpty(value)) { return; } string[] array = value.Split(new string[1] { ";;" }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrEmpty(text)) { _claimedDungeons.Add(text); } } } catch (Exception ex) { Log.LogWarning((object)("Falha ao carregar claims: " + ex.Message)); } } private static void SaveClaims() { try { if (ClaimedDungeonsData != null) { string[] array = new string[_claimedDungeons.Count]; _claimedDungeons.CopyTo(array); ClaimedDungeonsData.Value = string.Join(";;", array); } } catch (Exception ex) { Log.LogWarning((object)("Falha ao salvar claims: " + ex.Message)); } } } [HarmonyPatch(typeof(Piece), "Awake")] public static class PiecePatch { public static void Postfix(Piece __instance) { if (CarlosMMOUnderground.IsEnabled()) { CarlosMMOUnderground.ForcePiece(__instance); } } } public static class PlayerPlacePieceClaimPatch { public static void Postfix(Player __instance, Piece piece, Vector3 pos, Quaternion rot, bool doAttack) { if (CarlosMMOUnderground.IsEnabled() && CarlosMMOUnderground.ShouldUseClaim()) { string pieceName = (((Object)(object)piece != (Object)null) ? ((Object)piece).name : "UnknownPiece"); CarlosMMOUnderground.QueuePendingClaim(pieceName); } } }