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.)
This commit is contained in:
parent
40cb9f15cb
commit
962098d4e7
11
Changes.md
11
Changes.md
|
@ -1,6 +1,6 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## v0.11.0 (Feb 20 2022)
|
## v0.11.0 (Feb 21 2022)
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|
||||||
|
@ -34,8 +34,9 @@ are becoming more dangerous:
|
||||||
|
|
||||||
* The **Bird** now searches for the player diagonally in front of
|
* 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 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
|
it is dangerous when diving! When _playing_ as the bird, the dive sprite
|
||||||
is used when flying diagonally downwards.
|
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
|
* The **Azulians** will start to follow the player when you get
|
||||||
close and they are dangerous when they touch you -- but not if
|
close and they are dangerous when they touch you -- but not if
|
||||||
you're the **Thief.** The red Azulian has a wider search radius,
|
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
|
* 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
|
it can not die by fire or hostile enemies, and Anvils can not destroy
|
||||||
other Anvils.
|
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:
|
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.
|
caught and presented nicely in an in-game popup window.
|
||||||
* When playing as the Bird, the flying animation now loops while the
|
* When playing as the Bird, the flying animation now loops while the
|
||||||
player is staying still rather than pausing.
|
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
|
* When the game checks if there's an update available via
|
||||||
<https://download.sketchymaze.com/version.json> it will send a user
|
<https://download.sketchymaze.com/version.json> it will send a user
|
||||||
agent header like: "Sketchy Maze v0.10.2 on linux/amd64" sending only
|
agent header like: "Sketchy Maze v0.10.2 on linux/amd64" sending only
|
||||||
|
|
|
@ -144,9 +144,24 @@ function AI_ScanForPlayer() {
|
||||||
|
|
||||||
// If under control of the player character.
|
// If under control of the player character.
|
||||||
function player() {
|
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) => {
|
Events.OnKeypress((ev) => {
|
||||||
Vx = 0;
|
Vx = 0;
|
||||||
Vy = 0;
|
Vy = 0;
|
||||||
|
@ -158,31 +173,45 @@ function player() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dive!
|
// Dive!
|
||||||
if (ev.Down && ev.Right) {
|
if (ev.Down && ev.Right && falling) {
|
||||||
Self.StopAnimation();
|
Self.StopAnimation();
|
||||||
Self.ShowLayerNamed("dive-right");
|
Self.ShowLayerNamed("dive-right");
|
||||||
} else if (ev.Down && ev.Left) {
|
diving = falling;
|
||||||
|
} else if (ev.Down && ev.Left && falling) {
|
||||||
Self.StopAnimation();
|
Self.StopAnimation();
|
||||||
Self.ShowLayerNamed("dive-left");
|
Self.ShowLayerNamed("dive-left");
|
||||||
|
diving = falling;
|
||||||
} else if (ev.Right) {
|
} else if (ev.Right) {
|
||||||
// Fly right.
|
// Fly right.
|
||||||
if (!Self.IsAnimating()) {
|
if (!Self.IsAnimating()) {
|
||||||
Self.PlayAnimation("fly-right", null);
|
Self.PlayAnimation("fly-right", null);
|
||||||
}
|
}
|
||||||
Vx = playerSpeed;
|
Vx = playerSpeed;
|
||||||
|
diving = false;
|
||||||
} else if (ev.Left) {
|
} else if (ev.Left) {
|
||||||
// Fly left.
|
// Fly left.
|
||||||
if (!Self.IsAnimating()) {
|
if (!Self.IsAnimating()) {
|
||||||
Self.PlayAnimation("fly-left", null);
|
Self.PlayAnimation("fly-left", null);
|
||||||
}
|
}
|
||||||
Vx = -playerSpeed;
|
Vx = -playerSpeed;
|
||||||
|
diving = false;
|
||||||
} else {
|
} else {
|
||||||
// Hover in place.
|
// Hover in place.
|
||||||
if (!Self.IsAnimating()) {
|
if (!Self.IsAnimating()) {
|
||||||
Self.PlayAnimation("fly-"+direction);
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ const speed = 4,
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
Self.SetMobile(true);
|
Self.SetMobile(true);
|
||||||
|
Self.SetInvulnerable(true);
|
||||||
Self.SetGravity(true);
|
Self.SetGravity(true);
|
||||||
Self.SetHitbox(0, 0, size, size);
|
Self.SetHitbox(0, 0, size, size);
|
||||||
|
|
||||||
|
|
|
@ -152,16 +152,6 @@ func (d *Doodle) Run() error {
|
||||||
log.Debug("Shell: opening shell")
|
log.Debug("Shell: opening shell")
|
||||||
d.shell.Open = true
|
d.shell.Open = true
|
||||||
} else {
|
} 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) {
|
if keybind.Help(ev) {
|
||||||
// Launch the local guidebook
|
// Launch the local guidebook
|
||||||
native.OpenLocalURL(balance.GuidebookPath)
|
native.OpenLocalURL(balance.GuidebookPath)
|
||||||
|
@ -174,6 +164,16 @@ func (d *Doodle) Run() error {
|
||||||
// Make sure no UI modals (alerts, confirms)
|
// Make sure no UI modals (alerts, confirms)
|
||||||
// or loadscreen are currently visible.
|
// or loadscreen are currently visible.
|
||||||
if !modal.Handled(ev) {
|
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.
|
// Run the scene's logic.
|
||||||
err = d.Scene.Loop(d, ev)
|
err = d.Scene.Loop(d, ev)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -85,7 +85,9 @@ func FromEvent(ev *event.State) State {
|
||||||
|
|
||||||
// Shutdown (Escape) signals the game to start closing down.
|
// Shutdown (Escape) signals the game to start closing down.
|
||||||
func Shutdown(ev *event.State) bool {
|
func Shutdown(ev *event.State) bool {
|
||||||
return ev.Escape
|
result := ev.Escape
|
||||||
|
ev.Escape = false
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help (F1) can be checked one time.
|
// Help (F1) can be checked one time.
|
||||||
|
|
|
@ -13,7 +13,7 @@ func Alert(message string, args ...interface{}) *Modal {
|
||||||
if !ready {
|
if !ready {
|
||||||
panic("modal.Alert(): not ready")
|
panic("modal.Alert(): not ready")
|
||||||
} else if current != nil {
|
} else if current != nil {
|
||||||
return current
|
current.Dismiss(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the supervisor.
|
// Reset the supervisor.
|
||||||
|
@ -22,6 +22,7 @@ func Alert(message string, args ...interface{}) *Modal {
|
||||||
m := &Modal{
|
m := &Modal{
|
||||||
title: "Alert",
|
title: "Alert",
|
||||||
message: fmt.Sprintf(message, args...),
|
message: fmt.Sprintf(message, args...),
|
||||||
|
cancelable: true,
|
||||||
}
|
}
|
||||||
m.window = makeAlert(m)
|
m.window = makeAlert(m)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ func Confirm(message string, args ...interface{}) *Modal {
|
||||||
if !ready {
|
if !ready {
|
||||||
panic("modal.Confirm(): not ready")
|
panic("modal.Confirm(): not ready")
|
||||||
} else if current != nil {
|
} else if current != nil {
|
||||||
return current
|
current.Dismiss(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the supervisor.
|
// Reset the supervisor.
|
||||||
|
@ -21,6 +21,7 @@ func Confirm(message string, args ...interface{}) *Modal {
|
||||||
m := &Modal{
|
m := &Modal{
|
||||||
title: "Confirm",
|
title: "Confirm",
|
||||||
message: fmt.Sprintf(message, args...),
|
message: fmt.Sprintf(message, args...),
|
||||||
|
cancelable: true,
|
||||||
}
|
}
|
||||||
m.window = makeConfirm(m)
|
m.window = makeConfirm(m)
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,12 @@ func Handled(ev *event.State) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Escape key cancels the modal.
|
||||||
|
if keybind.Shutdown(ev) && current.cancelable {
|
||||||
|
current.Dismiss(false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
supervisor.Loop(ev)
|
supervisor.Loop(ev)
|
||||||
|
|
||||||
// Has the window changed size?
|
// Has the window changed size?
|
||||||
|
@ -111,6 +117,7 @@ type Modal struct {
|
||||||
message string
|
message string
|
||||||
window *ui.Window
|
window *ui.Window
|
||||||
callback func()
|
callback func()
|
||||||
|
cancelable bool // Escape key can cancel the modal
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTitle sets the title of the modal.
|
// WithTitle sets the title of the modal.
|
||||||
|
|
|
@ -435,6 +435,19 @@ func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, cen
|
||||||
// EditLevel toggles out of Play Mode to edit the level.
|
// EditLevel toggles out of Play Mode to edit the level.
|
||||||
func (s *PlayScene) EditLevel() {
|
func (s *PlayScene) EditLevel() {
|
||||||
log.Info("Edit Mode, Go!")
|
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)
|
gamepad.SetMode(gamepad.MouseMode)
|
||||||
s.d.Goto(&EditorScene{
|
s.d.Goto(&EditorScene{
|
||||||
Filename: s.Filename,
|
Filename: s.Filename,
|
||||||
|
@ -447,6 +460,7 @@ func (s *PlayScene) EditLevel() {
|
||||||
func (s *PlayScene) RestartLevel() {
|
func (s *PlayScene) RestartLevel() {
|
||||||
log.Info("Restart Level")
|
log.Info("Restart Level")
|
||||||
s.d.Goto(&PlayScene{
|
s.d.Goto(&PlayScene{
|
||||||
|
LevelPack: s.LevelPack,
|
||||||
Filename: s.Filename,
|
Filename: s.Filename,
|
||||||
Level: s.Level,
|
Level: s.Level,
|
||||||
CanEdit: s.CanEdit,
|
CanEdit: s.CanEdit,
|
||||||
|
@ -637,8 +651,7 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switching to Edit Mode?
|
// Switching to Edit Mode?
|
||||||
if s.CanEdit && keybind.GotoEdit(ev) {
|
if keybind.GotoEdit(ev) {
|
||||||
gamepad.SetMode(gamepad.MouseMode)
|
|
||||||
s.EditLevel()
|
s.EditLevel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,12 @@ func (u *PlayScene) setupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
////////
|
////////
|
||||||
// Level menu
|
// Level menu
|
||||||
levelMenu := menu.AddMenu("Level")
|
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)
|
levelMenu.AddItemAccel("Edit level", "E", u.EditLevel)
|
||||||
|
|
||||||
// Hilariously broken, someday!
|
// Hilariously broken, someday!
|
||||||
|
|
Loading…
Reference in New Issue
Block a user