using System; using System.Linq; using System.Threading.Tasks; using Cirno.Scripts.Enums; using Cirno.Scripts.Resources; using Cirno.Scripts.Utils; using Godot; using Godot.Collections; using GTweens.Builders; using GTweensGodot.Extensions; public partial class GlobalState : Node { public static GlobalState Instance { get; private set; } public static SessionSettings Session => GlobalState.Instance.SessionSettings; public Node CurrentScene { get; set; } private ColorRect _fader { get; set; } public SessionSettings SessionSettings { get; set; } = new(); private PackedScene _plaqueTemplate; private Control _loadingPlaque; private readonly StringName _menuMouseTexturePath = "uid://d3oxwik1uoe4j"; private readonly StringName _reticuleMouseTexturePath = "uid://b76bhqv247nft"; private readonly StringName _mapsDatabaseResource = "uid://blf2ii0j3fqil"; private MapsDatabase _mapsDatabase; public MapsDatabase MapsDatabase => _mapsDatabase; private Texture2D _menuMouseTexture; private Image _menuMouseImage; private Texture2D _reticuleMouseTexture; private Image _reticuleMouseImage; public bool UseMenuCursor { get; set; } = true; public override void _Ready() { Instance = this; this.ProcessMode = ProcessModeEnum.Always; _mapsDatabase = ResourceLoader.Load(_mapsDatabaseResource); Viewport root = GetTree().Root; // Using a negative index counts from the end, so this gets the last child node of `root`. CurrentScene = root.GetChild(-1); _fader = CreateFader(); _menuMouseTexture = ResourceLoader.Load(_menuMouseTexturePath); _menuMouseImage = _menuMouseTexture.GetImage(); _reticuleMouseTexture = ResourceLoader.Load(_reticuleMouseTexturePath); _reticuleMouseImage = _reticuleMouseTexture.GetImage(); GetTree().GetRoot().SizeChanged += OnSizeChanged; //_mouseTexture = OnSizeChanged(); //LoadPlaque(); } private void OnSizeChanged() { ResizeCursor(); } public void ChangeCursor(bool useMenu) { UseMenuCursor = useMenu; ResizeCursor(); } public void ResizeCursor() { var root = GetTree().GetRoot(); var baseSize = root.ContentScaleSize; var newSize = root.Size; int scaleX = newSize.X / baseSize.X; int scaleY = newSize.Y / baseSize.Y; int scale = Math.Min(scaleX, scaleY); // Ensure pixel-perfect scaling ResizeCursor(UseMenuCursor ? scale / 2 : scale, UseMenuCursor); } public void GotoScene(string path) { // This function will usually be called from a signal callback, // or some other function from the current scene. // Deleting the current scene at this point is // a bad idea, because it may still be executing code. // This will result in a crash or unexpected behavior. // The solution is to defer the load to a later time, when // we can be sure that no code from the current scene is running: //CallDeferred(MethodName.DeferredGotoScene, path, new MapStartDataResource()); GoToScene(path, new MapStartDataResource()); } public void GoToScene(string path, MapStartDataResource startData) { GTweenSequenceBuilder.New() .AppendCallback(() => { _loadingPlaque?.Show(); }) //.Append(_fader.TweenModulateAlpha(0, 0f)) .Append(_fader.TweenModulateAlpha(1, 0.5f)) .AppendCallback(() => { CallDeferred(MethodName.DeferredGotoScene, path, startData); }) .Build() .PlayUnpausable(); //CallDeferred(MethodName.DeferredGotoScene, path, startData); } public void GotoScene(MapResource map) { this.SessionSettings.LevelNumber = map.LevelId; GoToScene(map.ScenePath.ToString(), map.StartData); } private void DeferredGotoScene(string path, MapStartDataResource startData = null) { // It is now safe to remove the current scene. CurrentScene.Free(); //var sceneParent = CurrentScene.GetParent(); //sceneParent.RemoveChild(CurrentScene); //sceneParent.QueueFree(); // Load a new scene. var nextScene = GD.Load(path); // Instance the new scene. CurrentScene = nextScene.Instantiate(); // Add it to the active scene, as child of root. GetTree().Root.AddChild(CurrentScene); // Optionally, to make it compatible with the SceneTree.change_scene_to_file() API. GetTree().CurrentScene = CurrentScene; // // Update current scene here too // CurrentScene = GetTree().Root.GetChild(-1); if (startData is not null && GameManager.Instance is not null) { // Call deferred if it gives issues DeferredAddStartDataToGameManager(startData); } // Do not do this here because it might get the old gamemanager instead // if (GameManager.Instance is not null) { // GameManager.Instance.ApplySessionState(SessionSettings); // } FadeIn(); } private void DeferredAddStartDataToGameManager(MapStartDataResource resource) { GameManager.Instance?.ApplyMapStartData(resource); } private Control LoadPlaque() { _plaqueTemplate = GD.Load("res://Scenes/HUD/LoadingPlaque.tscn"); return _plaqueTemplate.Instantiate(); } private ColorRect CreateFader() { var canvas = new CanvasLayer(); canvas.ProcessMode = ProcessModeEnum.Always; var rect = new ColorRect(); rect.ZAsRelative = false; rect.ZIndex = 100; rect.SetAnchorsPreset(Control.LayoutPreset.FullRect); rect.Color = new Color(Colors.Black, 1f); rect.ProcessMode = ProcessModeEnum.Always; rect.Modulate = new Color(0, 0, 0, 0); rect.MouseFilter = Control.MouseFilterEnum.Ignore; canvas.CallDeferred("add_child", rect); this.CallDeferred("add_child", canvas); _loadingPlaque = LoadPlaque(); canvas.CallDeferred("add_child", _loadingPlaque); _loadingPlaque.Hide(); return rect; } public void FadeOut() { _fader.TweenModulateAlpha(1, 0.5f).PlayUnpausable(); _loadingPlaque?.Show(); } public void FadeIn() { _loadingPlaque?.Hide(); _fader.TweenModulateAlpha(0, 1f).PlayUnpausable(); } private readonly string SaveNameFile = "user://savegame.save"; public void SaveGame() { var items = InventoryManager.Instance.Save(); SessionSettings.Items = items; var serializedSavedata = new Godot.Collections.Dictionary() { { "Items", items }, { "Level", SessionSettings.LevelNumber }, { "MapId", SessionSettings.MapId }, { "Difficulty", (int)SessionSettings.Difficulty } }; var saveFile = FileAccess.Open(SaveNameFile, FileAccess.ModeFlags.Write); var jsonString = Json.Stringify(serializedSavedata); saveFile.StoreLine(jsonString); GD.Print("Saved data successfully"); } public bool LoadGame() { if (!FileAccess.FileExists(SaveNameFile)) { return false; // Error! We don't have a save to load. } using var saveFile = FileAccess.Open(SaveNameFile, FileAccess.ModeFlags.Read); var jsonString = saveFile.GetLine(); var json = new Json(); var parseResult = json.Parse(jsonString); if (parseResult != Error.Ok) { GD.Print($"JSON Parse Error: {json.GetErrorMessage()} in {jsonString} at line {json.GetErrorLine()}"); return false; } var deserializedSaveData = new Dictionary((Dictionary)json.Data); Dictionary items = (Dictionary)deserializedSaveData["Items"]; DifficultyLevel difficulty = (DifficultyLevel)deserializedSaveData["Difficulty"].AsInt32(); int levelNumber = deserializedSaveData["Level"].AsInt32(); StringName mapId = deserializedSaveData["MapId"].AsStringName(); var levelData = _mapsDatabase.FindMap(mapId); if (levelData is null) { return false; } this.SessionSettings.NewSession(); SessionSettings.LevelNumber = levelNumber; SessionSettings.MapId = mapId; SessionSettings.Items = items; SessionSettings.Difficulty = difficulty; this.GotoScene(levelData); return true; } private void ResizeCursor(float scale, bool useMenuCursor) { Image scaled; Vector2I size; if (useMenuCursor) { scaled = (Image)_menuMouseImage.Duplicate(); size = (Vector2I)(scale * _menuMouseTexture.GetSize()); } else { scaled = (Image)_reticuleMouseImage.Duplicate(); size = (Vector2I)(scale * _reticuleMouseTexture.GetSize()); } scaled.Resize(size.X, size.Y, Image.Interpolation.Nearest); Input.SetCustomMouseCursor(scaled, Input.CursorShape.Arrow, useMenuCursor ? Vector2.Zero : new Vector2(size.X / 2, size.Y / 2)); //DisplayServer.CursorSetCustomImage(scaled); } public void RestartLevel() { GotoScene(CurrentScene.GetSceneFilePath()); } }