* Overhaul the clunky old alpha Edit Level/Doodad menu with a modernized
version featuring the new ListBox widget.
* The new level loader is a Window that can be spawned from anywhere instead
of on a dedicated MenuScene.
Updates to doodad scripts:
* Actor.IsOnScreen() checks whether an actor's visual sprite box is on-screen
in the level viewport. `Self.IsOnScreen()` will check for the current actor.
Other changes
* PlaySound() to deduplicate the same sound effect from playing at once.
Updates the savegame.json file format:
* Levels now have a UUID value assigned at first save.
* The savegame.json will now track level completion/score based on UUID,
making it robust to filename changes in either levels or levelpacks.
* The savegame file is auto-migrated on startup - for any levels not
found or have no UUID, no change is made, it's backwards compatible.
* Level Properties window adds an "Advanced" tab to show/re-roll UUID.
New JavaScript API for doodad scripts:
* `Actors.CameraFollowPlayer()` tells the camera to return focus to the
player character. Useful for "cutscene" doodads that freeze the player,
call `Self.CameraFollowMe()` and do a thing before unfreezing and sending the
camera back to the player. (Or it will follow them at their next directional
input control).
* `Self.MoveBy(Point(x, y int))` to move the current actor a bit.
New option for the `doodad` command-line tool:
* `doodad resave <.level or .doodad>` will load and re-save a drawing, to
migrate it to the newest file format versions.
Small tweaks:
* On bounded levels, allow the camera to still follow the player if the player
finds themselves WELL far out of bounds (40 pixels margin). So on bounded
levels you can create "interior rooms" out-of-bounds to Warp Door into.
* New wallpaper: "Atmosphere" has a black starscape pattern that fades into a
solid blue atmosphere.
* Camera strictly follows the player the first 20 ticks, not 60 of level start
* If player is frozen, directional inputs do not take the camera focus back.
* Add "Options" support for Doodads: these allow for individual Actor instances
on your level to customize properties about the doodad. They're like "Tags"
except the player can customize them on a per-actor basis.
* Doodad Editor: you can specify the Options in the Doodad Properties window.
* Level Editor: when the Actor Tool is selected, on mouse-over of an actor,
clicking on the gear icon will open a new "Actor Properties" window which
shows metadata (title, author, ID, position) and an Options tab to configure
the actor's options.
Updates to the scripting API:
* Self.Options() returns a list of option names defined on the Doodad.
* Self.GetOption(name) returns the value for the named option, or nil if
neither the actor nor its doodad have the option defined. The return type
will be correctly a string, boolean or integer type.
Updates to the doodad command-line tool:
* `doodad show` will print the Options on a .doodad file and, when showing a
.level file with --actors, prints any customized Options with the actors.
* `doodad edit-doodad` adds a --option parameter to define options.
Options added to the game's built-in doodads:
* Warp Doors: "locked (exit only)" will make it so the door can not be opened
by the player, giving the "locked" message (as if it had no linked door),
but the player may still exit from the door if sent by another warp door.
* Electric Door & Electric Trapdoor: "opened" can make the door be opened by
default when the level begins instead of closed. A switch or a button that
removes power will close the door as normal.
* Colored Doors & Small Key Door: "unlocked" will make the door unlocked at
level start, not requiring a key to open it.
* Colored Keys & Small Key: "has gravity" will make the key subject to gravity
and set its Mobile flag so that if it falls onto a button, it will activate.
* Gemstones: they had gravity by default; you can now uncheck "has gravity" to
remove their Gravity and IsMobile status.
* Gemstone Totems: "has gemstone" will set the totem to its unlocked status by
default with the gemstone inserted. No power signal will be emitted; it is
cosmetic only.
* Fire Region: "name" can let you set a name for the fire region similarly to
names for fire pixels: "Watch out for ${name}!"
* Invisible Warp Door: "locked (exit only)" added as well.
* New script API method: Self.CameraFollowMe() to draw camera focus toward
your doodad (it sets the Canvas.FollowActor target.)
* The camera will go back to following the player on any action inputs
(arrow keys, jump, use, etc.); if the player is constantly on the move
the camera stays on him even if another actor is trying to take the focus.
* The first few ticks of Play Mode the player character is always followed,
to allow for Anvils to settle into place without taking the focus.
* Canvas FollowActor: if the actor is 4 times the max scroll speed away,
allow scrolling in greater leaps of 4 times the max scroll speed.
New and Changed Doodads
* Anvils will take the camera focus while they are falling.
* New doodad: "Look At Me" - a 'camera region' technical doodad. Link it to
any power source such as a Button - when this doodad receives power it will
take the camera focus for a few frames. Use it to highlight a door that
opened far off screen by linking the Button to both an Electric Door and
a "Look At Me" near the door.
Water pixels finally do something other than turn your character blue!
* When the player character is "wet" (touching water pixels, and so appearing in
a blue mask), water physics apply: gravity is slower, your jump height is
halved, but you get infinite jumps to swim higher in the water.
* Holding the jump key under water will incur a short delay between jumps, so
that you don't just fly straight up to the surface. Tap the jump button to
move up quicker, you can spam it all you want.
Azulians are also able to handle being under water:
* They'll sink to the bottom and keep walking back and forth normally.
* If you are above them and noticed, they'll jump (swim) up towards you,
aware of the water and it jumps like you do.
* The Blue Azulian has the poorest vertical aggro range so it isn't a
very good swimmer. The White Azulian is very good at navigating water
as it can pursue the player from the furthest distance of them all.
Changes to the editor:
* New brush pattern added: bubbles.png
* It's the default pattern now for the "water" color of all
of the built-in palettes instead of ink.png
* A repeating pattern of bubbles carved out showing the
level wallpaper.
* The old "Bubbles (circles.png)" is renamed "Circles"
* The last scroll position is saved with the Level file, so when you reload
the level later it's scrolled at where you left it.
* Bird is not solid when colliding with other birds.
* If the dev shell is used to run JavaScript during Play Mode, consider
it cheating (so player can't `$ d.Scene.ResetTimer()` for example)
* On Survival Mode levels, DieByFire immediately opens the End Level
(silver score) modal rather than respawn from checkpoint, so levels
don't need checkpoint contraptions to end the level.
* During level loading screens, wait and call doodads' main() function
until the very end.
New features:
* Flood Tool for the editor. It replaces pixels of one color with another,
contiguously. Has limits on how far from the original pixel it will color,
to avoid infinite loops in case the user clicked on wide open void. The
limit when clicking an existing color is 1200px or only a 600px limit if
clicking into the void.
* Cheat code: 'master key' to play locked Story Mode levels.
Level GameRules feature added:
* A new tab in the Level Properties dialog
* Difficulty has been moved to this tab
* Survival Mode: for silver high score, longest time alive is better than
fastest time, for Azulian Tag maps. Gold high score is still based on
fastest time - find the hidden level exit without dying!
Tweaks to the Azulians' jump heights:
* Blue Azulian: 12 -> 14
* Red Azulian: 14 -> 18
* White Azulian: 16 -> 20
Bugs fixed:
* When editing your Palette to rename a color or add a new color, it wasn't
possible to draw with that color until the editor was completely unloaded
and reloaded; this is now fixed.
* Minor bugfix in Difficulty.String() for Peaceful (-1) difficulty to avoid
a negative array index.
* Try and prevent user giving the same name to multiple swatches on their
palette. Replacing the whole palette can let duplication through still.
Added a new level property: Difficulty
* An enum ranging from -1, 0, 1 (Peaceful, Normal, Hard)
* Default difficulty is Normal; pre-existing levels are Normal by
default per the zero value.
Doodad scripts can read the difficulty via the new global variable
`Level.Difficulty` and some doodads have been updated:
* Azulians: on Peaceful they ignore all player characters, and on Hard
they are in "hunt mode": infinite aggro radius and they're aggressive
to all characters.
* Bird: on Peaceful they will not dive and attack any player character.
Other spit and polish:
* New Level/Level Properties UI reworked into a magicform.
* New "PromptPre(question, answer, func)" function for prompting the
user with the developer shell, but pre-filling in an answer for them
to either post or edit.
* magicform has a PromptUser field option for simple Text/Int fields
which present as buttons, so magicform can prompt and update the
variable itself.
* Don't show the _autosave.doodad in the Doodad Dropper window.
* 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.
Link a Doodad to a Checkpoint Flag (like you would a Start Flag) and
crossing the flag will replace the player with that doodad. Multiple
checkpoint flags like this can toggle you between characters.
* Azulians are now friendly to player characters who have the word
"Azulian" in their title.
* Improve Bird as the playable character:
* Dive animation if the player flies diagonally downwards
* Animation loop while hovering in the air instead of pausing
* Checkpoint flags don't spam each other on PubSub so much which could
sometimes lead to deadlocks!
SetPlayerCharacter added to the JavaScript API. The Checkpoint Flag
(not the region) can link to a doodad and replace the player character
with that linked doodad when you activate the checkpoint:
Actors.SetPlayerCharacter(filename string): like "boy.doodad"
Add various panic catchers to make JavaScript safer and log issues
to console.
New functions are available on the JavaScript API for doodads:
* Actors.At(Point) []*Actor: returns actors intersecting a point
* 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
With this the game's built-in doodads have been revised:
* Bird: will now scan 240 pixels diagonally searching for the player
character and will dive if seen. The Bird is dangerous while
diving. It will return to its original altitude once it touches
the ground.
* Azulians: the Azulians are now dangerous to player characters but
not to the Thief. Azulians will begin to follow the player when
they are within the aggro range and will hop if the player is
above them to try and overcome obstacles.
* Blue Azulian: aggro is (250, 100) jump speed 12 movement 2
* Red Azulian: aggro is (250, 200) jump speed 14 movement 4
* Tweak max gravity speed to match player max velocity.
* Boy's script watches for his velocity to flip suddenly and stops
animations, limiting the moonwalking a bit.
* JS API: Self.GetVelocity() added.
New category for the Doodad Dropper: "Technical"
Technical doodads have a dashed outline and label for now, and they
turn invisible on level start, and are for hidden technical effects on
your level.
The doodads include:
* Goal Region: acts like an invisible Exit Flag (128x128), the level is
won when the player character touches this region.
* Fire Region: acts like a death barrier (128x128), kills the player
when a generic "You have died!" message.
* Power Source: on level start, acts like a switch and emits a
power(true) signal to all linked doodads. Link it to your Electric
Door for it to be open by default in your level!
* Stall Player (250ms): The player is paused for a moment the first time
it touches this region. Useful to work around timing issues, e.g.
help prevent the player from winning a race against another character.
There are some UI improvements to the Doodad Dropper window:
* If the first page of doodads is short, extra spacers are added so the
alignment and size shows correctly.
* Added a 'background pattern' to the window: any unoccupied icon space
has an inset rectangle slot.
* "Last pages" which are short still render weirdly without reserving
the correct height in the TabFrame.
Doodad scripting engine updates:
* Self.Hide() and Self.Show() available.
* Subscribe to "broadcast:ready" to know when the level is ready, so you
can safely Publish messages without deadlocks!
A new property is added to the Doodad struct: Hitbox (Rect).
The uix.Actor for Play Mode will defer to the Doodad.Hitbox until the
JavaScript has manually set its own via Self.SetHitbox(). So in effect,
scripts no longer need to worry about their hitbox! The one assigned to
the Doodad will be the default.
Scripts can check if their hitbox is zero before setting a default:
if (Self.Hitbox().IsZero()) {
var size = Self.Size() // get doodad canvas size
Self.SetHitbox(0, 0, size, size) // the full square
}
The built-in generic doodad scripts have made this change, so that your
simple doodad can have a custom hitbox defined easily using in-game
tools.
Other changes:
* New script: Generic Collectible Item. Selecting it will add a
"quantity" tag to your doodad, to easily configure the script.
* JavaScript API: "Self.Hitbox()" returns your doodad's current hitbox.
You can check "Self.Hitbox.IsZero()" to check if it's empty.
In the Doodad Properties window, instead of browsing to select a .js
file to install your script, a SelectBox of built-in generic scripts are
available. These scripts implement simple behaviors and adapt to the
full canvas size of the doodad.
Built-in scripts so far include:
* generic-anvil.js: behaves just like the Anvil.
* generic-fire.js: the entire canvas hitbox acts like fire pixels,
"burning" mobile doodads and failing the level for the player.
* generic-solid.js: the entire canvas hitbox acts solid
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
* New doodad: Electric Trapdoor. It is a horizontal version of the
Electric Door. Opens while powered by a button or a switch and closes
when it loses power.
* The Box doodad will reset to its original location if it receives a
power signal from a linked Button or Switch. So for box pushing
puzzles you can add a reset button in case the boxes get stuck.
* Refactored the Doodad build scripts into many Makefiles for easier
iteration (don't need to compile ALL doodads to test one).
Updates to the JavaScript API for doodads:
* Self.MoveTo(Point) is now available to set the actor's position in
world coordinates.
* The "Use Key" (Q or Spacebar) now activates the Warp Door instead of a
collision event doing so.
* Warp Doors are now functional: the player opens a door, disappears,
the door closes; player is teleported to the linked door which opens,
appears the player and closes.
* If the player exits thru a Blue or Orange door which is disabled
(dotted outline), the door still opens and drops the player off but
returns to a Disabled state, acting as a one-way door.
* Clean up several debug log lines from Doodle and doodad scripts.