diff --git a/Changes.md b/Changes.md index 10f8387..8fa477c 100644 --- a/Changes.md +++ b/Changes.md @@ -4,10 +4,15 @@ New features: -* The **JavaScript Engine** for the game has been switched from +* **Game Controller** support has been added! The game can now be played + with an Xbox style controller, including Nintendo Pro Controllers. The + game supports an "X Style" and "N Style" button layout, the latter of + which swaps the A/B and the X/Y buttons so gameplay controls match the + button labels in your controller. +* The **JavaScript Engine** for doodad scripts has been switched from github.com/robertkrimen/otto to github.com/dop251/goja which helps "modernize" the experience of writing doodads. Goja supports many - common ES6 functions already, such as: + common ES6 features already, such as: * Arrow functions * `let` and `const` keywords * Promises @@ -23,11 +28,21 @@ 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! + it is dangerous when diving! When playing as the bird, the dive sprite + is used when flying diagonally downwards. * 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, higher jump and faster speed than the blue Azulian. +* A new **White Azulian** has been added to the game. It is even faster + than the Red Azulian! And it can jump higher, too! +* The **Checkpoint Flag** can now re-assign the player character when + activated! Just link a doodad to the Checkpoint Flag like you do the + Start Flag. When the player reaches the checkpoint, their character + sprite is replaced with the linked doodad! +* 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. New functions are available on the JavaScript API for doodads: @@ -35,17 +50,32 @@ New functions are available on the JavaScript API for doodads: * `Actors.FindPlayer() *Actor`: returns the nearest player character * `Actors.New(filename string)`: create a new actor (NOT TESTED YET!) * `Self.Grounded() bool`: query the grounded status of current actor +* `Actors.SetPlayerCharacter(filename string)`: replace the nearest + player character with the named doodad, e.g. "boy.doodad" +* `Self.Invulnerable() bool` and `Self.SetInvulnerable(bool)`: set a + doodad is invulnerable, especially for the player character, e.g. + if playing as the Anvil you can't be defeated by mobs or fire. -New cheat code: +New cheat codes: * `god mode`: toggle invincibility. When on, fire pixels and hostile mobs can't make you fail the level. +* `megaton weight`: play as the Anvil by default on levels that don't + specify a player character otherwise. Other changes: +* When respawning from a checkpoint, the player is granted 3 seconds of + invulnerability; so if hostile mobs are spawn camping the player, you + don't get soft-locked! * The draw order of actors on a level is now deterministic: the most recently added actor will always draw on top when overlapping another, and the player actor is always on top. +* JavaScript exceptions raised in doodad scripts will be logged to the + console instead of crashing the game. In the future these will be + 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. * 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/doors/electric-door.js b/dev-assets/doodads/doors/electric-door.js index c918a3b..5c35201 100644 --- a/dev-assets/doodads/doors/electric-door.js +++ b/dev-assets/doodads/doors/electric-door.js @@ -14,9 +14,9 @@ function setPoweredState(powered) { } animating = true; + opened = true; Sound.Play("electric-door.wav") Self.PlayAnimation("open", () => { - opened = true; animating = false; }); } else { diff --git a/dev-assets/doodads/objects/anvil.js b/dev-assets/doodads/objects/anvil.js index 4390b98..8ea5d59 100644 --- a/dev-assets/doodads/objects/anvil.js +++ b/dev-assets/doodads/objects/anvil.js @@ -6,6 +6,7 @@ function main() { Self.SetHitbox(0, 0, 48, 25); Self.SetMobile(true); Self.SetGravity(true); + Self.SetInvulnerable(true); // Monitor our Y position to tell if we've been falling. let lastPoint = Self.Position(); @@ -33,7 +34,7 @@ function main() { FailLevel("Watch out for anvils!"); return; } - else if (e.Actor.IsMobile()) { + else if (e.Actor.IsMobile() && !e.Actor.Invulnerable()) { // Destroy mobile doodads. Sound.Play("crumbly-break.wav"); e.Actor.Destroy(); diff --git a/pkg/balance/cheats.go b/pkg/balance/cheats.go index e2cb474..830a41b 100644 --- a/pkg/balance/cheats.go +++ b/pkg/balance/cheats.go @@ -29,5 +29,6 @@ var ( CheatPlayAsBoy = "pinocchio" CheatPlayAsAzuBlue = "the cell" CheatPlayAsThief = "play as thief" + CheatPlayAsAnvil = "megaton weight" CheatGodMode = "god mode" ) diff --git a/pkg/cheats.go b/pkg/cheats.go index b3806f5..789d6ff 100644 --- a/pkg/cheats.go +++ b/pkg/cheats.go @@ -128,6 +128,10 @@ func (c Command) cheatCommand(d *Doodle) bool { balance.PlayerCharacterDoodad = "thief.doodad" d.Flash("Set default player character to Thief") + case balance.CheatPlayAsAnvil: + balance.PlayerCharacterDoodad = "anvil.doodad" + d.Flash("Set default player character to the Anvil") + case balance.CheatGodMode: if isPlay { d.Flash("God mode toggled") diff --git a/pkg/play_scene.go b/pkg/play_scene.go index b75b072..0576864 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -399,6 +399,7 @@ func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, cen } s.Player = uix.NewActor("PLAYER", &level.Actor{}, player) + s.Player.SetInventory(true) // player always can pick up items s.Player.MoveTo(spawn) s.drawing.AddActor(s.Player) s.drawing.FollowActor = s.Player.ID() @@ -465,7 +466,7 @@ func (s *PlayScene) BeatLevel() { // FailLevel handles a level failure triggered by a doodad. func (s *PlayScene) FailLevel(message string) { - if s.godMode || s.godModeUntil.After(time.Now()) { + if s.Player.Invulnerable() || s.godMode || s.godModeUntil.After(time.Now()) { return } s.SetImperfect() diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index ed538a2..6c13ecc 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -40,6 +40,7 @@ type Actor struct { noclip bool // Disable collision detection hidden bool // invisible, via Hide() and Show() frozen bool // Frozen, via Freeze() and Unfreeze() + immortal bool // Invulnerable to damage hitbox render.Rect inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item. data map[string]string // arbitrary key/value store. DEPRECATED ?? @@ -141,6 +142,16 @@ func (a *Actor) HasGravity() bool { return a.hasGravity } +// Invulnerable returns whether the actor is marked as immortal. +func (a *Actor) Invulnerable() bool { + return a.immortal +} + +// SetInvulnerable sets the actor's immortal flag. +func (a *Actor) SetInvulnerable(v bool) { + a.immortal = v +} + // Size returns the size of the actor, from the underlying doodads.Drawing. func (a *Actor) Size() render.Rect { return a.Drawing.Size() diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index 207dc2c..d9fef1a 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -85,32 +85,34 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} { actor.MoveTo(p) actor.SetGrounded(false) }, - "Grounded": actor.Grounded, - "SetHitbox": actor.SetHitbox, - "Hitbox": actor.Hitbox, - "SetVelocity": actor.SetVelocity, - "GetVelocity": actor.Velocity, - "SetMobile": actor.SetMobile, - "SetInventory": actor.SetInventory, - "HasInventory": actor.HasInventory, - "SetGravity": actor.SetGravity, - "AddAnimation": actor.AddAnimation, - "IsAnimating": actor.IsAnimating, - "IsPlayer": actor.IsPlayer, - "PlayAnimation": actor.PlayAnimation, - "StopAnimation": actor.StopAnimation, - "ShowLayer": actor.ShowLayer, - "ShowLayerNamed": actor.ShowLayerNamed, - "Inventory": actor.Inventory, - "AddItem": actor.AddItem, - "RemoveItem": actor.RemoveItem, - "HasItem": actor.HasItem, - "ClearInventory": actor.ClearInventory, - "Destroy": actor.Destroy, - "Freeze": actor.Freeze, - "Unfreeze": actor.Unfreeze, - "Hide": actor.Hide, - "Show": actor.Show, + "Grounded": actor.Grounded, + "SetHitbox": actor.SetHitbox, + "Hitbox": actor.Hitbox, + "SetVelocity": actor.SetVelocity, + "GetVelocity": actor.Velocity, + "SetMobile": actor.SetMobile, + "SetInventory": actor.SetInventory, + "HasInventory": actor.HasInventory, + "SetGravity": actor.SetGravity, + "Invulnerable": actor.Invulnerable, + "SetInvulnerable": actor.SetInvulnerable, + "AddAnimation": actor.AddAnimation, + "IsAnimating": actor.IsAnimating, + "IsPlayer": actor.IsPlayer, + "PlayAnimation": actor.PlayAnimation, + "StopAnimation": actor.StopAnimation, + "ShowLayer": actor.ShowLayer, + "ShowLayerNamed": actor.ShowLayerNamed, + "Inventory": actor.Inventory, + "AddItem": actor.AddItem, + "RemoveItem": actor.RemoveItem, + "HasItem": actor.HasItem, + "ClearInventory": actor.ClearInventory, + "Destroy": actor.Destroy, + "Freeze": actor.Freeze, + "Unfreeze": actor.Unfreeze, + "Hide": actor.Hide, + "Show": actor.Show, "GetLinks": func() []map[string]interface{} { var result = []map[string]interface{}{} for _, linked := range w.GetLinkedActors(actor) {