#nullable disable using System.Collections.Generic; using StardewModdingAPI.Enums; using StardewModdingAPI.Events; using StardewModdingAPI.Utilities; using StardewValley; using StardewValley.Menus; namespace StardewModdingAPI { /// Provides information about the current game state. public static class Context { /********* ** Fields *********/ /// Whether the player has loaded a save and the world has finished initializing. private static readonly PerScreen IsWorldReadyForScreen = new(); /// The current stage in the game's loading process. private static readonly PerScreen LoadStageForScreen = new(); /// Whether a player save has been loaded. internal static bool IsSaveLoaded => Game1.hasLoadedGame && Game1.activeClickableMenu is not TitleMenu; /// Whether the game is currently writing to the save file. internal static bool IsSaving => Game1.activeClickableMenu is SaveGameMenu or ShippingMenu; // saving is performed by SaveGameMenu, but it's wrapped by ShippingMenu on days when the player shipping something /// The active split-screen instance IDs. internal static readonly ISet ActiveScreenIds = new HashSet(); /// The last screen ID that was removed from the game, used to synchronize . internal static int LastRemovedScreenId = -1; /// The current stage in the game's loading process. internal static LoadStage LoadStage { get => Context.LoadStageForScreen.Value; set => Context.LoadStageForScreen.Value = value; } /// Whether the in-game world is completely unloaded and not in the process of being loaded. The world may still exist in memory at this point, but should be ignored. internal static bool IsWorldFullyUnloaded => Context.LoadStage is LoadStage.ReturningToTitle or LoadStage.None; /********* ** Accessors *********/ /**** ** Game/player state ****/ /// Whether the game has performed core initialization. This becomes true right before the first update tick. public static bool IsGameLaunched { get; internal set; } /// Whether the player has loaded a save and the world has finished initializing. public static bool IsWorldReady { get => Context.IsWorldReadyForScreen.Value; set => Context.IsWorldReadyForScreen.Value = value; } /// Whether is true and the player is free to act in the world (no menu is displayed, no cutscene is in progress, etc). public static bool IsPlayerFree => Context.IsWorldReady && Game1.currentLocation != null && Game1.activeClickableMenu == null && !Game1.dialogueUp && (!Game1.eventUp || Game1.isFestival()); /// Whether is true and the player is free to move (e.g. not using a tool). public static bool CanPlayerMove => Context.IsPlayerFree && Game1.player.CanMove; /// Whether the game is currently running the draw loop. This isn't relevant to most mods, since you should use events to draw to the screen. public static bool IsInDrawLoop { get; internal set; } /**** ** Multiplayer ****/ /// The unique ID of the current screen in split-screen mode. A screen is always assigned a new ID when it's opened (so a player who quits and rejoins has a new screen ID). public static int ScreenId => Game1.game1?.instanceId ?? 0; /// Whether the game is running in multiplayer or split-screen mode (regardless of whether any other players are connected). See and for more specific checks. public static bool IsMultiplayer => Context.IsSplitScreen || (Context.IsWorldReady && Game1.multiplayerMode != Game1.singlePlayer); /// Whether this player is running on the main player's computer. This is true for both the main player and split-screen players. public static bool IsOnHostComputer => Context.IsMainPlayer || Context.IsSplitScreen; /// Whether the current player is playing in a split-screen. This is only applicable when is true, since split-screen players on another computer are just regular remote players. public static bool IsSplitScreen => LocalMultiplayer.IsLocalMultiplayer(); /// Whether there are players connected over the network. public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly; /// Whether the current player is the main player. This is always true in single-player, and true when hosting in multiplayer. public static bool IsMainPlayer => Game1.IsMasterGame && Context.ScreenId == 0 && TitleMenu.subMenu is not FarmhandMenu; /********* ** Public methods *********/ /// Get whether a screen ID is still active. /// The screen ID. public static bool HasScreenId(int id) { return Context.ActiveScreenIds.Contains(id); } } }