Skip to content
Bartholomew Ho edited this page Nov 9, 2026 · 1 revision

Game

The game itself: its thread, its frame, and whether it's ready. Use Game.Tick or Game.Run to run your code on the game's thread, where the other FH2 classes (Player, Garage, Profile...) are meant to be used.

Thread: the properties and Run/RunAsync can be used from any thread. Tick, Rewound and CarRelocated handlers run on the game thread. PhysicsStep handlers run on the game's physics thread.

Example

Shows a notification each time you beat your top speed by 10 km/h, and adds a console command that repairs your car on the game's thread.

using FH2;
using FH2ModPlatform;

public sealed class TopSpeed : Mod
{
    private float _best;

    public override ModInfo Info => new("TopSpeed", "1.0.0");

    protected override void OnInit()
    {
        Game.Tick += OnTick;
        AddCommand("repair", "repairs your car", args =>
        {
            Game.Run(() => Player.Car?.Repair());
            return "repairing";
        });
    }

    private void OnTick()
    {
        var car = Player.Car;
        if (car is null || car.SpeedKmh < _best + 10) return;
        _best = car.SpeedKmh;
        UI.Notify("New top speed", $"{_best:F0} km/h");
    }
}
Public Class TopSpeed
    Inherits [Mod]

    Private _best As Single

    Public Overrides ReadOnly Property Info As ModInfo
        Get
            Return New ModInfo("TopSpeed", "1.0.0")
        End Get
    End Property

    Protected Overrides Sub OnInit()
        AddHandler Game.Tick, AddressOf OnTick
        AddCommand("repair", "repairs your car",
            Function(args)
                Game.Run(Sub() Player.Car?.Repair())
                Return "repairing"
            End Function)
    End Sub

    Private Sub OnTick()
        Dim car = Player.Car
        If car Is Nothing OrElse car.SpeedKmh < _best + 10 Then Return
        _best = car.SpeedKmh
        UI.Notify("New top speed", $"{_best:F0} km/h")
    End Sub
End Class

Members

Properties

Member What it does
IsReady (property, get, bool) True once the SDK has checked the game's code and is hooked into the game's frame. Reading it starts the Game class if no mod has yet.
IsDriving (property, get, bool) True while you're driving: your car is being simulated (not paused, not loading) and the HUD speedometer is up (not in the garage, a menu, a race intro...). Any thread.
IsPaused (property, get, bool) True while the game is paused (the pause menu). Any thread.
IsGameThread (property, get, bool) True when your code is running on the game's thread (inside Tick or Run).
FrameCount (property, get, long) How many frames have ticked so far. Only frames while you drive are counted.
Language (property, get, string?) The game's text language as a code: "EN", "DE", "FR", "ES", "MX", "IT", "BR", "JP", "KO", "CHT", "PL", "RU", "NL" (and "GB", "CZ", "HU"... which use English text). Null until the game has loaded its text. Any thread.
LaunchLanguage (property, get, string) The language Azimuth starts the game in (from its data\azimuth.ini), as a Language code. This is what the game will most likely show before it has loaded its text. "EN" if it can't be told. Any thread.
Base (property, get, nint) For advanced code of your own only. 0 until the game is loaded.

Methods

Member What it does
Run(Action action) Runs action on the game's thread at the next frame. If you call it from inside a Tick, Run, Rewound or CarRelocated handler, it runs straight away.
RunAsync<T>(Func<T> func) → Task<T> Runs func on the game's thread and gives you its result. If func throws, the task fails with that exception. Never await it from the game's thread.
RunAsync(Action action) → Task The same, for code that returns nothing.

Events

Member What it does
Tick (event, Action) Once a frame, on the game's thread, while you drive. Keep it short: the game waits for it.
PhysicsStep (event, Action<Vehicle, float>) Every car's physics step (yours, Drivatars', traffic), on the game's physics thread. You get the car and the step's length in seconds. This is where to push a car: set Vehicle.Velocity here.
Rewound (event, Action<long>) You rewound: your car is back where it was at the moment given (an Environment.TickCount64 value), so undo what happened since. On the game's thread, when the car drives on. ⚠️ Not tested much in the game yet.
CarRelocated (event, Action) Your car was put somewhere new: a race started, you quit one, changed car or fast travelled. On the game's thread, when the car drives on. ⚠️ Not tested much in the game yet.

Notes

  • Tick only fires while you drive. Nothing ticks on the title screen or while the game is paused.
  • Work you pass to Run also waits for the next frame you drive. So a RunAsync task only finishes once you're driving.
  • Never wait for a RunAsync task from the game's thread (inside Tick, Run, an ANNA entry). It needs the game's thread to finish, so it would never finish.
  • If a Tick, Run, PhysicsStep, Rewound or CarRelocated handler throws, the error goes to platform.log. The handler stays subscribed.
  • PhysicsStep runs very often (every car, 60+ times a second). Keep it short, and use only the car you're given. Inside it, the Vehicle members you'd normally use on the game thread work too.
  • Rewound and CarRelocated fire when the car drives on again, before that frame's Tick handlers.
  • When your hosted mod is reloaded, its Tick, PhysicsStep, Rewound and CarRelocated handlers and its queued Run work are removed for you.
  • The Game class starts the first time a mod uses it. It checks the game's code first. On a game build the SDK doesn't know, IsReady stays false, Tick never fires, and platform.log says what didn't match. PhysicsStep can also be switched off on its own; platform.log says so ("Game.PhysicsStep off").
  • FH2 classes that need the game thread throw an InvalidOperationException when used from another thread. The message tells you to use Game.Tick or Game.Run.

See also

Clone this wiki locally