using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BaseRaidTimer.Configuration;
using BaseRaidTimer.Localization;
using BaseRaidTimer.Runtime;
using BaseRaidTimer.UI;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Fusion;
using HarmonyLib;
using ModSettingsMenu.Api;
using SunkenlandLocalizationAPI.Api;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BaseRaidTimer")]
[assembly: AssemblyDescription("Base raid countdown timer mod for Sunkenland by Ice Box Studio")]
[assembly: AssemblyCompany("Ice Box Studio")]
[assembly: AssemblyProduct("BaseRaidTimer")]
[assembly: AssemblyCopyright("Copyright © 2026 Ice Box Studio All rights reserved.")]
[assembly: ComVisible(false)]
[assembly: Guid("6d68fc4b-c0c5-4663-a743-f2da4e5a1268")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.1.0")]
namespace BaseRaidTimer
{
[BepInPlugin("IceBoxStudio.Sunkenland.BaseRaidTimer", "BaseRaidTimer", "1.0.1")]
[BepInDependency("IceBoxStudio.Sunkenland.ModSettingsMenu", "1.2.0")]
[BepInDependency("IceBoxStudio.Sunkenland.LocalizationAPI", "1.1.0")]
public sealed class BaseRaidTimer : BaseUnityPlugin
{
private Harmony _harmony;
internal static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
LocalizationApi.LanguageChanged += OnLanguageChanged;
try
{
Log.LogInfo((object)"=============================================");
Log.LogInfo((object)("BaseRaidTimer " + I18n.Text("plugin.initializing")));
Log.LogInfo((object)(I18n.Text("plugin.author_prefix") + "Ice Box Studio(https://steamcommunity.com/id/ibox666/)"));
ConfigManager.Bind(((BaseUnityPlugin)this).Config);
RegisterSettings();
_harmony = new Harmony("IceBoxStudio.Sunkenland.BaseRaidTimer");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
Log.LogInfo((object)("BaseRaidTimer " + I18n.Text("plugin.initialized")));
Log.LogInfo((object)"=============================================");
}
catch (Exception arg)
{
Log.LogError((object)$"Failed to initialize Base Raid Timer: {arg}");
}
}
private static void RegisterSettings()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Expected O, but got Unknown
ModSettingsRegistry.Register("IceBoxStudio.Sunkenland.BaseRaidTimer", new ModSettingsModOptions
{
Name = "Base Raid Timer",
LocalizedName = () => I18n.Text("mod.name"),
Description = I18n.Text("mod.description"),
Author = "Ice Box Studio",
Version = "1.0.1",
NexusModsId = 81,
ThunderstoreTeam = "Ice_Box_Studio_Sunkenland",
ThunderstoreModName = "BaseRaidTimer"
});
}
private static void OnLanguageChanged(string language)
{
RegisterSettings();
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "IceBoxStudio.Sunkenland.BaseRaidTimer";
public const string PLUGIN_NAME = "BaseRaidTimer";
public const string PLUGIN_VERSION = "1.0.1";
}
}
namespace BaseRaidTimer.UI
{
internal static class RaidTimerDisplay
{
internal static void Append(TextMeshProUGUI timeText)
{
if (ConfigManager.Enabled && !((Object)(object)timeText == (Object)null) && RaidCountdown.TryGet(out var state, out var seconds))
{
((TMP_Text)timeText).enableWordWrapping = false;
((TMP_Text)timeText).text = ((TMP_Text)timeText).text + " | " + GetText(state, seconds);
}
}
private static string GetText(RaidState state, int seconds)
{
if (state == RaidState.HostDataUnavailable)
{
return I18n.Text("ui.raid.host_data_unavailable");
}
return I18n.Text("ui.raid.countdown", FormatTime(seconds));
}
private static string FormatTime(int totalSeconds)
{
int num = totalSeconds / 86400;
int num2 = totalSeconds / 3600 % 24;
int num3 = totalSeconds / 60 % 60;
int num4 = totalSeconds % 60;
if (num > 0)
{
return I18n.Text("ui.duration.days", num, num2, num3, num4);
}
return I18n.Text("ui.duration.time", num2, num3, num4);
}
}
}
namespace BaseRaidTimer.Runtime
{
internal enum RaidState
{
Countdown,
HostDataUnavailable
}
internal static class RaidCountdown
{
internal static bool TryGet(out RaidState state, out int seconds)
{
state = RaidState.Countdown;
seconds = 0;
GameLogics code = GameLogics.code;
Mainframe code2 = Mainframe.code;
if ((Object)(object)code == (Object)null || (Object)(object)code2 == (Object)null || (Object)(object)code2.Networking == (Object)null)
{
return false;
}
if (!code2.Networking.IsDedicatedServer && !code2.Networking.IsInSinglePlayerGameSession && !code2.Networking.IsServerAndInGameSession)
{
if (!RaidSync.TryGet(out var visible, out seconds))
{
state = RaidState.HostDataUnavailable;
return true;
}
state = RaidState.Countdown;
return visible;
}
return TryGetHost(code, out seconds);
}
internal static bool TryGetHost(GameLogics logic, out int seconds)
{
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
seconds = 0;
Mainframe code = Mainframe.code;
if ((Object)(object)logic == (Object)null || (Object)(object)code == (Object)null || (Object)(object)code.WorldManager == (Object)null)
{
return false;
}
GlobalData globalData = code.GlobalData;
if ((Object)(object)globalData == (Object)null || (Object)(object)((SimulationBehaviour)globalData).Object == (Object)null)
{
return false;
}
if ((int)code.WorldManager.EnemyRaidDifficulty == 0 && !logic.SlaverAttacking)
{
return false;
}
if (globalData.GameTime <= 1 && !logic.SlaverAttacking)
{
return false;
}
if (float.IsNaN(logic.nextSiegeTime) || float.IsInfinity(logic.nextSiegeTime))
{
return false;
}
seconds = Mathf.Max(0, Mathf.FloorToInt(logic.nextSiegeTime));
return true;
}
}
internal static class RaidSync
{
private enum MessageType : byte
{
Hello,
Hidden,
Countdown
}
private const int PacketSize = 12;
private const byte ProtocolVersion = 1;
private const float StateTimeout = 5f;
private const float RequestInterval = 2f;
private const int MaxRequests = 3;
private static readonly HashSet<PlayerRef> Clients = new HashSet<PlayerRef>();
private static NetworkRunner _runner;
private static FusionCallbacksHandler _callbacks;
private static bool _hasState;
private static bool _visible;
private static int _seconds;
private static float _receivedAt;
private static float _lastRequestAt = -2f;
private static int _requestCount;
internal static void Bind()
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Expected O, but got Unknown
NetworkRunner runner = GetRunner();
if (_runner == runner)
{
return;
}
Unbind();
if (!((Object)(object)runner == (Object)null))
{
_runner = runner;
_callbacks = new FusionCallbacksHandler
{
PlayerLeft = OnPlayerLeft,
Shutdown = OnShutdown,
DisconnectedFromServer = OnDisconnected,
HostMigration = OnHostMigration,
ReliableDataReceived = OnData,
SceneLoadStart = OnSceneLoadStart
};
runner.AddCallbacks((INetworkRunnerCallbacks[])(object)new INetworkRunnerCallbacks[1] { (INetworkRunnerCallbacks)_callbacks });
if (runner.IsClient && !runner.IsServer)
{
Request(runner, force: true);
}
}
}
internal static void Tick(GameLogics logic)
{
Bind();
NetworkRunner runner = _runner;
if (!((Object)(object)runner == (Object)null))
{
if (runner.IsServer)
{
Broadcast(runner, logic);
}
else if (runner.IsClient && !IsFresh())
{
Request(runner, force: false);
}
}
}
internal static bool TryGet(out bool visible, out int seconds)
{
visible = false;
seconds = 0;
if (!IsFresh())
{
return false;
}
visible = _visible;
seconds = _seconds;
return true;
}
internal static bool IsPacket(ArraySegment<byte> data)
{
byte[] array = data.Array;
if (array == null || data.Count < 4)
{
return false;
}
int offset = data.Offset;
if (array[offset] == 66 && array[offset + 1] == 82 && array[offset + 2] == 84)
{
return array[offset + 3] == 83;
}
return false;
}
private static NetworkRunner GetRunner()
{
Mainframe code = Mainframe.code;
if ((Object)(object)code == (Object)null || (Object)(object)code.Networking == (Object)null)
{
return null;
}
return code.Networking.CurrentRunner;
}
private static void Broadcast(NetworkRunner runner, GameLogics logic)
{
//IL_0023: 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_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
if (Clients.Count == 0)
{
return;
}
byte[] array = CreateState(logic);
foreach (PlayerRef activePlayer in runner.ActivePlayers)
{
if (activePlayer != runner.LocalPlayer && Clients.Contains(activePlayer))
{
runner.SendReliableDataToPlayer(activePlayer, array);
}
}
}
private static void SendState(NetworkRunner runner, PlayerRef player)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
if (!(player == runner.LocalPlayer))
{
runner.SendReliableDataToPlayer(player, CreateState(GameLogics.code));
}
}
private static byte[] CreateState(GameLogics logic)
{
if (!RaidCountdown.TryGetHost(logic, out var seconds))
{
return CreatePacket(MessageType.Hidden, 0);
}
return CreatePacket(MessageType.Countdown, seconds);
}
private static void Request(NetworkRunner runner, bool force)
{
float time = Time.time;
if (force || (_requestCount < 3 && !(time - _lastRequestAt < 2f)))
{
runner.SendReliableDataToServer(CreatePacket(MessageType.Hello, 0));
_lastRequestAt = time;
_requestCount++;
}
}
private static void OnData(NetworkRunner runner, PlayerRef player, ArraySegment<byte> data)
{
//IL_0025: 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)
if (runner != _runner || !TryRead(data, out var type, out var seconds))
{
return;
}
if (runner.IsServer)
{
if (type == MessageType.Hello && Clients.Add(player))
{
SendState(runner, player);
}
}
else if (runner.IsClient && type != MessageType.Hello)
{
_hasState = true;
_visible = type == MessageType.Countdown;
_seconds = (_visible ? seconds : 0);
_receivedAt = Time.time;
_requestCount = 0;
}
}
private static void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
if (runner == _runner && runner.IsServer)
{
Clients.Remove(player);
}
}
private static void OnShutdown(NetworkRunner runner, ShutdownReason reason)
{
Drop(runner);
}
private static void OnDisconnected(NetworkRunner runner)
{
Drop(runner);
}
private static void OnHostMigration(NetworkRunner runner, HostMigrationToken token)
{
if (runner == _runner)
{
Reset();
}
}
private static void OnSceneLoadStart(NetworkRunner runner)
{
if (runner == _runner)
{
ClearState();
_lastRequestAt = -2f;
_requestCount = 0;
}
}
private static bool IsFresh()
{
if (_hasState)
{
return Time.time - _receivedAt <= 5f;
}
return false;
}
private static bool TryRead(ArraySegment<byte> data, out MessageType type, out int seconds)
{
type = MessageType.Hello;
seconds = 0;
if (data.Count != 12 || !IsPacket(data))
{
return false;
}
byte[] array = data.Array;
int offset = data.Offset;
if (array[offset + 4] != 1)
{
return false;
}
type = (MessageType)array[offset + 5];
if (type != MessageType.Hello && type != MessageType.Hidden && type != MessageType.Countdown)
{
return false;
}
seconds = array[offset + 8] | (array[offset + 9] << 8) | (array[offset + 10] << 16) | (array[offset + 11] << 24);
if (type == MessageType.Countdown)
{
return seconds >= 0;
}
return true;
}
private static byte[] CreatePacket(MessageType type, int seconds)
{
return new byte[12]
{
66,
82,
84,
83,
1,
(byte)type,
0,
0,
(byte)seconds,
(byte)(seconds >> 8),
(byte)(seconds >> 16),
(byte)(seconds >> 24)
};
}
private static void Unbind()
{
NetworkRunner runner = _runner;
FusionCallbacksHandler callbacks = _callbacks;
_runner = null;
_callbacks = null;
Reset();
if ((Object)(object)runner != (Object)null && callbacks != null)
{
runner.RemoveCallbacks((INetworkRunnerCallbacks[])(object)new INetworkRunnerCallbacks[1] { (INetworkRunnerCallbacks)callbacks });
}
}
private static void Drop(NetworkRunner runner)
{
if (runner == _runner)
{
_runner = null;
_callbacks = null;
Reset();
}
}
private static void Reset()
{
Clients.Clear();
ClearState();
_lastRequestAt = -2f;
_requestCount = 0;
}
private static void ClearState()
{
_hasState = false;
_visible = false;
_seconds = 0;
_receivedAt = 0f;
}
}
}
namespace BaseRaidTimer.Patches
{
[HarmonyPatch(typeof(GameLogics), "CS")]
internal static class GameLogicsCsPatch
{
private static void Postfix(GameLogics __instance)
{
RaidSync.Tick(__instance);
}
}
[HarmonyPatch(typeof(GameLogics), "Start")]
internal static class GameLogicsStartPatch
{
private static void Postfix()
{
RaidSync.Bind();
}
}
[HarmonyPatch(typeof(NetworkRunnerCallbackManager), "OnReliableDataReceived", new Type[]
{
typeof(NetworkRunner),
typeof(PlayerRef),
typeof(ArraySegment<byte>)
})]
internal static class ReliableDataLogPatch
{
private static bool Prefix(ArraySegment<byte> data)
{
return !RaidSync.IsPacket(data);
}
}
[HarmonyPatch(typeof(UICombat), "Update")]
internal static class UICombatUpdatePatch
{
private static void Postfix(TextMeshProUGUI ___tmpHour)
{
RaidTimerDisplay.Append(___tmpHour);
}
}
}
namespace BaseRaidTimer.Localization
{
internal static class I18n
{
private const string FileName = "BaseRaidTimer.Localization.json";
private static readonly ModLocalizer _localizer = Load();
internal static ModLocalizer Localizer => _localizer;
internal static string Text(string key, params object[] args)
{
return _localizer.GetLocalizedText(key, args);
}
private static ModLocalizer Load()
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
ModLocalizer obj = LocalizationApi.For("IceBoxStudio.Sunkenland.BaseRaidTimer");
obj.RegisterJson(Path.Combine(directoryName, "BaseRaidTimer.Localization.json"));
return obj;
}
}
}
namespace BaseRaidTimer.Configuration
{
internal static class ConfigManager
{
private const string Section = "Base Raid Timer";
private static ConfigEntry<bool> _enabled;
internal static bool Enabled
{
get
{
if (_enabled != null)
{
return _enabled.Value;
}
return true;
}
}
internal static void Bind(ConfigFile config)
{
_enabled = config.Bind<bool>("Base Raid Timer", "Enabled", true, I18n.Localizer.Config("config.enabled", 10, "Base Raid Timer", "config.section", 10, (AcceptableValueBase)null));
}
}
}