From 962098d4e711a741ac8b17c62c84735a6494affd Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 21 Feb 2022 13:09:51 -0800 Subject: [PATCH] v0.11.0 last minute tweaks * When playing as the Bird, the dive attack is able to destroy other mobile doodads such as Azulians and Thieves. * The Box has been made invulnerable so it can't be destroyed by Anvils or player-controlled Birds. * Bugfixes with pop-up modals: * The quit game confirm modal doesn't appear if another modal is already active on screen. * The Escape key can dismiss Alert and Confirm modals. * Add "Level" menu items to Play Mode to restart the level or retry from the last checkpoint (in case of softlocks, etc.) --- Changes.md | 11 ++++++--- dev-assets/doodads/bird/bird.js | 41 ++++++++++++++++++++++++++++----- dev-assets/doodads/box/box.js | 1 + pkg/doodle.go | 20 ++++++++-------- pkg/keybind/keybind.go | 4 +++- pkg/modal/alert.go | 7 +++--- pkg/modal/confirm.go | 7 +++--- pkg/modal/modal.go | 15 ++++++++---- pkg/play_scene.go | 23 ++++++++++++++---- pkg/play_scene_menubar.go | 6 +++++ 10 files changed, 100 insertions(+), 35 deletions(-) diff --git a/Changes.md b/Changes.md index d031458..8edcfd6 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,6 @@ # Changes -## v0.11.0 (Feb 20 2022) +## v0.11.0 (Feb 21 2022) New features: @@ -34,8 +34,9 @@ are becoming more dangerous: * The **Bird** now searches for the player diagonally in front of it for about 240px or so. If spotted it will dive toward you and - it is dangerous when diving! When playing as the bird, the dive sprite - is used when flying diagonally downwards. + it is dangerous when diving! When _playing_ as the bird, the dive sprite + is used when flying diagonally downwards. The player-controlled bird + can kill mobile doodads by diving into them. * The **Azulians** will start to follow the player when you get close and they are dangerous when they touch you -- but not if you're the **Thief.** The red Azulian has a wider search radius, @@ -49,6 +50,8 @@ are becoming more dangerous: * The **Anvil** is invulnerable -- if the player character is the Anvil it can not die by fire or hostile enemies, and Anvils can not destroy other Anvils. +* The **Box** is also made invulnerable so it can't be destroyed by a + player-controlled Anvil or Bird. New functions are available on the JavaScript API for doodads: @@ -82,6 +85,8 @@ Other changes: caught and presented nicely in an in-game popup window. * When playing as the Bird, the flying animation now loops while the player is staying still rather than pausing. +* The "Level" menu in Play Mode has options to restart the level or + retry from last checkpoint, in case a player got softlocked. * When the game checks if there's an update available via it will send a user agent header like: "Sketchy Maze v0.10.2 on linux/amd64" sending only diff --git a/dev-assets/doodads/bird/bird.js b/dev-assets/doodads/bird/bird.js index 7a11162..7fdb035 100644 --- a/dev-assets/doodads/bird/bird.js +++ b/dev-assets/doodads/bird/bird.js @@ -144,9 +144,24 @@ function AI_ScanForPlayer() { // If under control of the player character. function player() { - var playerSpeed = 12; + let playerSpeed = 12, + diving = false, + falling = false; + + // The player can dive by moving downwards and laterally, but + // de-cheese their ability to just sweep across the ground; if + // they aren't seen to be moving downwards, cancel the dive. + let lastPoint = Self.Position(); + setInterval(() => { + let nowAt = Self.Position(); + if (nowAt.Y > lastPoint.Y) { + falling = true; + } else { + falling = false; + } + lastPoint = nowAt; + }, 100); - Self.SetInventory(true); Events.OnKeypress((ev) => { Vx = 0; Vy = 0; @@ -158,31 +173,45 @@ function player() { } // Dive! - if (ev.Down && ev.Right) { + if (ev.Down && ev.Right && falling) { Self.StopAnimation(); Self.ShowLayerNamed("dive-right"); - } else if (ev.Down && ev.Left) { + diving = falling; + } else if (ev.Down && ev.Left && falling) { Self.StopAnimation(); Self.ShowLayerNamed("dive-left"); + diving = falling; } else if (ev.Right) { // Fly right. if (!Self.IsAnimating()) { Self.PlayAnimation("fly-right", null); } Vx = playerSpeed; + diving = false; } else if (ev.Left) { // Fly left. if (!Self.IsAnimating()) { Self.PlayAnimation("fly-left", null); } Vx = -playerSpeed; + diving = false; } else { // Hover in place. if (!Self.IsAnimating()) { Self.PlayAnimation("fly-"+direction); } + diving = false; } - // Self.SetVelocity(Vector(Vx, Vy)); - }) + // Player is invulnerable while diving. + Self.SetInvulnerable(diving); + }); + + Events.OnCollide((e) => { + // If the player is diving at an enemy mob, destroy it. + if (diving && e.Settled && e.Actor.IsMobile() && !e.Actor.Invulnerable()) { + Sound.Play("crumbly-break.wav"); + e.Actor.Destroy(); + } + }); } diff --git a/dev-assets/doodads/box/box.js b/dev-assets/doodads/box/box.js index 5a68e85..cd96a6e 100644 --- a/dev-assets/doodads/box/box.js +++ b/dev-assets/doodads/box/box.js @@ -5,6 +5,7 @@ const speed = 4, function main() { Self.SetMobile(true); + Self.SetInvulnerable(true); Self.SetGravity(true); Self.SetHitbox(0, 0, size, size); diff --git a/pkg/doodle.go b/pkg/doodle.go index 20b3f4c..825b371 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -152,16 +152,6 @@ func (d *Doodle) Run() error { log.Debug("Shell: opening shell") d.shell.Open = true } else { - // Global event handlers. - if keybind.Shutdown(ev) { - if d.Debug { // fast exit in -debug mode. - d.running = false - } else { - d.ConfirmExit() - } - continue - } - if keybind.Help(ev) { // Launch the local guidebook native.OpenLocalURL(balance.GuidebookPath) @@ -174,6 +164,16 @@ func (d *Doodle) Run() error { // Make sure no UI modals (alerts, confirms) // or loadscreen are currently visible. if !modal.Handled(ev) { + // Global event handlers. + if keybind.Shutdown(ev) { + if d.Debug { // fast exit in -debug mode. + d.running = false + } else { + d.ConfirmExit() + } + continue + } + // Run the scene's logic. err = d.Scene.Loop(d, ev) if err != nil { diff --git a/pkg/keybind/keybind.go b/pkg/keybind/keybind.go index 27ed9b8..771484b 100644 --- a/pkg/keybind/keybind.go +++ b/pkg/keybind/keybind.go @@ -85,7 +85,9 @@ func FromEvent(ev *event.State) State { // Shutdown (Escape) signals the game to start closing down. func Shutdown(ev *event.State) bool { - return ev.Escape + result := ev.Escape + ev.Escape = false + return result } // Help (F1) can be checked one time. diff --git a/pkg/modal/alert.go b/pkg/modal/alert.go index 595a2fa..6825823 100644 --- a/pkg/modal/alert.go +++ b/pkg/modal/alert.go @@ -13,15 +13,16 @@ func Alert(message string, args ...interface{}) *Modal { if !ready { panic("modal.Alert(): not ready") } else if current != nil { - return current + current.Dismiss(false) } // Reset the supervisor. supervisor = ui.NewSupervisor() m := &Modal{ - title: "Alert", - message: fmt.Sprintf(message, args...), + title: "Alert", + message: fmt.Sprintf(message, args...), + cancelable: true, } m.window = makeAlert(m) diff --git a/pkg/modal/confirm.go b/pkg/modal/confirm.go index c5131a5..1fbd9fc 100644 --- a/pkg/modal/confirm.go +++ b/pkg/modal/confirm.go @@ -12,15 +12,16 @@ func Confirm(message string, args ...interface{}) *Modal { if !ready { panic("modal.Confirm(): not ready") } else if current != nil { - return current + current.Dismiss(false) } // Reset the supervisor. supervisor = ui.NewSupervisor() m := &Modal{ - title: "Confirm", - message: fmt.Sprintf(message, args...), + title: "Confirm", + message: fmt.Sprintf(message, args...), + cancelable: true, } m.window = makeConfirm(m) diff --git a/pkg/modal/modal.go b/pkg/modal/modal.go index 7bf8331..3295381 100644 --- a/pkg/modal/modal.go +++ b/pkg/modal/modal.go @@ -66,6 +66,12 @@ func Handled(ev *event.State) bool { return true } + // Escape key cancels the modal. + if keybind.Shutdown(ev) && current.cancelable { + current.Dismiss(false) + return true + } + supervisor.Loop(ev) // Has the window changed size? @@ -107,10 +113,11 @@ func center(win *ui.Window) { // Modal is an instance of a modal, i.e. Alert or Confirm. type Modal struct { - title string - message string - window *ui.Window - callback func() + title string + message string + window *ui.Window + callback func() + cancelable bool // Escape key can cancel the modal } // WithTitle sets the title of the modal. diff --git a/pkg/play_scene.go b/pkg/play_scene.go index f3b57b0..8a3285c 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -435,6 +435,19 @@ func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, cen // EditLevel toggles out of Play Mode to edit the level. func (s *PlayScene) EditLevel() { log.Info("Edit Mode, Go!") + + // If they didn't come from the Level Editor originally, e.g. they are in Story Mode, + // confirm they want the editor in case they accidentally hit the "E" key due to + // its proximity to the WASD keys. + if !s.CanEdit { + modal.Confirm("Open this level in the editor?").Then(s.doEditLevel) + } else { + s.doEditLevel() + } +} + +// Common logic to transition into the Editor. +func (s *PlayScene) doEditLevel() { gamepad.SetMode(gamepad.MouseMode) s.d.Goto(&EditorScene{ Filename: s.Filename, @@ -447,9 +460,10 @@ func (s *PlayScene) EditLevel() { func (s *PlayScene) RestartLevel() { log.Info("Restart Level") s.d.Goto(&PlayScene{ - Filename: s.Filename, - Level: s.Level, - CanEdit: s.CanEdit, + LevelPack: s.LevelPack, + Filename: s.Filename, + Level: s.Level, + CanEdit: s.CanEdit, }) } @@ -637,8 +651,7 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error { } // Switching to Edit Mode? - if s.CanEdit && keybind.GotoEdit(ev) { - gamepad.SetMode(gamepad.MouseMode) + if keybind.GotoEdit(ev) { s.EditLevel() return nil } diff --git a/pkg/play_scene_menubar.go b/pkg/play_scene_menubar.go index 84624e3..8d3dd04 100644 --- a/pkg/play_scene_menubar.go +++ b/pkg/play_scene_menubar.go @@ -53,6 +53,12 @@ func (u *PlayScene) setupMenuBar(d *Doodle) *ui.MenuBar { //////// // Level menu levelMenu := menu.AddMenu("Level") + levelMenu.AddItem("Restart level", u.RestartLevel) + levelMenu.AddItem("Retry from checkpoint", func() { + u.SetImperfect() + u.RetryCheckpoint() + }) + levelMenu.AddSeparator() levelMenu.AddItemAccel("Edit level", "E", u.EditLevel) // Hilariously broken, someday!