using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TimePlayerControl.Data; 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: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("TimePlayerControl")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+1c2ded9f9375a47d6875b62e23375741c7b10cd6")] [assembly: AssemblyProduct("Valheim Time Player Control")] [assembly: AssemblyTitle("TimePlayerControl")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TimePlayerControl { public static class Loc { private static readonly Dictionary Es = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["player_fallback"] = "Jugador", ["unlimited"] = "Ilimitado", ["vip"] = "VIP", ["vip_admin"] = "VIP/Admin", ["time_available"] = "Tiempo disponible", ["remaining"] = "Restante", ["assigned"] = "Asignado", ["used"] = "Usado", ["total_played"] = "Total jugado", ["renewal"] = "Renovacion", ["low_time"] = "POCO TIEMPO", ["top_played"] = "Top tiempo jugado", ["compact"] = "Compactar", ["below_map"] = "Bajo mapa", ["out_of_time"] = "Te has quedado sin tiempo", ["out_of_time_renewal"] = "Te has quedado sin tiempo. Renovacion: {0}.", ["out_of_time_next"] = "Te has quedado sin tiempo. Proxima renovacion: {0}.", ["time_exhausted"] = "Tiempo agotado", ["warning_time"] = "ATENCION: Te quedan {0:F0} segundos ({1} min) de tiempo de juego.", ["hud_enabled"] = "activado", ["hud_disabled"] = "desactivado", ["hud_toggle"] = "HUD de tiempo {0}.", ["hud_toggle_short"] = "HUD {0}.", ["no_player_record"] = "No se encontro registro de tu usuario.", ["time_unlimited_vip"] = "Tiempo Ilimitado (VIP/Admin)", ["pause_active"] = " ( Pausa colectiva ACTIVADA: {0}/{1} jugadores - {2:F0}%)", ["time_status"] = " Tiempo restante: {0}. Total jugado: {1}. Renovacion: {2}.{3}", ["players_online"] = "Jugadores Conectados ({0}/{1} - {2:F0}%):", ["pause_discount"] = " [DESCUENTO PAUSADO]", ["no_record"] = "Sin registro", ["label_remaining"] = "Restante: {0}", ["label_assigned"] = "Asignado: {0}", ["label_used"] = "Usado: {0}", ["label_total"] = "Total jugado: {0}", ["label_renewal"] = "Renovacion: {0}", ["compact_with_key"] = "Compactar ({0})", ["syncing"] = "Sincronizando...", ["no_sync"] = "Sin datos del servidor" }; private static readonly Dictionary En = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["player_fallback"] = "Player", ["unlimited"] = "Unlimited", ["vip"] = "VIP", ["vip_admin"] = "VIP/Admin", ["time_available"] = "Available time", ["remaining"] = "Remaining", ["assigned"] = "Assigned", ["used"] = "Used", ["total_played"] = "Total played", ["renewal"] = "Renewal", ["low_time"] = "LOW TIME", ["top_played"] = "Top playtime", ["compact"] = "Compact", ["below_map"] = "Below map", ["out_of_time"] = "You have run out of time", ["out_of_time_renewal"] = "You have run out of time. Renewal: {0}.", ["out_of_time_next"] = "You have run out of time. Next renewal: {0}.", ["time_exhausted"] = "Time exhausted", ["warning_time"] = "WARNING: You have {0:F0} seconds ({1} min) of play time left.", ["hud_enabled"] = "enabled", ["hud_disabled"] = "disabled", ["hud_toggle"] = "Time HUD {0}.", ["hud_toggle_short"] = "HUD {0}.", ["no_player_record"] = "No record found for your user.", ["time_unlimited_vip"] = "Unlimited time (VIP/Admin)", ["pause_active"] = " ( Collective pause ON: {0}/{1} players - {2:F0}%)", ["time_status"] = " Time remaining: {0}. Total played: {1}. Renewal: {2}.{3}", ["players_online"] = "Online players ({0}/{1} - {2:F0}%):", ["pause_discount"] = " [TIME DEDUCTION PAUSED]", ["no_record"] = "No record", ["label_remaining"] = "Remaining: {0}", ["label_assigned"] = "Assigned: {0}", ["label_used"] = "Used: {0}", ["label_total"] = "Total played: {0}", ["label_renewal"] = "Renewal: {0}", ["compact_with_key"] = "Compact ({0})", ["syncing"] = "Syncing...", ["no_sync"] = "No server data" }; public static string Normalize(string language) { if (string.IsNullOrWhiteSpace(language)) { return "ES"; } string text = language.Trim().ToUpperInvariant(); if (text == "EN" || text == "ENG" || text == "ENGLISH") { return "EN"; } return "ES"; } public static string CurrentCode() { string language = (((Object)(object)TimeManager.Instance != (Object)null && TimeManager.Instance.DataStore != null) ? TimeManager.Instance.DataStore.ServerConfig.Language : "ES"); return Normalize(language); } public static bool IsEnglish() { return CurrentCode() == "EN"; } public static string T(string key) { Dictionary dictionary = (IsEnglish() ? En : Es); if (dictionary.TryGetValue(key, out var value)) { return value; } if (Es.TryGetValue(key, out value)) { return value; } return key; } public static string Tf(string key, params object[] args) { try { return string.Format(T(key), args); } catch { return T(key); } } } [BepInPlugin("TimePlayerControl", "Valheim Time Player Control", "1.0.1")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger; private Harmony _harmony; public static bool IsInfoLoggingEnabled() { if ((Object)(object)TimeManager.Instance != (Object)null && TimeManager.Instance.DataStore != null) { return TimeManager.Instance.DataStore.ServerConfig.EnableInfoLogs; } return true; } public static bool IsDebugLoggingEnabled() { if ((Object)(object)TimeManager.Instance != (Object)null && TimeManager.Instance.DataStore != null) { return TimeManager.Instance.DataStore.ServerConfig.EnableDebugLogs; } return false; } public static void LogInfo(string message) { if (Logger != null && IsInfoLoggingEnabled()) { Logger.LogInfo((object)message); } } public static void LogDebug(string message) { if (Logger != null && IsDebugLoggingEnabled()) { Logger.LogInfo((object)("[DEBUG] " + message)); } } private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; LogInfo("[TimePlayerControl] Cargando plugin v1.0.1..."); GameObject val = new GameObject("TimePlayerControlManager"); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent(); _harmony = new Harmony("TimePlayerControl"); _harmony.PatchAll(); LogInfo("[TimePlayerControl] Plugin cargado e iniciado exitosamente!"); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } public class TimeManager : MonoBehaviour { public sealed class LeaderboardEntry { public string PlayerName; public double TotalPlayedSeconds; } public static class ClientTimeInfo { public static double CurrentRemainingSeconds { get; set; } public static double CurrentMaxSeconds { get; set; } public static double CurrentAssignedSeconds { get; set; } public static double CurrentUsedSeconds { get; set; } public static double TotalPlayedSeconds { get; set; } public static bool IsExempt { get; set; } public static DateTime? NextReset { get; set; } public static bool ShowHud { get; set; } = true; public static bool HasReceivedSync { get; set; } public static List TopPlayers { get; set; } = new List(); public static string PendingMenuMessage { get; set; } public static float PendingMenuMessageUntil { get; set; } } public class ClientTimeHud : MonoBehaviour { private bool _enabled = true; private bool _compact = true; private bool _compactInitialized; private GUIStyle _boxStyle; private GUIStyle _labelStyle; private GUIStyle _titleStyle; private GUIStyle _buttonStyle; private GUIStyle _compactStyle; private Texture2D _bgTexture; private Texture2D _bgTextureAlert; private Texture2D _progressBgTexture; private Texture2D _progressFgTexture; private Texture2D _progressAlertTexture; private float _flashTimer = 0f; private float _lastAppliedOpacity = -1f; private float _lastUiScale = -1f; private bool _dragging; private Vector2 _dragOffset; private bool _dragMoved; private float _runtimePosX; private float _runtimePosY; private bool _hasRuntimePos; private const float FlashInterval = 0.5f; private const float ReferenceWidth = 1920f; private const float ReferenceHeight = 1080f; public bool IsDragging => _dragging; public void SetEnabled(bool enabled) { _enabled = enabled; } public void InvalidateStyles() { DestroyTexture(ref _bgTexture); DestroyTexture(ref _bgTextureAlert); DestroyTexture(ref _progressBgTexture); DestroyTexture(ref _progressFgTexture); DestroyTexture(ref _progressAlertTexture); _boxStyle = null; _labelStyle = null; _titleStyle = null; _buttonStyle = null; _compactStyle = null; _lastAppliedOpacity = -1f; _lastUiScale = -1f; } private void Awake() { InvalidateStyles(); _flashTimer = 0f; _compactInitialized = false; } private void OnDestroy() { InvalidateStyles(); } private void Update() { //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) if (ClientTimeInfo.CurrentRemainingSeconds <= 30.0 && !ClientTimeInfo.IsExempt && ClientTimeInfo.HasReceivedSync) { _flashTimer += Time.deltaTime; if (_flashTimer >= 1f) { _flashTimer = 0f; } } if (!_enabled || !ClientTimeInfo.ShowHud || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || (Object)(object)Player.m_localPlayer == (Object)null) { return; } try { if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return; } } catch { } ServerConfig serverConfig = (((Object)(object)Instance != (Object)null && Instance.DataStore != null) ? Instance.DataStore.ServerConfig : null); if (!_compactInitialized) { _compact = serverConfig?.HudStartCompact ?? true; _compactInitialized = true; } string keyName = ((serverConfig != null && !string.IsNullOrWhiteSpace(serverConfig.HudToggleKey)) ? serverConfig.HudToggleKey.Trim() : "F1"); if (!TryGetToggleKey(keyName, out var keyCode)) { keyCode = (KeyCode)282; } if (Input.GetKeyDown(keyCode)) { _compact = !_compact; } } private void OnGUI() { //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_049d: Unknown result type (might be due to invalid IL or missing references) //IL_04b3: Unknown result type (might be due to invalid IL or missing references) //IL_04be: Unknown result type (might be due to invalid IL or missing references) //IL_04cb: Unknown result type (might be due to invalid IL or missing references) //IL_0630: Unknown result type (might be due to invalid IL or missing references) //IL_066a: Unknown result type (might be due to invalid IL or missing references) //IL_0551: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_057c: Unknown result type (might be due to invalid IL or missing references) //IL_058f: Unknown result type (might be due to invalid IL or missing references) //IL_0597: Unknown result type (might be due to invalid IL or missing references) //IL_0560: Unknown result type (might be due to invalid IL or missing references) //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_0778: Unknown result type (might be due to invalid IL or missing references) //IL_07b0: Unknown result type (might be due to invalid IL or missing references) //IL_07e8: Unknown result type (might be due to invalid IL or missing references) //IL_0820: Unknown result type (might be due to invalid IL or missing references) //IL_0858: Unknown result type (might be due to invalid IL or missing references) //IL_071d: Unknown result type (might be due to invalid IL or missing references) //IL_074a: Unknown result type (might be due to invalid IL or missing references) //IL_089c: Unknown result type (might be due to invalid IL or missing references) //IL_08ca: Unknown result type (might be due to invalid IL or missing references) //IL_08cf: Unknown result type (might be due to invalid IL or missing references) //IL_08d1: Unknown result type (might be due to invalid IL or missing references) //IL_08e7: Unknown result type (might be due to invalid IL or missing references) //IL_0902: Unknown result type (might be due to invalid IL or missing references) //IL_09ee: Unknown result type (might be due to invalid IL or missing references) //IL_0933: Unknown result type (might be due to invalid IL or missing references) //IL_0a3d: Unknown result type (might be due to invalid IL or missing references) //IL_0992: Unknown result type (might be due to invalid IL or missing references) DrawPendingMenuMessageIfAny(); if (!_enabled || !ClientTimeInfo.ShowHud || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || (Object)(object)Player.m_localPlayer == (Object)null) { return; } ServerConfig serverConfig = (((Object)(object)Instance != (Object)null && Instance.DataStore != null) ? Instance.DataStore.ServerConfig : new ServerConfig()); if (!_compactInitialized) { _compact = serverConfig.HudStartCompact; _compactInitialized = true; } bool flag = IsPauseMenuVisible(); if (serverConfig.HudAutoHideOverlays && IsBlockingGameUiVisible(excludePauseMenu: true)) { return; } float gameHudScaleFactor = GetGameHudScaleFactor(); float num = Mathf.Clamp(Mathf.Min((float)Screen.width / 1920f, (float)Screen.height / 1080f) * gameHudScaleFactor, 0.6f, 3f); float opacity = Mathf.Clamp01((serverConfig.HudOpacity <= 0f) ? 0.75f : serverConfig.HudOpacity); InitializeStyles(num, opacity); float marginX = Mathf.Max(8f, serverConfig.HudPositionX * num); float marginY = Mathf.Max(8f, serverConfig.HudPositionY * num); float num2 = Mathf.Max(160f * num, serverConfig.HudWidth * num); float num3 = 10f * num; float num4 = 18f * num; float num5 = 22f * num; float num6 = 22f * num; float num7 = 8f * num; float num8 = 4f * num; bool flag2 = ClientTimeInfo.HasReceivedSync && ClientTimeInfo.CurrentRemainingSeconds <= 30.0 && !ClientTimeInfo.IsExempt; bool flag3 = flag2 && _flashTimer >= 0.5f; bool flag4 = serverConfig.HudShowProgressBar && !ClientTimeInfo.IsExempt && ClientTimeInfo.HasReceivedSync; string text = (string.IsNullOrWhiteSpace(serverConfig.HudToggleKey) ? "F1" : serverConfig.HudToggleKey.Trim().ToUpperInvariant()); int num9 = ((ClientTimeInfo.HasReceivedSync && ClientTimeInfo.TopPlayers != null) ? Math.Min(5, ClientTimeInfo.TopPlayers.Count) : 0); float num10; if (_compact) { num10 = num3 + num5 + (flag4 ? (num8 + num7) : 0f) + num3; if (flag) { num10 += num8 + num6; } } else { int num11 = 5; if (!ClientTimeInfo.HasReceivedSync) { num11 += 2; } if (ClientTimeInfo.IsExempt || flag2) { num11++; } int num12 = ((num9 > 0) ? (1 + num9) : 0); num10 = num3 + num5 + num8 + (float)num11 * num4 + num8 + num6 + num3; if (flag4) { num10 += num8 + num7; } if (num12 > 0) { num10 += num8 + (float)num12 * num4; } if (flag) { num10 += num8 + num6; } } float num13 = ((serverConfig.HudHeight > 0f) ? Mathf.Max(serverConfig.HudHeight * num, num10) : num10); ResolveHudPosition(serverConfig, marginX, marginY, num2, num13, gameHudScaleFactor, out var posX, out var posY); float num14 = num2 - num3 * 2f; float num15 = 56f * num; Rect val = default(Rect); ((Rect)(ref val))..ctor(posX + num2 - num3 - num15, posY + num3, num15, num6); float num16 = EstimateButtonRowY(posY, num3, num5, num8, num4, num7, num6, flag4, flag2, num9, _compact); float num17 = Mathf.Min(130f * num, num14); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(posX + num3, num16, num17, num6); Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(posX + num3, num16 + num6 + num8, num14, num6); HandleHudDragging(flag, ref posX, ref posY, num2, num13, serverConfig, val, val2, val3); ((Rect)(ref val))..ctor(posX + num2 - num3 - num15, posY + num3, num15, num6); num16 = EstimateButtonRowY(posY, num3, num5, num8, num4, num7, num6, flag4, flag2, num9, _compact); ((Rect)(ref val2))..ctor(posX + num3, num16, num17, num6); ((Rect)(ref val3))..ctor(posX + num3, num16 + num6 + num8, num14, num6); Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(posX, posY, num2, num13); DrawSemiTransparentBox(val4, flag3 ? _bgTextureAlert : _bgTexture); if (flag) { Color color = GUI.color; GUI.color = new Color(1f, 0.85f, 0.3f, 0.9f); GUI.Box(val4, GUIContent.none); GUI.color = color; } string text2 = ((!ClientTimeInfo.HasReceivedSync) ? "—" : (ClientTimeInfo.IsExempt ? Loc.T("unlimited") : FormatDuration(ClientTimeInfo.CurrentRemainingSeconds))); float num18 = posY + num3; if (_compact) { string text3 = ((!ClientTimeInfo.HasReceivedSync) ? Loc.T("syncing") : ((!ClientTimeInfo.IsExempt) ? text2 : Loc.T("vip"))); Color contentColor = GUI.contentColor; if (flag2) { GUI.contentColor = Color.yellow; } GUI.Label(new Rect(posX + num3, num18, num14 - num15 - num8, num5), text3, _compactStyle); GUI.contentColor = contentColor; if (GUI.Button(val, text, _buttonStyle)) { _compact = false; } num18 += num5; if (flag4) { num18 += num8; DrawProgressBar(new Rect(posX + num3, num18, num14, num7), GetRemainingRatio(), flag2); } if (flag && GUI.Button(val3, Loc.T("below_map"), _buttonStyle)) { ResetHudToMinimap(serverConfig, marginX, marginY, num2, num13, gameHudScaleFactor); } return; } GUI.Label(new Rect(posX + num3, num18, num14, num5), Loc.T("time_available"), _titleStyle); num18 += num5 + num8; if (flag4) { DrawProgressBar(new Rect(posX + num3, num18, num14, num7), GetRemainingRatio(), flag2); num18 += num7 + num8; } string text4 = ((!ClientTimeInfo.HasReceivedSync) ? "—" : (ClientTimeInfo.IsExempt ? Loc.T("unlimited") : FormatDuration(ClientTimeInfo.CurrentAssignedSeconds))); string text5 = ((!ClientTimeInfo.HasReceivedSync) ? "—" : (ClientTimeInfo.IsExempt ? Loc.T("unlimited") : FormatDuration(ClientTimeInfo.CurrentUsedSeconds))); string text6 = ((!ClientTimeInfo.HasReceivedSync) ? "—" : FormatPlayedTotal(ClientTimeInfo.TotalPlayedSeconds)); string assignmentHourLabel = GetAssignmentHourLabel(); if (!ClientTimeInfo.HasReceivedSync) { GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.T("syncing"), _labelStyle); num18 += num4; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.T("no_sync"), _labelStyle); num18 += num4; } GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.Tf("label_remaining", text2), _labelStyle); num18 += num4; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.Tf("label_assigned", text4), _labelStyle); num18 += num4; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.Tf("label_used", text5), _labelStyle); num18 += num4; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.Tf("label_total", text6), _labelStyle); num18 += num4; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.Tf("label_renewal", assignmentHourLabel), _labelStyle); num18 += num4; if (ClientTimeInfo.IsExempt) { GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.T("vip_admin"), _labelStyle); num18 += num4; } else if (flag2) { Color contentColor2 = GUI.contentColor; GUI.contentColor = Color.yellow; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.T("low_time"), _titleStyle); GUI.contentColor = contentColor2; num18 += num4; } if (num9 > 0) { num18 += num8; GUI.Label(new Rect(posX + num3, num18, num14, num4), Loc.T("top_played"), _titleStyle); num18 += num4; for (int i = 0; i < num9; i++) { LeaderboardEntry leaderboardEntry = ClientTimeInfo.TopPlayers[i]; string arg = TruncateName(leaderboardEntry.PlayerName, 14); string arg2 = FormatPlayedTotal(leaderboardEntry.TotalPlayedSeconds); GUI.Label(new Rect(posX + num3, num18, num14, num4), $"{i + 1}. {arg} {arg2}", _labelStyle); num18 += num4; } } num18 += num8; ((Rect)(ref val2))..ctor(posX + num3, num18, num17, num6); if (GUI.Button(val2, Loc.Tf("compact_with_key", text), _buttonStyle)) { _compact = true; } if (flag) { ((Rect)(ref val3))..ctor(posX + num3, num18 + num6 + num8, num14, num6); if (GUI.Button(val3, Loc.T("below_map"), _buttonStyle)) { ResetHudToMinimap(serverConfig, marginX, marginY, num2, num13, gameHudScaleFactor); } } } private static float EstimateButtonRowY(float posY, float pad, float titleLine, float gap, float line, float progressH, float buttonH, bool showProgress, bool isAlert, int topCount, bool compact) { if (compact) { float num = posY + pad + titleLine; if (showProgress) { num += gap + progressH; } return num + gap; } float num2 = posY + pad + titleLine + gap; if (showProgress) { num2 += progressH + gap; } int num3 = 5; if (isAlert || ClientTimeInfo.IsExempt) { num3++; } num2 += (float)num3 * line; if (topCount > 0) { num2 += gap + (float)(1 + topCount) * line; } return num2 + gap; } private void HandleHudDragging(bool pauseMenuOpen, ref float posX, ref float posY, float width, float height, ServerConfig cfg, params Rect[] ignoreRects) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Invalid comparison between Unknown and I4 //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Invalid comparison between Unknown and I4 //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) if (!pauseMenuOpen) { if (_dragging) { EndDrag(posX, posY, width, height, cfg); } return; } Event current = Event.current; Rect val = default(Rect); ((Rect)(ref val))..ctor(posX, posY, width, height); if ((int)current.type == 0 && current.button == 0 && ((Rect)(ref val)).Contains(current.mousePosition)) { for (int i = 0; i < ignoreRects.Length; i++) { if (((Rect)(ref ignoreRects[i])).width > 1f && ((Rect)(ref ignoreRects[i])).Contains(current.mousePosition)) { return; } } _dragging = true; _dragMoved = false; _dragOffset = current.mousePosition - new Vector2(posX, posY); current.Use(); } if (_dragging && (int)current.type == 3) { Vector2 val2 = current.mousePosition - _dragOffset; if (Vector2.Distance(val2, new Vector2(posX, posY)) > 1f) { _dragMoved = true; } posX = val2.x; posY = val2.y; ClampHudPosition(ref posX, ref posY, width, height); _runtimePosX = posX; _runtimePosY = posY; _hasRuntimePos = true; current.Use(); } if (_dragging && (int)current.type == 1) { EndDrag(posX, posY, width, height, cfg); current.Use(); } } private void EndDrag(float posX, float posY, float width, float height, ServerConfig cfg) { _dragging = false; ClampHudPosition(ref posX, ref posY, width, height); if (_dragMoved && cfg != null && Instance?.DataStore != null) { _runtimePosX = posX; _runtimePosY = posY; _hasRuntimePos = true; cfg.HudUseCustomPosition = true; cfg.HudCustomNormX = ((Screen.width > 0) ? (posX / (float)Screen.width) : 0f); cfg.HudCustomNormY = ((Screen.height > 0) ? (posY / (float)Screen.height) : 0f); Instance.DataStore.ServerConfig = cfg; Instance.DataStore.Save(); Plugin.LogInfo($"[Client] HUD reposicionado: norm=({cfg.HudCustomNormX:F3},{cfg.HudCustomNormY:F3})"); } } private void ResetHudToMinimap(ServerConfig cfg, float marginX, float marginY, float width, float height, float gameHudScale) { if (cfg != null && Instance?.DataStore != null) { cfg.HudUseCustomPosition = false; cfg.HudSnapBelowMinimap = true; cfg.HudCustomNormX = 0f; cfg.HudCustomNormY = 0f; _dragging = false; _dragMoved = false; _hasRuntimePos = false; ResolveHudPosition(cfg, marginX, marginY, width, height, gameHudScale, out var posX, out var posY); _runtimePosX = posX; _runtimePosY = posY; _hasRuntimePos = false; Instance.DataStore.ServerConfig = cfg; Instance.DataStore.Save(); Plugin.LogInfo($"[Client] HUD reseteado debajo del minimapa @ ({posX:F0},{posY:F0})."); } } private void ResolveHudPosition(ServerConfig cfg, float marginX, float marginY, float width, float height, float gameHudScale, out float posX, out float posY) { Rect imguiRect; if (_hasRuntimePos && (_dragging || cfg.HudUseCustomPosition)) { posX = _runtimePosX; posY = _runtimePosY; ClampHudPosition(ref posX, ref posY, width, height); } else if (cfg.HudUseCustomPosition) { posX = Mathf.Clamp01(cfg.HudCustomNormX) * (float)Screen.width; posY = Mathf.Clamp01(cfg.HudCustomNormY) * (float)Screen.height; ClampHudPosition(ref posX, ref posY, width, height); _runtimePosX = posX; _runtimePosY = posY; _hasRuntimePos = true; } else if (cfg.HudSnapBelowMinimap && TryGetMinimapScreenRect(out imguiRect)) { float num = Mathf.Max(2f, cfg.HudMinimapGap * gameHudScale * ((float)Screen.height / 1080f)); posX = ((Rect)(ref imguiRect)).xMax - width; posY = ((Rect)(ref imguiRect)).yMax + num; ClampHudPosition(ref posX, ref posY, width, height); } else if (cfg.HudSnapBelowMinimap) { float num2 = 250f * gameHudScale * ((float)Screen.height / 1080f); float num3 = 16f * gameHudScale * ((float)Screen.height / 1080f); float num4 = Mathf.Max(2f, cfg.HudMinimapGap * gameHudScale * ((float)Screen.height / 1080f)); posX = (float)Screen.width - width - num3; posY = num3 + num2 + num4; ClampHudPosition(ref posX, ref posY, width, height); } else { ResolveAnchoredPosition(cfg.HudAnchor, marginX, marginY, width, height, out posX, out posY); } } private static void ClampHudPosition(ref float posX, ref float posY, float width, float height) { posX = Mathf.Clamp(posX, 8f, Mathf.Max(8f, (float)Screen.width - width - 8f)); posY = Mathf.Clamp(posY, 8f, Mathf.Max(8f, (float)Screen.height - height - 8f)); } private static float GetGameHudScaleFactor() { try { if ((Object)(object)Minimap.instance != (Object)null) { FieldInfo field = typeof(Minimap).GetField("m_guiScale", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { float num = Convert.ToSingle(field.GetValue(Minimap.instance)); if (num > 0.01f) { return (num > 5f) ? (num / 100f) : num; } } } } catch { } try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType("GuiScaler") ?? assembly.GetType("GUIScaler"); if (type == null) { continue; } PropertyInfo propertyInfo = type.GetProperty("Scale", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("GuiScale", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null) { float num2 = Convert.ToSingle(propertyInfo.GetValue(null, null)); if (num2 > 0.01f) { return (num2 > 5f) ? (num2 / 100f) : num2; } } FieldInfo fieldInfo = type.GetField("m_largeGuiScale", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("m_guiScale", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null) { float num3 = Convert.ToSingle(fieldInfo.GetValue(null)); if (num3 > 0.01f) { return (num3 > 5f) ? (num3 / 100f) : num3; } } } } catch { } try { if (PlayerPrefs.HasKey("GuiScale")) { float num4 = PlayerPrefs.GetFloat("GuiScale", 1f); if (num4 > 0.01f) { return (num4 > 5f) ? (num4 / 100f) : num4; } } } catch { } return 1f; } private static bool TryGetMinimapScreenRect(out Rect imguiRect) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) imguiRect = default(Rect); try { if ((Object)(object)Minimap.instance == (Object)null) { return false; } RectTransform val = null; FieldInfo field = typeof(Minimap).GetField("m_smallRoot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value = field.GetValue(Minimap.instance); GameObject val2 = (GameObject)((value is GameObject) ? value : null); if (val2 != null && (Object)(object)val2 != (Object)null && val2.activeInHierarchy) { val = val2.GetComponent(); } else { RectTransform val3 = (RectTransform)((value is RectTransform) ? value : null); if (val3 != null && ((Component)val3).gameObject.activeInHierarchy) { val = val3; } else { Component val4 = (Component)((value is Component) ? value : null); if (val4 != null && val4.gameObject.activeInHierarchy) { val = (RectTransform)(((object)val4.GetComponent()) ?? ((object)/*isinst with value type is only supported in some contexts*/)); } } } } if ((Object)(object)val == (Object)null) { FieldInfo field2 = typeof(Minimap).GetField("m_mapImageSmall", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null) { object value2 = field2.GetValue(Minimap.instance); Component val5 = (Component)((value2 is Component) ? value2 : null); if (val5 != null) { val = (RectTransform)(((object)val5.GetComponent()) ?? ((object)/*isinst with value type is only supported in some contexts*/)); } } } if ((Object)(object)val == (Object)null) { return false; } Vector3[] array = (Vector3[])(object)new Vector3[4]; val.GetWorldCorners(array); float num = array.Min((Vector3 c) => c.x); float num2 = array.Max((Vector3 c) => c.x); float num3 = array.Min((Vector3 c) => c.y); float num4 = array.Max((Vector3 c) => c.y); float num5 = (float)Screen.height - num4; float num6 = (float)Screen.height - num3; imguiRect = new Rect(num, num5, num2 - num, num6 - num5); return ((Rect)(ref imguiRect)).width > 8f && ((Rect)(ref imguiRect)).height > 8f; } catch { return false; } } private static bool IsPauseMenuVisible() { try { return Menu.IsVisible(); } catch { return false; } } private static string TruncateName(string name, int maxChars) { if (string.IsNullOrEmpty(name)) { return Loc.T("player_fallback"); } if (name.Length <= maxChars) { return name; } return name.Substring(0, Math.Max(1, maxChars - 1)) + "…"; } private static bool TryGetToggleKey(string keyName, out KeyCode keyCode) { if (Enum.TryParse(keyName, ignoreCase: true, out keyCode)) { return true; } keyCode = (KeyCode)282; return string.Equals(keyName, "F1", StringComparison.OrdinalIgnoreCase); } private static void ResolveAnchoredPosition(string anchor, float marginX, float marginY, float width, float height, out float posX, out float posY) { string text = (string.IsNullOrEmpty(anchor) ? "TopRight" : anchor.Trim()); switch (text.ToLowerInvariant()) { case "topleft": posX = marginX; posY = marginY; break; case "bottomleft": posX = marginX; posY = (float)Screen.height - height - marginY; break; case "bottomright": posX = (float)Screen.width - width - marginX; posY = (float)Screen.height - height - marginY; break; default: posX = (float)Screen.width - width - marginX; posY = marginY; break; } ClampHudPosition(ref posX, ref posY, width, height); } private static bool IsBlockingGameUiVisible(bool excludePauseMenu = false) { try { if (InventoryGui.IsVisible()) { return true; } if (!excludePauseMenu && Menu.IsVisible()) { return true; } if (TextInput.IsVisible()) { return true; } if (StoreGui.IsVisible()) { return true; } if (Minimap.IsOpen()) { return true; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return true; } } catch { } return false; } private void DrawPendingMenuMessageIfAny() { //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(ClientTimeInfo.PendingMenuMessage)) { return; } if (Time.realtimeSinceStartup > ClientTimeInfo.PendingMenuMessageUntil) { ClientTimeInfo.PendingMenuMessage = null; return; } float num = Mathf.Clamp(Mathf.Min((float)Screen.width / 1920f, (float)Screen.height / 1080f), 0.75f, 2.5f); float num2 = Mathf.Min(520f * num, (float)Screen.width - 40f); float num3 = 70f * num; float num4 = ((float)Screen.width - num2) * 0.5f; float num5 = (float)Screen.height * 0.18f; if ((Object)(object)_bgTexture == (Object)null) { _bgTexture = CreateColorTexture(new Color(0.05f, 0.06f, 0.08f, 0.85f)); } if (_titleStyle == null) { _titleStyle = new GUIStyle(GUI.skin.label); _titleStyle.normal.textColor = new Color(1f, 0.82f, 0.28f); _titleStyle.fontSize = Mathf.Max(14, Mathf.RoundToInt(16f * num)); _titleStyle.fontStyle = (FontStyle)1; _titleStyle.alignment = (TextAnchor)4; _titleStyle.wordWrap = true; } Rect rect = default(Rect); ((Rect)(ref rect))..ctor(num4, num5, num2, num3); DrawSemiTransparentBox(rect, _bgTexture); GUI.Label(new Rect(num4 + 12f, num5 + 8f, num2 - 24f, num3 - 16f), ClientTimeInfo.PendingMenuMessage, _titleStyle); } private static float GetRemainingRatio() { if (ClientTimeInfo.IsExempt) { return 1f; } double currentAssignedSeconds = ClientTimeInfo.CurrentAssignedSeconds; if (currentAssignedSeconds <= 0.0) { return 0f; } return Mathf.Clamp01((float)(ClientTimeInfo.CurrentRemainingSeconds / currentAssignedSeconds)); } private string GetAssignmentHourLabel() { int num = (((Object)(object)Instance != (Object)null && Instance.DataStore != null) ? Instance.DataStore.ServerConfig.ResetHourUtc : 3); return ((num + 24 - 3) % 24).ToString("D2") + ":00"; } private void InitializeStyles(float uiScale, float opacity) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Expected O, but got Unknown //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Expected O, but got Unknown //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02f9: Expected O, but got Unknown //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Expected O, but got Unknown if (!Mathf.Approximately(_lastAppliedOpacity, opacity) || !Mathf.Approximately(_lastUiScale, uiScale)) { InvalidateStyles(); _lastAppliedOpacity = opacity; _lastUiScale = uiScale; } if ((Object)(object)_bgTexture == (Object)null) { _bgTexture = CreateColorTexture(new Color(0.05f, 0.06f, 0.08f, opacity)); } if ((Object)(object)_bgTextureAlert == (Object)null) { _bgTextureAlert = CreateColorTexture(new Color(0.55f, 0.12f, 0.12f, Mathf.Min(1f, opacity + 0.1f))); } if ((Object)(object)_progressBgTexture == (Object)null) { _progressBgTexture = CreateColorTexture(new Color(0.15f, 0.15f, 0.18f, Mathf.Min(1f, opacity + 0.15f))); } if ((Object)(object)_progressFgTexture == (Object)null) { _progressFgTexture = CreateColorTexture(new Color(0.85f, 0.7f, 0.2f, 0.95f)); } if ((Object)(object)_progressAlertTexture == (Object)null) { _progressAlertTexture = CreateColorTexture(new Color(0.95f, 0.35f, 0.2f, 0.95f)); } int fontSize = Mathf.Max(11, Mathf.RoundToInt(12f * uiScale)); int fontSize2 = Mathf.Max(12, Mathf.RoundToInt(13f * uiScale)); int fontSize3 = Mathf.Max(12, Mathf.RoundToInt(14f * uiScale)); if (_boxStyle == null) { _boxStyle = new GUIStyle(GUI.skin.box); _boxStyle.normal.background = _bgTexture; _boxStyle.normal.textColor = Color.white; _boxStyle.border = new RectOffset(4, 4, 4, 4); } if (_titleStyle == null) { _titleStyle = new GUIStyle(GUI.skin.label); _titleStyle.normal.textColor = new Color(1f, 0.82f, 0.28f); _titleStyle.fontSize = fontSize2; _titleStyle.fontStyle = (FontStyle)1; _titleStyle.alignment = (TextAnchor)3; } if (_labelStyle == null) { _labelStyle = new GUIStyle(GUI.skin.label); _labelStyle.normal.textColor = Color.white; _labelStyle.fontSize = fontSize; _labelStyle.alignment = (TextAnchor)3; } if (_compactStyle == null) { _compactStyle = new GUIStyle(GUI.skin.label); _compactStyle.normal.textColor = new Color(1f, 0.9f, 0.55f); _compactStyle.fontSize = fontSize3; _compactStyle.fontStyle = (FontStyle)1; _compactStyle.alignment = (TextAnchor)3; } if (_buttonStyle == null) { _buttonStyle = new GUIStyle(GUI.skin.button); _buttonStyle.fontSize = fontSize; _buttonStyle.fontStyle = (FontStyle)1; _buttonStyle.alignment = (TextAnchor)4; } } private void DrawProgressBar(Rect rect, float ratio, bool alert) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) GUI.DrawTexture(rect, (Texture)(object)_progressBgTexture); float num = Mathf.Clamp01(ratio) * ((Rect)(ref rect)).width; if (num > 0.5f) { Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref rect)).x, ((Rect)(ref rect)).y, num, ((Rect)(ref rect)).height); GUI.DrawTexture(val, (Texture)(object)(alert ? _progressAlertTexture : _progressFgTexture)); } } private void DrawSemiTransparentBox(Rect rect, Texture2D bgTexture = null) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bgTexture == (Object)null) { bgTexture = _bgTexture; } GUIStyle val = new GUIStyle(_boxStyle); val.normal.background = bgTexture; GUI.Box(rect, "", val); } private static Texture2D CreateColorTexture(Color color) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_000e: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, color); val.Apply(); return val; } private static void DestroyTexture(ref Texture2D texture) { if (!((Object)(object)texture == (Object)null)) { Object.Destroy((Object)(object)texture); texture = null; } } private static string FormatDuration(double totalSeconds) { if (double.IsNaN(totalSeconds) || double.IsInfinity(totalSeconds) || totalSeconds <= 0.0) { return "00:00:00"; } TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0.0, totalSeconds)); return $"{(int)timeSpan.TotalHours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}"; } private static string FormatPlayedTotal(double totalSeconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0.0, totalSeconds)); if (timeSpan.TotalDays >= 1.0) { return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h {timeSpan.Minutes:D2}m"; } return $"{(int)timeSpan.TotalHours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}"; } } private const string RpcTimeSync = "TimePlayerControl_TimeSync"; private const string RpcForceMenu = "TimePlayerControl_ForceMenu"; private float _saveTimer = 0f; private float _tickTimer = 0f; private float _configCheckTimer = 0f; private const float ConfigCheckIntervalSeconds = 10f; private ClientTimeHud _clientHud; private bool _isClientMode; private bool _isServerMode; private bool _clientRpcRegistered; private bool _lastHudEnabledState = true; private float _clientConfigCheckTimer = 0f; public static TimeManager Instance { get; private set; } public TimeDataStore DataStore { get; private set; } private void Awake() { Instance = this; DataStore = TimeDataStore.Load(); Plugin.LogInfo("[TimeManager] Inicializado correctamente."); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("TimePlayerControl_TimeSync", (Action)RPC_TimeSync); ZRoutedRpc.instance.Register("TimePlayerControl_ForceMenu", (Action)RPC_ForceMenu); _clientRpcRegistered = true; Plugin.LogInfo("[Client] RPCs registrados en Awake."); } else { Plugin.LogInfo("[Client] ZRoutedRpc.instance es null en Awake, se reintentará en Update."); } } private void Update() { if (!((Object)(object)ZNet.instance == (Object)null)) { _isServerMode = ZNet.instance.IsServer(); _isClientMode = !_isServerMode; if (_isClientMode) { EnsureClientRuntime(); ReloadClientHudConfigFromDisk(); } else if ((Object)(object)_clientHud != (Object)null) { _clientHud.SetEnabled(enabled: false); } if (_isServerMode) { ProcessServerTick(); } } } private void EnsureClientRuntime() { if (!_clientRpcRegistered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("TimePlayerControl_TimeSync", (Action)RPC_TimeSync); ZRoutedRpc.instance.Register("TimePlayerControl_ForceMenu", (Action)RPC_ForceMenu); _clientRpcRegistered = true; Plugin.LogInfo("[Client] RPCs registrados en EnsureClientRuntime."); } if ((Object)(object)_clientHud == (Object)null) { _clientHud = ((Component)this).gameObject.AddComponent(); Plugin.LogInfo("[Client] ClientTimeHud component creado."); } if ((Object)(object)_clientHud != (Object)null && DataStore != null) { bool enableClientHud = DataStore.ServerConfig.EnableClientHud; if (enableClientHud != _lastHudEnabledState) { _clientHud.SetEnabled(enableClientHud); _lastHudEnabledState = enableClientHud; Plugin.LogInfo($"[Client] HUD changed to: {enableClientHud}"); } } } private void ProcessServerTick() { float deltaTime = Time.deltaTime; _tickTimer += deltaTime; _saveTimer += deltaTime; _configCheckTimer += deltaTime; if (_tickTimer >= 1f) { _tickTimer = 0f; ProcessTimeTick(1f); } if (_configCheckTimer >= 10f) { _configCheckTimer = 0f; ReloadConfigFromDisk(); } float num = (float)DataStore.ServerConfig.SaveIntervalSeconds; if (_saveTimer >= num) { _saveTimer = 0f; DataStore.Save(); } } private void OnApplicationQuit() { if (DataStore != null) { DataStore.Save(); } } private void RPC_TimeSync(long sender, ZPackage pkg) { Plugin.LogDebug("[Client] RPC_TimeSync llamado."); if (pkg == null) { Plugin.LogDebug("[Client] RPC_TimeSync recibió pkg null!"); return; } try { double num = pkg.ReadDouble(); double num2 = pkg.ReadDouble(); double num3 = pkg.ReadDouble(); bool flag = pkg.ReadBool(); string text = pkg.ReadString(); bool flag2 = pkg.ReadBool(); double num4 = pkg.ReadDouble(); DateTime? nextReset = null; if (!string.IsNullOrEmpty(text) && DateTime.TryParse(text, out var result)) { nextReset = result; } ClientTimeInfo.CurrentRemainingSeconds = num; ClientTimeInfo.CurrentAssignedSeconds = num2; ClientTimeInfo.CurrentMaxSeconds = num2; ClientTimeInfo.CurrentUsedSeconds = num3; ClientTimeInfo.IsExempt = flag; ClientTimeInfo.NextReset = nextReset; ClientTimeInfo.ShowHud = flag2; ClientTimeInfo.TotalPlayedSeconds = Math.Max(0.0, num4); ClientTimeInfo.HasReceivedSync = true; List list = new List(); try { int num5 = pkg.ReadInt(); if (num5 < 0) { num5 = 0; } if (num5 > 20) { num5 = 20; } for (int i = 0; i < num5; i++) { string text2 = pkg.ReadString(); double val = pkg.ReadDouble(); list.Add(new LeaderboardEntry { PlayerName = (string.IsNullOrEmpty(text2) ? "Player" : text2), TotalPlayedSeconds = Math.Max(0.0, val) }); } } catch (Exception ex) { Plugin.LogDebug("[Client] Ranking omitido en sync: " + ex.Message); } ClientTimeInfo.TopPlayers = list; Plugin.LogDebug($"[Client] Sync OK: remaining={num:F0}s, assigned={num2:F0}s, used={num3:F0}s, total={num4:F0}s, top={list.Count}, exempt={flag}, hud={flag2}"); } catch (Exception ex2) { Plugin.LogInfo("[Client] Error en RPC_TimeSync: " + ex2.Message); } } private void RPC_ForceMenu(long sender, ZPackage pkg) { string text = Loc.T("out_of_time"); if (pkg != null) { string text2 = pkg.ReadString(); if (!string.IsNullOrEmpty(text2)) { text = text2; } } Plugin.LogInfo("[Client] RPC_ForceMenu recibido: " + text); ((MonoBehaviour)this).StartCoroutine(ForceReturnToMainMenuRoutine(text)); } private IEnumerator ForceReturnToMainMenuRoutine(string reason) { string message = (ClientTimeInfo.PendingMenuMessage = (string.IsNullOrWhiteSpace(reason) ? Loc.T("out_of_time") : reason.Trim())); ClientTimeInfo.PendingMenuMessageUntil = Time.realtimeSinceStartup + 12f; try { if ((Object)(object)MessageHud.instance != (Object)null && !string.IsNullOrEmpty(message)) { MessageHud hud = MessageHud.instance; MethodInfo[] methods = typeof(MessageHud).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); bool shown = false; MethodInfo[] array = methods; foreach (MethodInfo method in array) { if (method.Name != "ShowMessage") { continue; } ParameterInfo[] pars = method.GetParameters(); try { if (pars.Length == 2 && pars[0].ParameterType == typeof(MessageType) && pars[1].ParameterType == typeof(string)) { method.Invoke(hud, new object[2] { (object)(MessageType)2, message }); shown = true; break; } if (pars.Length == 3 && pars[0].ParameterType == typeof(MessageType) && pars[1].ParameterType == typeof(string)) { object third = (pars[2].ParameterType.IsValueType ? Activator.CreateInstance(pars[2].ParameterType) : null); method.Invoke(hud, new object[3] { (object)(MessageType)2, message, third }); shown = true; break; } } catch { } } if (!shown) { Plugin.LogDebug("[Client] No se encontro overload compatible de MessageHud.ShowMessage."); } } } catch (Exception ex) { Plugin.LogDebug("[Client] No se pudo mostrar MessageHud: " + ex.Message); } yield return (object)new WaitForSecondsRealtime(1.4f); try { if ((Object)(object)Game.instance != (Object)null) { MethodInfo logout = typeof(Game).GetMethod("Logout", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (logout != null) { ParameterInfo[] parameters = logout.GetParameters(); if (parameters.Length == 0) { logout.Invoke(Game.instance, null); } else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(bool)) { logout.Invoke(Game.instance, new object[1] { false }); } else { logout.Invoke(Game.instance, parameters.Select((ParameterInfo p) => p.HasDefaultValue ? p.DefaultValue : (p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null)).ToArray()); } } } else if ((Object)(object)ZNet.instance != (Object)null) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { ZNet.instance.Disconnect(serverPeer); } } } catch (Exception ex2) { Plugin.LogDebug("[Client] Logout/Disconnect local: " + ex2.Message); } yield return (object)new WaitForSecondsRealtime(0.25f); try { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != "start") { Plugin.LogInfo("[Client] Volviendo al menú principal. Motivo: " + message); SceneManager.LoadScene("start"); } } catch (Exception ex3) { Plugin.LogInfo("[Client] No se pudo volver al menú principal: " + ex3.Message); } } private void ReloadConfigFromDisk() { if (DataStore == null) { DataStore = TimeDataStore.Load(); return; } TimeDataStore timeDataStore = TimeDataStore.Load(); if (timeDataStore == null) { return; } DataStore.ServerConfig = timeDataStore.ServerConfig ?? new ServerConfig(); Dictionary dictionary = new Dictionary(DataStore.Players ?? new Dictionary()); if (timeDataStore.Players != null) { foreach (KeyValuePair player in timeDataStore.Players) { if (!dictionary.TryGetValue(player.Key, out var value)) { dictionary[player.Key] = player.Value; continue; } if (string.IsNullOrEmpty(value.PlayerName) && !string.IsNullOrEmpty(player.Value.PlayerName)) { value.PlayerName = player.Value.PlayerName; } if (string.IsNullOrEmpty(value.SteamID)) { value.SteamID = player.Value.SteamID; } if (!value.IsExempt && player.Value.IsExempt) { value.IsExempt = true; } } } DataStore.Players = dictionary; Plugin.LogDebug($"[TimePlayerControl] [TRACE] Revisión JSON cada {10f:F0}s: {DataStore.Players.Count} jugadores. save={DataStore.ServerConfig.SaveIntervalSeconds:F0}s, default={DataStore.ServerConfig.DefaultBlockSeconds:F0}s, warning={DataStore.ServerConfig.WarningThresholdSeconds:F0}s"); foreach (KeyValuePair item in DataStore.Players.OrderBy((KeyValuePair x) => x.Value.PlayerName)) { PlayerData value2 = item.Value; Plugin.LogDebug($"[TimePlayerControl] [TRACE] {value2.PlayerName} ({value2.SteamID}) -> restante={value2.RemainingSeconds:F0}s, siguiente_reset={value2.NextResetTime:O}, exento={value2.IsExempt}"); } } private void ReloadClientHudConfigFromDisk() { _clientConfigCheckTimer += Time.deltaTime; if (_clientConfigCheckTimer < 10f) { return; } _clientConfigCheckTimer = 0f; if (DataStore == null) { return; } TimeDataStore timeDataStore = TimeDataStore.Load(); if (timeDataStore?.ServerConfig != null && (!((Object)(object)_clientHud != (Object)null) || !_clientHud.IsDragging)) { DataStore.ServerConfig = timeDataStore.ServerConfig; if ((Object)(object)_clientHud != (Object)null) { _clientHud.InvalidateStyles(); } } } public static string GetPeerSteamID(ZNetPeer peer) { if (peer == null) { return string.Empty; } ISocket socket = peer.m_socket; string text = ((socket != null) ? socket.GetHostName() : null) ?? string.Empty; if (text.StartsWith("Steam_")) { text = text.Substring(6); } return string.IsNullOrEmpty(text) ? peer.m_uid.ToString() : text; } public bool OnPlayerConnecting(ZNetPeer peer, out string kickReason) { kickReason = string.Empty; string peerSteamID = GetPeerSteamID(peer); string text = (string.IsNullOrEmpty(peer.m_playerName) ? Loc.T("player_fallback") : peer.m_playerName); DateTime utcNow = DateTime.UtcNow; int onlineCount; int registeredCount; float currentRatio; bool flag = IsPauseRuleActive(out onlineCount, out registeredCount, out currentRatio); if (!DataStore.Players.TryGetValue(peerSteamID, out var value)) { value = new PlayerData { SteamID = peerSteamID, PlayerName = text, RemainingSeconds = 0.0, AssignedSeconds = 0.0, TotalPlayedSeconds = 0.0, LastAssignedTime = utcNow }; DateTime dateTime = utcNow.Date.AddHours(DataStore.ServerConfig.ResetHourUtc); if (dateTime <= utcNow) { dateTime = dateTime.AddDays(1.0); } double val = Math.Max(0.0, (dateTime - utcNow).TotalSeconds); double remainingSeconds = (value.AssignedSeconds = Math.Min(DataStore.ServerConfig.DefaultBlockSeconds, val)); value.RemainingSeconds = remainingSeconds; value.NextResetTime = dateTime; value.Warning30MinSent = false; DataStore.Players[peerSteamID] = value; DataStore.Save(); Plugin.LogInfo($"[TimePlayerControl] Nuevo registro para: {text} ({peerSteamID}) con {value.RemainingSeconds:F0}s restantes del bloque actual."); } else { value.PlayerName = text; if (value.NeedsReset(utcNow)) { value.ResetSeconds(DataStore.ServerConfig.DefaultBlockSeconds, DataStore.ServerConfig.ResetHourUtc, utcNow); DataStore.Save(); Plugin.LogInfo("[TimePlayerControl] Tiempo renovado para: " + text + " (" + peerSteamID + ")."); } } if (!value.IsExempt && !flag && value.RemainingSeconds <= 0.0) { kickReason = Loc.T("out_of_time"); SendForceMenuToPeer(peer, kickReason); Plugin.LogInfo("[TimePlayerControl] Bloqueando acceso de " + text + " (" + peerSteamID + "): " + kickReason); return false; } if (!value.IsExempt && value.RemainingSeconds <= 0.0) { DateTime dateTime2 = value.NextResetTime.ToLocalTime(); kickReason = Loc.Tf("out_of_time_renewal", dateTime2.ToString("dd/MM/yyyy HH:mm")); SendForceMenuToPeer(peer, kickReason); return false; } SendPlayerTimeSyncToPeer(peer, value); return true; } public bool IsPauseRuleActive(out int onlineCount, out int registeredCount, out float currentRatio) { registeredCount = DataStore.Players.Count; onlineCount = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeers() : new List())?.Count ?? 0; if (registeredCount <= 1) { currentRatio = 0f; return false; } currentRatio = (float)onlineCount / (float)registeredCount; return currentRatio >= DataStore.ServerConfig.PauseTimeMinOnlineRatio; } private void ProcessTimeTick(float deltaSeconds) { if ((Object)(object)ZNet.instance == (Object)null) { return; } List peers = ZNet.instance.GetPeers(); if (peers == null || peers.Count == 0) { return; } int onlineCount; int registeredCount; float currentRatio; bool flag = IsPauseRuleActive(out onlineCount, out registeredCount, out currentRatio); Plugin.LogDebug($"[TimePlayerControl] [TRACE] Tick de control: {peers.Count} conectados, {registeredCount} registrados, ratio={currentRatio:P0}, pausa_activa={flag}"); if (flag) { return; } double warningThresholdSeconds = DataStore.ServerConfig.WarningThresholdSeconds; foreach (ZNetPeer item in peers.ToList()) { if (item == null || !item.IsReady()) { continue; } string peerSteamID = GetPeerSteamID(item); if (!DataStore.Players.TryGetValue(peerSteamID, out var value)) { continue; } if (value.IsExempt) { value.TotalPlayedSeconds += deltaSeconds; SendPlayerTimeSyncToPeer(item, value); continue; } value.RemainingSeconds -= deltaSeconds; value.TotalPlayedSeconds += deltaSeconds; Plugin.LogDebug($"[TimePlayerControl] [TRACE] Revisando {value.PlayerName} ({peerSteamID}): restante={value.RemainingSeconds:F0}s, total={value.TotalPlayedSeconds:F0}s, threshold={warningThresholdSeconds:F0}s, nextReset={value.NextResetTime:O}"); SendPlayerTimeSyncToPeer(item, value); if (value.RemainingSeconds <= 0.0) { value.RemainingSeconds = 0.0; DateTime dateTime = value.NextResetTime.ToLocalTime(); string reason = Loc.Tf("out_of_time_next", dateTime.ToString("dd/MM/yyyy HH:mm")); SendCenterMessageToPeer(item, Loc.T("out_of_time")); SendForceMenuToPeer(item, reason); Plugin.LogInfo("[TimePlayerControl] Jugador " + value.PlayerName + " (" + peerSteamID + ") enviado al menu por limite de tiempo."); ((MonoBehaviour)this).StartCoroutine(DelayedSoftDisconnect(item, 2f, reason)); DataStore.Save(); } else if (value.RemainingSeconds <= warningThresholdSeconds && !value.Warning30MinSent) { value.Warning30MinSent = true; int num = Mathf.CeilToInt((float)(value.RemainingSeconds / 60.0)); string message = Loc.Tf("warning_time", value.RemainingSeconds, num); SendCenterMessageToPeer(item, message); SendChatMessageToPeer(item, message); Plugin.LogInfo($"[TimePlayerControl] Advertencia enviada a {value.PlayerName} ({value.RemainingSeconds:F0}s restantes)."); } } } private IEnumerator DelayedSoftDisconnect(ZNetPeer peer, float delaySeconds, string reason) { yield return (object)new WaitForSecondsRealtime(Mathf.Max(0.5f, delaySeconds)); SoftDisconnectPeer(peer, reason); } private static void SoftDisconnectPeer(ZNetPeer peer, string reason) { if (peer == null) { return; } try { if ((Object)(object)ZNet.instance != (Object)null) { ZNet.instance.Disconnect(peer); } } catch (Exception ex) { Plugin.LogInfo("[TimePlayerControl] SoftDisconnect fallo: " + ex.Message); } Plugin.LogDebug("[TimePlayerControl] SoftDisconnect aplicado. Motivo: " + reason); } private static void DisconnectPeerWithReason(ZNetPeer peer, string reason) { SoftDisconnectPeer(peer, reason); } public static void SendForceMenuToPeer(ZNetPeer peer, string reason) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown if (peer != null && ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(reason ?? Loc.T("time_exhausted")); ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "TimePlayerControl_ForceMenu", new object[1] { val }); Plugin.LogInfo($"[Server] Enviado RPC ForceMenu a {peer.m_playerName} ({peer.m_uid}): {reason}"); } } private static void SendPlayerTimeSyncToPeer(ZNetPeer peer, PlayerData player) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown if (peer == null || ZRoutedRpc.instance == null || player == null) { return; } double num = Instance?.DataStore?.ServerConfig?.DefaultBlockSeconds ?? 21600.0; bool flag = !((Object)(object)Instance != (Object)null) || Instance.DataStore == null || Instance.DataStore.ServerConfig.EnableClientHud; double num2 = ((player.AssignedSeconds > 0.0) ? player.AssignedSeconds : num); double num3 = Math.Max(0.0, player.RemainingSeconds); double num4 = Math.Max(0.0, num2 - num3); if (num4 > num2) { num4 = num2; } double num5 = Math.Max(0.0, player.TotalPlayedSeconds); List list = BuildTopPlayedLeaderboard(5); ZPackage val = new ZPackage(); val.Write(num3); val.Write(num2); val.Write(num4); val.Write(player.IsExempt); val.Write(player.NextResetTime.ToString("O")); val.Write(flag); val.Write(num5); val.Write(list.Count); foreach (LeaderboardEntry item in list) { val.Write(item.PlayerName ?? "Player"); val.Write(item.TotalPlayedSeconds); } Plugin.LogDebug($"[Server] Enviando sync a {player.PlayerName}: remaining={num3:F0}s, assigned={num2:F0}s, used={num4:F0}s, total={num5:F0}s, top={list.Count}, exempt={player.IsExempt}, hud={flag}"); ZRoutedRpc.instance.InvokeRoutedRPC(peer.m_uid, "TimePlayerControl_TimeSync", new object[1] { val }); } private static List BuildTopPlayedLeaderboard(int maxEntries) { List list = new List(); Dictionary dictionary = Instance?.DataStore?.Players; if (dictionary == null || dictionary.Count == 0) { return list; } foreach (PlayerData item in (from p in dictionary.Values where p != null orderby p.TotalPlayedSeconds descending, p.PlayerName select p).Take(Math.Max(1, maxEntries))) { string playerName = (string.IsNullOrWhiteSpace(item.PlayerName) ? Loc.T("player_fallback") : item.PlayerName.Trim()); list.Add(new LeaderboardEntry { PlayerName = playerName, TotalPlayedSeconds = Math.Max(0.0, item.TotalPlayedSeconds) }); } return list; } public static void SendCenterMessageToPeer(ZNetPeer peer, string message) { if (!string.IsNullOrEmpty(message) && peer != null) { TryInvokeMessageHud(peer, "ShowMessage", (MessageType)2, message); } } public static void SendChatMessageToPeer(ZNetPeer peer, string message) { if (!string.IsNullOrEmpty(message) && peer != null && !TryInvokeChatMethod(peer, "SendTextToPlayer", message) && !TryInvokeChatMethod(peer, "SendTextToClient", message)) { Plugin.LogDebug("[TimePlayerControl] No se pudo enviar mensaje directo a " + peer.m_playerName + ". Mensaje omitido: " + message); } } private static bool TryInvokeChatMethod(ZNetPeer peer, string methodName, string message) { try { Type typeFromHandle = typeof(Chat); object obj = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { return false; } MethodInfo[] methods = typeFromHandle.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo item in methods.Where((MethodInfo m) => m.Name == methodName)) { ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length == 2 && parameters[0].ParameterType == typeof(ZNetPeer) && parameters[1].ParameterType == typeof(string)) { item.Invoke(obj, new object[2] { peer, message }); return true; } if (parameters.Length == 2 && parameters[0].ParameterType == typeof(long) && parameters[1].ParameterType == typeof(string)) { item.Invoke(obj, new object[2] { peer.m_uid, message }); return true; } if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { item.Invoke(obj, new object[1] { message }); return true; } } } catch (Exception ex) { Plugin.LogInfo("[TimePlayerControl] No se pudo invocar Chat." + methodName + ": " + ex.Message); } return false; } private static bool TryInvokeMessageHud(ZNetPeer peer, string methodName, MessageType type, string message) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) try { Type typeFromHandle = typeof(MessageHud); object obj = typeFromHandle.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { return false; } MethodInfo method = typeFromHandle.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(MessageType), typeof(string) }, null); if (method == null) { return false; } method.Invoke(obj, new object[2] { type, message }); return true; } catch (Exception ex) { Plugin.LogInfo("[TimePlayerControl] No se pudo invocar MessageHud." + methodName + ": " + ex.Message); } return false; } public bool HandleChatCommand(ZNetPeer peer, string text) { if (string.IsNullOrEmpty(text)) { return false; } string text2 = text.Trim(); if (text2.Equals("/tiempo", StringComparison.OrdinalIgnoreCase) || text2.Equals("!tiempo", StringComparison.OrdinalIgnoreCase)) { RespondPlayerTime(peer); return true; } if (text2.Equals("/tiempos", StringComparison.OrdinalIgnoreCase) || text2.Equals("!tiempos", StringComparison.OrdinalIgnoreCase)) { RespondAllPlayerTimes(peer); return true; } if (text2.Equals("/hud", StringComparison.OrdinalIgnoreCase) || text2.Equals("!hud", StringComparison.OrdinalIgnoreCase) || text2.Equals("/tiempohud", StringComparison.OrdinalIgnoreCase) || text2.Equals("!tiempohud", StringComparison.OrdinalIgnoreCase)) { ToggleHudForPeer(peer); return true; } return false; } private void ToggleHudForPeer(ZNetPeer peer) { if (peer != null) { DataStore.ServerConfig.EnableClientHud = !DataStore.ServerConfig.EnableClientHud; DataStore.Save(); string peerSteamID = GetPeerSteamID(peer); string text = (DataStore.ServerConfig.EnableClientHud ? Loc.T("hud_enabled") : Loc.T("hud_disabled")); SendChatMessageToPeer(peer, Loc.Tf("hud_toggle", text)); SendCenterMessageToPeer(peer, Loc.Tf("hud_toggle_short", text)); if (DataStore.Players.TryGetValue(peerSteamID, out var value)) { SendPlayerTimeSyncToPeer(peer, value); } } } private void RespondPlayerTime(ZNetPeer peer) { string peerSteamID = GetPeerSteamID(peer); if (!DataStore.Players.TryGetValue(peerSteamID, out var value)) { SendChatMessageToPeer(peer, Loc.T("no_player_record")); return; } TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0.0, value.RemainingSeconds)); DateTime dateTime = value.NextResetTime.ToLocalTime(); int onlineCount; int registeredCount; float currentRatio; bool flag = IsPauseRuleActive(out onlineCount, out registeredCount, out currentRatio); string text = (value.IsExempt ? Loc.T("time_unlimited_vip") : $"{timeSpan.Hours}h {timeSpan.Minutes}m {timeSpan.Seconds}s ({value.RemainingSeconds:F0}s)"); string text2 = (flag ? Loc.Tf("pause_active", onlineCount, registeredCount, currentRatio * 100f) : ""); string text3 = FormatPlayedTotal(value.TotalPlayedSeconds); string message = Loc.Tf("time_status", text, text3, dateTime.ToString("dd/MM/yyyy HH:mm"), text2); SendChatMessageToPeer(peer, message); SendCenterMessageToPeer(peer, message); } private static string FormatPlayedTotal(double totalSeconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0.0, totalSeconds)); if (timeSpan.TotalDays >= 1.0) { return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours}h {timeSpan.Minutes}m"; } return $"{(int)timeSpan.TotalHours}h {timeSpan.Minutes}m {timeSpan.Seconds}s"; } private void RespondAllPlayerTimes(ZNetPeer peer) { List peers = ZNet.instance.GetPeers(); int onlineCount; int registeredCount; float currentRatio; bool flag = IsPauseRuleActive(out onlineCount, out registeredCount, out currentRatio); string text = Loc.Tf("players_online", onlineCount, registeredCount, currentRatio * 100f); if (flag) { text += Loc.T("pause_discount"); } SendChatMessageToPeer(peer, text); foreach (ZNetPeer item in peers) { string peerSteamID = GetPeerSteamID(item); string text2 = (string.IsNullOrEmpty(item.m_playerName) ? Loc.T("player_fallback") : item.m_playerName); if (DataStore.Players.TryGetValue(peerSteamID, out var value)) { TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0.0, value.RemainingSeconds)); string text3 = (value.IsExempt ? Loc.T("unlimited") : $"{timeSpan.Hours}h {timeSpan.Minutes}m ({value.RemainingSeconds:F0}s)"); SendChatMessageToPeer(peer, " - " + text2 + ": " + text3); } else { SendChatMessageToPeer(peer, " - " + text2 + ": " + Loc.T("no_record")); } } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "TimePlayerControl"; public const string PLUGIN_NAME = "Valheim Time Player Control"; public const string PLUGIN_VERSION = "1.0.1"; } } namespace TimePlayerControl.Patches { [HarmonyPatch(typeof(Chat))] public static class ChatPatch { [HarmonyPatch("RPC_ChatMessage")] [HarmonyPrefix] public static bool RPC_ChatMessage_Prefix(long sender, string text) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return true; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null) { return true; } if ((Object)(object)TimeManager.Instance != (Object)null && TimeManager.Instance.HandleChatCommand(peer, text)) { return false; } return true; } } [HarmonyPatch(typeof(ZNet))] public static class ZNetPatch { [HarmonyPatch("RPC_PeerInfo")] [HarmonyPostfix] public static void RPC_PeerInfo_Postfix(ZNet __instance, ZRpc rpc) { if (!((Object)(object)__instance == (Object)null) && __instance.IsServer()) { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null && (Object)(object)TimeManager.Instance != (Object)null && !TimeManager.Instance.OnPlayerConnecting(peer, out var kickReason)) { Plugin.Logger.LogInfo((object)("[ZNetPatch] Rechazando conexión de " + peer.m_playerName + ": " + kickReason)); TimeManager.SendCenterMessageToPeer(peer, kickReason); TimeManager.SendChatMessageToPeer(peer, kickReason); __instance.Disconnect(peer); } } } [HarmonyPatch("Disconnect")] [HarmonyPrefix] public static void Disconnect_Prefix(ZNetPeer peer) { if ((Object)(object)TimeManager.Instance != (Object)null && TimeManager.Instance.DataStore != null) { TimeManager.Instance.DataStore.Save(); } } } } namespace TimePlayerControl.Data { [Serializable] public class PlayerData { public string SteamID { get; set; } = ""; public string PlayerName { get; set; } = ""; public double RemainingSeconds { get; set; } = 21600.0; public double AssignedSeconds { get; set; } = 21600.0; public double TotalPlayedSeconds { get; set; } = 0.0; public DateTime LastAssignedTime { get; set; } = DateTime.UtcNow; public DateTime NextResetTime { get; set; } = DateTime.UtcNow; public bool IsExempt { get; set; } = false; public bool Warning30MinSent { get; set; } = false; public bool NeedsReset(DateTime now) { return now >= NextResetTime; } private static DateTime CalculateNextResetTime(int resetHourUtc, DateTime now) { DateTime dateTime = now.Date.AddHours(resetHourUtc); if (dateTime <= now) { dateTime = dateTime.AddDays(1.0); } return dateTime; } public void ResetSeconds(double blockSeconds, int resetHourUtc, DateTime now) { AssignedSeconds = blockSeconds; RemainingSeconds = blockSeconds; LastAssignedTime = now; NextResetTime = CalculateNextResetTime(resetHourUtc, now); Warning30MinSent = false; } } [Serializable] public class ServerConfig { public double DefaultBlockSeconds { get; set; } = 21600.0; public int ResetHourUtc { get; set; } = 3; public double WarningThresholdSeconds { get; set; } = 1800.0; public float PauseTimeMinOnlineRatio { get; set; } = 0.75f; public double SaveIntervalSeconds { get; set; } = 300.0; public bool EnableInfoLogs { get; set; } = true; public bool EnableDebugLogs { get; set; } = false; public bool EnableClientHud { get; set; } = true; public string Language { get; set; } = "ES"; public string HudAnchor { get; set; } = "TopRight"; public float HudPositionX { get; set; } = 16f; public float HudPositionY { get; set; } = 220f; public float HudWidth { get; set; } = 240f; public float HudHeight { get; set; } = 0f; public float HudOpacity { get; set; } = 0.75f; public bool HudAutoHideOverlays { get; set; } = true; public bool HudStartCompact { get; set; } = true; public bool HudShowProgressBar { get; set; } = true; public string HudToggleKey { get; set; } = "F1"; public bool HudSnapBelowMinimap { get; set; } = true; public float HudMinimapGap { get; set; } = 8f; public bool HudUseCustomPosition { get; set; } = false; public float HudCustomNormX { get; set; } = 0f; public float HudCustomNormY { get; set; } = 0f; } public class TimeDataStore { private static readonly string FilePath = Path.Combine(Paths.ConfigPath, "TimePlayerControl.json"); private static readonly object FileLock = new object(); public ServerConfig ServerConfig { get; set; } = new ServerConfig(); public Dictionary Players { get; set; } = new Dictionary(); public static TimeDataStore Load() { lock (FileLock) { try { if (File.Exists(FilePath)) { string text = File.ReadAllText(FilePath); TimeDataStore timeDataStore = JsonConvert.DeserializeObject(text); if (timeDataStore != null) { if (timeDataStore.ServerConfig == null) { timeDataStore.ServerConfig = new ServerConfig(); } if (timeDataStore.Players == null) { timeDataStore.Players = new Dictionary(); } Plugin.LogDebug($"[TimePlayerControl] JSON recargado correctamente ({timeDataStore.Players.Count} jugadores). "); return timeDataStore; } } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogError((object)("[TimePlayerControl] Error al cargar JSON: " + ex.Message)); } } TimeDataStore timeDataStore2 = new TimeDataStore(); timeDataStore2.Save(); return timeDataStore2; } } public void Save() { lock (FileLock) { try { string contents = JsonConvert.SerializeObject((object)this, (Formatting)1); File.WriteAllText(FilePath, contents); } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogError((object)("[TimePlayerControl] Error al guardar JSON: " + ex.Message)); } } } } } }