Skip to content

[BUG]S1API.GameTime.TimeManager.OnTick stops firing after hot load #16

@Khundiann

Description

@Khundiann

Summary

In Schedule I, S1API.GameTime.TimeManager.OnTick fires normally on a fresh load (cold start → load save), but stops firing completely after returning to the main menu and loading a save again (hot load). Handlers remain subscribed and no errors are thrown, but they never get called in the second gameplay session.

Environment

  • Game: Schedule I 0.4.1f13 (IL2CPP)
  • S1API: 2.7.4 (Forked by Bars)
  • MelonLoader: 0.7.0 Open-Beta

Reproduction

  1. Create a tiny test mod:

    using MelonLoader;
    using S1API.GameTime;
    
    public sealed class TickTesterMod : MelonMod
    {
        public override void OnInitializeMelon()
        {
            MelonLogger.Msg("[TickTester] Initialized.");
            TimeManager.OnTick -= OnTick;
            TimeManager.OnTick += OnTick;
            MelonLogger.Msg("[TickTester] TimeManager.OnTick handler registered.");
        }
    
        private static void OnTick()
        {
            MelonLogger.Msg("[TickTester] OnTick fired.");
        }
    }
  2. Start the game, load any save from the main menu.

  3. In the gameplay scene, wait a few in‑game minutes.

    • Observe [TickTester] OnTick fired. repeatedly in the log.
  4. Press Esc → Quit to Main Menu (do not close the game).

  5. From the main menu, load the same save again.

  6. In the second gameplay session, wait again.

    • Observe: no [TickTester] OnTick fired. lines appear anymore.

Expected vs Actual

  • Expected: TimeManager.OnTick continues to fire in every gameplay session within the same process, including after returning to the main menu and loading a save again.
  • Actual: TimeManager.OnTick handlers fire only in the first gameplay session after the process starts. After a hot load, they never fire again, despite the handler still being subscribed.

Relevant S1API Code

From S1API/GameTime/TimeManager.cs:

#if (IL2CPPMELON)
using S1GameTime = Il2CppScheduleOne.GameTime;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1GameTime = ScheduleOne.GameTime;
#endif

public static class TimeManager
{
    public static Action OnTick = delegate { };

    static TimeManager()
    {
        if (S1GameTime.TimeManager.Instance != null)
        {
            S1GameTime.TimeManager.Instance.onTick += (Action)(() => OnTick());
            // ... other event wiring ...
        }
    }

    // ... properties forwarding to S1GameTime.TimeManager.Instance ...
}

In Schedule I the underlying ScheduleOne.GameTime.TimeManager.Instance appears to be recreated when the Main gameplay scene is reloaded. Since the S1API static constructor runs only once, OnTick stays wired to the first instance and never reattaches to later ones.

Likely Cause & Suggestion

  • Cause: S1API.GameTime.TimeManager hooks onTick only once in its static constructor and does not rebind when S1GameTime.TimeManager.Instance changes on subsequent scene loads.
  • Suggestion: Rebind S1API’s OnDayPass/OnWeekPass/OnTick/OnSleepStart/OnSleepEnd delegates whenever the underlying TimeManager.Instance changes (e.g. via a scene‑loaded hook or a lazy check before access).

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions