Invulnerable Anvil and other fixes

* Add methods `Invulnerable() bool` and `SetInvulnerable(bool)` to the
  Actor API accessible in JavaScript (e.g. `Self.SetInvulnerable(true)`)
* The Anvil is invulnerable - when played as, it can crush other mobs by
  jumping on them but is not defeated by those mobs at the same time.
* Anvils don't destroy invulnerable mobs, such as other Anvils.
* Bugfix: the Electric Door is considered to be opened from the first
  frame of animation when the door begins opening, and remains opened
  until the final frame of animation when it is closing.
* New cheat code: `megaton weight` to play as the Anvil by default.
pull/84/head
Noah 2022-02-20 11:48:36 -08:00
parent 0fc046250e
commit 1205dc2cd3
8 changed files with 83 additions and 33 deletions

View File

@ -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
<https://download.sketchymaze.com/version.json> it will send a user
agent header like: "Sketchy Maze v0.10.2 on linux/amd64" sending only

View File

@ -14,9 +14,9 @@ function setPoweredState(powered) {
}
animating = true;
opened = true;
Sound.Play("electric-door.wav")
Self.PlayAnimation("open", () => {
opened = true;
animating = false;
});
} else {

View File

@ -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();

View File

@ -29,5 +29,6 @@ var (
CheatPlayAsBoy = "pinocchio"
CheatPlayAsAzuBlue = "the cell"
CheatPlayAsThief = "play as thief"
CheatPlayAsAnvil = "megaton weight"
CheatGodMode = "god mode"
)

View File

@ -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")

View File

@ -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()

View File

@ -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()

View File

@ -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) {