diff --git a/Celeste64.csproj b/Celeste64.csproj index fd14a13..a15310f 100644 --- a/Celeste64.csproj +++ b/Celeste64.csproj @@ -10,6 +10,7 @@ true false x64 + true diff --git a/Content/Maps/1.map b/Content/Maps/1.map index 2bcf466..2f44a14 100644 --- a/Content/Maps/1.map +++ b/Content/Maps/1.map @@ -3917,7 +3917,7 @@ } // brush 422 { -( 3456 3104 576 ) ( 3456 3105 576 ) ( 3456 3104 577 ) rock_1 0 0 0 1 1 +( 3456 3104 576 ) ( 3456 3105 576 ) ( 3456 3104 577 ) wood_3 0 0 0 1 1 ( 3456 3104 576 ) ( 3456 3104 577 ) ( 3457 3104 576 ) rock_1 0 0 0 1 1 ( 3584 3104 544 ) ( 3712 3616 288 ) ( 3584 3616 288 ) wall_ruined_2 0 0 0 1 1 ( 3456 3104 288 ) ( 3457 3104 288 ) ( 3456 3105 288 ) rock_1 0 0 0 1 1 @@ -14991,12 +14991,12 @@ "_tb_layer" "3" // brush 0 { -( 3136 3168 480 ) ( 3136 3169 480 ) ( 3136 3168 481 ) rock_1 0 0 0 1 1 -( 3040 3360 480 ) ( 3040 3360 481 ) ( 3041 3360 480 ) rock_1 0 0 0 1 1 -( 3040 3168 288 ) ( 3041 3168 288 ) ( 3040 3169 288 ) rock_1 0 0 0 1 1 -( 3136 3296 480 ) ( 3136 3297 480 ) ( 3137 3296 480 ) wall_ruined_6 -64 0 0 1 1 -( 3136 3518 512 ) ( 3137 3518 512 ) ( 3136 3518 513 ) wood_4 0 0 0 1 1 -( 3264 3296 512 ) ( 3264 3296 513 ) ( 3264 3297 512 ) rock_1 0 0 0 1 1 +( 3136 3360 480 ) ( 3136 3424 288 ) ( 3136 3518 288 ) rock_1 0 0 0 1 1 +( 3264 3360 480 ) ( 3264 3424 288 ) ( 3136 3424 288 ) rock_1 0 0 0 1 1 +( 3264 3424 288 ) ( 3264 3518 288 ) ( 3136 3518 288 ) rock_1 0 0 0 1 1 +( 3136 3518 480 ) ( 3264 3518 480 ) ( 3264 3360 480 ) wall_ruined_6 -64 0 0 1 1 +( 3264 3518 288 ) ( 3264 3518 480 ) ( 3136 3518 480 ) wood_4 0 0 0 1 1 +( 3264 3360 480 ) ( 3264 3518 480 ) ( 3264 3518 288 ) rock_1 0 0 0 1 1 } } // entity 657 diff --git a/Content/Sprites/Controls/PC/cancel.png b/Content/Sprites/Controls/PC/cancel.png new file mode 100644 index 0000000..170b55c Binary files /dev/null and b/Content/Sprites/Controls/PC/cancel.png differ diff --git a/Content/Sprites/Controls/PC/confirm.png b/Content/Sprites/Controls/PC/confirm.png new file mode 100644 index 0000000..d87b2d9 Binary files /dev/null and b/Content/Sprites/Controls/PC/confirm.png differ diff --git a/ReadMe.md b/ReadMe.md index db242c5..a281ccd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -16,14 +16,14 @@ You can find prebuilt version of the game on [itch.io](https://maddymakesgamesin ### Tools Used - [TrenchBroom](https://trenchbroom.github.io/): For Level Editing - - [Blender](https://www.blender.org/): For creating 3D Model + - [Blender](https://www.blender.org/): For creating 3D Models - [Aseprite](https://www.aseprite.org/): For drawing Textures ### Resources Used - [khronos glTF Tutorials](https://github.khronos.org/glTF-Tutorials/gltfTutorial/gltfTutorial_020_Skins.html#the-joint-matrices): To figure out how Mesh Skins/Bones work - [LearnOpenGL](https://learnopengl.com/Advanced-OpenGL/Depth-testing): For general rendering concepts / normalizing Depth - [Kenny's Input Prompts](https://kenney.nl/assets/input-prompts): For UI Button Prompts - - [Renogare](https://www.dafont.com/renogare.font): Font for Text + - [Renogare](https://www.dafont.com/renogare.font): Main font ### Created By ... - [Maddy Thorson](http://maddymakesgames.com/) diff --git a/Source/Actors/Badeline.cs b/Source/Actors/Badeline.cs index b9cf559..05397ca 100644 --- a/Source/Actors/Badeline.cs +++ b/Source/Actors/Badeline.cs @@ -19,7 +19,7 @@ public Badeline() : base(Assets.Models["badeline"]) mat.Color = hairColor; mat.Effects = 0; } - mat.Set("u_silhouette_color", hairColor); + mat.SilhouetteColor = hairColor; } hair = new() diff --git a/Source/Actors/Player.cs b/Source/Actors/Player.cs index 2d8864d..cab2b9a 100644 --- a/Source/Actors/Player.cs +++ b/Source/Actors/Player.cs @@ -629,7 +629,7 @@ public void SetHairColor(Color color) mat.Color = color; mat.Effects = 0; } - mat.Set("u_silhouette_color", color); + mat.SilhouetteColor = color; } Hair.Color = color; @@ -1489,7 +1489,7 @@ private void StClimbingUpdate() // don't climb over ledges into spikes // (you can still climb up into spikes if they're on the same wall as you) - if (move.Z > 0 && World.Overlaps(Position + Vec3.UnitZ * 6 + forward * (ClimbCheckDist + 1))) + if (move.Z > 0 && World.Overlaps(Position + Vec3.UnitZ * ClimbCheckDist + forward * (ClimbCheckDist + 1))) move.Z = 0; // don't move left/right around into a spikes @@ -2067,16 +2067,30 @@ private CoEnumerator StCassetteRoutine() if (cassette != null) { - if (World.Entry.Submap || !Assets.Maps.ContainsKey(cassette.Map)) + if (World.Entry.Submap) { - Game.Instance.Goto(new Transition() + Game.Instance.Goto(new Transition() { Mode = Transition.Modes.Pop, ToPause = true, ToBlack = new SpotlightWipe(), StopMusic = true }); - } + } + //Saves and quits game if you collect a cassette with an empty map property when you're not in a submap + else if (!Assets.Maps.ContainsKey(cassette.Map)) + { + Game.Instance.Goto(new Transition() + { + Mode = Transition.Modes.Replace, + Scene = () => new Overworld(true), + ToPause = true, + ToBlack = new SpotlightWipe(), + FromBlack = new SlideWipe(), + StopMusic = true, + Saving = true + }); + } else { Game.Instance.Goto(new Transition() @@ -2282,4 +2296,4 @@ public bool CeilingCheck(out Vec3 pushout) public void Stop() => velocity = Vec3.Zero; #endregion -} \ No newline at end of file +} diff --git a/Source/Controls.cs b/Source/Controls.cs index bfbc722..5781f04 100644 --- a/Source/Controls.cs +++ b/Source/Controls.cs @@ -88,13 +88,16 @@ public static void Consume() private static string GetPromptLocation(string name) { - var gamepad = Input.Controllers[0].Gamepad; + var gamepadPure = Input.Controllers[0]; + var gamepad = gamepadPure.Gamepad; if (!prompts.TryGetValue(gamepad, out var list)) prompts[gamepad] = list = new(); if (!list.TryGetValue(name, out var lookup)) - list[name] = lookup = $"Controls/{GetControllerName(gamepad)}/{name}"; + list[name] = lookup = !gamepadPure.Connected + ? $"Controls/PC/{name}" + : $"Controls/{GetControllerName(gamepad)}/{name}"; return lookup; } @@ -113,4 +116,4 @@ public static Subtexture GetPrompt(VirtualButton button) { return Assets.Subtextures.GetValueOrDefault(GetPromptLocation(button)); } -} \ No newline at end of file +} diff --git a/Source/Game.cs b/Source/Game.cs index cf460c3..8b069d8 100644 --- a/Source/Game.cs +++ b/Source/Game.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Text.Json; using Celeste64.SpeedrunUtils; @@ -177,10 +177,7 @@ public override void Update() // perform game save between transitions if (transition.Saving) - { - using var stream = File.Create(Path.Join(App.UserPath, Save.FileName)); - Save.Serialize(stream, Save.Instance); - } + Save.Instance.SaveToFile(); // perform transition switch (transition.Mode) @@ -337,7 +334,7 @@ public override void Render() private FMOD.RESULT MusicTimelineCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr _event, IntPtr parameters) { - // notify that an audio event happend (but handle it on the main thread) + // notify that an audio event happened (but handle it on the main thread) if (transitionStep == TransitionStep.None) audioBeatCounterEvent = true; return FMOD.RESULT.OK; diff --git a/Source/Graphics/Hair.cs b/Source/Graphics/Hair.cs index 8bf1861..323be6d 100644 --- a/Source/Graphics/Hair.cs +++ b/Source/Graphics/Hair.cs @@ -154,7 +154,7 @@ public override void Render(ref RenderState state) state.ModelMatrix = was; Materials[0].Color = Color; - Materials[0].Set("u_silhouette_color", Color); + Materials[0].SilhouetteColor = Color; // draw hair var call = new DrawCommand(state.Camera.Target, mesh, Materials[0]) diff --git a/Source/Graphics/Materials.cs b/Source/Graphics/Materials.cs index abe4b72..e7bb2b5 100644 --- a/Source/Graphics/Materials.cs +++ b/Source/Graphics/Materials.cs @@ -25,7 +25,11 @@ public class DefaultMaterial : Material public DefaultMaterial(Texture? texture = null) : base(Assets.Shaders["Default"]) { - Debug.Assert(Shader != null && Shader.Has(MatrixUniformName), $"Shader '{Shader.Name}' is missing '{MatrixUniformName}' uniform"); + if (!(Shader?.Has(MatrixUniformName) ?? false)) + { + Log.Warning($"Shader '{Shader?.Name}' is missing '{MatrixUniformName}' uniform"); + } + Texture = texture; Color = Color.White; Effects = 1.0f; @@ -34,13 +38,23 @@ public DefaultMaterial(Texture? texture = null) public Matrix MVP { get => matrix; - set => Set(MatrixUniformName, matrix = value); + set + { + matrix = value; + if (Shader?.Has(MatrixUniformName) ?? false) + Set(MatrixUniformName, value); + } } public Matrix Model { get => model; - set => Set("u_model", model = value); + set + { + model = value; + if (Shader?.Has("u_model") ?? false) + Set("u_model", value); + } } public Texture? Texture diff --git a/Source/Graphics/Skybox.cs b/Source/Graphics/Skybox.cs index 43296fc..6637848 100644 --- a/Source/Graphics/Skybox.cs +++ b/Source/Graphics/Skybox.cs @@ -60,10 +60,14 @@ private static void AddFace(List verts, List indices, in Vec3 public void Render(in Camera camera, in Matrix transform, float size) { var mat = Matrix.CreateScale(size) * transform * camera.ViewProjection; - material.Set("u_matrix", mat); - material.Set("u_near", camera.NearPlane); - material.Set("u_far", camera.FarPlane); - material.Set("u_texture", Texture); + if (material.Shader?.Has("u_matrix") ?? false) + material.Set("u_matrix", mat); + if (material.Shader?.Has("u_near") ?? false) + material.Set("u_near", camera.NearPlane); + if (material.Shader?.Has("u_far") ?? false) + material.Set("u_far", camera.FarPlane); + if (material.Shader?.Has("u_texture") ?? false) + material.Set("u_texture", Texture); DrawCommand cmd = new(camera.Target, mesh, material) { diff --git a/Source/Graphics/SpriteRenderer.cs b/Source/Graphics/SpriteRenderer.cs index a49d0d6..b626cf8 100644 --- a/Source/Graphics/SpriteRenderer.cs +++ b/Source/Graphics/SpriteRenderer.cs @@ -58,13 +58,17 @@ public void Render(ref RenderState state, List sprites, bool postEffects spriteMesh.SetVertices(CollectionsMarshal.AsSpan(spriteVertices)); spriteMesh.SetIndices(CollectionsMarshal.AsSpan(spriteIndices)); - spriteMaterial.Set("u_matrix", state.Camera.ViewProjection); - spriteMaterial.Set("u_far", state.Camera.FarPlane); - spriteMaterial.Set("u_near", state.Camera.NearPlane); + if (spriteMaterial.Shader?.Has("u_matrix") ?? false) + spriteMaterial.Set("u_matrix", state.Camera.ViewProjection); + if (spriteMaterial.Shader?.Has("u_far") ?? false) + spriteMaterial.Set("u_far", state.Camera.FarPlane); + if (spriteMaterial.Shader?.Has("u_near") ?? false) + spriteMaterial.Set("u_near", state.Camera.NearPlane); foreach (var batch in spriteBatches) { - spriteMaterial.Set("u_texture", batch.Texture); + if (spriteMaterial.Shader?.Has("u_texture") ?? false) + spriteMaterial.Set("u_texture", batch.Texture); var call = new DrawCommand(state.Camera.Target, spriteMesh, spriteMaterial) { diff --git a/Source/Helpers/Menu.cs b/Source/Helpers/Menu.cs index c9ca104..42c16f0 100644 --- a/Source/Helpers/Menu.cs +++ b/Source/Helpers/Menu.cs @@ -5,6 +5,7 @@ public class Menu { public const float Spacing = 4 * Game.RelativeScale; public const float SpacerHeight = 12 * Game.RelativeScale; + public const float TitleScale = 0.75f; public abstract class Item { @@ -14,7 +15,25 @@ public abstract class Item public virtual void Slide(int dir) {} } - public class Spacer : Item + public class Submenu(string label, Menu? rootMenu, Menu? submenu = null) : Item + { + private readonly string label = label; + public override string Label => label; + public override bool Pressed() + { + if (submenu != null) + { + Audio.Play(Sfx.ui_select); + submenu.Index = 0; + rootMenu?.PushSubMenu(submenu); + return true; + } + + return false; + } + } + + public class Spacer : Item { public override bool Selectable => false; } @@ -75,26 +94,33 @@ public override bool Pressed() } } - public int Index - { - get => index; - set => index = value; - } + public int Index; + public string Title = string.Empty; public bool Focused = true; private readonly SpriteFont font; private readonly List items = []; - private int index = 0; + private readonly Stack submenus = []; public string UpSound = Sfx.ui_move; public string DownSound = Sfx.ui_move; + public bool IsInMainMenu => submenus.Count <= 0; + private Menu CurrentMenu => submenus.Count > 0 ? submenus.Peek() : this; + public Vec2 Size { get { Vec2 size = Vec2.Zero; - + + if (!string.IsNullOrEmpty(Title)) + { + size.X = font.WidthOf(Title) * TitleScale; + size.Y += font.LineHeight * TitleScale; + size.Y += SpacerHeight + Spacing; + } + foreach (var item in items) { if (string.IsNullOrEmpty(item.Label)) @@ -108,58 +134,102 @@ public Vec2 Size } size.Y += Spacing; } - + if (items.Count > 0) size.Y -= Spacing; - + return size; } } - + public Menu() { font = Assets.Fonts.First().Value; } - + public Menu Add(Item item) { items.Add(item); return this; } + + protected void PushSubMenu(Menu menu) + { + submenus.Push(menu); + } + + public void CloseSubMenus() + { + submenus.Clear(); + } - public void Update() + private void HandleInput() { - if (items.Count > 0 && Focused) + if (items.Count > 0) { - var was = index; + var was = Index; var step = 0; + if (Controls.Menu.Vertical.Positive.Pressed) step = 1; if (Controls.Menu.Vertical.Negative.Pressed) step = -1; - - index += step; - while (!items[(items.Count + index) % items.Count].Selectable) - index += step; - index = (items.Count + index) % items.Count; - - if (was != index) + + Index += step; + while (!items[(items.Count + Index) % items.Count].Selectable) + Index += step; + Index = (items.Count + Index) % items.Count; + + if (was != Index) Audio.Play(step < 0 ? UpSound : DownSound); - + if (Controls.Menu.Horizontal.Negative.Pressed) - items[index].Slide(-1); + items[Index].Slide(-1); if (Controls.Menu.Horizontal.Positive.Pressed) - items[index].Slide(1); - - if (Controls.Confirm.Pressed && items[index].Pressed()) + items[Index].Slide(1); + + if (Controls.Confirm.Pressed && items[Index].Pressed()) Controls.Consume(); } } - public void Render(Batcher batch, Vec2 position) + public void Update() + { + if (Focused) + { + CurrentMenu.HandleInput(); + + if (Controls.Cancel.Pressed && !IsInMainMenu) + { + Audio.Play(Sfx.main_menu_toggle_off); + submenus.Pop(); + } + } + } + + private void RenderItems(Batcher batch) { var size = Size; + var position = Vec2.Zero; batch.PushMatrix(-size / 2); + + if(!string.IsNullOrEmpty(Title)) + { + var at = position + new Vec2(size.X / 2, 0); + var text = Title; + var justify = new Vec2(0.5f, 0); + var color = new Color(8421504); + + batch.PushMatrix( + Matrix3x2.CreateScale(TitleScale) * + Matrix3x2.CreateTranslation(at)); + UI.Text(batch, text, Vec2.Zero, justify, color); + batch.PopMatrix(); + + position.Y += font.LineHeight * TitleScale; + position.Y += SpacerHeight + Spacing; + } + for (int i = 0; i < items.Count; i ++) { if (string.IsNullOrEmpty(items[i].Label)) @@ -167,17 +237,24 @@ public void Render(Batcher batch, Vec2 position) position.Y += SpacerHeight; continue; } - + var at = position + new Vec2(size.X / 2, 0); var text = items[i].Label; var justify = new Vec2(0.5f, 0); - var color = index == i && Focused ? (Time.BetweenInterval(0.1f) ? 0x84FF54 : 0xFCFF59) : Color.White; + var color = Index == i && Focused ? (Time.BetweenInterval(0.1f) ? 0x84FF54 : 0xFCFF59) : Color.White; UI.Text(batch, text, at, justify, color); - + position.Y += font.LineHeight; - position.Y += Spacing; - } + position.Y += Spacing; + } + batch.PopMatrix(); + } + + public void Render(Batcher batch, Vec2 position) + { + batch.PushMatrix(position); + CurrentMenu.RenderItems(batch); batch.PopMatrix(); } } \ No newline at end of file diff --git a/Source/Helpers/StateMachine.cs b/Source/Helpers/StateMachine.cs index 6c8c452..5419b5d 100644 --- a/Source/Helpers/StateMachine.cs +++ b/Source/Helpers/StateMachine.cs @@ -5,8 +5,8 @@ public unsafe sealed class StateMachine where TIndex : unmanaged, Enum where TEvent : unmanaged, Enum { - private static readonly int StateCount = Enum.GetValues(typeof(TIndex)).Length; - private static readonly int EventCount = Enum.GetValues(typeof(TEvent)).Length; + private static readonly int StateCount = Enum.GetValues().Length; + private static readonly int EventCount = Enum.GetValues().Length; private static int StateToIndex(TIndex state) => *(int*)(&state); private static int EventToIndex(TEvent state) => *(int*)(&state); diff --git a/Source/Program.cs b/Source/Program.cs index f1f1fc7..853e9c7 100644 --- a/Source/Program.cs +++ b/Source/Program.cs @@ -22,7 +22,7 @@ public static void Main(string[] args) try { - App.Run(Game.GamePath, 1920, 1080); + App.Run(Game.GamePath, 1280, 720); } catch (Exception e) { diff --git a/Source/Save.cs b/Source/Save.cs index f85fbce..befdd1a 100644 --- a/Source/Save.cs +++ b/Source/Save.cs @@ -147,6 +147,26 @@ public void SyncSettings() Audio.SetVCAVolume("vca:/sfx", Calc.Clamp(SfxVolume / 10.0f, 0, 1)); } + public void SaveToFile() + { + var savePath = Path.Join(App.UserPath, FileName); + var tempPath = Path.Join(App.UserPath, FileName + ".backup"); + + // first save to a temporary file + { + using var stream = File.Create(tempPath); + Serialize(stream, this); + stream.Flush(); + } + + // validate that the temp path worked, and overwride existing if it did. + if (File.Exists(tempPath) && + Deserialize(File.ReadAllText(tempPath)) != null) + { + File.Copy(tempPath, savePath, true); + } + } + public static void Serialize(Stream stream, Save instance) { JsonSerializer.Serialize(stream, instance, SaveContext.Default.Save); @@ -154,7 +174,15 @@ public static void Serialize(Stream stream, Save instance) public static Save? Deserialize(string data) { - return JsonSerializer.Deserialize(data, SaveContext.Default.Save); + try + { + return JsonSerializer.Deserialize(data, SaveContext.Default.Save); + } + catch (Exception e) + { + Log.Error(e.ToString()); + return null; + } } } diff --git a/Source/Scenes/Overworld.cs b/Source/Scenes/Overworld.cs index f92cf41..fbe5ec0 100644 --- a/Source/Scenes/Overworld.cs +++ b/Source/Scenes/Overworld.cs @@ -328,11 +328,16 @@ public override void Render(Target target) Matrix.CreateRotationZ((state == States.Entering ? -1 : 1) * rotation * MathF.PI) * Matrix.CreateTranslation(position); - material.Set("u_matrix", matrix * camera.ViewProjection); - material.Set("u_near", camera.NearPlane); - material.Set("u_far", camera.FarPlane); - material.Set("u_texture", it.Target); - material.Set("u_texture_sampler", new TextureSampler(TextureFilter.Linear, TextureWrap.ClampToEdge, TextureWrap.ClampToEdge)); + if (material.Shader?.Has("u_matrix") ?? false) + material.Set("u_matrix", matrix * camera.ViewProjection); + if (material.Shader?.Has("u_near") ?? false) + material.Set("u_near", camera.NearPlane); + if (material.Shader?.Has("u_far") ?? false) + material.Set("u_far", camera.FarPlane); + if (material.Shader?.Has("u_texture") ?? false) + material.Set("u_texture", it.Target); + if (material.Shader?.Has("u_texture_sampler") ?? false) + material.Set("u_texture_sampler", new TextureSampler(TextureFilter.Linear, TextureWrap.ClampToEdge, TextureWrap.ClampToEdge)); var cmd = new DrawCommand(target, mesh, material) { @@ -377,8 +382,11 @@ public override void Render(Target target) var width = 0.0f; UI.Prompt(batch, Controls.Cancel, cancelPrompt, at, out width, 1.0f); at.X -= width + 8 * Game.RelativeScale; - UI.Prompt(batch, Controls.Confirm, "Confirm", at, out _, 1.0f); - } + UI.Prompt(batch, Controls.Confirm, "Confirm", at, out _, 1.0f); + + // show version number on Overworld as well + UI.Text(batch, Game.VersionString, bounds.BottomLeft + new Vec2(4, -4) * Game.RelativeScale, new Vec2(0, 1), Color.White * 0.25f); + } if (cameraCloseUpEase > 0) { diff --git a/Source/Scenes/World.cs b/Source/Scenes/World.cs index d8f9c09..d9a3d51 100644 --- a/Source/Scenes/World.cs +++ b/Source/Scenes/World.cs @@ -78,21 +78,24 @@ public World(EntryInfo entry) // setup pause menu { - pauseMenu.Add(new Menu.Option("Resume", () => SetPaused(false))); + Menu optionsMenu = new Menu(); + optionsMenu.Title = "Options"; + optionsMenu.Add(new Menu.Toggle("Fullscreen", Save.Instance.ToggleFullscreen, () => Save.Instance.Fullscreen)); + optionsMenu.Add(new Menu.Toggle("Z-Guide", Save.Instance.ToggleZGuide, () => Save.Instance.ZGuide)); + optionsMenu.Add(new Menu.Toggle("Timer", Save.Instance.ToggleTimer, () => Save.Instance.SpeedrunTimer)); + optionsMenu.Add(new Menu.Spacer()); + optionsMenu.Add(new Menu.Slider("BGM", 0, 10, () => Save.Instance.MusicVolume, Save.Instance.SetMusicVolume)); + optionsMenu.Add(new Menu.Slider("SFX", 0, 10, () => Save.Instance.SfxVolume, Save.Instance.SetSfxVolume)); + + pauseMenu.Title = "Paused"; + pauseMenu.Add(new Menu.Option("Resume", () => SetPaused(false))); pauseMenu.Add(new Menu.Option("Retry", () => { SetPaused(false); Audio.StopBus(Sfx.bus_dialog, false); Get()?.Kill(); })); - pauseMenu.Add(new Menu.Spacer()); - pauseMenu.Add(new Menu.Toggle("Fullscreen", Save.Instance.ToggleFullscreen, () => Save.Instance.Fullscreen)); - pauseMenu.Add(new Menu.Toggle("Z-Guide", Save.Instance.ToggleZGuide, () => Save.Instance.ZGuide)); - pauseMenu.Add(new Menu.Toggle("Timer", Save.Instance.ToggleTimer, () => Save.Instance.SpeedrunTimer)); - pauseMenu.Add(new Menu.Spacer()); - pauseMenu.Add(new Menu.Slider("BGM", 0, 10, () => Save.Instance.MusicVolume, Save.Instance.SetMusicVolume)); - pauseMenu.Add(new Menu.Slider("SFX", 0, 10, () => Save.Instance.SfxVolume, Save.Instance.SetSfxVolume)); - pauseMenu.Add(new Menu.Spacer()); + pauseMenu.Add(new Menu.Submenu("Options", pauseMenu, optionsMenu)); pauseMenu.Add(new Menu.Option("Save & Quit", () => Game.Instance.Goto(new Transition() { Mode = Transition.Modes.Replace, @@ -369,8 +372,9 @@ public override void Update() // unpause else { - if (Controls.Pause.Pressed || Controls.Cancel.Pressed) + if ((Controls.Pause.Pressed || Controls.Cancel.Pressed) && pauseMenu.IsInMainMenu) { + pauseMenu.CloseSubMenus(); SetPaused(false); Audio.Play(Sfx.ui_unpause); } @@ -393,8 +397,10 @@ public void SetPaused(bool paused) Audio.Play(Sfx.ui_pause); pauseSnapshot = Audio.Play(Sfx.snapshot_pause); } - else + else { + pauseMenu.Index = 0; pauseSnapshot.Stop(); + } Controls.Consume(); Paused = paused; @@ -793,6 +799,12 @@ public override void Render(Target target) UI.Strawberries(batch, Save.CurrentRecord.Strawberries.Count, Vec2.Zero); batch.PopMatrix(); } + + // show version number when paused / in ending area + if (IsInEndingArea || Paused) + { + UI.Text(batch, Game.VersionString, bounds.BottomLeft + new Vec2(4, -4) * Game.RelativeScale, new Vec2(0, 1), Color.White * 0.25f); + } } // overlay @@ -831,9 +843,12 @@ private void ApplyPostEffects() // apply post fx postMaterial.SetShader(Assets.Shaders["Edge"]); - postMaterial.Set("u_depth", Camera.Target.Attachments[1]); - postMaterial.Set("u_pixel", new Vec2(1.0f / postCam.Target.Width, 1.0f / postCam.Target.Height)); - postMaterial.Set("u_edge", new Color(0x110d33)); + if (postMaterial.Shader?.Has("u_depth") ?? false) + postMaterial.Set("u_depth", Camera.Target.Attachments[1]); + if (postMaterial.Shader?.Has("u_pixel") ?? false) + postMaterial.Set("u_pixel", new Vec2(1.0f / postCam.Target.Width, 1.0f / postCam.Target.Height)); + if (postMaterial.Shader?.Has("u_edge") ?? false) + postMaterial.Set("u_edge", new Color(0x110d33)); batch.PushMaterial(postMaterial); batch.Image(Camera.Target.Attachments[0], Color.White); batch.Render(postTarget); @@ -857,4 +872,4 @@ private void RenderModels(ref RenderState state, List models, ModelF it.Model.Render(ref state); } } -} \ No newline at end of file +}